Porting from static mut to SingleThreadValue
Starting in 2025 Tock is attempting to remove the use of static mut global
variables in Tock code. Correctly using static mut and avoiding unsound code
is very difficult. However, there are still valid uses of global mutable
variables, and Tock uses the
SingleThreadValue type
to enable safe global variables.
How to Use SingleThreadValue
To enable safe shared global state, first define a static SingleThreadValue:
#![allow(unused)] fn main() { static FOO: SingleThreadValue<...> = SingleThreadValue::new(...); }
The type parameter is the type of the global variable. For example storing boolean flag. To enable mutability, use interior mutability. For example:
#![allow(unused)] fn main() { static FOO: SingleThreadValue<Cell<bool>> = SingleThreadValue::new(Cell::new(false)); }
To make the contained value accessible (in this case the Cell<bool>), the
SingleThreadValue must be bound to a thread. This makes the contained value
safe to access. To do this, you need to provide an implementation of the
ThreadIdProvider trait.
This is commonly provided by the board's Chip. For many boards there is a
ChipHw type alias that is suitable, for example:
#![allow(unused)] fn main() { FOO.bind_to_thread::<<ChipHw as kernel::platform::chip::Chip>::ThreadIdProvider>(); }
This needs to be executed from the executing thread that will need to be able to
access the same state later. In single-core, single-thread CPUs there are
logically only two threads: the main execution and an interrupt context. In most
cases you will want to bind the SingleThreadValue to the main execution
thread.
From here, you can use the SingleThreadValue using the .get() method:
#![allow(unused)] fn main() { FOO.get().map(|value| { value.set(true); }); }
Porting Board main.rs Files
A major use case for global mutable variable is sharing state between the main execution of the Tock kernel and the panic handler. Because panics are asynchronous in nature, there is no intuitive method to pass state that would be useful for a panic handler to the panic handler when it starts executing. The workaround is to use a global variable.
Prior to SingleThreadValue these globals were typically defined as
static mut variables. With SingleThreadValue, we can now declare them as a
static SingleThreadValue.
To port from the prior static mut approach to the new SingleThreadValue
approach, follow these steps:
-
Add these imports to
main.rs:#![allow(unused)] fn main() { use kernel::debug::PanicResources; use kernel::utilities::single_thread_value::SingleThreadValue; } -
Replace
static muts for process, chip, and panic printer in main.rs with this:#![allow(unused)] fn main() { /// Resources for when a board panics used by io.rs. static PANIC_RESOURCES: SingleThreadValue<PanicResources<ChipHw, ProcessPrinterInUse>> = SingleThreadValue::new(PanicResources::new()); } -
If the board doesn't already have
type ChipHw, add something like this near the top of main.rs:#![allow(unused)] fn main() { pub type ChipHw = nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>; } -
Add a type for
ProcessPrinterInUse:#![allow(unused)] fn main() { type ProcessPrinterInUse = capsules_system::process_printer::ProcessPrinterText; } -
Near the start of
start()add this:#![allow(unused)] fn main() { // Bind global variables to this thread. PANIC_RESOURCES.bind_to_thread::<<ChipHw as kernel::platform::chip::Chip>::ThreadIdProvider>(); } -
Replace
PROCESSES = Some(processes);with#![allow(unused)] fn main() { PANIC_RESOURCES.get().map(|resources| { resources.processes.put(processes.as_slice()); }); } -
Replace
CHIP = Some(chip);with#![allow(unused)] fn main() { PANIC_RESOURCES.get().map(|resources| { resources.chip.put(chip); }); } -
Replace
PROCESS_PRINTER = Some(process_printer);with#![allow(unused)] fn main() { PANIC_RESOURCES.get().map(|resources| { resources.printer.put(process_printer); }); } -
In io.rs, where
debug::panic(...)is called, replace these arguments:#![allow(unused)] fn main() { PROCESSES.unwrap().as_slice(), &*addr_of!(CHIP), &*addr_of!(PROCESS_PRINTER), }with:
#![allow(unused)] fn main() { crate::PANIC_RESOURCES.get(), }