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

concurrency chapter #48

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
**/.idea/
*.o
.#*
.stderr
.stdout
Cargo.lock
book
target
**/.idea/
target
33 changes: 33 additions & 0 deletions ci/concurrency/.cargo/config
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 5 additions & 0 deletions ci/concurrency/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**/*.rs.bk
.#*
.gdb_history
Cargo.lock
target/
18 changes: 18 additions & 0 deletions ci/concurrency/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
authors = ["Jorge Aparicio <[email protected]>"]
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
15 changes: 15 additions & 0 deletions ci/concurrency/build.rs
Original file line number Diff line number Diff line change
@@ -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");
}
28 changes: 28 additions & 0 deletions ci/concurrency/examples/atomic.rs
Original file line number Diff line number Diff line change
@@ -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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using relaxed ordering is probably fine for this toy example since there's no other important memory accesses that could be reordered across the load or store, but if someone copies this example and starts making the wrong assumptions, it would be quite easy for their code to become unsound/incorrect. It might be better to default to sequentially consistent atomics for the examples.

}
40 changes: 40 additions & 0 deletions ci/concurrency/examples/coop.rs
Original file line number Diff line number Diff line change
@@ -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;
}
45 changes: 45 additions & 0 deletions ci/concurrency/examples/cs1.rs
Original file line number Diff line number Diff line change
@@ -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;
}
64 changes: 64 additions & 0 deletions ci/concurrency/examples/cs2.rs
Original file line number Diff line number Diff line change
@@ -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;
}
76 changes: 76 additions & 0 deletions ci/concurrency/examples/cs3.rs
Original file line number Diff line number Diff line change
@@ -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)
}
Loading