cortex-m-asm 0.1.2

ASM intrinsics for Cortex-M CPUs
Documentation
// Many of these intrinsics are copied from
// https://github.com/rust-embedded/cortex-m/blob/master/asm/inline.rs
// Please refer to that repository for original authorship.

#![no_std]
#![feature(asm)]
#![allow(unused_unsafe, missing_safety_doc)]

#[cfg(cortex_m)]
pub use asm::*;

#[cfg(cortex_m)]
mod asm {

    use core::sync::atomic::{compiler_fence, Ordering};

    /// Puts the processor in Debug state. Debuggers can pick this up as a "breakpoint".
    ///
    /// **NOTE** calling `bkpt` when the processor is not connected to a debugger will cause an
    /// exception.
    #[inline(always)]
    pub fn bkpt() {
        unsafe { asm!("bkpt") };
    }

    /// Reads the CONTROL register.
    #[inline(always)]
    pub fn control_r() -> u32 {
        let r;
        unsafe { asm!("mrs {}, CONTROL", out(reg) r) };
        r
    }

    /// Writes the CONTROL register.
    #[inline(always)]
    pub unsafe fn control_w(w: u32) {
        // ISB is required after writing to CONTROL,
        // per ARM architectural requirements (see Application Note 321).
        unsafe {
            asm!(
                "msr CONTROL, {}",
                "isb",
                in(reg) w
            )
        };

        // Ensure memory accesses are not reordered around the CONTROL update.
        compiler_fence(Ordering::SeqCst);
    }

    /// Disables all interrupts.
    #[inline(always)]
    pub fn cpsid() {
        unsafe { asm!("cpsid i") };

        // Ensure no subsequent memory accesses are reordered to before interrupts are disabled.
        compiler_fence(Ordering::SeqCst);
    }

    /// Enables all interrupts.
    ///
    /// # Safety
    ///
    /// - Do not call this function inside an `interrupt::free` critical section.
    #[inline(always)]
    pub unsafe fn cpsie() {
        // Ensure no preceeding memory accesses are reordered to after interrupts are enabled.
        compiler_fence(Ordering::SeqCst);

        unsafe { asm!("cpsie i") };
    }

    /// Blocks the program for *at least* `cycles` CPU cycles.
    ///
    /// This is implemented in assembly so its execution time is independent of the optimization
    /// level, however it is dependent on the specific architecture and core configuration.
    ///
    /// NOTE that the delay can take much longer if interrupts are serviced during its execution
    /// and the execution time may vary with other factors. This delay is mainly useful for simple
    /// timer-less initialization of peripherals if and only if accurate timing is not essential.
    /// In any other case please use a more accurate method to produce a delay.
    #[inline]
    pub fn delay(cyc: u32) {
        // The loop will normally take 3 to 4 CPU cycles per iteration, but superscalar cores (eg.
        // Cortex-M7) can potentially do it in 2, so we use that as the lower bound, since delaying
        // for more cycles is okay.
        // Add 1 to prevent an integer underflow which would cause a long freeze
        let real_cyc = 1 + cyc / 2;
        unsafe {
            asm!(
                // Use local labels to avoid R_ARM_THM_JUMP8 relocations which fail on thumbv6m.
                "1:",
                "subs {}, #1",
                "bne 1b",
                inout(reg) real_cyc => _
            )
        };
    }

    /// Data Memory Barrier
    ///
    /// Ensures that all explicit memory accesses that appear in program order before the `DMB`
    /// instruction are observed before any explicit memory accesses that appear in program order
    /// after the `DMB` instruction.
    #[inline(always)]
    pub fn dmb() {
        compiler_fence(Ordering::SeqCst);
        unsafe { asm!("dmb") };
        compiler_fence(Ordering::SeqCst);
    }

    /// Data Synchronization Barrier
    ///
    /// Acts as a special kind of memory barrier. No instruction in program order after this
    /// instruction can execute until this instruction completes. This instruction completes only
    /// when both:
    ///
    ///  * any explicit memory access made before this instruction is complete
    ///  * all cache and branch predictor maintenance operations before this instruction complete
    #[inline(always)]
    pub fn dsb() {
        compiler_fence(Ordering::SeqCst);
        unsafe { asm!("dsb") };
        compiler_fence(Ordering::SeqCst);
    }

    /// Instruction Synchronization Barrier
    ///
    /// Flushes the pipeline in the processor, so that all instructions following the `ISB` are
    /// fetched from cache or memory, after the instruction has been completed.
    #[inline(always)]
    pub fn isb() {
        compiler_fence(Ordering::SeqCst);
        unsafe { asm!("isb") };
        compiler_fence(Ordering::SeqCst);
    }

    /// Read the MSP register.
    #[inline(always)]
    pub fn msp_r() -> u32 {
        let r;
        unsafe { asm!("mrs {}, MSP", out(reg) r) };
        r
    }

    /// Write the MSP register.
    #[inline(always)]
    pub unsafe fn msp_w(val: u32) {
        unsafe { asm!("msr MSP, {}", in(reg) val) };
    }

    /// Read the APSR register.
    #[inline(always)]
    pub fn apsr_r() -> u32 {
        let r;
        unsafe { asm!("mrs {}, APSR", out(reg) r) };
        r
    }

    /// A no-operation. Useful to prevent delay loops from being optimized away.
    #[inline(always)]
    pub fn nop() {
        // NOTE: This is a `pure` asm block, but applying that option allows the compiler to
        // eliminate the nop entirely (or to collapse multiple subsequent ones). Since the user
        // probably wants N nops when they call `nop` N times, let's not add that option.
        unsafe { asm!("nop") };
    }

    /// Read the PC register.
    #[inline(always)]
    pub fn pc_r() -> u32 {
        let r;
        unsafe { asm!("mov {}, pc", out(reg) r) };
        r
    }

    /// Write to the PC register.
    #[inline(always)]
    pub unsafe fn pc_w(val: u32) {
        unsafe { asm!("mov pc, {}", in(reg) val) };
    }

    /// Read the LR register.
    #[inline(always)]
    pub fn lr_r() -> u32 {
        let r;
        unsafe { asm!("mov {}, lr", out(reg) r) };
        r
    }

    /// Write to the LR register.
    #[inline(always)]
    pub unsafe fn lr_w(val: u32) {
        unsafe { asm!("mov lr, {}", in(reg) val) };
    }

    /// Read the PRIMASK register.
    #[inline(always)]
    pub fn primask_r() -> u32 {
        let r;
        unsafe { asm!("mrs {}, PRIMASK", out(reg) r) };
        r
    }

    /// Read the PSP register.
    #[inline(always)]
    pub fn psp_r() -> u32 {
        let r;
        unsafe { asm!("mrs {}, PSP", out(reg) r) };
        r
    }

    /// Write the PSP register.
    #[inline(always)]
    pub unsafe fn psp_w(val: u32) {
        unsafe { asm!("msr PSP, {}", in(reg) val) };
    }

    /// Send Event.
    #[inline(always)]
    pub fn sev() {
        unsafe { asm!("sev") };
    }

    /// Generate an Undefined Instruction exception.
    ///
    /// Can be used as a stable alternative to `core::intrinsics::abort`.
    #[inline(always)]
    pub fn udf() -> ! {
        unsafe { asm!("udf #0", options(noreturn)) };
    }

    /// Wait For Event.
    #[inline(always)]
    pub fn wfe() {
        unsafe { asm!("wfe") };
    }

    /// Wait For Interrupt.
    #[inline(always)]
    pub fn wfi() {
        unsafe { asm!("wfi") };
    }

    /// Semihosting syscall.
    #[inline(always)]
    pub unsafe fn sh_syscall(mut nr: u32, arg: u32) -> u32 {
        unsafe { asm!("bkpt #0xab", inout("r0") nr, in("r1") arg) };
        nr
    }

    /// Set CONTROL.SPSEL to 0, write `msp` to MSP, branch to `rv`.
    #[allow(clippy::not_unsafe_ptr_arg_deref)]
    #[inline(always)]
    pub unsafe fn bootstrap(msp: *const u32, rv: *const u32) -> ! {
        let msp = msp as u32;
        let rv = rv as u32;
        unsafe {
            asm!(
                "mrs {tmp}, CONTROL",
                "bics {tmp}, {spsel}",
                "msr CONTROL, {tmp}",
                "isb",
                "msr MSP, {msp}",
                "bx {rv}",
                // `out(reg) _` is not permitted in a `noreturn` asm! call,
                // so instead use `in(reg) 0` and don't restore it afterwards.
                tmp = in(reg) 0,
                spsel = in(reg) 2,
                msp = in(reg) msp,
                rv = in(reg) rv,
                options(noreturn),
            )
        };
    }

    // v7m *AND* v8m.main, but *NOT* v8m.base
    #[cfg(any(armv7m, armv8m_main))]
    pub use self::v7m::*;
    #[cfg(any(armv7m, armv8m_main))]
    mod v7m {
        use core::sync::atomic::{compiler_fence, Ordering};

