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

[RFC] user defined memory layout #402

Open
japaric opened this issue Jan 12, 2019 · 8 comments
Open

[RFC] user defined memory layout #402

japaric opened this issue Jan 12, 2019 · 8 comments

Comments

@japaric
Copy link
Member

japaric commented Jan 12, 2019

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

@therealprof
Copy link
Contributor

I like the idea but if we're going to introduce such major breakage I would propose that we collect additional breaking changes (e.g. from the wishlist) and do it in one big swoop. E.g. I'd like to pitch my idea of splitting the #[entry] attribute up in two: One for initialisation and one for the main application. The initialisation function is an optional function which will run in a critical section and allows for safe initialisation of the MCU peripherals and interrupts. This could also incorporate the functionality of the Reset function proposed in this RFC.

If there's interest in this I'd be happy to put it into an RFC.

@korken89
Copy link
Contributor

Overall I like this idea as it gives a sane interface to memory layout. What I am worried about though is that this might not be transparent for people using it and being a bit too much "magic".
So if we go with this, proper documentation and design files would be very important.

If this starts going forward I can help test is as well.

@adamgreig
Copy link
Member

Sounds good! This seems a lot friendlier than requiring users put additional custom MEMORY areas
and new SECTIONS into their memory.x and then use #[link_section], without really adding too much magic. Plus it neatly lets us wrap up the ramfuncs and uninit etc features.

Does this make it harder to use a custom linker script entirely? At the moment you can just copy link.x from this repository, stick it in your crate, and update your .cargo/config to point at it. If the linker script becomes a lot more complicated I think this use case would also become more difficult. Perhaps the linker script construction API could permit including extra snippets of user-specified linker scripts? Or I suppose it could continue to generate link.x and my_link.x could include that and then make its own changes.

I wonder if we can combine the proposed new reset handling with the worries in rust-embedded/bare-metal#15 and @therealprof's suggestion of a separate optional #[init] function.

@pie-flavor
Copy link

While we're talking about higher level customizability I'd love the ability to make the reset handler naked and manually set up the stack inside it, since in my context it's being called by a bootloader.

@pie-flavor
Copy link

As for suggestions on how to make this less complicated - obviously you can't be reductive with a cargo feature, but who says it's got to still all be the same crate? Why not split up the frameworkiness of this crate with the 'default profile' that loads memory.x, pick one to keep in this crate, put the other in a new one, and set the framework as a dependency from the profile? Depending on how it's implemented, it might not even warrant a minor release bump. Would this work?

bors bot referenced this issue in rust-embedded/cortex-m-rt Dec 10, 2019
224: Allow general exception / interrupt discovery in cortex-m-rt-macros r=korken89 a=mciantyre

We propose changes to the `cortex-m-rt-macros` crate that should help us use the macros independent of the `cortex-m-rt` runtime. The changes in this PR should be backwards compatible for all `cortex-m-rt` users, while expanding the utility of the macros beyond the cortex-m-rt repository.

In the [Teensy 4 libraries](https://github.com/mciantyre/teensy4-rs) we're developing, we've opted to create our own runtime crate, `teensy4-rt`. We require more support than what's available in `cortex-m-rt` to boot and take advantage of NXP iMXRT106x variants. Specifically, we have a unique start-up process, and a custom memory layout with tightly-couple memory (TCM) regions. As discussed in #164, the `cortex-m-rt` crate does not support custom memory layouts. To address the limitations and provide a faster proof-of-concept, we forked the `cortex-m-rt` crate, focusing on the runtime needs of our specific system.

Despite the fork, we strive for compatibility with the `cortex-m-rt` crate. Our eventual goal is to drop the `teensy4-rt` crate, and rely on a future version of the `cortex-m-rt` crate that supports our use-case. Compatibility means supporting the macros; just as the `cortex-m-rt` crate exports the macros, the `teensy4-rt` crate exports the same macros. By requiring that the macros maintain `extern crate cortex_m_rt` declarations, we assume that the `cortex_m_rt` crate is available. However, we don't believe this is a necessary requirement.

To take advantage of the `#[interrupt]` and `#[exception]` macros, a set of crates need to export two identifiers: `interrupt`, an enumeration of valid interrupt handlers; and `exception`, an enumeration of exceptions for the Cortex M variant. We have a precedent for this pattern, in that crates generated by `svd2rust` export the enumeration of valid interrupt handlers (provided the correct features are enabled) for discovery by the `#[interrupt]` macros. The PR proposes a similar strategy: export the `Exceptions` enumeration as `exception` (lower case) from the `cortex-m-rt` crate, so that exception variant discovery occurs the same as it does for interrupts.

After the rename, and with the removal of `extern crate cortex_m_rt` in the two macros, it doesn't matter where the `exception` or `interrupt` enums are defined. The changes let us define a similar `exception` enumeration in our `teensy4-rt` crate, which may be picked up by the `#[exception]` macro. We've shown this to be a successful strategy in the Teensy 4 Rust libraries, which are based on our fork of the macros crate.

We realize that the macros are a feature of the `cortex-m-rt` crate, not a library that others should directly depend on. Ideally, we rally around the `cortex-m-rt` crate, and keep the macros coupled to that implementation. But until we reach that point, having the macros defined without expectations of the `cortex-m-rt` crate lets us bring up new embedded targets faster while maintaining compatibility with the existing ecosystem.

Co-authored-by: Ian McIntyre <[email protected]>
@korken89
Copy link
Contributor

I don't think there was a decision on this RFC.

@jonas-schievink
Copy link
Contributor

Adding this to the 1.0 milestone. It would be very unfortunate if we shipped 1.0 without being able to have this sort of fine-grained control over the memory layout.

@teburd
Copy link

teburd commented Apr 8, 2020

I started working on the basics provided in this API as the NXP i.MX RT series has quite a few features that are usable only with varying linker settings. See https://github.com/imxrt-rs/imxrt-rt-gen
I think its a good proving ground for this sort of thing.

mitchmindtree referenced this issue in mitchmindtree/teensy4-rs Jun 28, 2020
This adds a `cortex-m-rt-patch` directory that contains a tiny crate
that re-exports `teensy4-rt` in its entirety under the crate name
`cortex-m-rt` to provide a convenient way of patching upstream crates.

The hope is to make workarounds for issues like mciantyre#62 a little easier
pending solving rust-embedded/cortex-m-rt#164 and the reconciliation of
`cortex-m-rt` and `teensy4-rt`.
@adamgreig adamgreig transferred this issue from rust-embedded/cortex-m-rt Jan 23, 2022
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

7 participants