cortex_m/asm.rs
1//! Miscellaneous assembly instructions
2
3#![allow(missing_docs)]
4
5#[cfg(cortex_m)]
6use core::arch::asm;
7#[cfg(cortex_m)]
8use core::sync::atomic::{Ordering, compiler_fence};
9use cortex_m_macros::asm_cfg;
10
11/// Puts the processor in Debug state. Debuggers can pick this up as a "breakpoint".
12///
13/// **NOTE** calling `bkpt` when the processor is not connected to a debugger will cause an
14/// exception.
15#[inline(always)]
16#[asm_cfg(cortex_m)]
17pub fn bkpt() {
18 unsafe { asm!("bkpt", options(nomem, nostack, preserves_flags)) };
19}
20
21/// Blocks the program for *at least* `cycles` CPU cycles.
22///
23/// This is implemented in assembly as a fixed number of iterations of a loop, so that execution
24/// time is independent of the optimization level.
25///
26/// The loop code is the same for all architectures, however the number of CPU cycles required for
27/// one iteration varies substantially between architectures. This means that with a 48MHz CPU
28/// clock, a call to `delay(48_000_000)` is guaranteed to take at least 1 second, but for example
29/// could take 3 or more seconds.
30///
31/// In particular, Cortex-M7 cores can sometimes retire two instructions per clock cycle, leading
32/// to this particular loop generally taking one clock cycle per iteration. Most other Cortex-M
33/// cores will take three cycles per iteration.
34///
35/// NOTE that the delay can take much longer if interrupts are serviced during its execution and the
36/// execution time may vary with other factors. This delay is mainly useful for simple timer-less
37/// initialization of peripherals if and only if accurate timing is not essential. In any other case
38/// please use a more accurate method to produce a delay.
39#[inline]
40#[asm_cfg(cortex_m)]
41pub fn delay(cycles: u32) {
42 // Add 1 to prevent underflow on 0 which would cause a long freeze.
43 let real_cyc = cycles.saturating_add(1);
44 unsafe {
45 asm!(
46 // The `bne` on some cores (eg Cortex-M4) will take a different number of instructions
47 // depending on the alignment of the branch target. Set the alignment of the top of the
48 // loop to prevent surprising timing changes when the alignment of the delay() changes.
49 ".p2align 3",
50 // Use local labels to avoid R_ARM_THM_JUMP8 relocations which fail on thumbv6m.
51 "2:", // not 1 or 0 because of https://github.com/llvm/llvm-project/issues/99547
52 "subs {}, #1", // subtract 1 from real_cyc
53 "bne 2b", // branch to 2 if result is non-zero
54 inout(reg) real_cyc => _,
55 options(nomem, nostack),
56 )
57 };
58}
59
60/// A no-operation. Useful to prevent delay loops from being optimized away.
61#[inline]
62#[asm_cfg(cortex_m)]
63pub fn nop() {
64 // NOTE: This is a `pure` asm block, but applying that option allows the compiler to eliminate
65 // the nop entirely (or to collapse multiple subsequent ones). Since the user probably wants N
66 // nops when they call `nop` N times, let's not add that option.
67 unsafe { asm!("nop", options(nomem, nostack, preserves_flags)) };
68}
69
70/// Generate an Undefined Instruction exception.
71///
72/// Can be used as a stable alternative to `core::intrinsics::abort`.
73#[inline]
74#[asm_cfg(cortex_m)]
75pub fn udf() -> ! {
76 unsafe { asm!("udf #0", options(noreturn, nomem, nostack, preserves_flags)) };
77}
78
79/// Wait For Event
80#[inline]
81#[asm_cfg(cortex_m)]
82pub fn wfe() {
83 unsafe { asm!("wfe", options(nomem, nostack, preserves_flags)) };
84}
85
86/// Wait For Interrupt
87#[inline]
88#[asm_cfg(cortex_m)]
89pub fn wfi() {
90 unsafe { asm!("wfi", options(nomem, nostack, preserves_flags)) };
91}
92
93/// Send Event
94#[inline]
95#[asm_cfg(cortex_m)]
96pub fn sev() {
97 unsafe { asm!("sev", options(nomem, nostack, preserves_flags)) };
98}
99
100/// Instruction Synchronization Barrier
101///
102/// Flushes the pipeline in the processor, so that all instructions following the `ISB` are fetched
103/// from cache or memory, after the instruction has been completed.
104#[inline]
105#[asm_cfg(cortex_m)]
106pub fn isb() {
107 compiler_fence(Ordering::SeqCst);
108 unsafe { asm!("isb", options(nostack, preserves_flags)) };
109 compiler_fence(Ordering::SeqCst);
110}
111
112/// Data Synchronization Barrier
113///
114/// Acts as a special kind of memory barrier. No instruction in program order after this instruction
115/// can execute until this instruction completes. This instruction completes only when both:
116///
117/// * any explicit memory access made before this instruction is complete
118/// * all cache and branch predictor maintenance operations before this instruction complete
119#[inline]
120#[asm_cfg(cortex_m)]
121pub fn dsb() {
122 compiler_fence(Ordering::SeqCst);
123 unsafe { asm!("dsb", options(nostack, preserves_flags)) };
124 compiler_fence(Ordering::SeqCst);
125}
126
127/// Data Memory Barrier
128///
129/// Ensures that all explicit memory accesses that appear in program order before the `DMB`
130/// instruction are observed before any explicit memory accesses that appear in program order
131/// after the `DMB` instruction.
132#[inline]
133#[asm_cfg(cortex_m)]
134pub fn dmb() {
135 compiler_fence(Ordering::SeqCst);
136 unsafe { asm!("dmb", options(nostack, preserves_flags)) };
137 compiler_fence(Ordering::SeqCst);
138}
139
140/// Test Target
141///
142/// Queries the Security state and access permissions of a memory location.
143/// Returns a Test Target Response Payload (cf section D1.2.215 of
144/// Armv8-M Architecture Reference Manual).
145#[inline]
146#[asm_cfg(armv8m)]
147// The __tt function does not dereference the pointer received.
148#[allow(clippy::not_unsafe_ptr_arg_deref)]
149pub fn tt(addr: *mut u32) -> u32 {
150 let mut addr = addr as u32;
151 unsafe {
152 asm!(
153 "tt {addr}, {addr}",
154 addr = inout(reg) addr,
155 options(nomem, nostack, preserves_flags),
156 )
157 };
158 addr
159}
160
161/// Test Target Unprivileged
162///
163/// Queries the Security state and access permissions of a memory location for an unprivileged
164/// access to that location.
165/// Returns a Test Target Response Payload (cf section D1.2.215 of
166/// Armv8-M Architecture Reference Manual).
167#[inline]
168#[asm_cfg(armv8m)]
169// The __ttt function does not dereference the pointer received.
170#[allow(clippy::not_unsafe_ptr_arg_deref)]
171pub fn ttt(addr: *mut u32) -> u32 {
172 let mut addr = addr as u32;
173 unsafe {
174 asm!(
175 "ttt {addr}, {addr}",
176 addr = inout(reg)addr,
177 options(nomem, nostack, preserves_flags),
178 )
179 };
180 addr
181}
182
183/// Test Target Alternate Domain
184///
185/// Queries the Security state and access permissions of a memory location for a Non-Secure access
186/// to that location. This instruction is only valid when executing in Secure state and is
187/// undefined if used from Non-Secure state.
188/// Returns a Test Target Response Payload (cf section D1.2.215 of
189/// Armv8-M Architecture Reference Manual).
190#[inline]
191#[asm_cfg(armv8m)]
192// The __tta function does not dereference the pointer received.
193#[allow(clippy::not_unsafe_ptr_arg_deref)]
194pub fn tta(addr: *mut u32) -> u32 {
195 let mut addr = addr as u32;
196 unsafe {
197 asm!(
198 "tta {addr}, {addr}",
199 addr = inout(reg) addr,
200 options(nomem, nostack, preserves_flags),
201 )
202 };
203 addr
204}
205
206/// Test Target Alternate Domain Unprivileged
207///
208/// Queries the Security state and access permissions of a memory location for a Non-Secure and
209/// unprivileged access to that location. This instruction is only valid when executing in Secure
210/// state and is undefined if used from Non-Secure state.
211/// Returns a Test Target Response Payload (cf section D1.2.215 of
212/// Armv8-M Architecture Reference Manual).
213#[inline]
214#[asm_cfg(armv8m)]
215// The __ttat function does not dereference the pointer received.
216#[allow(clippy::not_unsafe_ptr_arg_deref)]
217pub fn ttat(addr: *mut u32) -> u32 {
218 let mut addr = addr as u32;
219 unsafe {
220 asm!(
221 "ttat {addr}, {addr}",
222 addr = inout(reg) addr,
223 options(nomem, nostack, preserves_flags),
224 )
225 };
226 addr
227}
228
229/// Branch and Exchange Non-secure
230///
231/// See section C2.4.26 of Armv8-M Architecture Reference Manual for details.
232/// Undefined if executed in Non-Secure state.
233#[inline]
234#[asm_cfg(armv8m)]
235pub unsafe fn bx_ns(addr: u32) {
236 unsafe { asm!("BXNS {}", in(reg) addr, options(nomem, nostack, preserves_flags)) };
237}
238
239/// Semihosting syscall.
240///
241/// This method is used by cortex-m-semihosting to provide semihosting syscalls.
242#[inline]
243#[asm_cfg(cortex_m)]
244pub unsafe fn semihosting_syscall(mut nr: u32, arg: u32) -> u32 {
245 unsafe {
246 asm!("bkpt #0xab", inout("r0") nr, in("r1") arg, options(nomem, nostack, preserves_flags))
247 };
248 nr
249}
250
251/// Switch to unprivileged mode using the Process Stack
252///
253/// Sets CONTROL.SPSEL (setting the Process Stack to be the active
254/// stack) and CONTROL.nPRIV (setting unprivileged mode), updates the
255/// program stack pointer to the address in `psp`, then jumps to the
256/// address in `entry`.
257///
258/// # Safety
259///
260/// * `psp` and `entry` must point to valid stack memory and executable code,
261/// respectively.
262/// * `psp` must be 8 bytes aligned and point to stack top as stack grows
263/// towards lower addresses.
264/// * The size of the stack provided here must be large enough for your
265/// program - stack overflows are obviously UB. If your processor supports
266/// it, you may wish to set the `PSPLIM` register to guard against this.
267#[inline(always)]
268#[asm_cfg(cortex_m)]
269pub unsafe fn enter_unprivileged_psp(psp: *const u32, entry: extern "C" fn() -> !) -> ! {
270 use crate::register::control::{Control, Npriv, Spsel};
271 const CONTROL_FLAGS: u32 = {
272 Control::from_bits(0)
273 .with_npriv(Npriv::Unprivileged)
274 .with_spsel(Spsel::Psp)
275 .bits()
276 };
277 unsafe {
278 core::arch::asm!(
279 "msr PSP, {psp}",
280 "mrs {tmp}, CONTROL",
281 "orrs {tmp}, {flags}",
282 "msr CONTROL, {tmp}",
283 "isb",
284 "bx {ent}",
285 tmp = in(reg) 0,
286 flags = in(reg) CONTROL_FLAGS,
287 psp = in(reg) psp,
288 ent = in(reg) entry,
289 options(noreturn, nostack)
290 );
291 }
292}
293
294/// Switch to using the Process Stack, but remain in Privileged Mode
295///
296/// Sets CONTROL.SPSEL (setting the Process Stack to be the active stack) but
297/// leaves CONTROL.nPRIV alone, updates the program stack pointer to the
298/// address in `psp`, then jumps to the address in `entry`.
299///
300/// # Safety
301///
302/// * `psp` and `entry` must point to valid stack memory and executable code,
303/// respectively.
304/// * `psp` must be 8 bytes aligned and point to stack top as stack grows
305/// towards lower addresses.
306/// * The size of the stack provided here must be large enough for your
307/// program - stack overflows are obviously UB. If your processor supports
308/// it, you may wish to set the `PSPLIM` register to guard against this.
309#[inline(always)]
310#[asm_cfg(cortex_m)]
311pub unsafe fn enter_privileged_psp(psp: *const u32, entry: extern "C" fn() -> !) -> ! {
312 use crate::register::control::{Control, Npriv, Spsel};
313 const CONTROL_FLAGS: u32 = {
314 Control::from_bits(0)
315 .with_npriv(Npriv::Privileged)
316 .with_spsel(Spsel::Psp)
317 .bits()
318 };
319 unsafe {
320 core::arch::asm!(
321 "msr PSP, {psp}",
322 "mrs {tmp}, CONTROL",
323 "orrs {tmp}, {flags}",
324 "msr CONTROL, {tmp}",
325 "isb",
326 "bx {ent}",
327 tmp = in(reg) 0,
328 flags = in(reg) CONTROL_FLAGS,
329 psp = in(reg) psp,
330 ent = in(reg) entry,
331 options(noreturn, nostack)
332 );
333 }
334}
335
336/// Bootstrap.
337///
338/// Clears CONTROL.SPSEL (setting the main stack to be the active stack),
339/// updates the main stack pointer to the address in `msp`, then jumps
340/// to the address in `rv`.
341///
342/// # Safety
343///
344/// `msp` and `rv` must point to valid stack memory and executable code,
345/// respectively.
346#[inline]
347#[asm_cfg(cortex_m)]
348pub unsafe fn bootstrap(msp: *const u32, rv: *const u32) -> ! {
349 // Ensure thumb mode is set.
350 let rv = (rv as u32) | 1;
351 let msp = msp as u32;
352 unsafe {
353 asm!(
354 "mrs {tmp}, CONTROL",
355 "bics {tmp}, {spsel}",
356 "msr CONTROL, {tmp}",
357 "isb",
358 "msr MSP, {msp}",
359 "bx {rv}",
360 // `out(reg) _` is not permitted in a `noreturn` asm! call,
361 // so instead use `in(reg) 0` and don't restore it afterwards.
362 tmp = in(reg) 0,
363 spsel = in(reg) 2,
364 msp = in(reg) msp,
365 rv = in(reg) rv,
366 options(noreturn, nomem, nostack),
367 )
368 };
369}
370
371/// Bootload.
372///
373/// Reads the initial stack pointer value and reset vector from
374/// the provided vector table address, sets the active stack to
375/// the main stack, sets the main stack pointer to the new initial
376/// stack pointer, then jumps to the reset vector.
377///
378/// # Safety
379///
380/// The provided `vector_table` must point to a valid vector
381/// table, with a valid stack pointer as the first word and
382/// a valid reset vector as the second word.
383#[inline]
384#[asm_cfg(cortex_m)]
385pub unsafe fn bootload(vector_table: *const u32) -> ! {
386 unsafe {
387 let msp = core::ptr::read_volatile(vector_table);
388 let rv = core::ptr::read_volatile(vector_table.offset(1));
389 bootstrap(msp as *const u32, rv as *const u32);
390 }
391}
392
393/// Transfer control to the Non-Secure application. Does not return.
394///
395/// This performs the standard Secure→Non-Secure boot handoff:
396/// 1. Sets `SCB_NS->VTOR` to `ns_vtor` so the Non-Secure world finds its vector table.
397/// 2. Loads `MSP_NS` from the first word of the NS vector table (the initial NS stack pointer).
398/// 3. Reads the NS reset handler address from the second word of the NS vector table.
399/// 4. Executes `BXNS` to atomically switch to Non-Secure state and jump to the handler.
400///
401/// # Safety
402/// - Must be called from the Secure world after all SAU/GTZC setup is complete.
403/// - `ns_vtor` must point to a valid Non-Secure vector table. The Cortex-M33 requires the VTOR
404/// to be at least 32-byte aligned; in practice 128-byte or 256-byte alignment is typical.
405/// - The NS reset handler at `*(ns_vtor + 1)` must be a valid Thumb function address (bit 0 set
406/// in the vector table entry, as per the ARM ABI convention for vector tables).
407/// - Available on ARMv8-M only (`thumbv8m.base` and `thumbv8m.main`).
408#[cfg(all(armv8m, feature = "secure-mode"))]
409pub unsafe fn bootload_ns(ns_vtor: *const u32, scb_ns: crate::peripheral::SCBNS) -> ! {
410 // Set NS_VTOR, so nonsecure mode uses that vector table
411 unsafe {
412 scb_ns.vtor.write(ns_vtor as usize as u32);
413 }
414
415 // Load the initial NS stack pointer from the first word of the NS vector table
416 // and write it into MSP_NS.
417 let ns_sp = unsafe { ns_vtor.read_volatile() };
418
419 // Set MSP_NS, so nonsecure mode uses that stack pointer
420 unsafe {
421 crate::register::msp::write_ns(ns_sp);
422 }
423
424 // Read the NS reset handler address from the second word of the NS vector table.
425 // ARM ABI: bit 0 is set in the stored value (Thumb mode marker).
426 // BXNS requires bit 0 = 0; if bit 0 is set, it raises SecureFault (SFSR.INVTRAN).
427 let ns_reset = unsafe { ns_vtor.add(1).read_volatile() };
428
429 // BXNS switches the processor to the state given in the LSB
430 // so we must clear that bit.
431 unsafe extern "C" {
432 fn _bx_ns_trampoline(boot: u32) -> !;
433 }
434 unsafe {
435 _bx_ns_trampoline(ns_reset & 0xFFFF_FFFE);
436 }
437}
438
439#[cfg(all(armv8m, feature = "secure-mode"))]
440core::arch::global_asm!(
441 r#"
442 .type _bx_ns_trampoline,%function
443 .global _bx_ns_trampoline
444 _bx_ns_trampoline:
445 vlstm sp // Push secure FPU state to stack, and zero secure FPU registers (nop if no FPU present)
446 mov lr, r0 // Put target address in LR
447 mov r0, 0 // Zero all the other registers
448 mov r1, 0 // Except secure MSP, as nonsecure has its own MSP, which we set
449 mov r2, 0
450 mov r3, 0
451 mov r4, 0
452 mov r5, 0
453 mov r6, 0
454 mov r7, 0
455 mov r8, 0
456 mov r9, 0
457 mov r10, 0
458 mov r11, 0
459 mov r12, 0
460 msr apsr_nzcvq, r0 // Also clear processor flags
461 bxns lr // Branch to nonsecure mode
462 .size _bx_ns_trampoline, . - _bx_ns_trampoline
463 "#,
464);
465
466/// This instruction moves one Register to a Coprocessor Register.
467///
468/// This function generates inline assembly and needs the instruction configuration
469/// during compilation time (i.e. as `const`).
470///
471/// The values of the constants required by this function should be defined by
472/// the coprocessor's reference manual.
473///
474/// - CP: The coprocessor's index.
475/// - OP1: First optional operation for the coprocessor.
476/// - CRN: Coprocessor register N.
477/// - CRM: Coprocessor register M.
478/// - OP2: Second optional operation for the coprocessor.
479#[inline(always)]
480#[asm_cfg(any(armv7m, armv8m))]
481pub unsafe fn mcr<const CP: u32, const OP1: u32, const CRN: u32, const CRM: u32, const OP2: u32>(
482 value: u32,
483) {
484 unsafe {
485 core::arch::asm!(
486 "MCR p{cp}, #{op1}, {0}, c{crn}, c{crm}, #{op2}",
487 in(reg) value,
488 cp = const CP,
489 op1 = const OP1,
490 crn = const CRN,
491 crm = const CRM,
492 op2 = const OP2,
493 options(nostack, nomem)
494 )
495 };
496}
497
498/// This instruction moves one Coprocessor Register to a Register.
499///
500/// This function generates inline assembly and needs the instruction configuration
501/// during compilation time (i.e. as `const`).
502///
503/// The values of the constants required by this function should be defined by
504/// the coprocessor's reference manual.
505///
506/// - CP: The coprocessor's index.
507/// - OP1: First optional operation for the coprocessor.
508/// - CRN: Coprocessor register N.
509/// - CRM: Coprocessor register M.
510/// - OP2: Second optional operation for the coprocessor.
511#[inline(always)]
512#[asm_cfg(any(armv7m, armv8m))]
513pub unsafe fn mrc<const CP: u32, const OP1: u32, const CRN: u32, const CRM: u32, const OP2: u32>()
514-> u32 {
515 let a: u32;
516
517 unsafe {
518 core::arch::asm!(
519 "MRC p{cp}, #{op1}, {0}, c{crn}, c{crm}, #{op2}",
520 out(reg) a,
521 cp = const CP,
522 op1 = const OP1,
523 crn = const CRN,
524 crm = const CRM,
525 op2 = const OP2,
526 options(nostack, nomem)
527 )
528 };
529
530 a
531}
532
533/// This instruction moves two Registers to Coprocessor Registers.
534///
535/// This function generates inline assembly and needs the instruction configuration
536/// during compilation time (i.e. as `const`).
537///
538/// The values of the constants required by this function should be defined by
539/// the coprocessor's reference manual.
540///
541/// - CP: The coprocessor's index.
542/// - OP1: First optional operation for the coprocessor.
543/// - CRM: Coprocessor register M.
544#[inline(always)]
545#[asm_cfg(any(armv7m, armv8m))]
546pub unsafe fn mcrr<const CP: u32, const OP1: u32, const CRM: u32>(a: u32, b: u32) {
547 unsafe {
548 core::arch::asm!(
549 "MCRR p{cp}, #{op1}, {0}, {1}, c{crm}",
550 in(reg) a,
551 in(reg) b,
552 cp = const CP,
553 op1 = const OP1,
554 crm = const CRM,
555 options(nostack, nomem)
556 )
557 };
558}
559
560/// This instruction moves two Coprocessor Registers to Registers.
561///
562/// This function generates inline assembly and needs the instruction configuration
563/// during compilation time (i.e. as `const`).
564///
565/// The values of the constants required by this function should be defined by
566/// the coprocessor's reference manual.
567///
568/// - CP: The coprocessor's index.
569/// - OP1: First optional operation for the coprocessor.
570/// - CRM: Coprocessor register M.
571#[inline(always)]
572#[asm_cfg(any(armv7m, armv8m))]
573pub unsafe fn mrrc<const CP: u32, const OPC: u32, const CRM: u32>() -> (u32, u32) {
574 // Preallocate the values.
575 let a: u32;
576 let b: u32;
577
578 unsafe {
579 core::arch::asm!(
580 "MRRC p{cp}, #{opc}, {0}, {1}, c{crm}",
581 out(reg) a,
582 out(reg) b,
583 cp = const CP,
584 opc = const OPC,
585 crm = const CRM,
586 options(nostack, nomem)
587 )
588 };
589
590 (a, b)
591}