        /// Write the BASEPRI_MAX register.
        #[inline(always)]
        pub unsafe fn basepri_max(val: u8) {
            unsafe { asm!("msr BASEPRI_MAX, {}", in(reg) val) };
        }

        /// Read the BASEPRI register.
        #[inline(always)]
        pub fn basepri_r() -> u8 {
            let r;
            unsafe { asm!("mrs {}, BASEPRI", out(reg) r) };
            r
        }

        /// Write the BASEPRI register.
        #[inline(always)]
        pub unsafe fn basepri_w(val: u8) {
            unsafe { asm!("msr BASEPRI, {}", in(reg) val) };
        }

        /// Read the FAULTMASK register.
        #[inline(always)]
        pub fn faultmask_r() -> u32 {
            let r;
            unsafe { asm!("mrs {}, FAULTMASK", out(reg) r) };
            r
        }

        /// Enable ICACHE.
        ///
        /// The ICACHE must be invalidated before enabling.
        ///
        /// This method manages exclusive access to the SCB registers using a critical section.
        #[inline(always)]
        pub unsafe fn enable_icache() {
            unsafe {
                asm!(
                    "ldr {0}, =0xE000ED14",         // CCR
                    "mrs {2}, PRIMASK",             // save critical nesting info
                    "cpsid i",                      // mask interrupts
                    "ldr {1}, [{0}]",               // read CCR
                    "orr.w {1}, {1}, #(1 << 17)",   // Set bit 17, IC
                    "str {1}, [{0}]",               // write it back
                    "dsb",                          // ensure store completes
                    "isb",                          // synchronize pipeline
                    "msr PRIMASK, {2}",             // unnest critical section
                    out(reg) _,
                    out(reg) _,
                    out(reg) _,
                )
            };
            compiler_fence(Ordering::SeqCst);
        }

        /// Enable DCACHE.
        ///
        /// The DCACHE must be invalidated before enabling.
        ///
        /// This method manages exclusive access to the SCB registers using a critical section.
        #[inline(always)]
        pub unsafe fn enable_dcache() {
            unsafe {
                asm!(
                    "ldr {0}, =0xE000ED14",         // CCR
                    "mrs {2}, PRIMASK",             // save critical nesting info
                    "cpsid i",                      // mask interrupts
                    "ldr {1}, [{0}]",               // read CCR
                    "orr.w {1}, {1}, #(1 << 16)",   // Set bit 16, DC
                    "str {1}, [{0}]",               // write it back
                    "dsb",                          // ensure store completes
                    "isb",                          // synchronize pipeline
                    "msr PRIMASK, {2}",             // unnest critical section
                    out(reg) _,
                    out(reg) _,
                    out(reg) _,
                )
            };
            compiler_fence(Ordering::SeqCst);
        }
    }

    #[cfg(armv7em)]
    pub use self::v7em::*;
    #[cfg(armv7em)]
    mod v7em {
        /// Write to BASEPRI_MAX on Cortex-M7 r0p1 CPUs.
        /// Accounts for erratum 837070.
        #[inline(always)]
        pub unsafe fn basepri_max_cm7_r0p1(val: u8) {
            unsafe {
                asm!(
                    "mrs {1}, PRIMASK",
                    "cpsid i",
                    "tst.w {1}, #1",
                    "msr BASEPRI_MAX, {0}",
                    "it ne",
                    "bxne lr",
                    "cpsie i",
                    in(reg) val,
                    out(reg) _,
                )
            };
        }

        /// Write to BASEPRI on Cortex-M7 r0p1 CPUs.
        /// Accounts for erratum 837070.
        #[inline(always)]
        pub unsafe fn basepri_w_cm7_r0p1(val: u8) {
            unsafe {
                asm!(
                    "mrs {1}, PRIMASK",
                    "cpsid i",
                    "tst.w {1}, #1",
                    "msr BASEPRI, {0}",
                    "it ne",
                    "bxne lr",
                    "cpsie i",
                    in(reg) val,
                    out(reg) _,
                )
            };
        }
    }

    #[cfg(armv8m)]
    pub use self::v8m::*;
    /// Baseline and Mainline.
    #[cfg(armv8m)]
    mod v8m {
        /// Test Target
        ///
        /// Queries the Security state and access permissions of a memory location.
        ///
        /// Returns a Test Target Response Payload (cf section D1.2.215 of
        /// Armv8-M Architecture Reference Manual).
        #[allow(clippy::not_unsafe_ptr_arg_deref)]
        #[inline(always)]
        pub fn tt(mut target: *mut u32) -> u32 {
            let mut target = target as u32;
            unsafe { asm!("tt {target}, {target}", target = inout(reg) target) };
            target
        }

