Skip to content

[RFC] user defined memory layout #402

Open
@japaric

Description

@japaric

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions