]>
Commit | Line | Data |
---|---|---|
9fa01778 XL |
1 | # Exceptions |
2 | ||
3 | Exceptions, and interrupts, are a hardware mechanism by which the processor | |
4 | handles asynchronous events and fatal errors (e.g. executing an invalid | |
5 | instruction). Exceptions imply preemption and involve exception handlers, | |
6 | subroutines executed in response to the signal that triggered the event. | |
7 | ||
8 | The `cortex-m-rt` crate provides an [`exception`] attribute to declare exception | |
9 | handlers. | |
10 | ||
11 | [`exception`]: https://docs.rs/cortex-m-rt-macros/latest/cortex_m_rt_macros/attr.exception.html | |
12 | ||
13 | ``` rust,ignore | |
14 | // Exception handler for the SysTick (System Timer) exception | |
15 | #[exception] | |
16 | fn SysTick() { | |
17 | // .. | |
18 | } | |
19 | ``` | |
20 | ||
21 | Other than the `exception` attribute exception handlers look like plain | |
22 | functions but there's one more difference: `exception` handlers can *not* be | |
23 | called by software. Following the previous example, the statement `SysTick();` | |
24 | would result in a compilation error. | |
25 | ||
26 | This behavior is pretty much intended and it's required to provide a feature: | |
27 | `static mut` variables declared *inside* `exception` handlers are *safe* to use. | |
28 | ||
29 | ``` rust,ignore | |
30 | #[exception] | |
31 | fn SysTick() { | |
32 | static mut COUNT: u32 = 0; | |
33 | ||
f035d41b | 34 | // `COUNT` has transformed to type `&mut u32` and it's safe to use |
9fa01778 XL |
35 | *COUNT += 1; |
36 | } | |
37 | ``` | |
38 | ||
39 | As you may know, using `static mut` variables in a function makes it | |
532ac7d7 | 40 | [*non-reentrant*](https://en.wikipedia.org/wiki/Reentrancy_(computing)). It's undefined behavior to call a non-reentrant function, |
9fa01778 XL |
41 | directly or indirectly, from more than one exception / interrupt handler or from |
42 | `main` and one or more exception / interrupt handlers. | |
43 | ||
44 | Safe Rust must never result in undefined behavior so non-reentrant functions | |
45 | must be marked as `unsafe`. Yet I just told that `exception` handlers can safely | |
46 | use `static mut` variables. How is this possible? This is possible because | |
47 | `exception` handlers can *not* be called by software thus reentrancy is not | |
48 | possible. | |
49 | ||
f035d41b XL |
50 | > Note that the `exception` attribute transforms definitions of static variables |
51 | > inside the function by wrapping them into `unsafe` blocks and providing us | |
52 | > with new appropriate variables of type `&mut` of the same name. | |
53 | > Thus we can derefence the reference via `*` to access the values of the variables without | |
54 | > needing to wrap them in an `unsafe` block. | |
55 | ||
9fa01778 XL |
56 | ## A complete example |
57 | ||
58 | Here's an example that uses the system timer to raise a `SysTick` exception | |
59 | roughly every second. The `SysTick` exception handler keeps track of how many | |
60 | times it has been called in the `COUNT` variable and then prints the value of | |
61 | `COUNT` to the host console using semihosting. | |
62 | ||
63 | > **NOTE**: You can run this example on any Cortex-M device; you can also run it | |
64 | > on QEMU | |
65 | ||
532ac7d7 | 66 | ```rust,ignore |
9fa01778 XL |
67 | #![deny(unsafe_code)] |
68 | #![no_main] | |
69 | #![no_std] | |
70 | ||
f035d41b | 71 | use panic_halt as _; |
9fa01778 XL |
72 | |
73 | use core::fmt::Write; | |
74 | ||
75 | use cortex_m::peripheral::syst::SystClkSource; | |
76 | use cortex_m_rt::{entry, exception}; | |
77 | use cortex_m_semihosting::{ | |
78 | debug, | |
79 | hio::{self, HStdout}, | |
80 | }; | |
81 | ||
82 | #[entry] | |
83 | fn main() -> ! { | |
84 | let p = cortex_m::Peripherals::take().unwrap(); | |
85 | let mut syst = p.SYST; | |
86 | ||
87 | // configures the system timer to trigger a SysTick exception every second | |
88 | syst.set_clock_source(SystClkSource::Core); | |
89 | // this is configured for the LM3S6965 which has a default CPU clock of 12 MHz | |
90 | syst.set_reload(12_000_000); | |
416331ca | 91 | syst.clear_current(); |
9fa01778 XL |
92 | syst.enable_counter(); |
93 | syst.enable_interrupt(); | |
94 | ||
95 | loop {} | |
96 | } | |
97 | ||
98 | #[exception] | |
99 | fn SysTick() { | |
100 | static mut COUNT: u32 = 0; | |
101 | static mut STDOUT: Option<HStdout> = None; | |
102 | ||
103 | *COUNT += 1; | |
104 | ||
105 | // Lazy initialization | |
106 | if STDOUT.is_none() { | |
107 | *STDOUT = hio::hstdout().ok(); | |
108 | } | |
109 | ||
110 | if let Some(hstdout) = STDOUT.as_mut() { | |
111 | write!(hstdout, "{}", *COUNT).ok(); | |
112 | } | |
113 | ||
114 | // IMPORTANT omit this `if` block if running on real hardware or your | |
115 | // debugger will end in an inconsistent state | |
116 | if *COUNT == 9 { | |
117 | // This will terminate the QEMU process | |
118 | debug::exit(debug::EXIT_SUCCESS); | |
119 | } | |
120 | } | |
121 | ``` | |
122 | ||
123 | ``` console | |
124 | $ tail -n5 Cargo.toml | |
125 | ``` | |
126 | ||
127 | ``` toml | |
128 | [dependencies] | |
129 | cortex-m = "0.5.7" | |
130 | cortex-m-rt = "0.6.3" | |
131 | panic-halt = "0.2.0" | |
132 | cortex-m-semihosting = "0.3.1" | |
133 | ``` | |
134 | ||
135 | ``` console | |
136 | $ cargo run --release | |
137 | Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..) | |
138 | 123456789 | |
139 | ``` | |
140 | ||
141 | If you run this on the Discovery board you'll see the output on the OpenOCD | |
142 | console. Also, the program will *not* stop when the count reaches 9. | |
143 | ||
144 | ## The default exception handler | |
145 | ||
146 | What the `exception` attribute actually does is *override* the default exception | |
147 | handler for a specific exception. If you don't override the handler for a | |
148 | particular exception it will be handled by the `DefaultHandler` function, which | |
149 | defaults to: | |
150 | ||
151 | ``` rust,ignore | |
152 | fn DefaultHandler() { | |
153 | loop {} | |
154 | } | |
155 | ``` | |
156 | ||
157 | This function is provided by the `cortex-m-rt` crate and marked as | |
158 | `#[no_mangle]` so you can put a breakpoint on "DefaultHandler" and catch | |
159 | *unhandled* exceptions. | |
160 | ||
161 | It's possible to override this `DefaultHandler` using the `exception` attribute: | |
162 | ||
163 | ``` rust,ignore | |
164 | #[exception] | |
165 | fn DefaultHandler(irqn: i16) { | |
166 | // custom default handler | |
167 | } | |
168 | ``` | |
169 | ||
170 | The `irqn` argument indicates which exception is being serviced. A negative | |
171 | value indicates that a Cortex-M exception is being serviced; and zero or a | |
172 | positive value indicate that a device specific exception, AKA interrupt, is | |
173 | being serviced. | |
174 | ||
175 | ## The hard fault handler | |
176 | ||
177 | The `HardFault` exception is a bit special. This exception is fired when the | |
178 | program enters an invalid state so its handler can *not* return as that could | |
179 | result in undefined behavior. Also, the runtime crate does a bit of work before | |
180 | the user defined `HardFault` handler is invoked to improve debuggability. | |
181 | ||
182 | The result is that the `HardFault` handler must have the following signature: | |
183 | `fn(&ExceptionFrame) -> !`. The argument of the handler is a pointer to | |
184 | registers that were pushed into the stack by the exception. These registers are | |
185 | a snapshot of the processor state at the moment the exception was triggered and | |
186 | are useful to diagnose a hard fault. | |
187 | ||
188 | Here's an example that performs an illegal operation: a read to a nonexistent | |
189 | memory location. | |
190 | ||
191 | > **NOTE**: This program won't work, i.e. it won't crash, on QEMU because | |
192 | > `qemu-system-arm -machine lm3s6965evb` doesn't check memory loads and will | |
193 | > happily return `0 `on reads to invalid memory. | |
194 | ||
532ac7d7 | 195 | ```rust,ignore |
9fa01778 XL |
196 | #![no_main] |
197 | #![no_std] | |
198 | ||
f035d41b | 199 | use panic_halt as _; |
9fa01778 XL |
200 | |
201 | use core::fmt::Write; | |
202 | use core::ptr; | |
203 | ||
204 | use cortex_m_rt::{entry, exception, ExceptionFrame}; | |
205 | use cortex_m_semihosting::hio; | |
206 | ||
207 | #[entry] | |
208 | fn main() -> ! { | |
209 | // read a nonexistent memory location | |
210 | unsafe { | |
211 | ptr::read_volatile(0x3FFF_FFFE as *const u32); | |
212 | } | |
213 | ||
214 | loop {} | |
215 | } | |
216 | ||
217 | #[exception] | |
218 | fn HardFault(ef: &ExceptionFrame) -> ! { | |
219 | if let Ok(mut hstdout) = hio::hstdout() { | |
220 | writeln!(hstdout, "{:#?}", ef).ok(); | |
221 | } | |
222 | ||
223 | loop {} | |
224 | } | |
225 | ``` | |
226 | ||
227 | The `HardFault` handler prints the `ExceptionFrame` value. If you run this | |
228 | you'll see something like this on the OpenOCD console. | |
229 | ||
230 | ``` console | |
231 | $ openocd | |
232 | (..) | |
233 | ExceptionFrame { | |
234 | r0: 0x3ffffffe, | |
235 | r1: 0x00f00000, | |
236 | r2: 0x20000000, | |
237 | r3: 0x00000000, | |
238 | r12: 0x00000000, | |
239 | lr: 0x080008f7, | |
240 | pc: 0x0800094a, | |
241 | xpsr: 0x61000000 | |
242 | } | |
243 | ``` | |
244 | ||
245 | The `pc` value is the value of the Program Counter at the time of the exception | |
246 | and it points to the instruction that triggered the exception. | |
247 | ||
248 | If you look at the disassembly of the program: | |
249 | ||
250 | ||
251 | ``` console | |
29967ef6 | 252 | $ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex |
9fa01778 XL |
253 | (..) |
254 | ResetTrampoline: | |
255 | 8000942: movw r0, #0xfffe | |
256 | 8000946: movt r0, #0x3fff | |
257 | 800094a: ldr r0, [r0] | |
258 | 800094c: b #-0x4 <ResetTrampoline+0xa> | |
259 | ``` | |
260 | ||
532ac7d7 XL |
261 | You can lookup the value of the program counter `0x0800094a` in the dissassembly. |
262 | You'll see that a load operation (`ldr r0, [r0]` ) caused the exception. | |
263 | The `r0` field of `ExceptionFrame` will tell you the value of register `r0` | |
264 | was `0x3fff_fffe` at that time. |