Description
Summary
Change how the memory layout of cortex-m-rt
programs is defined. Let the user
specify memory regions and linker sections using a high level API from build
scripts.
Background
Some embedded devices contain more than a single RAM region; these memory
regions usually have different performance characteristics (e.g. Tightly Coupled
Memory (TCM) is faster to access than regular RAM but it may have the downside
that it cannot be accessed by the DMA). In these cases one may want fine grained
control over the location of static
variables and the (call) stack(s).
Other devices may have a mixture of Cortex-M and Cortex-A cores where the M
cores have no Flash and the A cores directly load programs onto the M cores'
RAM. In these cases one would like to skip doing the initialization of .bss and
.data on the M cores as the A cores will take care of that.
None of the above scenarios can be properly handled with the current system.
Design
User API
End users will define the memory layout of their device in a build script. The
code below depicts a build script that defines a complex memory layout that
makes use of two RAM regions. Note that the presented API is not final.
// ..
fn main() -> Result<(), ExitFailure> {
// most of these methods are `unsafe`
unsafe {
// builder pattern
let mut ls = LinkerScript::new();
// define the memory regions
let flash = ls.memory(
"FLASH",
/* start */ 0x0800_0000,
/* size */ 256.k(),
)?;
let ram = ls.memory("RAM", 0x2000_0000, 40.k())?;
let ccram = ls.memory("CCRAM", 0x1000_0000, 40.k())?;
// mandatory: ".text" section
ls.text(&flash)?;
// mandatory: call stack location
ls.stack(&ccram)?;
// usually required: ".bss" section (zeroed and 4-byte aligned)
ls.bss(/* prefix? */ false, /* VMA */ &ram)?;
// usually required: ".data" section (initialized and 4-byte aligned)
ls.data(
/* prefix? */ false, /* VMA */ &ram, /* LMA */ &flash,
)?;
// additional: ".CCRAM.bss" section (zeroed and 4-byte aligned)
ls.bss(true, &ccram)?;
// additional: ".CCRAM.data" section (initialized and 4-byte aligned)
ls.data(true, &ccram, &flash)?;
// additional: ".ramfunc" section (initialized and 4-byte aligned)
ls.ramfunc(
/* prefix? */ false, /* VMA */ &ccram, /* LMA */ &flash,
)?;
// additional: ".uninit" section (uninitialized)
ls.uninit(/* prefix? */ false, /* VMA */ &ccram)?;
// generate `link.x` and `reset.rs`
ls.generate()?;
}
Ok(())
}
Library changes
The Reset
handler will be removed from the cortex-m-rt
crate. Instead the
#[entry]
attribute will expand into both Reset
and main
.
// this
#[entry]
fn main() -> ! {
// .. user code ..
}
// expands into
#[link_name = "Reset"]
fn random_identifier() -> ! {
include!(concat!(env!(OUT_DIR), "/reset.rs"));
}
#[link_name = "main"]
fn random_identifier2() -> ! {
// .. user code ..
}
main
will continue to be the user entry point. The contents of the reset.rs
file will be generated by LinkerScript::generate
. The code in reset.rs
will
call pre_init
; initialize .bss
, .data
and other user defined sections;
initialize the FPU, if present; and finally call main
.
#[ramfunc]
, #[bss]
, #[data]
, #[uninit]
Attributes to place static [mut]
variables and functions in custom sections
will be added to cortex-m-rt
.
#[ramfunc]
can be used to place a function in a RAM region.
// will be placed in the output ".ramfunc" section
#[ramfunc]
fn foo() { /* .. */}
// will be placed in the output ".CCRAM.ramfunc" section
#[ramfunc(CCRAM)]
fn bar() { /* .. */}
#[data]
can be used to place a static [mut]
variable in a user defined
.data
section.
// will be placed in the output ".CCRAM.data" section
// NOTE: the argument is mandatory
#[data(CCRAM)]
static mut COUNTDOWN: u32 = 10;
#[bss]
can be used to place a static [mut]
variable in a user defined
.bss
section. As this section is always zeroed the user must assert this fact
by wrapping the initial value, which should evaluate to "all zeros", in an
unsafe
block.
// will be placed in the output ".CCRAM.bss" section
// NOTE: the argument is mandatory
#[bss(CCRAM)]
static mut BUFFER: [u8; 128] = unsafe { [0; 128] };
// this produces a compiler error
// #[bss(CCRAM)]
// static mut BAZ: u32 = 0;
#[uninit]
can be used to leave static [mut]
uninitialized. These variables
will be placed in a .uninit
section.
// this
#[uninit]
static mut BUFFER: [u8; 128] = ();
// expands into
#[link_section = ".uninit.random-string"]
static mut BUFFER: MaybeUninit<[u8; 128]> = MaybeUninit::uninitialized();
// will be placed in the output ".CCRAM.uninit" section
#[uninit(CCRAM)]
static mut FOO: [u8; 128] = ();
Drawbacks
This is a breaking change.
It's not possible to retain the existing "just define a memory.x
file"
model with the proposed changes. The reason is that the build script that
describes the memory layout of the target must live in the top crate
(otherwise reset.rs
can't be located by #[entry]
) and there must only exist
one of such build scripts in the dependency graph (or you'll end with multiple
link.x
and the linker will pick one randomly). Thus, all end users will have
to deal with build scripts.
We can ease the migration by providing a helper function that replicates today's
behavior but the user will have to modify their projects to include a build
script that looks like this:
// ..
fn main() {
LinkerScript::default();
}
Also note that we can't put "user defined memory layout" behind a Cargo feature
because Cargo features must be strictly additive since any dependency is free to
enable them. If this were a feature enabling it would require adding or
modifying the top build script.
Unresolved questions
An API to let users specify linker section offsets (required on NXP devices to
avoid placing .text on top of Flash configuration memory) has yet to be
designed.
Alternatives
We could rename the #[ramfunc]
to #[text]
and make its argument mandatory.
Accordingly, instead of output [.$REGION].ramfunc
sections we would use
.$REGION.text
sections to place functions in RAM.
This proposal obsoletes rust-embedded/cortex-m-rt#32, rust-embedded/cortex-m-rt#100 and #398
cc @rust-embedded/cortex-m