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

Added Booting a Cortex-M #115

Merged
merged 1 commit into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion training-slides/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ Rust for the Linux Kernel and other no-std environments with an pre-existing C A
Topics about using Rust on ARM Cortex-M Microcontrollers (and similar). Requires [Applied Rust](#applied-rust).

* [Overview of Bare-Metal Rust](./rust-bare-metal.md)
* [Booting a Cortex-M Microcontroller](./booting-cortex-m.md)
* [PACs and svd2rust](./pac-svd2rust.md)
* [Writing Drivers](./writing-drivers.md)
* [The Embedded HAL and its implementations](./embedded-hals.md)
* [Board Support Crates](./board-support.md)

## Under development

* [Booting a Cortex-M Microcontroller]()
* [Exceptions and Interrupts on a Cortex-M Microcontroller]()
* [Using RTIC v1]()

Expand Down
158 changes: 158 additions & 0 deletions training-slides/src/booting-cortex-m.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Booting a Cortex-M Microcontroller

---

In this deck, we're talking specifically about Arm Cortex-M based microcontrollers.

Other Arm processors, and processors from other companies may vary.

## Terms

* Processor - the core that executes instructions
* SoC - the *system-on-a-chip* that contains a processor, some peripherals, and usually some memory
* Flash - the *flash memory* that the code and the constants live in
* RAM - the *random-access memory* that the global variables, heap and stack live in

## An example

* Arm Cortex-M4 - a processor core from Arm
* Use the `thumbv7em-none-eabi` or `thumbv7em-none-eabihf` targets
* nRF52840 - a SoC from Nordic Semi that uses that processor core

## An example (2)

* Arm Cortex-M0+ - a smaller, simpler, processor core from Arm
* Use the `thumbv6m-none-eabi` target
* RP2040 - a SoC from Raspberry Pi that uses *two* of those processor cores

## Booting a Cortex-M

The [Arm Architecture Reference Manual](https://developer.arm.com/documentation/ddi0403/ee/?lang=en) explains:

* The CPU boots at a well-defined address
* That word should contain a 32-bit RAM address for the stack pointer
* The word after should contain a 32-bit code address for the 'Reset' function
* The following 14 32-bit words are the exception handlers
* After that comes words for each interrupt handler

The chip does everything else.

## The steps

1. Make an array, or struct, with those two (or more) words in it
2. Convince the linker to put it at the right memory address
3. Profit

## C vector table

```c
__attribute__ ((section(".nvic_table"))) unsigned long myvectors[] =
{
(unsigned long) &_stack_top,
(unsigned long) rst_handler,
(unsigned long) nmi_handler,
// ...
}
```

## Rust vector table

```rust ignore
#[link_section=".nvic_table"]
#[no_mangle]
pub static ISR_VECTORS: [Option<Handler>; 155] = [
Some(_stack_top),
Some(rst_handler),
Some(nmi_handler),
// ...
]
```

Note:

The cortex-m-rt crate does it more nicely than this. Stuffing the `_stack_top` address in an array of function-pointers - yuck!

## C Reset Handler

Can be written in C! But it's hazardous.

```c
extern unsigned long _start_data_flash, _start_data, _end_data;
extern unsigned long _bss_start, _bss_end;

void rst_handler(void) {
unsigned long *src = &_start_data_flash;
unsigned long *dest = &_start_data;
while (dest < &_end_data) {
*dest++ = *src++;
}
dest = &_bss_start,
while (dest < &_bss_end) {
*dest++ = 0;
}
main();
while(1) { }
}
```

Note:

Global variables are not initialised when this function is executed. What if the C code touches an uninitialised global variable? C programmers don't worry so much about this. Rust programmers definitely worry about this.

## Rust Reset Handler (1)

```rust ignore
extern "C" {
static mut _start_data_flash: usize;
static mut _start_data: usize;
static mut _end_data: usize;
static mut _bss_start: usize;
static mut _bss_end: usize;
}
```

## Rust Reset Handler (2)

```rust ignore
#[no_mangle]
pub unsafe extern "C" fn rst_handler() {
let mut src: *mut usize = &mut _start_data_flash;
let mut dest: *mut usize = &mut _start_data;
while dest < &mut _end_data as *mut usize {
dest.volatile_write(src.read());
dest = dest.add(1);
src = src.add(1);
}
dest = &mut _bss_start as *mut usize;
while dest < &mut _end_data as *mut usize {
dest.volatile_write(0);
dest = dest.add(1);
}
main();
}
```

Note:

This is technically undefined behaviour because globals haven't been initialised yet.

## Linker scripts

* In Rust, they work exactly like they do in C.
* Same `.text`, `.rodata`, `.data`, `.bss` sections

## The cortex-m-rt crate

Does all this work for you, in raw Arm assembly language to avoid UB.

See [Reset](https://github.com/rust-embedded/cortex-m/blob/c-m-rt-v0.7.3/cortex-m-rt/src/lib.rs#L501), [Linker script](https://github.com/rust-embedded/cortex-m/blob/c-m-rt-v0.7.3/cortex-m-rt/link.x.in), and [Vector table](https://github.com/rust-embedded/cortex-m/blob/c-m-rt-v0.7.3/cortex-m-rt/src/lib.rs#L1130)

## The #[entry] macro

* Attaches your `fn main()` to the reset function in cmrt
* Hides your `fn main()` so no-one else can call it
* Remaps `static mut FOO: T` to `static FOO: &mut T` so they are safe

## Using the crate

See [Cortex-M Quickstart](https://github.com/rust-embedded/cortex-m-quickstart)