Skip to main content

rpi_hal/
stack.rs

1//! Extent of the main stack, and how much of it is left.
2//!
3//! The stack is a region the linker script reserves and `boot.s` points
4//! `sp` at, rather than whatever happened to sit below the load address
5//! — so its size is a number in a file (`__stack_size`, 1 MiB by
6//! default, overridable with `-Wl,--defsym=__stack_size=...`), the same
7//! on both architectures.
8//!
9//! What this module is for is the question that is otherwise
10//! unanswerable from inside a program: *how close am I?* An overflow
11//! runs off the bottom of the region and takes a data abort — the `mmu`
12//! feature leaves the margin below it unmapped for exactly that reason —
13//! and with no handler for it that abort parks silently and looks like a
14//! hang in a driver. `rpi-hal`'s `fault-report` feature prints it
15//! instead, and names the overflow rather than leaving a plain
16//! translation fault to be recognised. One line at startup —
17//!
18//! ```ignore
19//! writeln!(uart, "sp {:#x}, {} KiB free", stack::pointer(),
20//!          stack::headroom().unwrap_or(0) / 1024)?;
21//! ```
22//!
23//! — turns that class of bug from a debugging session into a number.
24//!
25//! Only core 0's main stack lives in this region. A secondary core runs
26//! on the [`Stack<BYTES>`](crate::multicore::Stack) its application
27//! supplied, and the AArch32 exception modes run on their own banked
28//! regions, which is why [`used`] and
29//! [`headroom`] are `Option` — they
30//! report `None` rather than a meaningless number when `sp` isn't in
31//! this region at all.
32
33extern "C" {
34    /// Lowest address of the main stack region. Reserved by the linker
35    /// script; `sp` passing below this is an overflow.
36    static __stack_bottom: u8;
37    /// One past the highest address of the main stack region — the
38    /// value `boot.s` loads into `sp`, since the stack is
39    /// full-descending.
40    static __stack_top: u8;
41}
42
43/// Lowest address of the main stack region.
44pub fn bottom() -> usize {
45    &raw const __stack_bottom as usize
46}
47
48/// One past the highest address of the main stack region — where `sp`
49/// starts.
50pub fn top() -> usize {
51    &raw const __stack_top as usize
52}
53
54/// Size of the main stack region in bytes (`__stack_size`).
55pub fn size() -> usize {
56    top() - bottom()
57}
58
59/// The calling core's current stack pointer.
60///
61/// On a secondary core, or in an AArch32 exception mode, this is a
62/// pointer into that context's own stack rather than into this region —
63/// see this module's doc comment.
64pub fn pointer() -> usize {
65    let sp: usize;
66    // `mov <reg>, sp` is spelled the same on both architectures.
67    unsafe {
68        core::arch::asm!("mov {}, sp", out(reg) sp, options(nomem, nostack, preserves_flags))
69    };
70    sp
71}
72
73/// Bytes of the main stack currently in use, or `None` if the caller
74/// isn't running on it.
75pub fn used() -> Option<usize> {
76    let sp = pointer();
77    (bottom()..=top()).contains(&sp).then(|| top() - sp)
78}
79
80/// Bytes between the current stack pointer and the bottom of the main
81/// stack — the headroom left before an overflow — or `None` if the
82/// caller isn't running on it.
83///
84/// Worth logging once at startup in any program whose deepest call path
85/// isn't obvious: a `rustls` configuration or a mounted FAT volume can
86/// put tens of KiB in a single frame.
87pub fn headroom() -> Option<usize> {
88    let sp = pointer();
89    (bottom()..=top()).contains(&sp).then(|| sp - bottom())
90}