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}