        /// Test Target Unprivileged
        ///
        /// Queries the Security state and access permissions of a memory location for an
        /// unprivileged access to that location.
        ///
        /// Returns a Test Target Response Payload (cf section D1.2.215 of
        /// Armv8-M Architecture Reference Manual).
        #[allow(clippy::not_unsafe_ptr_arg_deref)]
        #[inline(always)]
        pub fn ttt(mut target: *mut u32) -> u32 {
            let mut target = target as u32;
            unsafe { asm!("ttt {target}, {target}", target = inout(reg) target) };
            target
        }

        /// Test Target Alternate Domain
        ///
        /// Queries the Security state and access permissions of a memory location for a Non-Secure
        /// access to that location. This instruction is only valid when executing in Secure state
        /// and is undefined if used from Non-Secure state.
        ///
        /// Returns a Test Target Response Payload (cf section D1.2.215 of
        /// Armv8-M Architecture Reference Manual).
        #[allow(clippy::not_unsafe_ptr_arg_deref)]
        #[inline(always)]
        pub fn tta(mut target: *mut u32) -> u32 {
            let mut target = target as u32;
            unsafe { asm!("tta {target}, {target}", target = inout(reg) target) };
            target
        }

        /// Test Target Alternate Domain Unprivileged
        ///
        /// Queries the Security state and access permissions of a memory location for a Non-Secure
        /// and unprivileged access to that location. This instruction is only valid when executing
        /// in Secure state and is undefined if used from Non-Secure state.
        ///
        /// Returns a Test Target Response Payload (cf section D1.2.215 of
        /// Armv8-M Architecture Reference Manual).
        #[allow(clippy::not_unsafe_ptr_arg_deref)]
        #[inline(always)]
        pub fn ttat(mut target: *mut u32) -> u32 {
            let mut target = target as u32;
            unsafe { asm!("ttat {target}, {target}", target = inout(reg) target) };
            target
        }

        /// Reads the Non-Secure MSP register from the Secure state.
        ///
        /// Executing this function in Non-Secure state will return 0.
        #[inline(always)]
        pub fn msp_ns_r() -> u32 {
            let r;
            unsafe { asm!("mrs {}, MSP_NS", out(reg) r) };
            r
        }

        /// Writes to the Non-Secure MSP register from the Secure state.
        ///
        /// Executing this function in Non-Seure state will be ignored.
        #[inline(always)]
        pub unsafe fn msp_ns_w(val: u32) {
            unsafe { asm!("msr MSP_NS, {}", in(reg) val) };
        }

        /// Branch and Exchange Non-secure
        ///
        /// See section C2.4.26 of Armv8-M Architecture Reference Manual for details.
        /// Undefined if executed in Non-Secure state.
        #[inline(always)]
        pub unsafe fn bxns(val: u32) {
            unsafe { asm!("BXNS {}", in(reg) val) };
        }
    }

    #[cfg(armv8m_main)]
    pub use self::v8m_main::*;
    /// Mainline only.
    #[cfg(armv8m_main)]
    mod v8m_main {
        /// Reads the MSPLIM register.
        #[inline(always)]
        pub fn msplim_r() -> u32 {
            let r;
            unsafe { asm!("mrs {}, MSPLIM", out(reg) r) };
            r
        }

        /// Writes to the MSPLIM register.
        #[inline(always)]
        pub unsafe fn msplim_w(val: u32) {
            unsafe { asm!("msr MSPLIM, {}", in(reg) val) };
        }

        /// Reads the PSPLIM register.
        #[inline(always)]
        pub fn psplim_r() -> u32 {
            let r;
            unsafe { asm!("mrs {}, PSPLIM", out(reg) r) };
            r
        }

        /// Writes to the PSPLIM register.
        #[inline(always)]
        pub unsafe fn psplim_w(val: u32) {
            unsafe { asm!("msr PSPLIM, {}", in(reg) val) };
        }
    }

    #[cfg(has_fpu)]
    pub use self::fpu::*;

    /// All targets with FPU.
    #[cfg(has_fpu)]
    mod fpu {
        /// Reads the FPSCR register.
        #[inline(always)]
        pub fn fpscr_r() -> u32 {
            let r;
            unsafe { asm!("vmrs {}, fpscr", out(reg) r) };
            r
        }

        /// Writes to the FPSCR register.
        #[inline(always)]
        pub unsafe fn fpscr_w(val: u32) {
            unsafe { asm!("vmsr fpscr, {}", in(reg) val) };
        }
    }
}