riscv_stack/lib.rs
1#![no_std]
2#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
3
4use core::{arch::asm, mem::size_of, ops::Range};
5
6/// The value used to paint the stack.
7pub const STACK_PAINT_VALUE: u32 = 0xCCCC_CCCC;
8
9/// The [Range] currently in use for the current hart's stack.
10///
11/// Note: the stack is defined in reverse, as it runs from 'start' to 'end' downwards.
12/// Hence this range is technically empty because `start >= end`.
13///
14/// If you want to use this range to do range-like things, use [stack_rev] instead.
15#[inline]
16pub fn stack() -> Range<*mut u32> {
17 unsafe extern "C" {
18 static mut _stack_start: u32;
19 static _hart_stack_size: usize;
20 }
21
22 // Current hart's ID
23 let hartid: usize;
24 // SAFETY: We are just reading from a CSR
25 unsafe { asm!("csrr {}, mhartid", out(reg) hartid) };
26
27 // The _hart_stack_size symbol's value, which is the size obviously,
28 // is represented by the address of the symbol.
29 //
30 // So we have to first make a fake pointer then treat it as an actual usize.
31 let stksz = &raw const _hart_stack_size as usize;
32
33 // Each hart has equal (_hart_stack_size) stack sizes.
34 //
35 // Thus the Nth hart's stack can be found by offsetting from the very top of stack
36 // down to _hart_stack_size * hartid.
37 //
38 // The below safety requirements would only end up violated if linker script is incorrect.
39 // The linker script from `riscv-rt` should satisfy these requirements.
40 //
41 // SAFETY: Linker script must ensure that `_stack_start - (_hart_stack_size * hartid)`
42 // is always within the available stack space.
43 let start = unsafe { (&raw mut _stack_start).byte_sub(hartid * stksz) };
44 // SAFETY: Linker script must also ensure that the above address, offset downward by another
45 // _hart_stack_size, is always within the available stack space and does not interfere
46 // with another hart's stack.
47 let end = unsafe { start.byte_sub(stksz) };
48
49 // But we want to ensure boundaries are 4 byte aligned before dereferencing them.
50 //
51 // So different harts will actually have slightly different stack sizes depending
52 // on if _hart_stack_size is divisble by 4 or not.
53 let start = start.map_addr(|p| p & !0b11);
54 let end = end.map_addr(|p| p & !0b11);
55
56 start..end
57}
58
59/// The [Range] currently in use for the current hart's stack,
60/// defined in reverse such that [Range] operations are viable.
61///
62/// Hence the `end` of this [Range] is where the current hart's stack starts.
63#[inline]
64pub fn stack_rev() -> Range<*mut u32> {
65 stack().end..stack().start
66}
67
68/// Convenience function to fetch the current hart's stack pointer.
69#[inline]
70pub fn current_stack_ptr() -> *mut u32 {
71 let res;
72 // SAFETY: Just reading the stack pointer nothing crazy
73 unsafe { asm!("mv {}, sp", out(reg) res) };
74 res
75}
76
77/// The number of bytes that are reserved for the current hart's stack at compile time.
78///
79/// Note: Although all harts have equal stack space reserved, their effective stack space
80/// may differ slightly due to alignment issues.
81#[inline]
82pub fn stack_size() -> usize {
83 // SAFETY: start >= end. If this is not the case your linker did something wrong.
84 unsafe { stack().start.byte_offset_from_unsigned(stack().end) }
85}
86
87/// The number of bytes of the current hart's stack that are currently in use.
88#[inline]
89pub fn current_stack_in_use() -> usize {
90 // SAFETY: start >= end. If this is not the case your linker did something wrong.
91 unsafe { stack().start.byte_offset_from_unsigned(current_stack_ptr()) }
92}
93
94/// The number of bytes of the current hart's stack that are currently free.
95///
96/// If the stack has overflowed, this function returns 0.
97#[inline]
98pub fn current_stack_free() -> usize {
99 stack_size().saturating_sub(current_stack_in_use())
100}
101
102/// What fraction of the current hart's stack is currently in use.
103#[inline]
104pub fn current_stack_fraction() -> f32 {
105 current_stack_in_use() as f32 / stack_size() as f32
106}
107
108/// Paint the part of the current hart's stack that is currently not in use.
109///
110/// **Note:** this can take some time, and an ISR could possibly interrupt this process,
111/// dirtying up your freshly painted stack.
112/// If you wish to prevent this, run this inside a critical section using `riscv::interrupt::free`.
113///
114/// Runs in *O(n)* where *n* is the size of the stack.
115/// This function is inefficient in the sense that it repaints the entire stack,
116/// even the parts that still have the [STACK_PAINT_VALUE].
117#[inline(never)]
118pub fn repaint_stack() {
119 // SAFETY: `stack()` has ensured we are staying within the bounds of the current hart's stack
120 unsafe {
121 asm!(
122 "0:",
123 "bgeu {ptr}, sp, 1f",
124 "sw {paint}, 0({ptr})",
125 "addi {ptr}, {ptr}, 4",
126 "j 0b",
127 "1:",
128 ptr = inout(reg) stack().end => _,
129 paint = in(reg) STACK_PAINT_VALUE,
130 )
131 };
132}
133
134/// Finds the number of bytes that have not been overwritten on the current hart's stack since the last repaint.
135///
136/// In other words: shows the worst case free stack space since [repaint_stack] was last called.
137///
138/// This measurement can only ever be an ESTIMATE, and not a guarantee, as the amount of
139/// stack can change immediately, even during an interrupt while we are measuring, or
140/// by a devious user or compiler that re-paints the stack, obscuring the max
141/// measured value. This measurement MUST NOT be used for load-bearing-safety
142/// guarantees, only as a (generally accurate but non-guaranteed) measurement.
143///
144/// Runs in *O(n)* where *n* is the size of the stack.
145#[inline(never)]
146pub fn stack_painted() -> usize {
147 let res: *const u32;
148 // SAFETY: As per the [rust reference], inline asm is allowed to look below the
149 // stack pointer. We read the values between the end of stack and the current stack
150 // pointer, which are all valid locations.
151 //
152 // In the case of interruption, there could be false negatives where we don't see
153 // stack that was used "behind" our cursor, however this is fine because we do not
154 // rely on this number for any safety-bearing contents, only as a metrics estimate.
155 //
156 // [rust reference]: https://doc.rust-lang.org/reference/inline-assembly.html#r-asm.rules.stack-below-sp
157 unsafe {
158 asm!(
159 "0:",
160 "bgeu {ptr}, sp, 1f",
161 "lw {value}, 0({ptr})",
162 "bne {value}, {paint}, 1f",
163 "addi {ptr}, {ptr}, 4",
164 "j 0b",
165 "1:",
166 ptr = inout(reg) stack().end => res,
167 value = out(reg) _,
168 paint = in(reg) STACK_PAINT_VALUE,
169 options(nostack, readonly)
170 )
171 };
172 // SAFETY: res >= stack.end() because we start at stack.end()
173 unsafe { res.byte_offset_from_unsigned(stack().end) }
174}
175
176/// Finds the number of bytes that have not been overwritten on the current hart's stack since the last repaint using binary search.
177///
178/// In other words: shows the worst case free stack space since [repaint_stack] was last called.
179///
180/// Uses binary search to find the point after which the stack is written.
181/// This will assume that the stack is written in a consecutive fashion.
182/// Writing somewhere out-of-order into the painted stack will not be detected.
183///
184/// Runs in *O(log(n))* where *n* is the size of the stack.
185///
186/// **Danger:** if the current (active) stack contains the [STACK_PAINT_VALUE] this computation may be very incorrect.
187///
188/// # Safety
189///
190/// This function aliases the inactive stack, which is considered to be Undefined Behaviour.
191/// Do not use if you care about such things.
192pub unsafe fn stack_painted_binary() -> usize {
193 // SAFETY: we should be able to read anywhere on the stack using this,
194 // but this is considered UB because we are aliasing memory out of nowhere.
195 // Will probably still work though.
196 let slice = unsafe { &*core::ptr::slice_from_raw_parts(stack().end, current_stack_free() / 4) };
197 slice.partition_point(|&word| word == STACK_PAINT_VALUE) * size_of::<usize>()
198}