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 { asm!("bkpt #0xab", inout("r0") nr, in("r1") arg, options(nostack, preserves_flags)) };
246    nr
247}
248
249/// Switch to unprivileged mode using the Process Stack
250///
251/// Sets CONTROL.SPSEL (setting the Process Stack to be the active
252/// stack) and CONTROL.nPRIV (setting unprivileged mode), updates the
253/// program stack pointer to the address in `psp`, then jumps to the
254/// address in `entry`.
255///
256/// # Safety
257///
258/// * `psp` and `entry` must point to valid stack memory and executable code,
259///   respectively.
260/// * `psp` must be 8 bytes aligned and point to stack top as stack grows
261///   towards lower addresses.
262/// * The size of the stack provided here must be large enough for your
263///   program - stack overflows are obviously UB. If your processor supports
264///   it, you may wish to set the `PSPLIM` register to guard against this.
265#[inline(always)]
266#[asm_cfg(cortex_m)]
267pub unsafe fn enter_unprivileged_psp(psp: *const u32, entry: extern "C" fn() -> !) -> ! {
268    use crate::register::control::{Control, Npriv, Spsel};
269    const CONTROL_FLAGS: u32 = {
270        Control::from_bits(0)
271            .with_npriv(Npriv::Unprivileged)
272            .with_spsel(Spsel::Psp)
273            .bits()
274    };
275    unsafe {
276        core::arch::asm!(
277            "msr     PSP, {psp}",
278            "mrs     {tmp}, CONTROL",
279            "orrs    {tmp}, {flags}",
280            "msr     CONTROL, {tmp}",
281            "isb",
282            "bx      {ent}",
283            tmp = in(reg) 0,
284            flags = in(reg) CONTROL_FLAGS,
285            psp = in(reg) psp,
286            ent = in(reg) entry,
287            options(noreturn, nostack)
288        );
289    }
290}
291
292/// Switch to using the Process Stack, but remain in Privileged Mode
293///
294/// Sets CONTROL.SPSEL (setting the Process Stack to be the active stack) but
295/// leaves CONTROL.nPRIV alone, updates the program stack pointer to the
296/// address in `psp`, then jumps to the address in `entry`.
297///
298/// # Safety
299///
300/// * `psp` and `entry` must point to valid stack memory and executable code,
301///   respectively.
302/// * `psp` must be 8 bytes aligned and point to stack top as stack grows
303///   towards lower addresses.
304/// * The size of the stack provided here must be large enough for your
305///   program - stack overflows are obviously UB. If your processor supports
306///   it, you may wish to set the `PSPLIM` register to guard against this.
307#[inline(always)]
308#[asm_cfg(cortex_m)]
309pub unsafe fn enter_privileged_psp(psp: *const u32, entry: extern "C" fn() -> !) -> ! {
310    use crate::register::control::{Control, Npriv, Spsel};
311    const CONTROL_FLAGS: u32 = {
312        Control::from_bits(0)
313            .with_npriv(Npriv::Privileged)
314            .with_spsel(Spsel::Psp)
315            .bits()
316    };
317    unsafe {
318        core::arch::asm!(
319            "msr     PSP, {psp}",
320            "mrs     {tmp}, CONTROL",
321            "orrs    {tmp}, {flags}",
322            "msr     CONTROL, {tmp}",
323            "isb",
324            "bx      {ent}",
325            tmp = in(reg) 0,
326            flags = in(reg) CONTROL_FLAGS,
327            psp = in(reg) psp,
328            ent = in(reg) entry,
329            options(noreturn, nostack)
330        );
331    }
332}
333
334/// Bootstrap.
335///
336/// Clears CONTROL.SPSEL (setting the main stack to be the active stack),
337/// updates the main stack pointer to the address in `msp`, then jumps
338/// to the address in `rv`.
339///
340/// # Safety
341///
342/// `msp` and `rv` must point to valid stack memory and executable code,
343/// respectively.
344#[inline]
345#[asm_cfg(cortex_m)]
346pub unsafe fn bootstrap(msp: *const u32, rv: *const u32) -> ! {
347    // Ensure thumb mode is set.
348    let rv = (rv as u32) | 1;
349    let msp = msp as u32;
350    unsafe {
351        asm!(
352            "mrs {tmp}, CONTROL",
353            "bics {tmp}, {spsel}",
354            "msr CONTROL, {tmp}",
355            "isb",
356            "msr MSP, {msp}",
357            "bx {rv}",
358            // `out(reg) _` is not permitted in a `noreturn` asm! call,
359            // so instead use `in(reg) 0` and don't restore it afterwards.
360            tmp = in(reg) 0,
361            spsel = in(reg) 2,
362            msp = in(reg) msp,
363            rv = in(reg) rv,
364            options(noreturn, nostack),
365        )
366    };
367}
368
369/// Bootload.
370///
371/// Reads the initial stack pointer value and reset vector from
372/// the provided vector table address, sets the active stack to
373/// the main stack, sets the main stack pointer to the new initial
374/// stack pointer, then jumps to the reset vector.
375///
376/// # Safety
377///
378/// The provided `vector_table` must point to a valid vector
379/// table, with a valid stack pointer as the first word and
380/// a valid reset vector as the second word.
381#[inline]
382#[asm_cfg(cortex_m)]
383pub unsafe fn bootload(vector_table: *const u32) -> ! {
384    unsafe {
385        let msp = core::ptr::read_volatile(vector_table);
386        let rv = core::ptr::read_volatile(vector_table.offset(1));
387        bootstrap(msp as *const u32, rv as *const u32);
388    }
389}
390
391/// Transfer control to the Non-Secure application. Does not return.
392///
393/// This performs the standard Secure→Non-Secure boot handoff:
394/// 1. Sets `SCB_NS->VTOR` to `ns_vtor` so the Non-Secure world finds its vector table.
395/// 2. Loads `MSP_NS` from the first word of the NS vector table (the initial NS stack pointer).
396/// 3. Reads the NS reset handler address from the second word of the NS vector table.
397/// 4. Executes `BXNS` to atomically switch to Non-Secure state and jump to the handler.
398///
399/// # Safety
400/// - Must be called from the Secure world after all SAU/GTZC setup is complete.
401/// - `ns_vtor` must point to a valid Non-Secure vector table. The Cortex-M33 requires the VTOR
402///   to be at least 32-byte aligned; in practice 128-byte or 256-byte alignment is typical.
403/// - The NS reset handler at `*(ns_vtor + 1)` must be a valid Thumb function address (bit 0 set
404///   in the vector table entry, as per the ARM ABI convention for vector tables).
405/// - Available on ARMv8-M only (`thumbv8m.base` and `thumbv8m.main`).
406#[cfg(all(armv8m, feature = "secure-mode"))]
407pub unsafe fn bootload_ns(ns_vtor: *const u32, scb_ns: crate::peripheral::SCBNS) -> ! {
408    // Set NS_VTOR, so nonsecure mode uses that vector table
409    unsafe {
410        scb_ns.vtor.write(ns_vtor as usize as u32);
411    }
412
413    // Load the initial NS stack pointer from the first word of the NS vector table
414    // and write it into MSP_NS.
415    let ns_sp = unsafe { ns_vtor.read_volatile() };
416
417    // Set MSP_NS, so nonsecure mode uses that stack pointer
418    unsafe {
419        crate::register::msp::write_ns(ns_sp);
420    }
421
422    // Read the NS reset handler address from the second word of the NS vector table.
423    // ARM ABI: bit 0 is set in the stored value (Thumb mode marker).
424    // BXNS requires bit 0 = 0; if bit 0 is set, it raises SecureFault (SFSR.INVTRAN).
425    let ns_reset = unsafe { ns_vtor.add(1).read_volatile() };
426
427    // BXNS switches the processor to the state given in the LSB
428    // so we must clear that bit.
429    unsafe extern "C" {
430        fn _bx_ns_trampoline(boot: u32) -> !;
431    }
432    unsafe {
433        _bx_ns_trampoline(ns_reset & 0xFFFF_FFFE);
434    }
435}
436
437#[cfg(all(armv8m, feature = "secure-mode"))]
438core::arch::global_asm!(
439    r#"
440        .type _bx_ns_trampoline,%function
441        .global _bx_ns_trampoline
442    _bx_ns_trampoline:
443        vlstm   sp             // Push secure FPU state to stack, and zero secure FPU registers (nop if no FPU present)
444        mov     lr, r0         // Put target address in LR
445        mov     r0, 0          // Zero all the other registers
446        mov     r1, 0          // Except secure MSP, as nonsecure has its own MSP, which we set
447        mov     r2, 0
448        mov     r3, 0
449        mov     r4, 0
450        mov     r5, 0
451        mov     r6, 0
452        mov     r7, 0
453        mov     r8, 0
454        mov     r9, 0
455        mov     r10, 0
456        mov     r11, 0
457        mov     r12, 0
458        msr     apsr_nzcvq, r0 // Also clear processor flags
459        bxns    lr             // Branch to nonsecure mode
460        .size _bx_ns_trampoline, . - _bx_ns_trampoline
461    "#,
462);
463
464/// This instruction moves one Register to a Coprocessor Register.
465///
466/// This function generates inline assembly and needs the instruction configuration
467/// during compilation time (i.e. as `const`).
468///
469/// The values of the constants required by this function should be defined by
470/// the coprocessor's reference manual.
471///
472///  - CP: The coprocessor's index.
473///  - OP1: First optional operation for the coprocessor.
474///  - CRN: Coprocessor register N.
475///  - CRM: Coprocessor register M.
476///  - OP2: Second optional operation for the coprocessor.
477#[inline(always)]
478#[asm_cfg(any(armv7m, armv8m))]
479pub unsafe fn mcr<const CP: u32, const OP1: u32, const CRN: u32, const CRM: u32, const OP2: u32>(
480    value: u32,
481) {
482    unsafe {
483        core::arch::asm!(
484            "MCR p{cp}, #{op1}, {0}, c{crn}, c{crm}, #{op2}",
485            in(reg) value,
486            cp  = const CP,
487            op1 = const OP1,
488            crn = const CRN,
489            crm = const CRM,
490            op2 = const OP2,
491            options(nostack)
492        )
493    };
494}
495
496/// This instruction moves one Coprocessor Register to a Register.
497///
498/// This function generates inline assembly and needs the instruction configuration
499/// during compilation time (i.e. as `const`).
500///
501/// The values of the constants required by this function should be defined by
502/// the coprocessor's reference manual.
503///
504///  - CP: The coprocessor's index.
505///  - OP1: First optional operation for the coprocessor.
506///  - CRN: Coprocessor register N.
507///  - CRM: Coprocessor register M.
508///  - OP2: Second optional operation for the coprocessor.
509#[inline(always)]
510#[asm_cfg(any(armv7m, armv8m))]
511pub unsafe fn mrc<const CP: u32, const OP1: u32, const CRN: u32, const CRM: u32, const OP2: u32>()
512-> u32 {
513    let a: u32;
514
515    unsafe {
516        core::arch::asm!(
517            "MRC p{cp}, #{op1}, {0}, c{crn}, c{crm}, #{op2}",
518            out(reg) a,
519            cp  = const CP,
520            op1 = const OP1,
521            crn = const CRN,
522            crm = const CRM,
523            op2 = const OP2,
524            options(nostack)
525        )
526    };
527
528    a
529}
530
531/// This instruction moves two Registers to Coprocessor Registers.
532///
533/// This function generates inline assembly and needs the instruction configuration
534/// during compilation time (i.e. as `const`).
535///
536/// The values of the constants required by this function should be defined by
537/// the coprocessor's reference manual.
538///
539///  - CP: The coprocessor's index.
540///  - OP1: First optional operation for the coprocessor.
541///  - CRM: Coprocessor register M.
542#[inline(always)]
543#[asm_cfg(any(armv7m, armv8m))]
544pub unsafe fn mcrr<const CP: u32, const OP1: u32, const CRM: u32>(a: u32, b: u32) {
545    unsafe {
546        core::arch::asm!(
547            "MCRR p{cp}, #{op1}, {0}, {1}, c{crm}",
548            in(reg) a,
549            in(reg) b,
550            cp  = const CP,
551            op1 = const OP1,
552            crm = const CRM,
553            options(nostack)
554        )
555    };
556}
557
558/// This instruction moves two Coprocessor Registers to Registers.
559///
560/// This function generates inline assembly and needs the instruction configuration
561/// during compilation time (i.e. as `const`).
562///
563/// The values of the constants required by this function should be defined by
564/// the coprocessor's reference manual.
565///
566///  - CP: The coprocessor's index.
567///  - OP1: First optional operation for the coprocessor.
568///  - CRM: Coprocessor register M.
569#[inline(always)]
570#[asm_cfg(any(armv7m, armv8m))]
571pub unsafe fn mrrc<const CP: u32, const OPC: u32, const CRM: u32>() -> (u32, u32) {
572    // Preallocate the values.
573    let a: u32;
574    let b: u32;
575
576    unsafe {
577        core::arch::asm!(
578            "MRRC p{cp}, #{opc}, {0}, {1}, c{crm}",
579            out(reg) a,
580            out(reg) b,
581            cp  = const CP,
582            opc = const OPC,
583            crm = const CRM,
584            options(nostack)
585        )
586    };
587
588    (a, b)
589}