rp2040_multicore_per_cpu/lib.rs
1//! ## RP2040 Per-core static data
2//!
3//! The RP2040 has two cores. The rp2040-hal crate's [`multicore`][multicore]
4//! module makes it easy to start code on the second core. However, this brings
5//! with it the same challenges as multi-threaded programming: state in shared
6//! memory between the cores.
7//!
8//! This crate allows you to make use of the ([unstable][]) `#[thread_local]`
9//! attribute on static variables to make them per-core state. This allows the
10//! same code to run on both cores but with its own core-specific static state,
11//! such maintaining program state, or for things like DMA buffers.
12//!
13//! For example:
14//! ```rust,ignore
15//! #![feature(thread_local)]
16//! # use core::cell::RefCell;
17//! extern crate rp2040_multicore_per_cpu;
18//!
19//! #[thread_local]
20//! static MY_COUNTER: RefCell<usize> = RefCell::new(0);
21//!
22//! fn next_id() -> usize {
23//! MY_COUNTER.replace_with(|c| *c + 1)
24//! }
25//! ```
26//!
27//! Each core will get its own instance of the `MY_COUNTER` variable. Since
28//! these are not shared, they do not need atomic operations to update.
29//!
30//! These core-local variables are initialized on program startup and retain
31//! their value from there on, even between invocations of
32//! [`Core::spawn`][spawn].
33//!
34//! If the variables are zero-initialized then they will be reserved space in
35//! the `.tbss` section in the executable, and then space in `.bss` for each
36//! core. Similarly, variables initialized with non-zero constants will be in
37//! the executable's `.tdata` section, and have space reserved in `.bss`; the
38//! initial values are copied at program startup. Note that this uses the
39//! `__pre_init` hook to do this, so it won't be available for other uses.
40//!
41//! ## Build setup
42//!
43//! Note that this requires some setup in the linker script to allocate space
44//! for the static data. See memory.x for details. You also need to make you
45//! explicitly depend on this crate with `extern crate
46//! rp2040_multicore_per_cpu;`. This crate has no Rust API of its own, but must
47//! still be included in the linker line to ensure the `__aeabi_read_tp`
48//! function is defined.
49//!
50//! Also note this uses the `__pre_init` hook from [`cortex-m-rt`] to set up the
51//! per-core state at reset time, making it unavailable for other uses (this is
52//! rare however).
53//!
54//! [multicore]:
55//! https://docs.rs/rp2040-hal/latest/rp2040_hal/multicore/index.html
56//! [unstable]:
57//! https://doc.rust-lang.org/unstable-book/language-features/thread-local.html
58//! [spawn]:
59//! https://docs.rs/rp2040-hal/latest/rp2040_hal/multicore/struct.Core.html#method.spawn
60#![no_std]
61
62use core::arch::global_asm;
63
64extern "C" {
65 static mut TLS_CORE_0: u8;
66 static mut TLS_CORE_1: u8;
67}
68
69// Define `__aeabi_read_tp` called by the compiler to get access to
70// thread-local storage.
71global_asm! {
72 ".pushsection .text.__aeabi_read_tp",
73 ".align 4",
74 ".p2align 4,,15",
75 ".global __aeabi_read_tp",
76 ".type __aeabi_read_tp,%function",
77
78 "__aeabi_read_tp:",
79 " ldr r0, =0xd0000000", // Load SIO CPUID addr
80 " ldr r0, [r0]", // Load CPUID
81 " cmp r0, #0", // Check core 0
82 " ldr r0, ={core_0}", // Set TLS_CORE_0
83 " beq 1f", // skip if done
84 " ldr r0, ={core_1}", // Set TLS_CORE_1
85 "1: bx lr",
86
87 ".popsection",
88 core_0 = sym TLS_CORE_0,
89 core_1 = sym TLS_CORE_1,
90}
91
92// This must be pub for linkage but isn't a public API.
93mod inner {
94 use super::{TLS_CORE_0, TLS_CORE_1};
95 use core::ptr::{addr_of, addr_of_mut};
96
97 extern "C" {
98 static __tdata_start: u8;
99 static __tdata_end: u8;
100 static __tbss_start: u8;
101 static __tbss_end: u8;
102 }
103
104 /// Intercept __pre_init to hook into the startup code to copy the tdata into
105 /// TLS_CORE_0/1. TLS_CORE_0/1 are outside of the regular .bss segment, so we
106 /// also need to zero out the bss parts.
107 ///
108 /// NB: Run as the very first thing, nothing has been initialized and memory
109 /// could be in arbitrary state, so we only deal with things via raw pointers.
110 /// Assumes a stack has been set up.
111 #[cortex_m_rt::pre_init]
112 unsafe fn tls_pre_init_hook() {
113 for dst in [addr_of_mut!(TLS_CORE_0), addr_of_mut!(TLS_CORE_1)] {
114 let datalen = addr_of!(__tdata_end).offset_from(addr_of!(__tdata_start)) as usize;
115 if datalen > 0 {
116 core::ptr::copy(addr_of!(__tdata_start), dst, datalen);
117 }
118 let bsslen = addr_of!(__tbss_end).offset_from(addr_of!(__tbss_start)) as usize;
119 if bsslen > 0 {
120 dst.add(datalen).write_bytes(0, bsslen);
121 }
122 }
123 }
124}