Skip to main content

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}