diff --git a/.gitignore b/.gitignore index 249829e..d393d87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ +**/.idea/ *.o +.#* .stderr .stdout Cargo.lock book -target -**/.idea/ \ No newline at end of file +target \ No newline at end of file diff --git a/ci/concurrency/.cargo/config b/ci/concurrency/.cargo/config new file mode 100644 index 0000000..5d98277 --- /dev/null +++ b/ci/concurrency/.cargo/config @@ -0,0 +1,33 @@ +[target.thumbv7m-none-eabi] +# uncomment this to make `cargo run` execute programs on QEMU +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# uncomment ONE of these three option to make `cargo run` start a GDB session +# which option to pick depends on your system +# runner = "arm-none-eabi-gdb -q -x openocd.gdb" +# runner = "gdb-multiarch -q -x openocd.gdb" +# runner = "gdb -q -x openocd.gdb" + +rustflags = [ + # LLD (shipped with the Rust toolchain) is used as the default linker + "-C", "link-arg=-Tlink.x", + + # if you run into problems with LLD switch to the GNU linker by commenting out + # this line + # "-C", "linker=arm-none-eabi-ld", + + # if you need to link to pre-compiled C libraries provided by a C toolchain + # use GCC as the linker by commenting out both lines above and then + # uncommenting the three lines below + # "-C", "linker=arm-none-eabi-gcc", + # "-C", "link-arg=-Wl,-Tlink.x", + # "-C", "link-arg=-nostartfiles", +] + +[build] +# Pick ONE of these compilation targets +# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ +target = "thumbv7m-none-eabi" # Cortex-M3 +# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) +# target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) diff --git a/ci/concurrency/.gitignore b/ci/concurrency/.gitignore new file mode 100644 index 0000000..59a4524 --- /dev/null +++ b/ci/concurrency/.gitignore @@ -0,0 +1,5 @@ +**/*.rs.bk +.#* +.gdb_history +Cargo.lock +target/ diff --git a/ci/concurrency/Cargo.toml b/ci/concurrency/Cargo.toml new file mode 100644 index 0000000..d6120f3 --- /dev/null +++ b/ci/concurrency/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["Jorge Aparicio "] +edition = "2018" +readme = "README.md" +name = "concurrency" +version = "0.1.0" + +[dependencies] +cortex-m = "0.5.8" +cortex-m-rt = "=0.6.7" +cortex-m-semihosting = "0.3.2" +panic-halt = "0.2.0" +bare-metal = "0.2.4" + +[profile.release] +codegen-units = 1 # better optimizations +debug = true # symbols are nice and they don't increase the size on Flash +lto = true # better optimizations diff --git a/ci/concurrency/build.rs b/ci/concurrency/build.rs new file mode 100644 index 0000000..97d2806 --- /dev/null +++ b/ci/concurrency/build.rs @@ -0,0 +1,15 @@ +use std::{env, fs::File, io::Write, path::PathBuf}; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // Only re-run the build script when memory.x is changed, + // instead of when any part of the source code changes. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/ci/concurrency/examples/atomic.rs b/ci/concurrency/examples/atomic.rs new file mode 100644 index 0000000..e9d7518 --- /dev/null +++ b/ci/concurrency/examples/atomic.rs @@ -0,0 +1,28 @@ +// source: examples/atomic.rs +#![no_main] +#![no_std] + +extern crate panic_halt; + +use core::sync::atomic::{AtomicBool, Ordering}; + +use cortex_m_rt::{entry, exception}; + +static X: AtomicBool = AtomicBool::new(false); + +#[entry] +fn main() -> ! { + // omitted: configuring and enabling the `SysTick` interrupt + + // wait until `SysTick` returns before starting the main logic + while !X.load(Ordering::Relaxed) {} + + loop { + // main logic + } +} + +#[exception] +fn SysTick() { + X.store(true, Ordering::Relaxed); +} diff --git a/ci/concurrency/examples/coop.rs b/ci/concurrency/examples/coop.rs new file mode 100644 index 0000000..7d4932e --- /dev/null +++ b/ci/concurrency/examples/coop.rs @@ -0,0 +1,40 @@ +// source: examples/coop.rs + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::asm; +use cortex_m_rt::{entry, exception}; + +// priority = 0 (lowest) +#[inline(never)] +#[entry] +fn main() -> ! { + // omitted: enabling interrupts and setting their priorities + + loop { + asm::nop(); + } +} + +static mut COUNTER: u64 = 0; + +// priority = 1 +#[exception] +fn SysTick() { + // exclusive access to `COUNTER` + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counter += 1; +} + +// priority = 1 +#[exception] +fn SVCall() { + // exclusive access to `COUNTER` + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counter *= 2; +} diff --git a/ci/concurrency/examples/cs1.rs b/ci/concurrency/examples/cs1.rs new file mode 100644 index 0000000..a5eb7db --- /dev/null +++ b/ci/concurrency/examples/cs1.rs @@ -0,0 +1,45 @@ +// source: examples/cs1.rs + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::interrupt; +use cortex_m_rt::{entry, exception}; + +static mut COUNTER: u64 = 0; + +#[inline(never)] +#[entry] +fn main() -> ! { + loop { + // `SysTick` can preempt `main` at this point + + // start of critical section: disable interrupts + interrupt::disable(); // = `asm!("CPSID I" : : : "memory" : "volatile")` + // ^^^^^^^^ + + // `SysTick` can not preempt this block + { + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counter += 1; + } + + // end of critical section: re-enable interrupts + unsafe { interrupt::enable() } + //^= `asm!("CPSIE I" : : : "memory" : "volatile")` + // ^^^^^^^^ + + // `SysTick` can start at this point + } +} + +#[exception] +fn SysTick() { + // exclusive access to `COUNTER` + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counter += 1; +} diff --git a/ci/concurrency/examples/cs2.rs b/ci/concurrency/examples/cs2.rs new file mode 100644 index 0000000..daca000 --- /dev/null +++ b/ci/concurrency/examples/cs2.rs @@ -0,0 +1,64 @@ +// source: examples/cs2.rs + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use core::sync::atomic::{self, Ordering}; + +use cortex_m_rt::{entry, exception}; + +static mut COUNTER: u64 = 0; + +#[inline(never)] +#[entry] +fn main() -> ! { + let mut syst = cortex_m::Peripherals::take().unwrap().SYST; + + // omitted: configuring and enabling the `SysTick` interrupt + + loop { + // `SysTick` can preempt `main` at this point + + // start of critical section: disable the `SysTick` interrupt + syst.disable_interrupt(); + // ^ this method is implemented as shown in the comment below + // + // ``` + // let csr = ptr::read_volatile(0xE000_E010);` + // ptr::write_volatile(0xE000_E010, csr & !(1 << 1)); + // ``` + + // a compiler barrier equivalent to the "memory" clobber + atomic::compiler_fence(Ordering::SeqCst); + + // `SysTick` can not preempt this block + { + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counter += 1; + } + + atomic::compiler_fence(Ordering::SeqCst); + + // end of critical section: re-enable the `SysTick` interrupt + syst.enable_interrupt(); + // ^ this method is implemented as shown in the comment below + // + // ``` + // let csr = ptr::read_volatile(0xE000_E010);` + // ptr::write_volatile(0xE000_E010, csr | (1 << 1)); + // ``` + + // `SysTick` can start at this point + } +} + +#[exception] +fn SysTick() { + // exclusive access to `COUNTER` + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counter += 1; +} diff --git a/ci/concurrency/examples/cs3.rs b/ci/concurrency/examples/cs3.rs new file mode 100644 index 0000000..a9b5ea4 --- /dev/null +++ b/ci/concurrency/examples/cs3.rs @@ -0,0 +1,76 @@ +// source: examples/cs3.rs + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::{asm, register::basepri}; +use cortex_m_rt::{entry, exception}; + +// priority = 0 (lowest) +#[inline(never)] +#[entry] +fn main() -> ! { + // omitted: enabling interrupts and setting up their priorities + + loop { + asm::nop(); + } +} + +static mut COUNTER: u64 = 0; + +// priority = 2 +#[exception] +fn SysTick() { + // exclusive access to `COUNTER` + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counter += 1; +} + +// priority = 1 +#[exception] +fn SVCall() { + // `SysTick` can preempt `SVCall` at this point + + // start of critical section: raise the running priority to 2 + raise(2); + + // `SysTick` can *not* preempt this block because it has a priority of 2 (equal) + // `PendSV` *can* preempt this block because it has a priority of 3 (higher) + { + // exclusive access to `COUNTER` + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counter *= 2; + } + + // start of critical section: lower the running priority to its original value + unsafe { lower() } + + // `SysTick` can preempt `SVCall` again +} + +// priority = 3 +#[exception] +fn PendSV() { + // .. does not access `COUNTER` .. +} + +fn raise(priority: u8) { + const PRIO_BITS: u8 = 3; + + // (priority is encoded in hardware in the higher order bits of a byte) + // (also in this encoding a bigger number means lower priority) + let p = ((1 << PRIO_BITS) - priority) << (8 - PRIO_BITS); + + unsafe { basepri::write(p) } + //^= `asm!("MSR BASEPRI, $0" : "=r"(p) : : "memory" : "volatile")` + // ^^^^^^^^ +} + +unsafe fn lower() { + basepri::write(0) +} diff --git a/ci/concurrency/examples/init.rs b/ci/concurrency/examples/init.rs new file mode 100644 index 0000000..2bf3523 --- /dev/null +++ b/ci/concurrency/examples/init.rs @@ -0,0 +1,65 @@ +// source: examples/init.rs + +#![feature(maybe_uninit)] +#![no_main] +#![no_std] + +extern crate panic_halt; + +use core::mem::MaybeUninit; + +use cortex_m::{asm, interrupt}; +use cortex_m_rt::{entry, exception}; + +struct Thing { + _state: (), +} + +impl Thing { + // NOTE the constructor is not `const` + fn new() -> Self { + Thing { _state: () } + } + + fn do_stuff(&mut self) { + // .. + } +} + +// uninitialized static variable +static mut THING: MaybeUninit = MaybeUninit::uninitialized(); + +#[entry] +fn main() -> ! { + // # Initialization phase + + // done as soon as the device boots + interrupt::disable(); + + // critical section that can't be preempted by any interrupt + { + // initialize the static variable at runtime + unsafe { THING.set(Thing::new()) }; + + // omitted: configuring and enabling the `SysTick` interrupt + } + + // reminder: this is a compiler barrier + unsafe { interrupt::enable() } + + // # main loop + + // `SysTick` can preempt `main` at this point + + loop { + asm::nop(); + } +} + +#[exception] +fn SysTick() { + // this handler always observes the variable as initialized + let thing: &mut Thing = unsafe { &mut *THING.as_mut_ptr() }; + + thing.do_stuff(); +} diff --git a/ci/concurrency/examples/mutex.rs b/ci/concurrency/examples/mutex.rs new file mode 100644 index 0000000..c03d828 --- /dev/null +++ b/ci/concurrency/examples/mutex.rs @@ -0,0 +1,54 @@ +// source: examples/mutex.rs + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use core::cell::{RefCell, UnsafeCell}; + +use bare_metal::CriticalSection; +use cortex_m::interrupt; +use cortex_m_rt::{entry, exception}; + +struct Mutex(UnsafeCell); + +// TODO does T require a Sync / Send bound? +unsafe impl Sync for Mutex {} + +impl Mutex { + const fn new(value: T) -> Mutex { + Mutex(UnsafeCell::new(value)) + } + + // NOTE: the `'cs` constraint prevents the returned reference from outliving + // the `CriticalSection` token + fn borrow<'cs>(&self, _cs: &'cs CriticalSection) -> &'cs T { + unsafe { &*self.0.get() } + } +} + +static COUNTER: Mutex> = Mutex::new(RefCell::new(0)); + +#[inline(never)] +#[entry] +fn main() -> ! { + loop { + // `interrupt::free` runs the closure in a critical section (interrupts disabled) + interrupt::free(|cs: &CriticalSection| { + let counter: &RefCell = COUNTER.borrow(cs); + + *counter.borrow_mut() += 1; + + // &*counter.borrow() //~ ERROR: this reference cannot outlive the closure + }); + } +} + +#[exception] +fn SysTick() { + interrupt::free(|cs| { + let counter = COUNTER.borrow(cs); + *counter.borrow_mut() *= 2; + }); +} diff --git a/ci/concurrency/examples/state.rs b/ci/concurrency/examples/state.rs new file mode 100644 index 0000000..fa53140 --- /dev/null +++ b/ci/concurrency/examples/state.rs @@ -0,0 +1,29 @@ +// source: examples/state.rs + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::asm; +use cortex_m_rt::{entry, exception}; + +#[inline(never)] +#[entry] +fn main() -> ! { + loop { + // SysTick(); //~ ERROR: cannot find function `SysTick` in this scope + + asm::nop(); + } +} + +#[exception] +fn SysTick() { + static mut COUNTER: u64 = 0; + + // user code + *COUNTER += 1; + + // SysTick(); //~ ERROR: cannot find function `SysTick` in this scope +} diff --git a/ci/concurrency/examples/static-mut.rs b/ci/concurrency/examples/static-mut.rs new file mode 100644 index 0000000..c234707 --- /dev/null +++ b/ci/concurrency/examples/static-mut.rs @@ -0,0 +1,42 @@ +//! THIS PROGRAM IS UNSOUND! +// source: examples/static-mut.rs + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::asm; +use cortex_m_rt::{entry, exception}; + +static mut X: u32 = 0; + +#[inline(never)] +#[entry] +fn main() -> ! { + // omitted: configuring and enabling the `SysTick` interrupt + + let x: &mut u32 = unsafe { &mut X }; + + loop { + *x = 0; + + // <~ preemption could occur here and change the value behind `x` + + if *x != 0 { + // the compiler may optimize away this branch + panic!(); + } else { + asm::nop(); + } + } +} + +#[exception] +fn SysTick() { + unsafe { + X = 1; + + asm::nop(); + } +} diff --git a/ci/concurrency/examples/systick.rs b/ci/concurrency/examples/systick.rs new file mode 100644 index 0000000..7bab7b2 --- /dev/null +++ b/ci/concurrency/examples/systick.rs @@ -0,0 +1,33 @@ +// source: examples/systick.rs + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use cortex_m::{asm, peripheral::syst::SystClkSource, Peripherals}; +use cortex_m_rt::{entry, exception}; +use cortex_m_semihosting::hprint; + +// program entry point +#[entry] +fn main() -> ! { + let mut syst = Peripherals::take().unwrap().SYST; + + // configures the system timer to trigger a SysTick interrupt every second + syst.set_clock_source(SystClkSource::Core); + syst.set_reload(12_000_000); // period = 1s + syst.enable_counter(); + syst.enable_interrupt(); + + loop { + asm::nop(); + } +} + +// interrupt handler +// NOTE: the function name must match the name of the interrupt +#[exception] +fn SysTick() { + hprint!(".").unwrap(); +} diff --git a/ci/concurrency/examples/volatile.rs b/ci/concurrency/examples/volatile.rs new file mode 100644 index 0000000..92cc7e1 --- /dev/null +++ b/ci/concurrency/examples/volatile.rs @@ -0,0 +1,44 @@ +//! THIS PROGRAM IS UNSOUND! +// source: examples/volatile.rs + +#![no_main] +#![no_std] + +extern crate panic_halt; + +use core::ptr; + +use cortex_m::asm; +use cortex_m_rt::{entry, exception}; + +#[repr(u64)] +enum Enum { + A = 0x0000_0000_ffff_ffff, + B = 0xffff_ffff_0000_0000, +} + +static mut X: Enum = Enum::A; + +#[entry] +fn main() -> ! { + // omitted: configuring and enabling the `SysTick` interrupt + + loop { + // this write operation is not atomic: it's performed in two moves + unsafe { ptr::write_volatile(&mut X, Enum::A) } // <~ preemption + + unsafe { ptr::write_volatile(&mut X, Enum::B) } + } +} + +#[exception] +fn SysTick() { + unsafe { + // here we may observe `X` having the value `0x0000_0000_0000_0000` + // or `0xffff_ffff_ffff_ffff` which are not valid `Enum` variants + match X { + Enum::A => asm::nop(), + Enum::B => asm::bkpt(), + } + } +} diff --git a/ci/concurrency/memory.x b/ci/concurrency/memory.x new file mode 100644 index 0000000..b271f22 --- /dev/null +++ b/ci/concurrency/memory.x @@ -0,0 +1,34 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* TODO Adjust these memory regions to match your device memory layout */ + /* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 64K +} + +/* This is where the call stack will be allocated. */ +/* The stack is of the full descending type. */ +/* You may want to use this variable to locate the call stack and static + variables in different memory regions. Below is shown the default value */ +/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */ + +/* You can use this symbol to customize the location of the .text section */ +/* If omitted the .text section will be placed right after the .vector_table + section */ +/* This is required only on microcontrollers that store some configuration right + after the vector table */ +/* _stext = ORIGIN(FLASH) + 0x400; */ + +/* Example of putting non-initialized variables into custom RAM locations. */ +/* This assumes you have defined a region RAM2 above, and in the Rust + sources added the attribute `#[link_section = ".ram2bss"]` to the data + you want to place there. */ +/* Note that the section will not be zero-initialized by the runtime! */ +/* SECTIONS { + .ram2bss (NOLOAD) : ALIGN(4) { + *(.ram2bss); + . = ALIGN(4); + } > RAM2 + } INSERT AFTER .bss; +*/ diff --git a/ci/script.sh b/ci/script.sh index b5bc27f..dd97484 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -227,6 +227,14 @@ main() { cargo build --examples popd fi + + # # Concurrency + # NOTE(nightly) this will require nightly until `MaybeUninit` is stabilized + if [ $TRAVIS_RUST_VERSION = nightly ]; then + pushd concurrency + cargo build --examples + popd + fi } # checks that 2018 idioms are being used @@ -251,6 +259,25 @@ qemu_check() { rm .stdout .stderr } +# fake Travis variables to be able to run this on a local machine +if [ -z ${TRAVIS_BRANCH-} ]; then + TRAVIS_BRANCH=auto +fi + +if [ -z ${TRAVIS_RUST_VERSION-} ]; then + case $(rustc -V) in + *nightly*) + TRAVIS_RUST_VERSION=nightly + ;; + *beta*) + TRAVIS_RUST_VERSION=beta + ;; + *) + TRAVIS_RUST_VERSION=stable + ;; + esac +fi + # don't run this on successful merges if [[ $TRAVIS_BRANCH != main || $TRAVIS_PULL_REQUEST != false ]]; then main diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 7a25c7e..31942b7 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -9,5 +9,6 @@ - [Logging with symbols](./logging.md) - [Global singletons](./singleton.md) - [DMA](./dma.md) +- [Concurrency](./concurrency.md) --- [A note on compiler support](./compiler-support.md) diff --git a/src/concurrency.md b/src/concurrency.md new file mode 100644 index 0000000..da60d68 --- /dev/null +++ b/src/concurrency.md @@ -0,0 +1,760 @@ +# Concurrency + +This section discusses `no_std` concurrency as usually found on +microcontrollers, and memory safe patterns for sharing memory with / between +interrupt handlers. The focus of this text is on uses of `unsafe` code that are +memory safety rather than building safe abstractions. + +> **NOTE:** Unlike other chapters, this text has been written assuming that the +> reader is *not* familiar with the interrupt mechanism commonly found in +> microcontrollers. The motivation is making this text accessible to more people +> who then can audit our `unsafe` code. + +# Interrupts + +In bare metal systems, systems without an OS (operating system), usually the +only form of concurrency available are *hardware* interrupts. An interrupt +is a preemption mechanism that works as follows: when an *interrupt signal* +arrives the processor suspends the execution of the current subroutine, (maybe) +saves some registers (the current state of the program) to the stack and then +jumps to another subroutine called the *interrupt handler*. When the processor +returns from the interrupt handler, it restores the registers that it previously +saved on the stack (if any) and then resumes the subroutine that was +interrupted. (If you are familiar with POSIX signal handling, the semantics are +pretty much the same) + +Interrupt signals usually come from peripherals and are fired *asynchronously*. +Some examples of interrupt signals are: a counter reaching zero, an input pin +changing its electrical / logical state, and the arrival of a new byte of data. +In some multi-core devices a core can send an interrupt signal to a different +core. + +How the processor locates the right interrupt handler to execute depends on the +architecture. In the ARM Cortex-M architecture, there's one handler per +interrupt signal and there's a table somewhere in memory that holds function +pointers to all interrupt handlers. Each interrupt is given an index in this +table. For example, a timer interrupt could be interrupt #0 and an input pin +interrupt could be interrupt #1. If we were to depict this as Rust code it would +look as follows: + +``` rust +// `link_section` places this in some known memory location +#[link_section = ".interrupt_table"] +static INTERRUPT_TABLE: [extern "C" fn(); 32] = [ + // entry 0: timer 0 + on_timer0_interrupt, + + // entry 1: pin 0 + on_pin0_interrupt, + + // .. 30 more entries .. +]; + +// provided by the application author +extern "C" fn on_timer0_interrupt() { + // .. +} + +extern "C" fn on_pin0_interrupt() { + // .. +} +``` + +In another common interrupt model all interrupts signals map to the *same* +interrupt handler (subroutine) and there's a hardware register that the software +has to read when it enters the handler to figure out which interrupt signal +triggered the interrupt. In this text, we'll focus on the ARM Cortex-M +architecture which follows the one handler per interrupt signal model. + +## Interrupt handling API + +The most basic interrupt handling API lets the programmer *statically* register +a function for each interrupt handler *only once*. On top of this basic API +it's possible to implement APIs to *dynamically* register closures as interrupt +handlers. In this text we'll focus on the former, simpler API. + +To illustrate this kind of API let's look at the [`cortex-m-rt`] crate (v0.6.7). +It provides two attributes to statically register interrupts: `#[exception]` and +`#[interrupt]`. The former is for device agnostic interrupts, whose number and +names are the same for all Cortex-M devices; the latter is for device specific +interrupts, whose number and names vary per device / vendor. We'll stick to the +device agnostic interrupts ("exceptions") in our examples. + +[`cortex-m-quickstart`]: https://github.com/rust-embedded/cortex-m-quickstart +[`cortex-m-rt`]: https://crates.io/crates/cortex-m-rt/0.6.7 + +The following example showcases the system timer (`SysTick`) interrupt, which +fires periodically. The interrupt is handled using the `SysTick` handler +(function), which prints a dot to the console. + +> **NOTE:** The code for the following example and all other examples can be +> found in the `ci/concurrency` directory at the root of [this repository]. + +[this repository]: https://github.com/rust-embedded/embedonomicon + +``` rust +{{#include ../ci/concurrency/examples/systick.rs}}``` + +If you are not familiar with embedded / Cortex-M programs the most important +thing to note here is that the function marked with the `entry` attribute is the +entry point of the user program. When the device (re)boots (e.g. it's first +powered) the "runtime" (the `cortex-m-rt` crate) initializes `static` variables +(the content of RAM is random on power on) and then calls the user program entry +point. As the user program is the only process running it is not allowed to end +/ exit; this is enforced in the signature of the `entry` function: `fn() -> !` +-- a divergent function can't return. + +You can run this example on an x86 machine using QEMU. Make sure you have +`qemu-system-arm` installed and run the following command + +``` console +$ cargo run --example systick +(..) + Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel target/thumbv7m-none-eabi/debug/examples/systick` +................. +``` + +## `static` variables: what is safe and what's not + +As interrupt handlers have their own (call) stack they can't refer to (access) +local variables in `main` or in functions called by `main`. The only way `main` +and an interrupt handler can share state is through `static` variables, which +have statically known addresses. + +To really drive this point I find it useful to visualize the call stack of the +program in the presence of interrupts. Consider the following example: + +``` rust +#[entry] +fn main() -> ! { + + loop { + { + let x = 42; + foo(); + } + + { + let w = 66; + bar(); + } + } +} + +fn foo() { + let y = 24; + + // .. +} + +fn bar() { + let z = 33; + + // .. + + foo(); + + // .. +} + +#[exception] +fn SysTick() { + // can't access `x` or `y` because their addresses are not statically known +} +``` + +If we take snapshots of the call stack every time the `SysTick` interrupt +handler is called we'll observe something like this: + +``` text + +---------+ + | SysTick | + | | + +---------+ +---------+ +#########+ + | SysTick | | SysTick | | foo | + | | | | | y = 24 | + +#########+ +#########+ +---------+ + | foo | | bar | | bar | + | y = 24 | | z = 33 | | z = 33 | + +---------+ +---------+ +---------+ + | main | | main | | main | + | x = 42 | | w = 66 | | w = 66 | + +---------+ +---------+ +---------+ + t = 1ms t = 2ms t = 3ms +``` + +From the call stack `SysTick` looks like a normal function since it's contiguous +in memory to `main` and the functions called from it. However, that's not the +case: `SysTick` is invoked asynchronously. At time `t = 1ms` `SysTick` could, in +theory, access `y` since it's in the previous stack frame; however, at time `t = +2ms` `y` doesn't exist; and at time `t = 3ms` `y` exists but has a different +location in memory (address). + +I hope that explains why `SysTick` can't safely access the stack frames that +belong to `main`. + +Let's now go over all the `unsafe` and safe ways in which `main` and interrupt +handlers can share state (memory). We'll start assuming the program will run on +a single core device, then we'll revisit our safe patterns in the context of a +multi-core device. + +### `static mut` + +Unsynchronized access to `static mut` variables is undefined behavior (UB). The +compiler *will* mis-optimize all those accesses. + +Consider the following *unsound* program: + +``` rust +{{#include ../ci/concurrency/examples/static-mut.rs}}``` + +This program compiles: both `main` and `SysTick` can refer to the static +variable `X`, which has a known, fixed location in memory. However, the program +is mis-optimized to the following machine code: + +``` armasm +00000400
: + 400: bf00 nop + 402: e7fd b.n 400
+ +00000404 : + 404: bf00 nop + 406: 4770 bx lr +``` + +As you can see all accesses to `X` were optimized away changing the intended +semantics. + +### Volatile + +Using volatile operations to access `static mut` variables does *not* prevent +UB. Volatile operations will prevent the compiler from mis-optimizing accesses +to the variables but they don't help with torn reads and writes which lead to +UB. + +``` rust +{{#include ../ci/concurrency/examples/volatile.rs}}``` + +In this program the interrupt handler could preempt the 2-step write operation +that changes `X` from variant `A` to variant `B` (or vice versa) mid way. If +that happens the handler could observe `X` having the value +`0x0000_0000_0000_0000` or `0xffff_ffff_ffff_ffff`, neither of which are valid +values for the enum. + +Let me say that again: *Relying only on volatile operations for memory safety +is likely wrong*. The only semantics that volatile operations provide are: +"tell the compiler to not remove this operation, or merge it with another +operation" and "tell the compiler to not reorder this operation with respect to +other *volatile* operations"; neither is directly related to synchronized +access to memory. + +### Atomics + +Accessing [atomic types] stored in `static` variables is memory safe. If you are +building abstractions like channels on top of them (which likely will require +`unsafe` code to access some shared buffer) make sure you use the right +`Ordering` or your abstraction will be unsound. + +[atomic types]: https://doc.rust-lang.org/core/sync/atomic/index.html + +Here's an example of using a static variable for synchronization (a delay in +this case). + +> **NOTE:** not all embedded targets have atomic CAS instructions in their ISA. +> MSP430 and ARMv6-M are prime examples. API like `AtomicUsize.fetch_add` is not +> available in `core` for those targets. + +``` rust +static X: AtomicBool = AtomicBool::new(false); + +#[entry] +fn main() -> ! { + // omitted: configuring and enabling the `SysTick` interrupt + + // wait until `SysTick` returns before starting the main logic + while !X.load(Ordering::Relaxed) {} + + loop { + // main logic + } +} + +#[exception] +fn SysTick() { + X.store(true, Ordering::Relaxed); +} +``` + +### State and re-entrancy + +A common pattern in embedded C is to use a `static` variable to preserve state +between invocations of an interrupt handler. + +``` c +void handler() { + static int counter = 0; + + counter += 1; + + // .. +} +``` + +This makes the function non-reentrant, meaning that calling this function from +itself, from `main` or an interrupt handler is UB (it breaks mutable aliasing +rules). + +We can make this C pattern safe in Rust if we make the non-reentrant function +`unsafe` to call or impossible to call. `cortex-m-rt` v0.5.x supports this +pattern and uses the latter approach to prevent calling non-reentrant functions +from safe code. + +Consider this example: + +``` rust +{{#include ../ci/concurrency/examples/state.rs}}``` + +The `#[exception]` attribute performs the following source-level transformation: + +``` rust +#[link_name = "SysTick"] // places this function in the vector table +fn randomly_generated_identifier() { + let COUNTER: &mut u64 = unsafe { + static mut COUNTER: u64 = 0; + + &mut COUNTER + }; + + // user code + *COUNTER += 1; + + // .. +} +``` + +Placing the `static mut` variable inside a block makes it impossible to create +more references to it from user code. + +This transformation ensures that the software can't call the interrupt handler +from safe code, but could the hardware invoke the interrupt handler in a way +that breaks memory safety? The answer is: *it depends*, on the target +architecture. + +In the ARM Cortex-M architecture once an instance of an interrupt handler starts +another one won't start until the first one ends (if the same interrupt signal +arrives again it is withheld). On the other hand, in the ARM Cortex-R +architecture there's a single handler for all interrupts; receiving two +different interrupt signals can cause the handler (function) to be invoked twice +and that would break the memory safety of the source level transformation we +presented above. + +### Critical sections + +When it's necessary to share state between `main` and an interrupt handler a +critical section can be used to synchronize access. The simplest critical +section implementation consists of temporarily disabling *all* interrupts while +`main` accesses the shared `static` variable. Example below: + +``` rust +{{#include ../ci/concurrency/examples/cs1.rs}}``` + +Note the use of the `"memory"` clobber; this acts as a compiler barrier that +prevents the compiler from reordering the operation on `COUNTER` to outside the +critical section. It's also important to *not* access `COUNTER` in `main` +outside a critical section; thus references to `COUNTER` should not escape the +critical section. With these two restrictions in place, the mutable reference to +`COUNTER` created in `SysTick` is guaranteed to be unique for the whole +execution of the handler. + +Disabling all the interrupt is not the only way to create a critical section; +other ways include masking interrupts (disabling one or a subset of all +interrupts) and increasing the running priority (see next section). + +Masking interrupts to create a critical section deserves an example because it +doesn't use inline `asm!` and thus requires explicit compiler barriers +(`atomic::compiler_fence`) for memory safety. + +``` rust +{{#include ../ci/concurrency/examples/cs2.rs}}``` + +The code is very similar to the one that disabled all interrupts except for the +start and end of the critical section, which now include a `compiler_fence` +(compiler barrier). + +### Priorities + +Architectures like ARM Cortex-M allow interrupt prioritization, meaning that an +interrupt that's given high priority can preempt a lower priority interrupt +handler. Priorities must be considered when sharing state between interrupt +handlers. + +When two interrupt handlers, say `A` and `B`, have the *same* priority no +preemption can occur. Meaning that when signals for both interrupts arrive +around the same time then the handlers will be executed sequentially: that is +first `A` and then `B`, or vice versa. In this scenario, both handlers can +access the same `static mut` variable *without* using a critical section; each +handler will "take turns" at getting exclusive access (`&mut-`) to the static +variable. Example below. + +``` rust +{{#include ../ci/concurrency/examples/coop.rs}}``` + +When two interrupt handlers have *different* priorities then one can preempt +the other. Safely sharing state between these two interrupts requires a critical +section in the lower priority handler -- just like in the case of `main` and an +interrupt handler. However, one more constraint is required: the priority of the +interrupts must remain fixed at runtime; reversing the priorities at runtime, +for example, would result in a data race. + +The following example showcases safe state sharing between two interrupt +handlers using a priority-based critical section. + +``` rust +{{#include ../ci/concurrency/examples/cs3.rs}}``` + +### Runtime initialization + +A common need in embedded Rust programs is moving, at runtime, a value from +`main` into an interrupt handler. This can be accomplished at zero cost by +enforcing sequential access to `static mut` variables. + +``` rust +{{#include ../ci/concurrency/examples/init.rs}}``` + +In this pattern is important to disable interrupts before yielding control to +the user program and enforcing that the end user initializes all the +uninitialized static variables before interrupts are re-enabled. Failure to do +so would result in interrupt handlers observing uninitialized static variables. + +## Redefining `Send` and `Sync` + +The core / standard library defines these two marker traits as: + +> `Sync`: types for which it is safe to share references between threads. +> +> `Send`: types that can be transferred across thread boundaries + +Threads are an OS abstraction so they don't exist "out of the box" in bare metal +context, though they can be implemented on top of interrupts. We'll broaden the +definition of these two marker traits to include bare metal code: + +- `Sync`: types for which it is safe to share references between *execution + contexts* that may preempt each other. + +- `Send`: types that can be transferred between *execution contexts* that may + preempt each other. + +An interrupt handler is an execution context independent of the `main` function, +which can be seen as the "bottom" execution context. An OS thread is also an +execution context. Each execution context has its own (call) stack and operates +independently of other execution contexts though they can share state. + +Preemption between any two execution contexts may or may not be possible. For +example, preemption can occur between two interrupt handlers if they have +different priorities, but no preemption can occur between the two if they have +the same priority. In the case of OS threads, it depends on the exact +implementation; in the most common case, any two threads can preempt each other +because the scheduler periodically context switches between them. + +Broadening the definitions of these marker traits does not change the rules +around `static` variables. They must still hold values that implement the `Sync` +trait. Atomics implement `Sync` so they are valid to place in `static` variables +in bare metal context. + +Let's now revisit the safe patterns we described before and see where the `Sync` +and `Send` bounds need to be enforced for safety. + +### State + +``` rust +#[exception] +fn SysTick() { + static mut X: Type = Type::new(); +} +``` + +Does `Type` need to satisfy `Sync` or `Send`? `X` is effectively owned by the +`SysTick` interrupt and not shared with any other execution context so neither +bound is required for this pattern. + +### Critical section + +We can abstract the "disable all interrupts" critical section pattern into a +`Mutex` type. + +> Aside: "MutEx" stands for Mutual Exclusion and it's a synchronization +> mechanism that ensures that execution contexts (threads or interrupt handlers) +> get access to a single memory location in a "mutually exclusive" fashion. +> Meaning that at any point in time at most one execution context gets exclusive +> access over the memory location; only the execution context with exclusive +> access can read / write to said memory location. + +``` rust +{{#include ../ci/concurrency/examples/mutex.rs}}``` + +Here we use a `CriticalSection` token to prevent references escaping the +critical section / closure (see the lifetime constraints in `Mutex.borrow`). + +It's important to note that a `Mutex.borrow_mut` method with no additional +runtime checks would be unsound as it would let the end user break Rust aliasing +rules: + +``` rust +#[exception] +fn SysTick() { + interrupt::free(|cs| { + // both `counter` and `alias` refer to the same memory location + let counter: &mut u64 = COUNTER.borrow_mut(cs); + let alias: &mut u64 = COUNTER.borrow_mut(cs); + }); +} +``` + +Changing the signature of `borrow_mut` to `fn<'cs>(&self, &'cs mut +CriticalSection) -> &'cs mut T` does *not* help because it's possible to nest +calls to `interrupt::free`. + +``` rust +#[exception] +fn SysTick() { + interrupt::free(|cs: &mut CriticalSection| { + let counter: &mut u64 = COUNTER.borrow_mut(cs); + + // let alias: &mut u64 = COUNTER.borrow_mut(cs); + //~^ ERROR: `cs` already mutably borrowed + + interrupt::free(|cs2: &mut CriticalSection| { + // this breaks aliasing rules + let alias: &mut u64 = COUNTER.borrow_mut(cs2); + }); + }); +} +``` + +As for the bounds required on the value of type `T` protected by the `Mutex`: +`T` must implement the `Send` trait because a `Mutex` can be used as a channel +to move values from `main` to an interrupt handler. See below: + +``` rust +struct Thing { + _state: (), +} + +static CHANNEL: Mutex>> = Mutex::new(RefCell::new(None)); + +#[entry] +fn main() -> ! { + interrupt::free(|cs| { + let channel = CHANNEL.borrow(cs); + + *channel.borrow_mut() = Some(Thing::new()); + }); + + loop { + asm::nop(); + } +} + +#[exception] +fn SysTick() { + interrupt::free(|cs| { + let channel = CHANNEL.borrow(cs); + let maybe_thing = channel.borrow_mut().take(); + if let Some(thing) = mabye_thing { + // `thing` has been moved into the interrupt handler + } + }); +} +``` + +So the `Sync` implementation must look like this: + +``` rust +unsafe impl Sync for Mutex where T: Send {} +``` + +This constraint applies to all types of critical sections. + +### Cooperative handlers + +In the case of interrupt handlers that run at the same priority and access the +same static variable (see `examples/coop.rs`) no bound is required as no +preemption is possible. + +### Runtime initialization + +For the pattern of moving values from `main` to an interrupt handler this is +clearly a "send" operation so the moved value must implement the `Send` trait. +We won't give an example of an abstraction for that pattern in this text but any +such abstraction must enforce at compile time that values to be moved implement +the `Send` trait. + +## Multi-core + +So far we have discussed single core devices. Let's see how having multiple +cores affects the memory safety of the abstractions and patterns we have +covered. + +### `Mutex: !Sync` + +The `Mutex` abstraction we created and that disables interrupts to create a +critical section is unsound in multi-core context. The reason is that the +critical section doesn't prevent *other* cores from making progress so if more +than one core gets a reference to the data behind the `Mutex` all accesses +become data races. + +Here an example where we assume a dual-core device and a framework that lets you +write bare-metal multi-core in a single source file. + +``` rust +// THIS PROGRAM IS UNSOUND! + +// single memory location visible to both cores +static COUNTER: Mutex> = Mutex::new(Cell::new(0)); + +// runs on the first core +#[core(0)] +#[entry] +fn main() -> ! { + loop { + interrupt::free(|cs| { + let counter = COUNTER.borrow(cs); + + counter.set(counter.get() + 1); + }); + } +} + +// runs on the second core +#[core(1)] +#[entry] +fn main() -> ! { + loop { + interrupt::free(|cs| { + let counter = COUNTER.borrow(cs); + + counter.set(counter.get() * 2); + }); + } +} +``` + +Here each core accesses the `COUNTER` variable in their `main` context in an +unsynchronized manner; this is undefined behavior. + +The problem with `Mutex` is not the critical section that uses; it's the fact +that it can be stored in a `static` variable making accessible to all cores. +Thus in multi-core context the `Mutex` abstraction should not implement the +`Sync` trait. + +Critical sections based on interrupt masking *can* be used safely on +architectures / devices where it's possible to assign a *single* core to an +interrupt and any core can mask that interrupt, provided that scoping is +enforced somehow. Here's an example: + +``` rust +static mut COUNTER: u64 = 0; + +// runs on the first core +// priority = 2 +#[core(0)] +#[exception] +fn SysTick() { + // exclusive access to `COUNTER` + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counte += 1; +} + +// initialized in the second core's `main` function using the runtime +// initialization pattern +static mut SYST: MaybeUninit = MaybeUninit::ununitialized(); + +// runs on the second core +// priority = 1 +#[core(1)] +#[exception] +fn SVCall() { + // `SYST` is owned by this core / interrupt + let syst = unsafe { &mut *SYST.as_mut_ptr() }; + + // start of critical section: disable the `SysTick` interrupt + syst.disable_interrupt(); + + atomic::compiler_fence(Ordering::SeqCst); + + // `SysTick` can not preempt this block + { + let counter: &mut u64 = unsafe { &mut COUNTER }; + + *counter += 1; + } + + atomic::compiler_fence(Ordering::SeqCst); + + // end of critical section: re-enable the `SysTick` interrupt + syst.enable_interrupt(); +} +``` + +### Atomics + +Atomics are safe to use in multi-core context provided that memory barrier +instructions are inserted where appropriate. If you are using the correct +`Ordering` then the compiler will insert the required barriers for you. Critical +sections based on atomics, AKA spinlocks, are memory safe to use on multi-core +devices though they can deadlock. + +> Aside: an spinlock is a Mutual Exclusion mechanism that uses an atomic +> variable to synchronize access to a memory location. While an execution +> context has exclusive access over the memory location, any other execution +> context that attempts to access the memory location will continuously check +> the state of the atomic variable in a loop ("spin") until it indicates that +> the shared memory location is free to access. It's probably easiest to look at +> the [implementation] of a spinlock to understand how it works. + +[implementation]: https://docs.rs/spin/0.5.0/src/spin/mutex.rs.html#129-164 + +``` rust +// spin = "0.5.0" +use spin::Mutex; + +static COUNTER: Mutex = Mutex::new(0); + +// runs on the first core +#[core(0)] +#[entry] +fn main() -> ! { + loop { + *COUNTER.lock() += 1; + } +} + +// runs on the second core +#[core(1)] +#[entry] +fn main() -> ! { + loop { + *COUNTER.lock() *= 2; + } +} +``` + +### State + +The stateful interrupt handler pattern remains safe if and only if the target +architecture / device supports assigning a handler to a single core and the +program has been configured to not share stateful interrupts between cores -- +that is cores should *not* execute the exact same handler when the corresponding +signal arrives. + +### Cooperative handlers + +The cooperative handlers pattern remains sound if and only if the handlers that +share state are serviced by a single core. + +### Runtime initialization + +As the runtime initialization pattern is used to initialize the "state" of +interrupt handlers so all the additional constraints required for multi-core +memory safety of the state pattern are also required here.