diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index d0883485..47bc5b28 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@1.61 with: components: clippy - run: cargo clippy --all --features cortex-m/critical-section-single-core -- --deny warnings diff --git a/cortex-m-semihosting/src/export.rs b/cortex-m-semihosting/src/export.rs index 03604bf0..76709302 100644 --- a/cortex-m-semihosting/src/export.rs +++ b/cortex-m-semihosting/src/export.rs @@ -1,53 +1,68 @@ //! IMPLEMENTATION DETAILS USED BY MACROS -// This must be replaced by a different solution before rust edition 2024 -// https://doc.rust-lang.org/nightly/edition-guide/rust-2024/static-mut-references.html -#![allow(static_mut_refs)] - +use core::cell::RefCell; use core::fmt::{self, Write}; use crate::hio::{self, HostStream}; -static mut HSTDOUT: Option = None; +static HSTDOUT: critical_section::Mutex>> = + critical_section::Mutex::new(RefCell::new(None)); pub fn hstdout_str(s: &str) { - let _result = critical_section::with(|_| unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout()?); + critical_section::with(|cs| { + let mut hstdout_opt = HSTDOUT.borrow_ref_mut(cs); + if hstdout_opt.is_none() { + if let Ok(hstdout) = hio::hstdout() { + hstdout_opt.replace(hstdout); + } else { + return; + } } - - HSTDOUT.as_mut().unwrap().write_str(s).map_err(drop) + let hstdout = hstdout_opt.as_mut().unwrap(); + let _ = hstdout.write_str(s); }); } pub fn hstdout_fmt(args: fmt::Arguments) { - let _result = critical_section::with(|_| unsafe { - if HSTDOUT.is_none() { - HSTDOUT = Some(hio::hstdout()?); + critical_section::with(|cs| { + let mut hstdout_opt = HSTDOUT.borrow_ref_mut(cs); + if hstdout_opt.is_none() { + if let Ok(hstdout) = hio::hstdout() { + hstdout_opt.replace(hstdout); + } else { + return; + } } - - HSTDOUT.as_mut().unwrap().write_fmt(args).map_err(drop) + let hstdout = hstdout_opt.as_mut().unwrap(); + let _ = hstdout.write_fmt(args); }); } -static mut HSTDERR: Option = None; +static HSTDERR: critical_section::Mutex>> = + critical_section::Mutex::new(RefCell::new(None)); pub fn hstderr_str(s: &str) { - let _result = critical_section::with(|_| unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr()?); + critical_section::with(|cs| { + let mut hstderr_opt = HSTDERR.borrow_ref_mut(cs); + if let Ok(hstderr) = hio::hstderr() { + hstderr_opt.replace(hstderr); + } else { + return; } - - HSTDERR.as_mut().unwrap().write_str(s).map_err(drop) + let hstderr = hstderr_opt.as_mut().unwrap(); + let _ = hstderr.write_str(s); }); } pub fn hstderr_fmt(args: fmt::Arguments) { - let _result = critical_section::with(|_| unsafe { - if HSTDERR.is_none() { - HSTDERR = Some(hio::hstderr()?); + critical_section::with(|cs| { + let mut hstderr_opt = HSTDERR.borrow_ref_mut(cs); + if let Ok(hstderr) = hio::hstderr() { + hstderr_opt.replace(hstderr); + } else { + return; } - - HSTDERR.as_mut().unwrap().write_fmt(args).map_err(drop) + let hstderr = hstderr_opt.as_mut().unwrap(); + let _ = hstderr.write_fmt(args); }); } diff --git a/cortex-m/CHANGELOG.md b/cortex-m/CHANGELOG.md index b13405da..7508e6cb 100644 --- a/cortex-m/CHANGELOG.md +++ b/cortex-m/CHANGELOG.md @@ -7,37 +7,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -### Breaking changes +- MSRV is 1.61 to match cortex-m-rt crate -- `NVIC::request()` no longer requires `&mut self`. -- `embedded-hal` version 0.2 delay implementations now required the `eh0` feature. +## [v0.7.7] - 2023-01-03 -### Added -- Updated `SCB.ICSR.VECTACTIVE`/`SCB::vect_active()` to be 9 bits instead of 8. - Also fixes `VectActive::from` to take a `u16` and subtract `16` for - `VectActive::Interrupt`s to match `SBC::vect_active()` (#373). -- DWT: add `configure` API for address, cycle count comparison (#342, #367). -- ITM: add `configure` API (#342). -- TPIU: add API for *Formatter and Flush Control* (FFCR) and *Selected Pin Control* (SPPR) registers (#342). -- TPIU: add `swo_supports` for checking what SWO configurations the target supports. (#381) -- Add `std` and `serde` crate features for improved host-side ITM decode functionality when working with the downstream `itm`, `cargo-rtic-scope` crates (#363, #366). -- Added the ability to name the statics generated by `singleton!()` for better debuggability (#364, #380). -- Added `critical-section-single-core` feature which provides an implementation for the `critical_section` crate for single-core systems, based on disabling all interrupts. (#447) -- Added support for `embedded-hal` version 1 delay traits, requiring rust 1.60. -- `singleton!()` now forwards attributes (#522). -- Added `set_sevonpend` and `clear_sevonpend` (#539). +- Add missing documentation for `critical-section-single-core` feature added + in v0.7.6. -### Fixed -- Fixed `singleton!()` statics sometimes ending up in `.data` instead of `.bss` (#364, #380). -- `interrupt::free` no longer hands out a `CriticalSection` token because it is unsound on multi-core. Use `critical_section::with` instead. (#447) +## [v0.7.6] - 2022-08-12 + +- Added `critical-section-single-core` feature which provides an implementation for the `critical-section` crate for single-core systems, based on disabling all interrupts. (#448) + +## [v0.7.5] - 2022-05-15 + +### Deprecated +- the `ptr()` function on all peripherals register blocks in favor of + the associated constant `PTR` (#386). ### Changed -- Inline assembly is now always used, requiring Rust 1.59. -- Bumped MSRV to 1.61 for compatibility with syn versions >=2.0.68. -### Removed -- removed all peripherals `ptr()` functions in favor of the associated constant `PTR` (#385). -- removed `inline-asm` feature which is now always enabled +- The `inline-asm` feature no longer requires a nightly Rust compiler, but + does require Rust 1.59 or above. + +### Fixed +- Fixed `singleton!()` statics sometimes ending up in `.data` instead of `.bss` (#364, #380). + (Backported from upcoming 0.8 release). ## [v0.7.4] - 2021-12-31 @@ -70,7 +64,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). [C-GETTER]: https://rust-lang.github.io/api-guidelines/naming.html#c-getter - ## [v0.7.3] - 2021-07-03 ### Fixed @@ -764,7 +757,10 @@ fn main() { - Functions to get the vector table - Wrappers over miscellaneous instructions like `bkpt` -[Unreleased]: https://github.com/rust-embedded/cortex-m/compare/v0.7.4...HEAD +[Unreleased]: https://github.com/rust-embedded/cortex-m/compare/v0.7.7...HEAD +[v0.7.7]: https://github.com/rust-embedded/cortex-m/compare/v0.7.6...v0.7.7 +[v0.7.6]: https://github.com/rust-embedded/cortex-m/compare/v0.7.5...v0.7.6 +[v0.7.5]: https://github.com/rust-embedded/cortex-m/compare/v0.7.4...v0.7.5 [v0.7.4]: https://github.com/rust-embedded/cortex-m/compare/v0.7.3...v0.7.4 [v0.7.3]: https://github.com/rust-embedded/cortex-m/compare/v0.7.2...v0.7.3 [v0.7.2]: https://github.com/rust-embedded/cortex-m/compare/v0.7.1...v0.7.2 diff --git a/cortex-m/Cargo.toml b/cortex-m/Cargo.toml index 0c317c64..2858631f 100644 --- a/cortex-m/Cargo.toml +++ b/cortex-m/Cargo.toml @@ -11,29 +11,34 @@ license = "MIT OR Apache-2.0" name = "cortex-m" readme = "README.md" repository = "https://github.com/rust-embedded/cortex-m" -version = "0.7.4" -edition = "2021" -rust-version = "1.61" +version = "0.7.7" +edition = "2018" links = "cortex-m" # prevent multiple versions of this crate to be linked together +rust-version = "1.61" [dependencies] -critical-section = "1.0.0" -volatile-register = "0.2.2" -bitfield = "0.15.0" -eh0 = { package = "embedded-hal", version = "0.2.4", optional = true } -eh1 = { package = "embedded-hal", version = "1.0.0" } +bare-metal = { version = "0.2.4", features = ["const-fn"] } +critical-section = { version = "1.0.0", optional = true } +volatile-register = "0.2.0" +bitfield = "0.13.2" +embedded-hal = "0.2.4" [dependencies.serde] version = "1" features = [ "derive" ] optional = true +[dependencies.serde_json] +version = "1" +optional = true + [features] cm7 = [] cm7-r0p1 = ["cm7"] +inline-asm = [] linker-plugin-lto = [] std = [] -critical-section-single-core = ["critical-section/restore-state-u32"] +critical-section-single-core = ["critical-section/restore-state-bool"] [package.metadata.docs.rs] targets = [ diff --git a/cortex-m/README.md b/cortex-m/README.md index aacc6ddc..6a7f98c2 100644 --- a/cortex-m/README.md +++ b/cortex-m/README.md @@ -11,7 +11,7 @@ This project is developed and maintained by the [Cortex-M team][team]. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.61.0 and up. It might compile with older versions but that may change in any new patch release. +This crate is guaranteed to compile on stable Rust 1.61 and up. It might compile with older versions but that may change in any new patch release. ## License diff --git a/cortex-m/asm-toolchain b/cortex-m/asm-toolchain new file mode 100644 index 00000000..cc5dbb24 --- /dev/null +++ b/cortex-m/asm-toolchain @@ -0,0 +1 @@ +nightly-2021-12-16 diff --git a/cortex-m/asm/inline.rs b/cortex-m/asm/inline.rs new file mode 100644 index 00000000..f5d6b32d --- /dev/null +++ b/cortex-m/asm/inline.rs @@ -0,0 +1,448 @@ +//! Inline assembly implementing the routines exposed in `cortex_m::asm`. +//! +//! If the `inline-asm` feature is enabled, these functions will be directly called by the +//! `cortex-m` wrappers. Otherwise, `cortex-m` links against them via prebuilt archives. +//! +//! All of these functions should be blanket-`unsafe`. `cortex-m` provides safe wrappers where +//! applicable. + +use core::arch::asm; +use core::sync::atomic::{compiler_fence, Ordering}; + +#[inline(always)] +pub unsafe fn __bkpt() { + asm!("bkpt", options(nomem, nostack, preserves_flags)); +} + +#[inline(always)] +pub unsafe fn __control_r() -> u32 { + let r; + asm!("mrs {}, CONTROL", out(reg) r, options(nomem, nostack, preserves_flags)); + r +} + +#[inline(always)] +pub unsafe fn __control_w(w: u32) { + // ISB is required after writing to CONTROL, + // per ARM architectural requirements (see Application Note 321). + asm!( + "msr CONTROL, {}", + "isb", + in(reg) w, + options(nomem, nostack, preserves_flags), + ); + + // Ensure memory accesses are not reordered around the CONTROL update. + compiler_fence(Ordering::SeqCst); +} + +#[inline(always)] +pub unsafe fn __cpsid() { + asm!("cpsid i", options(nomem, nostack, preserves_flags)); + + // Ensure no subsequent memory accesses are reordered to before interrupts are disabled. + compiler_fence(Ordering::SeqCst); +} + +#[inline(always)] +pub unsafe fn __cpsie() { + // Ensure no preceeding memory accesses are reordered to after interrupts are enabled. + compiler_fence(Ordering::SeqCst); + + asm!("cpsie i", options(nomem, nostack, preserves_flags)); +} + +#[inline(always)] +pub unsafe 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; + asm!( + // Use local labels to avoid R_ARM_THM_JUMP8 relocations which fail on thumbv6m. + "1:", + "subs {}, #1", + "bne 1b", + inout(reg) real_cyc => _, + options(nomem, nostack), + ); +} + +#[inline(always)] +pub unsafe fn __dmb() { + compiler_fence(Ordering::SeqCst); + asm!("dmb", options(nostack, preserves_flags)); + compiler_fence(Ordering::SeqCst); +} + +#[inline(always)] +pub unsafe fn __dsb() { + compiler_fence(Ordering::SeqCst); + asm!("dsb", options(nostack, preserves_flags)); + compiler_fence(Ordering::SeqCst); +} + +#[inline(always)] +pub unsafe fn __isb() { + compiler_fence(Ordering::SeqCst); + asm!("isb", options(nostack, preserves_flags)); + compiler_fence(Ordering::SeqCst); +} + +#[inline(always)] +pub unsafe fn __msp_r() -> u32 { + let r; + asm!("mrs {}, MSP", out(reg) r, options(nomem, nostack, preserves_flags)); + r +} + +#[inline(always)] +pub unsafe fn __msp_w(val: u32) { + // Technically is writing to the stack pointer "not pushing any data to the stack"? + // In any event, if we don't set `nostack` here, this method is useless as the new + // stack value is immediately mutated by returning. Really this is just not a good + // method and its higher-level use is marked as deprecated in cortex-m. + asm!("msr MSP, {}", in(reg) val, options(nomem, nostack, preserves_flags)); +} + +// NOTE: No FFI shim, this requires inline asm. +#[inline(always)] +pub unsafe fn __apsr_r() -> u32 { + let r; + asm!("mrs {}, APSR", out(reg) r, options(nomem, nostack, preserves_flags)); + r +} + +#[inline(always)] +pub unsafe 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. + asm!("nop", options(nomem, nostack, preserves_flags)); +} + +// NOTE: No FFI shim, this requires inline asm. +#[inline(always)] +pub unsafe fn __pc_r() -> u32 { + let r; + asm!("mov {}, pc", out(reg) r, options(nomem, nostack, preserves_flags)); + r +} + +// NOTE: No FFI shim, this requires inline asm. +#[inline(always)] +pub unsafe fn __pc_w(val: u32) { + asm!("mov pc, {}", in(reg) val, options(nomem, nostack, preserves_flags)); +} + +// NOTE: No FFI shim, this requires inline asm. +#[inline(always)] +pub unsafe fn __lr_r() -> u32 { + let r; + asm!("mov {}, lr", out(reg) r, options(nomem, nostack, preserves_flags)); + r +} + +// NOTE: No FFI shim, this requires inline asm. +#[inline(always)] +pub unsafe fn __lr_w(val: u32) { + asm!("mov lr, {}", in(reg) val, options(nomem, nostack, preserves_flags)); +} + +#[inline(always)] +pub unsafe fn __primask_r() -> u32 { + let r; + asm!("mrs {}, PRIMASK", out(reg) r, options(nomem, nostack, preserves_flags)); + r +} + +#[inline(always)] +pub unsafe fn __psp_r() -> u32 { + let r; + asm!("mrs {}, PSP", out(reg) r, options(nomem, nostack, preserves_flags)); + r +} + +#[inline(always)] +pub unsafe fn __psp_w(val: u32) { + // See comment on __msp_w. Unlike MSP, there are legitimate use-cases for modifying PSP + // if MSP is currently being used as the stack pointer. + asm!("msr PSP, {}", in(reg) val, options(nomem, nostack, preserves_flags)); +} + +#[inline(always)] +pub unsafe fn __sev() { + asm!("sev", options(nomem, nostack, preserves_flags)); +} + +#[inline(always)] +pub unsafe fn __udf() -> ! { + asm!("udf #0", options(noreturn, nomem, nostack, preserves_flags)); +} + +#[inline(always)] +pub unsafe fn __wfe() { + asm!("wfe", options(nomem, nostack, preserves_flags)); +} + +#[inline(always)] +pub unsafe fn __wfi() { + asm!("wfi", options(nomem, nostack, preserves_flags)); +} + +/// Semihosting syscall. +#[inline(always)] +pub unsafe fn __sh_syscall(mut nr: u32, arg: u32) -> u32 { + asm!("bkpt #0xab", inout("r0") nr, in("r1") arg, options(nomem, nostack, preserves_flags)); + nr +} + +/// Set CONTROL.SPSEL to 0, write `msp` to MSP, branch to `rv`. +#[inline(always)] +pub unsafe fn __bootstrap(msp: u32, rv: u32) -> ! { + 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, nomem, nostack), + ); +} + +// 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::arch::asm; + use core::sync::atomic::{compiler_fence, Ordering}; + + #[inline(always)] + pub unsafe fn __basepri_max(val: u8) { + asm!("msr BASEPRI_MAX, {}", in(reg) val, options(nomem, nostack, preserves_flags)); + } + + #[inline(always)] + pub unsafe fn __basepri_r() -> u8 { + let r; + asm!("mrs {}, BASEPRI", out(reg) r, options(nomem, nostack, preserves_flags)); + r + } + + #[inline(always)] + pub unsafe fn __basepri_w(val: u8) { + asm!("msr BASEPRI, {}", in(reg) val, options(nomem, nostack, preserves_flags)); + } + + #[inline(always)] + pub unsafe fn __faultmask_r() -> u32 { + let r; + asm!("mrs {}, FAULTMASK", out(reg) r, options(nomem, nostack, preserves_flags)); + r + } + + #[inline(always)] + pub unsafe fn __enable_icache() { + 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) _, + options(nostack), + ); + compiler_fence(Ordering::SeqCst); + } + + #[inline(always)] + pub unsafe fn __enable_dcache() { + 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) _, + options(nostack), + ); + compiler_fence(Ordering::SeqCst); + } +} + +#[cfg(armv7em)] +pub use self::v7em::*; +#[cfg(armv7em)] +mod v7em { + use core::arch::asm; + + #[inline(always)] + pub unsafe fn __basepri_max_cm7_r0p1(val: u8) { + 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) _, + options(nomem, nostack, preserves_flags), + ); + } + + #[inline(always)] + pub unsafe fn __basepri_w_cm7_r0p1(val: u8) { + asm!( + "mrs {1}, PRIMASK", + "cpsid i", + "tst.w {1}, #1", + "msr BASEPRI, {0}", + "it ne", + "bxne lr", + "cpsie i", + in(reg) val, + out(reg) _, + options(nomem, nostack, preserves_flags), + ); + } +} + +#[cfg(armv8m)] +pub use self::v8m::*; +/// Baseline and Mainline. +#[cfg(armv8m)] +mod v8m { + use core::arch::asm; + + #[inline(always)] + pub unsafe fn __tt(mut target: u32) -> u32 { + asm!( + "tt {target}, {target}", + target = inout(reg) target, + options(nomem, nostack, preserves_flags), + ); + target + } + + #[inline(always)] + pub unsafe fn __ttt(mut target: u32) -> u32 { + asm!( + "ttt {target}, {target}", + target = inout(reg) target, + options(nomem, nostack, preserves_flags), + ); + target + } + + #[inline(always)] + pub unsafe fn __tta(mut target: u32) -> u32 { + asm!( + "tta {target}, {target}", + target = inout(reg) target, + options(nomem, nostack, preserves_flags), + ); + target + } + + #[inline(always)] + pub unsafe fn __ttat(mut target: u32) -> u32 { + asm!( + "ttat {target}, {target}", + target = inout(reg) target, + options(nomem, nostack, preserves_flags), + ); + target + } + + #[inline(always)] + pub unsafe fn __msp_ns_r() -> u32 { + let r; + asm!("mrs {}, MSP_NS", out(reg) r, options(nomem, nostack, preserves_flags)); + r + } + + #[inline(always)] + pub unsafe fn __msp_ns_w(val: u32) { + asm!("msr MSP_NS, {}", in(reg) val, options(nomem, nostack, preserves_flags)); + } + + #[inline(always)] + pub unsafe fn __bxns(val: u32) { + asm!("BXNS {}", in(reg) val, options(nomem, nostack, preserves_flags)); + } +} + +#[cfg(armv8m_main)] +pub use self::v8m_main::*; +/// Mainline only. +#[cfg(armv8m_main)] +mod v8m_main { + use core::arch::asm; + + #[inline(always)] + pub unsafe fn __msplim_r() -> u32 { + let r; + asm!("mrs {}, MSPLIM", out(reg) r, options(nomem, nostack, preserves_flags)); + r + } + + #[inline(always)] + pub unsafe fn __msplim_w(val: u32) { + asm!("msr MSPLIM, {}", in(reg) val, options(nomem, nostack, preserves_flags)); + } + + #[inline(always)] + pub unsafe fn __psplim_r() -> u32 { + let r; + asm!("mrs {}, PSPLIM", out(reg) r, options(nomem, nostack, preserves_flags)); + r + } + + #[inline(always)] + pub unsafe fn __psplim_w(val: u32) { + asm!("msr PSPLIM, {}", in(reg) val, options(nomem, nostack, preserves_flags)); + } +} + +#[cfg(has_fpu)] +pub use self::fpu::*; +/// All targets with FPU. +#[cfg(has_fpu)] +mod fpu { + use core::arch::asm; + + #[inline(always)] + pub unsafe fn __fpscr_r() -> u32 { + let r; + asm!("vmrs {}, fpscr", out(reg) r, options(nomem, nostack, preserves_flags)); + r + } + + #[inline(always)] + pub unsafe fn __fpscr_w(val: u32) { + asm!("vmsr fpscr, {}", in(reg) val, options(nomem, nostack)); + } +} diff --git a/cortex-m/asm/lib.rs b/cortex-m/asm/lib.rs new file mode 100644 index 00000000..48f3dc21 --- /dev/null +++ b/cortex-m/asm/lib.rs @@ -0,0 +1,143 @@ +//! FFI shim around the inline assembly in `inline.rs`. +//! +//! We use this file to precompile some assembly stubs into the static libraries you can find in +//! `bin`. Apps using the `cortex-m` crate then link against those static libraries and don't need +//! to build this file themselves. +//! +//! Nowadays the assembly stubs are no longer actual assembly files, but actually just this small +//! Rust crate that uses unstable inline assembly, coupled with the `xtask` tool to invoke rustc +//! and build the files. +//! +//! Precompiling this to a static lib allows users to call assembly routines from stable Rust, but +//! also perform [linker plugin LTO] with the precompiled artifacts to completely inline the +//! assembly routines into their code, which brings the "outline assembly" on par with "real" inline +//! assembly. +//! +//! For developers and contributors to `cortex-m`, this setup means that they don't have to install +//! any binutils, assembler, or C compiler to hack on the crate. All they need is to run `cargo +//! xtask assemble` to rebuild the archives from this file. +//! +//! Cool, right? +//! +//! # Rust version management +//! +//! Since inline assembly is still unstable, and we want to ensure that the created blobs are +//! up-to-date in CI, we have to pin the nightly version we use for this. The nightly toolchain is +//! stored in `asm-toolchain`. +//! +//! The `cargo xtask` automation will automatically install the `asm-toolchain` as well as all +//! Cortex-M targets needed to generate the blobs. +//! +//! [linker plugin LTO]: https://doc.rust-lang.org/stable/rustc/linker-plugin-lto.html + +#![feature(asm)] +#![no_std] +#![crate_type = "staticlib"] +#![deny(warnings)] +// Don't warn about feature(asm) being stable on Rust >= 1.59.0 +#![allow(stable_features)] + +mod inline; + +macro_rules! shims { + ( + $( fn $name:ident( $($arg:ident: $argty:ty),* ) $(-> $ret:ty)?; )+ + ) => { + $( + #[no_mangle] + pub unsafe extern "C" fn $name( + $($arg: $argty),* + ) $(-> $ret)? { + crate::inline::$name($($arg),*) + } + )+ + }; +} + +shims! { + fn __bkpt(); + fn __control_r() -> u32; + fn __control_w(w: u32); + fn __cpsid(); + fn __cpsie(); + fn __delay(cyc: u32); + fn __dmb(); + fn __dsb(); + fn __isb(); + fn __msp_r() -> u32; + fn __msp_w(val: u32); + fn __nop(); + fn __primask_r() -> u32; + fn __psp_r() -> u32; + fn __psp_w(val: u32); + fn __sev(); + fn __udf() -> !; + fn __wfe(); + fn __wfi(); + fn __sh_syscall(nr: u32, arg: u32) -> u32; + fn __bootstrap(msp: u32, rv: u32) -> !; +} + +// v7m *AND* v8m.main, but *NOT* v8m.base +#[cfg(any(armv7m, armv8m_main))] +shims! { + fn __basepri_max(val: u8); + fn __basepri_r() -> u8; + fn __basepri_w(val: u8); + fn __faultmask_r() -> u32; + fn __enable_icache(); + fn __enable_dcache(); +} + +#[cfg(armv7em)] +shims! { + fn __basepri_max_cm7_r0p1(val: u8); + fn __basepri_w_cm7_r0p1(val: u8); +} + +// Baseline and Mainline. +#[cfg(armv8m)] +shims! { + fn __tt(target: u32) -> u32; + fn __ttt(target: u32) -> u32; + fn __tta(target: u32) -> u32; + fn __ttat(target: u32) -> u32; + fn __msp_ns_r() -> u32; + fn __msp_ns_w(val: u32); + fn __bxns(val: u32); +} + +// Mainline only. +#[cfg(armv8m_main)] +shims! { + fn __msplim_r() -> u32; + fn __msplim_w(val: u32); + fn __psplim_r() -> u32; + fn __psplim_w(val: u32); +} + +// All targets with FPU. +#[cfg(has_fpu)] +shims! { + fn __fpscr_r() -> u32; + fn __fpscr_w(val: u32); +} + +/// We *must* define a panic handler here, even though nothing here should ever be able to panic. +/// +/// We prove that nothing will ever panic by calling a function that doesn't exist. If the panic +/// handler gets linked in, this causes a linker error. We always build this file with optimizations +/// enabled, but even without them the panic handler should never be linked in. +#[panic_handler] +#[link_section = ".text.asm_panic_handler"] +fn panic(_: &core::panic::PanicInfo) -> ! { + extern "C" { + #[link_name = "cortex-m internal error: panic handler not optimized out, please file an \ + issue at https://github.com/rust-embedded/cortex-m"] + fn __cortex_m_should_not_panic() -> !; + } + + unsafe { + __cortex_m_should_not_panic(); + } +} diff --git a/cortex-m/bin/thumbv6m-none-eabi-lto.a b/cortex-m/bin/thumbv6m-none-eabi-lto.a new file mode 100644 index 00000000..c9600fde Binary files /dev/null and b/cortex-m/bin/thumbv6m-none-eabi-lto.a differ diff --git a/cortex-m/bin/thumbv6m-none-eabi.a b/cortex-m/bin/thumbv6m-none-eabi.a new file mode 100644 index 00000000..9640a699 Binary files /dev/null and b/cortex-m/bin/thumbv6m-none-eabi.a differ diff --git a/cortex-m/bin/thumbv7em-none-eabi-lto.a b/cortex-m/bin/thumbv7em-none-eabi-lto.a new file mode 100644 index 00000000..5d9350fc Binary files /dev/null and b/cortex-m/bin/thumbv7em-none-eabi-lto.a differ diff --git a/cortex-m/bin/thumbv7em-none-eabi.a b/cortex-m/bin/thumbv7em-none-eabi.a new file mode 100644 index 00000000..88acbddf Binary files /dev/null and b/cortex-m/bin/thumbv7em-none-eabi.a differ diff --git a/cortex-m/bin/thumbv7em-none-eabihf-lto.a b/cortex-m/bin/thumbv7em-none-eabihf-lto.a new file mode 100644 index 00000000..f0860c16 Binary files /dev/null and b/cortex-m/bin/thumbv7em-none-eabihf-lto.a differ diff --git a/cortex-m/bin/thumbv7em-none-eabihf.a b/cortex-m/bin/thumbv7em-none-eabihf.a new file mode 100644 index 00000000..cf91a7a5 Binary files /dev/null and b/cortex-m/bin/thumbv7em-none-eabihf.a differ diff --git a/cortex-m/bin/thumbv7m-none-eabi-lto.a b/cortex-m/bin/thumbv7m-none-eabi-lto.a new file mode 100644 index 00000000..baee2364 Binary files /dev/null and b/cortex-m/bin/thumbv7m-none-eabi-lto.a differ diff --git a/cortex-m/bin/thumbv7m-none-eabi.a b/cortex-m/bin/thumbv7m-none-eabi.a new file mode 100644 index 00000000..ff4bf211 Binary files /dev/null and b/cortex-m/bin/thumbv7m-none-eabi.a differ diff --git a/cortex-m/bin/thumbv8m.base-none-eabi-lto.a b/cortex-m/bin/thumbv8m.base-none-eabi-lto.a new file mode 100644 index 00000000..9016d369 Binary files /dev/null and b/cortex-m/bin/thumbv8m.base-none-eabi-lto.a differ diff --git a/cortex-m/bin/thumbv8m.base-none-eabi.a b/cortex-m/bin/thumbv8m.base-none-eabi.a new file mode 100644 index 00000000..c0cc96c4 Binary files /dev/null and b/cortex-m/bin/thumbv8m.base-none-eabi.a differ diff --git a/cortex-m/bin/thumbv8m.main-none-eabi-lto.a b/cortex-m/bin/thumbv8m.main-none-eabi-lto.a new file mode 100644 index 00000000..de23794e Binary files /dev/null and b/cortex-m/bin/thumbv8m.main-none-eabi-lto.a differ diff --git a/cortex-m/bin/thumbv8m.main-none-eabi.a b/cortex-m/bin/thumbv8m.main-none-eabi.a new file mode 100644 index 00000000..d017a15b Binary files /dev/null and b/cortex-m/bin/thumbv8m.main-none-eabi.a differ diff --git a/cortex-m/bin/thumbv8m.main-none-eabihf-lto.a b/cortex-m/bin/thumbv8m.main-none-eabihf-lto.a new file mode 100644 index 00000000..df0a29c3 Binary files /dev/null and b/cortex-m/bin/thumbv8m.main-none-eabihf-lto.a differ diff --git a/cortex-m/bin/thumbv8m.main-none-eabihf.a b/cortex-m/bin/thumbv8m.main-none-eabihf.a new file mode 100644 index 00000000..223ff1df Binary files /dev/null and b/cortex-m/bin/thumbv8m.main-none-eabihf.a differ diff --git a/cortex-m/build.rs b/cortex-m/build.rs index cfcd394e..37af277c 100644 --- a/cortex-m/build.rs +++ b/cortex-m/build.rs @@ -1,12 +1,38 @@ -use std::env; +use std::path::PathBuf; +use std::{env, fs}; fn main() { let target = env::var("TARGET").unwrap(); let host_triple = env::var("HOST").unwrap(); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let name = env::var("CARGO_PKG_NAME").unwrap(); + + if host_triple == target { + println!("cargo:rustc-cfg=native"); + } + + if target.starts_with("thumb") { + let suffix = if env::var_os("CARGO_FEATURE_LINKER_PLUGIN_LTO").is_some() { + "-lto" + } else { + "" + }; + + fs::copy( + format!("bin/{}{}.a", target, suffix), + out_dir.join(format!("lib{}.a", name)), + ) + .unwrap(); + + println!("cargo:rustc-link-lib=static={}", name); + println!("cargo:rustc-link-search={}", out_dir.display()); + } println!("cargo:rustc-check-cfg=cfg(armv6m)"); println!("cargo:rustc-check-cfg=cfg(armv7em)"); println!("cargo:rustc-check-cfg=cfg(armv7m)"); + println!("cargo:rustc-check-cfg=cfg(armv7m)"); + println!("cargo:rustc-check-cfg=cfg(armv8m)"); println!("cargo:rustc-check-cfg=cfg(armv8m)"); println!("cargo:rustc-check-cfg=cfg(armv8m_base)"); println!("cargo:rustc-check-cfg=cfg(armv8m_main)"); @@ -14,10 +40,6 @@ fn main() { println!("cargo:rustc-check-cfg=cfg(has_fpu)"); println!("cargo:rustc-check-cfg=cfg(native)"); - if host_triple == target { - println!("cargo:rustc-cfg=native"); - } - if target.starts_with("thumbv6m-") { println!("cargo:rustc-cfg=cortex_m"); println!("cargo:rustc-cfg=armv6m"); @@ -27,7 +49,7 @@ fn main() { } else if target.starts_with("thumbv7em-") { println!("cargo:rustc-cfg=cortex_m"); println!("cargo:rustc-cfg=armv7m"); - println!("cargo:rustc-cfg=armv7em"); + println!("cargo:rustc-cfg=armv7em"); // (not currently used) } else if target.starts_with("thumbv8m.base") { println!("cargo:rustc-cfg=cortex_m"); println!("cargo:rustc-cfg=armv8m"); diff --git a/cortex-m/src/asm.rs b/cortex-m/src/asm.rs index 477a5f71..4dc1ab07 100644 --- a/cortex-m/src/asm.rs +++ b/cortex-m/src/asm.rs @@ -1,111 +1,73 @@ //! Miscellaneous assembly instructions -#[cfg(cortex_m)] -use core::arch::asm; -use core::sync::atomic::{compiler_fence, Ordering}; +// When inline assembly is enabled, pull in the assembly routines here. `call_asm!` will invoke +// these routines. +#[cfg(feature = "inline-asm")] +#[path = "../asm/inline.rs"] +pub(crate) mod inline; /// 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. -#[cfg(cortex_m)] #[inline(always)] pub fn bkpt() { - unsafe { asm!("bkpt", options(nomem, nostack, preserves_flags)) }; + call_asm!(__bkpt()); } /// Blocks the program for *at least* `cycles` CPU cycles. /// -/// This is implemented in assembly as a fixed number of iterations of a loop, so that execution -/// time is independent of the optimization level. +/// 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. /// -/// The loop code is the same for all architectures, however the number of CPU cycles required for -/// one iteration varies substantially between architectures. This means that with a 48MHz CPU -/// clock, a call to `delay(48_000_000)` is guaranteed to take at least 1 second, but for example -/// could take 2 seconds. -/// -/// 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. -#[cfg(cortex_m)] +/// 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(cycles: 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_cycles = 1 + cycles / 2; - unsafe { - asm!( - // The `bne` on some cores (eg Cortex-M4) will take a different number of cycles - // depending on the alignment of the branch target. Set the alignment of the top of the - // loop to prevent surprising timing changes when the alignment of `fn delay()` changes. - ".p2align 3", - // Use local labels to avoid R_ARM_THM_JUMP8 relocations which fail on thumbv6m. - "1:", - "subs {}, #1", - "bne 1b", - inout(reg) real_cycles => _, - options(nomem, nostack), - ) - }; + call_asm!(__delay(cycles: u32)); } /// A no-operation. Useful to prevent delay loops from being optimized away. -#[inline(always)] +#[inline] 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. - #[cfg(cortex_m)] - unsafe { - asm!("nop", options(nomem, nostack, preserves_flags)) - }; + call_asm!(__nop()); } /// Generate an Undefined Instruction exception. /// /// Can be used as a stable alternative to `core::intrinsics::abort`. -#[cfg(cortex_m)] -#[inline(always)] +#[inline] pub fn udf() -> ! { - unsafe { asm!("udf #0", options(noreturn, nomem, nostack, preserves_flags)) }; + call_asm!(__udf() -> !) } /// Wait For Event -#[cfg(cortex_m)] -#[inline(always)] +#[inline] pub fn wfe() { - unsafe { asm!("wfe", options(nomem, nostack, preserves_flags)) }; + call_asm!(__wfe()) } /// Wait For Interrupt -#[cfg(cortex_m)] -#[inline(always)] +#[inline] pub fn wfi() { - unsafe { asm!("wfi", options(nomem, nostack, preserves_flags)) }; + call_asm!(__wfi()) } /// Send Event -#[cfg(cortex_m)] -#[inline(always)] +#[inline] pub fn sev() { - unsafe { asm!("sev", options(nomem, nostack, preserves_flags)) }; + call_asm!(__sev()) } /// 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)] +#[inline] pub fn isb() { - compiler_fence(Ordering::SeqCst); - #[cfg(cortex_m)] - unsafe { - asm!("isb", options(nostack, preserves_flags)) - }; - compiler_fence(Ordering::SeqCst); + call_asm!(__isb()) } /// Data Synchronization Barrier @@ -115,14 +77,9 @@ pub fn isb() { /// /// * any explicit memory access made before this instruction is complete /// * all cache and branch predictor maintenance operations before this instruction complete -#[inline(always)] +#[inline] pub fn dsb() { - compiler_fence(Ordering::SeqCst); - #[cfg(cortex_m)] - unsafe { - asm!("dsb", options(nostack, preserves_flags)) - }; - compiler_fence(Ordering::SeqCst); + call_asm!(__dsb()) } /// Data Memory Barrier @@ -130,14 +87,9 @@ pub fn dsb() { /// 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)] +#[inline] pub fn dmb() { - compiler_fence(Ordering::SeqCst); - #[cfg(cortex_m)] - unsafe { - asm!("dmb", options(nostack, preserves_flags)) - }; - compiler_fence(Ordering::SeqCst); + call_asm!(__dmb()) } /// Test Target @@ -145,20 +97,13 @@ pub fn dmb() { /// 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). -#[inline(always)] +#[inline] #[cfg(armv8m)] // The __tt function does not dereference the pointer received. #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn tt(addr: *mut u32) -> u32 { - let mut target = addr as u32; - unsafe { - asm!( - "tt {target}, {target}", - target = inout(reg) target, - options(nomem, nostack, preserves_flags), - ) - }; - target + let addr = addr as u32; + call_asm!(__tt(addr: u32) -> u32) } /// Test Target Unprivileged @@ -167,20 +112,13 @@ pub fn tt(addr: *mut u32) -> u32 { /// access to that location. /// Returns a Test Target Response Payload (cf section D1.2.215 of /// Armv8-M Architecture Reference Manual). -#[inline(always)] +#[inline] #[cfg(armv8m)] // The __ttt function does not dereference the pointer received. #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn ttt(addr: *mut u32) -> u32 { - let mut target = addr as u32; - unsafe { - asm!( - "ttt {target}, {target}", - target = inout(reg) target, - options(nomem, nostack, preserves_flags), - ) - }; - target + let addr = addr as u32; + call_asm!(__ttt(addr: u32) -> u32) } /// Test Target Alternate Domain @@ -190,20 +128,13 @@ pub fn ttt(addr: *mut u32) -> u32 { /// undefined if used from Non-Secure state. /// Returns a Test Target Response Payload (cf section D1.2.215 of /// Armv8-M Architecture Reference Manual). -#[inline(always)] +#[inline] #[cfg(armv8m)] // The __tta function does not dereference the pointer received. #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn tta(addr: *mut u32) -> u32 { - let mut target = addr as u32; - unsafe { - asm!( - "tta {target}, {target}", - target = inout(reg) target, - options(nomem, nostack, preserves_flags), - ) - }; - target + let addr = addr as u32; + call_asm!(__tta(addr: u32) -> u32) } /// Test Target Alternate Domain Unprivileged @@ -213,40 +144,31 @@ pub fn tta(addr: *mut u32) -> u32 { /// 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). -#[inline(always)] +#[inline] #[cfg(armv8m)] // The __ttat function does not dereference the pointer received. #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn ttat(addr: *mut u32) -> u32 { - let mut target = addr as u32; - unsafe { - asm!( - "ttat {target}, {target}", - target = inout(reg) target, - options(nomem, nostack, preserves_flags), - ) - }; - target + let addr = addr as u32; + call_asm!(__ttat(addr: u32) -> u32) } /// 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)] +#[inline] #[cfg(armv8m)] pub unsafe fn bx_ns(addr: u32) { - asm!("bxns {}", in(reg) addr, options(nomem, nostack, preserves_flags)); + call_asm!(__bxns(addr: u32)); } /// Semihosting syscall. /// /// This method is used by cortex-m-semihosting to provide semihosting syscalls. -#[cfg(cortex_m)] -#[inline(always)] -pub unsafe fn semihosting_syscall(mut nr: u32, arg: u32) -> u32 { - asm!("bkpt #0xab", inout("r0") nr, in("r1") arg, options(nostack, preserves_flags)); - nr +#[inline] +pub unsafe fn semihosting_syscall(nr: u32, arg: u32) -> u32 { + call_asm!(__sh_syscall(nr: u32, arg: u32) -> u32) } /// Bootstrap. @@ -259,27 +181,12 @@ pub unsafe fn semihosting_syscall(mut nr: u32, arg: u32) -> u32 { /// /// `msp` and `rv` must point to valid stack memory and executable code, /// respectively. -#[cfg(cortex_m)] #[inline] pub unsafe fn bootstrap(msp: *const u32, rv: *const u32) -> ! { // Ensure thumb mode is set. let rv = (rv as u32) | 1; let msp = msp as u32; - 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, nomem, nostack), - ); + call_asm!(__bootstrap(msp: u32, rv: u32) -> !); } /// Bootload. @@ -294,7 +201,6 @@ pub unsafe fn bootstrap(msp: *const u32, rv: *const u32) -> ! { /// The provided `vector_table` must point to a valid vector /// table, with a valid stack pointer as the first word and /// a valid reset vector as the second word. -#[cfg(cortex_m)] #[inline] pub unsafe fn bootload(vector_table: *const u32) -> ! { let msp = core::ptr::read_volatile(vector_table); diff --git a/cortex-m/src/call_asm.rs b/cortex-m/src/call_asm.rs new file mode 100644 index 00000000..295277f3 --- /dev/null +++ b/cortex-m/src/call_asm.rs @@ -0,0 +1,24 @@ +/// An internal macro to invoke an assembly routine. +/// +/// Depending on whether the unstable `inline-asm` feature is enabled, this will either call into +/// the inline assembly implementation directly, or through the FFI shim (see `asm/lib.rs`). +macro_rules! call_asm { + ( $func:ident ( $($args:ident: $tys:ty),* ) $(-> $ret:ty)? ) => {{ + #[allow(unused_unsafe)] + unsafe { + match () { + #[cfg(feature = "inline-asm")] + () => crate::asm::inline::$func($($args),*), + + #[cfg(not(feature = "inline-asm"))] + () => { + extern "C" { + fn $func($($args: $tys),*) $(-> $ret)?; + } + + $func($($args),*) + }, + } + } + }}; +} diff --git a/cortex-m/src/cmse.rs b/cortex-m/src/cmse.rs index 7826bb8d..36d74475 100644 --- a/cortex-m/src/cmse.rs +++ b/cortex-m/src/cmse.rs @@ -174,9 +174,9 @@ impl TestTarget { /// * the TT instruction was executed from an unprivileged mode and the A flag was not specified. #[inline] pub fn mpu_region(self) -> Option { - if self.tt_resp.mrvalid() { - // Cast is safe as MREGION field is defined on 8 bits. - Some(self.tt_resp.mregion() as u8) + if self.tt_resp.srvalid() { + // Cast is safe as SREGION field is defined on 8 bits. + Some(self.tt_resp.sregion() as u8) } else { None } diff --git a/cortex-m/src/critical_section.rs b/cortex-m/src/critical_section.rs index 6bedfffa..d33e90ff 100644 --- a/cortex-m/src/critical_section.rs +++ b/cortex-m/src/critical_section.rs @@ -1,24 +1,25 @@ -use critical_section::{set_impl, Impl, RawRestoreState}; +#[cfg(all(cortex_m, feature = "critical-section-single-core"))] +mod single_core_critical_section { + use critical_section::{set_impl, Impl, RawRestoreState}; -use crate::interrupt; -use crate::register::primask; + use crate::interrupt; + use crate::register::primask; -struct SingleCoreCriticalSection; -set_impl!(SingleCoreCriticalSection); + struct SingleCoreCriticalSection; + set_impl!(SingleCoreCriticalSection); -unsafe impl Impl for SingleCoreCriticalSection { - unsafe fn acquire() -> RawRestoreState { - // Backup previous state of PRIMASK register. We access the entire register directly as a - // u32 instead of using the primask::read() function to minimize the number of processor - // cycles during which interrupts are disabled. - let restore_state = primask::read_raw(); - // NOTE: Fence guarantees are provided by interrupt::disable(), which performs a `compiler_fence(SeqCst)`. - interrupt::disable(); - restore_state - } + unsafe impl Impl for SingleCoreCriticalSection { + unsafe fn acquire() -> RawRestoreState { + let was_active = primask::read().is_active(); + interrupt::disable(); + was_active + } - unsafe fn release(restore_state: RawRestoreState) { - // NOTE: Fence guarantees are provided by primask::write_raw(), which performs a `compiler_fence(SeqCst)`. - primask::write_raw(restore_state); + unsafe fn release(was_active: RawRestoreState) { + // Only re-enable interrupts if they were enabled before the critical section. + if was_active { + interrupt::enable() + } + } } } diff --git a/cortex-m/src/delay.rs b/cortex-m/src/delay.rs index 11b0c284..66a63bf6 100644 --- a/cortex-m/src/delay.rs +++ b/cortex-m/src/delay.rs @@ -1,7 +1,7 @@ //! A delay driver based on SysTick. use crate::peripheral::{syst::SystClkSource, SYST}; -use eh1::delay::DelayNs; +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; /// System timer (SysTick) as a delay provider. pub struct Delay { @@ -75,8 +75,7 @@ impl Delay { } } -#[cfg(feature = "eh0")] -impl eh0::blocking::delay::DelayMs for Delay { +impl DelayMs for Delay { #[inline] fn delay_ms(&mut self, ms: u32) { Delay::delay_ms(self, ms); @@ -84,8 +83,7 @@ impl eh0::blocking::delay::DelayMs for Delay { } // This is a workaround to allow `delay_ms(42)` construction without specifying a type. -#[cfg(feature = "eh0")] -impl eh0::blocking::delay::DelayMs for Delay { +impl DelayMs for Delay { #[inline(always)] fn delay_ms(&mut self, ms: i32) { assert!(ms >= 0); @@ -93,24 +91,21 @@ impl eh0::blocking::delay::DelayMs for Delay { } } -#[cfg(feature = "eh0")] -impl eh0::blocking::delay::DelayMs for Delay { +impl DelayMs for Delay { #[inline(always)] fn delay_ms(&mut self, ms: u16) { Delay::delay_ms(self, u32::from(ms)); } } -#[cfg(feature = "eh0")] -impl eh0::blocking::delay::DelayMs for Delay { +impl DelayMs for Delay { #[inline(always)] fn delay_ms(&mut self, ms: u8) { Delay::delay_ms(self, u32::from(ms)); } } -#[cfg(feature = "eh0")] -impl eh0::blocking::delay::DelayUs for Delay { +impl DelayUs for Delay { #[inline] fn delay_us(&mut self, us: u32) { Delay::delay_us(self, us); @@ -118,8 +113,7 @@ impl eh0::blocking::delay::DelayUs for Delay { } // This is a workaround to allow `delay_us(42)` construction without specifying a type. -#[cfg(feature = "eh0")] -impl eh0::blocking::delay::DelayUs for Delay { +impl DelayUs for Delay { #[inline(always)] fn delay_us(&mut self, us: i32) { assert!(us >= 0); @@ -127,39 +121,16 @@ impl eh0::blocking::delay::DelayUs for Delay { } } -#[cfg(feature = "eh0")] -impl eh0::blocking::delay::DelayUs for Delay { +impl DelayUs for Delay { #[inline(always)] fn delay_us(&mut self, us: u16) { Delay::delay_us(self, u32::from(us)) } } -#[cfg(feature = "eh0")] -impl eh0::blocking::delay::DelayUs for Delay { +impl DelayUs for Delay { #[inline(always)] fn delay_us(&mut self, us: u8) { Delay::delay_us(self, u32::from(us)) } } - -impl DelayNs for Delay { - #[inline] - fn delay_ns(&mut self, ns: u32) { - // from the rp2040-hal: - let us = ns / 1000 + if ns % 1000 == 0 { 0 } else { 1 }; - // With rustc 1.73, this can be replaced by: - // let us = ns.div_ceil(1000); - Delay::delay_us(self, us) - } - - #[inline] - fn delay_us(&mut self, us: u32) { - Delay::delay_us(self, us) - } - - #[inline] - fn delay_ms(&mut self, ms: u32) { - Delay::delay_ms(self, ms) - } -} diff --git a/cortex-m/src/interrupt.rs b/cortex-m/src/interrupt.rs index aa792201..0fd1284b 100644 --- a/cortex-m/src/interrupt.rs +++ b/cortex-m/src/interrupt.rs @@ -1,9 +1,6 @@ //! Interrupts -#[cfg(cortex_m)] -use core::arch::asm; -#[cfg(cortex_m)] -use core::sync::atomic::{compiler_fence, Ordering}; +pub use bare_metal::{CriticalSection, Mutex, Nr}; /// Trait for enums of external interrupt numbers. /// @@ -26,71 +23,51 @@ pub unsafe trait InterruptNumber: Copy { fn number(self) -> u16; } -/// Disables all interrupts in the current core. -#[cfg(cortex_m)] -#[inline] -pub fn disable() { - unsafe { - asm!("cpsid i", options(nomem, nostack, preserves_flags)); +/// Implement InterruptNumber for the old bare_metal::Nr trait. +/// This implementation is for backwards compatibility only and will be removed in cortex-m 0.8. +unsafe impl InterruptNumber for T { + #[inline] + fn number(self) -> u16 { + self.nr() as u16 } +} - // Ensure no subsequent memory accesses are reordered to before interrupts are disabled. - compiler_fence(Ordering::SeqCst); +/// Disables all interrupts +#[inline] +pub fn disable() { + call_asm!(__cpsid()); } -/// Enables all the interrupts in the current core. +/// Enables all the interrupts /// /// # Safety /// -/// - Do not call this function inside a critical section. -#[cfg(cortex_m)] +/// - Do not call this function inside an `interrupt::free` critical section #[inline] pub unsafe fn enable() { - // Ensure no preceeding memory accesses are reordered to after interrupts are enabled. - compiler_fence(Ordering::SeqCst); - - asm!("cpsie i", options(nomem, nostack, preserves_flags)); + call_asm!(__cpsie()); } -/// Execute closure `f` with interrupts disabled in the current core. +/// Execute closure `f` in an interrupt-free context. /// -/// This method does not synchronize multiple cores and may disable required -/// interrupts on some platforms; see the `critical-section` crate for a cross-platform -/// way to enter a critical section which provides a `CriticalSection` token. -/// -/// This crate provides an implementation for `critical-section` suitable for single-core systems, -/// based on disabling all interrupts. It can be enabled with the `critical-section-single-core` feature. -#[cfg(cortex_m)] +/// This as also known as a "critical section". #[inline] pub fn free(f: F) -> R where - F: FnOnce() -> R, + F: FnOnce(&CriticalSection) -> R, { - // Backup previous state of PRIMASK register. We access the entire register directly as a - // u32 instead of using the primask::read() function to minimize the number of processor - // cycles during which interrupts are disabled. - let primask = crate::register::primask::read_raw(); + let primask = crate::register::primask::read(); // disable interrupts disable(); - let r = f(); + let r = f(unsafe { &CriticalSection::new() }); - unsafe { - crate::register::primask::write_raw(primask); + // If the interrupts were active before our `disable` call, then re-enable + // them. Otherwise, keep them disabled + if primask.is_active() { + unsafe { enable() } } r } - -// Make a `free()` function available to allow checking dependencies without specifying a target, -// but that will panic at runtime if executed. -#[doc(hidden)] -#[cfg(not(cortex_m))] -#[inline] -pub fn free(_: F) -> R -where - F: FnOnce() -> R, -{ - panic!("cortex_m::interrupt::free() is only functional on cortex-m platforms"); -} diff --git a/cortex-m/src/itm.rs b/cortex-m/src/itm.rs index 905aefb8..72cb0d9a 100644 --- a/cortex-m/src/itm.rs +++ b/cortex-m/src/itm.rs @@ -57,7 +57,7 @@ unsafe fn write_aligned_impl(port: &mut Stim, buffer: &[u8]) { struct Port<'p>(&'p mut Stim); -impl fmt::Write for Port<'_> { +impl<'p> fmt::Write for Port<'p> { #[inline] fn write_str(&mut self, s: &str) -> fmt::Result { write_all(self.0, s.as_bytes()); diff --git a/cortex-m/src/lib.rs b/cortex-m/src/lib.rs index b0ca35c9..4ba0ad86 100644 --- a/cortex-m/src/lib.rs +++ b/cortex-m/src/lib.rs @@ -9,30 +9,61 @@ //! //! # Optional features //! +//! ## `inline-asm` +//! +//! When this feature is enabled the implementation of all the functions inside the `asm` and +//! `register` modules use inline assembly (`asm!`) instead of external assembly (FFI into separate +//! assembly files pre-compiled using `arm-none-eabi-gcc`). The advantages of enabling `inline-asm` +//! are: +//! +//! - Reduced overhead. FFI eliminates the possibility of inlining so all operations include a +//! function call overhead when `inline-asm` is not enabled. +//! +//! - Some of the `register` API only becomes available only when `inline-asm` is enabled. Check the +//! API docs for details. +//! +//! The disadvantage is that `inline-asm` requires a Rust version at least 1.59 to use the `asm!()` +//! macro. In the future 0.8 and above versions of `cortex-m`, this feature will always be enabled. +//! //! ## `critical-section-single-core` //! //! This feature enables a [`critical-section`](https://github.com/rust-embedded/critical-section) //! implementation suitable for single-core targets, based on disabling interrupts globally. //! //! It is **unsound** to enable it on multi-core targets or for code running in unprivileged mode, -//! and may cause functional problems in systems where some interrupts must not be disabled +//! and may cause functional problems in systems where some interrupts must be not be disabled //! or critical sections are managed as part of an RTOS. In these cases, you should use //! a target-specific implementation instead, typically provided by a HAL or RTOS crate. //! -//! The critical section has been optimized to block interrupts for as few cycles as possible, -//! but -- due to `critical-section` implementation details -- incurs branches in a normal build -//! configuration. For minimal interrupt latency, you can achieve inlining by enabling -//! [linker-plugin-based LTO](https://doc.rust-lang.org/rustc/linker-plugin-lto.html). -//! //! ## `cm7-r0p1` //! //! This feature enables workarounds for errata found on Cortex-M7 chips with revision r0p1. Some //! functions in this crate only work correctly on those chips if this Cargo feature is enabled //! (the functions are documented accordingly). //! +//! ## `linker-plugin-lto` +//! +//! This feature links against prebuilt assembly blobs that are compatible with [Linker-Plugin LTO]. +//! This allows inlining assembly routines into the caller, even without the `inline-asm` feature, +//! and works on stable Rust (but note the drawbacks below!). +//! +//! If you want to use this feature, you need to be aware of a few things: +//! +//! - You need to make sure that `-Clinker-plugin-lto` is passed to rustc. Please refer to the +//! [Linker-Plugin LTO] documentation for details. +//! +//! - You have to use a Rust version whose LLVM version is compatible with the toolchain in +//! `asm-toolchain`. +//! +//! - Due to a [Rust bug][rust-lang/rust#75940] in compiler versions **before 1.49**, this option +//! does not work with optimization levels `s` and `z`. +//! +//! [Linker-Plugin LTO]: https://doc.rust-lang.org/stable/rustc/linker-plugin-lto.html +//! [rust-lang/rust#75940]: https://github.com/rust-lang/rust/issues/75940 +//! //! # Minimum Supported Rust Version (MSRV) //! -//! This crate is guaranteed to compile on stable Rust 1.61 and up. It *might* +//! This crate is guaranteed to compile on stable Rust 1.59 and up. It *might* //! compile with older versions but that may change in any new patch release. #![deny(missing_docs)] @@ -58,27 +89,24 @@ // Don't warn about feature(asm) being stable on Rust >= 1.59.0 #![allow(stable_features)] +extern crate bare_metal; +extern crate volatile_register; + +#[macro_use] +mod call_asm; #[macro_use] mod macros; pub mod asm; #[cfg(armv8m)] pub mod cmse; +mod critical_section; pub mod delay; pub mod interrupt; #[cfg(all(not(armv6m), not(armv8m_base)))] pub mod itm; pub mod peripheral; +pub mod prelude; pub mod register; pub use crate::peripheral::Peripherals; - -#[cfg(all(cortex_m, feature = "critical-section-single-core"))] -mod critical_section; - -/// Used to reexport items for use in macros. Do not use directly. -/// Not covered by semver guarantees. -#[doc(hidden)] -pub mod _export { - pub use critical_section; -} diff --git a/cortex-m/src/macros.rs b/cortex-m/src/macros.rs index c4483b87..512c9323 100644 --- a/cortex-m/src/macros.rs +++ b/cortex-m/src/macros.rs @@ -31,13 +31,10 @@ macro_rules! iprintln { /// at most once in the whole lifetime of the program. /// /// # Notes +/// This macro is unsound on multi core systems. /// -/// This macro requires a `critical-section` implementation to be set. For most single core systems, -/// you can enable the `critical-section-single-core` feature for this crate. For other systems, you -/// have to provide one from elsewhere, typically your chip's HAL crate. -/// -/// For debuggability, you can set an explicit name for a singleton. This name only shows up the -/// debugger and is not referenceable from other code. See example below. +/// For debuggability, you can set an explicit name for a singleton. This name only shows up the +/// the debugger and is not referencable from other code. See example below. /// /// # Example /// @@ -64,12 +61,11 @@ macro_rules! iprintln { /// ``` #[macro_export] macro_rules! singleton { - ($(#[$meta:meta])* $name:ident: $ty:ty = $expr:expr) => { - $crate::_export::critical_section::with(|_| { + ($name:ident: $ty:ty = $expr:expr) => { + $crate::interrupt::free(|_| { // this is a tuple of a MaybeUninit and a bool because using an Option here is // problematic: Due to niche-optimization, an Option could end up producing a non-zero // initializer value which would move the entire static from `.bss` into `.data`... - $(#[$meta])* static mut $name: (::core::mem::MaybeUninit<$ty>, bool) = (::core::mem::MaybeUninit::uninit(), false); @@ -83,13 +79,14 @@ macro_rules! singleton { #[allow(unsafe_code)] unsafe { $name.1 = true; - Some($name.0.write(expr)) + $name.0 = ::core::mem::MaybeUninit::new(expr); + Some(&mut *$name.0.as_mut_ptr()) } } }) }; - ($(#[$meta:meta])* : $ty:ty = $expr:expr) => { - $crate::singleton!($(#[$meta])* VAR: $ty = $expr) + (: $ty:ty = $expr:expr) => { + $crate::singleton!(VAR: $ty = $expr) }; } @@ -115,15 +112,3 @@ const CFAIL: () = (); /// ``` #[allow(dead_code)] const CPASS: () = (); - -/// ``` -/// use cortex_m::singleton; -/// -/// fn foo() { -/// // check that attributes are forwarded -/// singleton!(#[link_section = ".bss"] FOO: u8 = 0); -/// singleton!(#[link_section = ".bss"]: u8 = 1); -/// } -/// ``` -#[allow(dead_code)] -const CPASS_ATTR: () = (); diff --git a/cortex-m/src/peripheral/ac.rs b/cortex-m/src/peripheral/ac.rs index 6169b654..1ac5be10 100644 --- a/cortex-m/src/peripheral/ac.rs +++ b/cortex-m/src/peripheral/ac.rs @@ -16,7 +16,7 @@ pub struct RegisterBlock { /// AHB Slave Control Register pub ahbscr: RW, reserved0: u32, - /// Auxiliary Bus Fault Status Register + /// Auxilary Bus Fault Status Register pub abfsr: RW, } diff --git a/cortex-m/src/peripheral/dcb.rs b/cortex-m/src/peripheral/dcb.rs index a4db9fc3..4a63c889 100644 --- a/cortex-m/src/peripheral/dcb.rs +++ b/cortex-m/src/peripheral/dcb.rs @@ -6,7 +6,6 @@ use crate::peripheral::DCB; use core::ptr; const DCB_DEMCR_TRCENA: u32 = 1 << 24; -const DCB_DEMCR_MON_EN: u32 = 1 << 16; /// Register block #[repr(C)] @@ -26,10 +25,6 @@ impl DCB { /// `peripheral::DWT` cycle counter to work properly. /// As by STM documentation, this flag is not reset on /// soft-reset, only on power reset. - /// - /// Note: vendor-specific registers may have to be set to completely - /// enable tracing. For example, on the STM32F401RE, `TRACE_MODE` - /// and `TRACE_IOEN` must be configured in `DBGMCU_CR` register. #[inline] pub fn enable_trace(&mut self) { // set bit 24 / TRCENA @@ -47,22 +42,6 @@ impl DCB { } } - /// Enables the [`DebugMonitor`](crate::peripheral::scb::Exception::DebugMonitor) exception - #[inline] - pub fn enable_debug_monitor(&mut self) { - unsafe { - self.demcr.modify(|w| w | DCB_DEMCR_MON_EN); - } - } - - /// Disables the [`DebugMonitor`](crate::peripheral::scb::Exception::DebugMonitor) exception - #[inline] - pub fn disable_debug_monitor(&mut self) { - unsafe { - self.demcr.modify(|w| w & !DCB_DEMCR_MON_EN); - } - } - /// Is there a debugger attached? (see note) /// /// Note: This function is [reported not to diff --git a/cortex-m/src/peripheral/dwt.rs b/cortex-m/src/peripheral/dwt.rs index 05657f33..58d91fd3 100644 --- a/cortex-m/src/peripheral/dwt.rs +++ b/cortex-m/src/peripheral/dwt.rs @@ -5,13 +5,12 @@ use volatile_register::WO; use volatile_register::{RO, RW}; use crate::peripheral::DWT; -use bitfield::bitfield; /// Register block #[repr(C)] pub struct RegisterBlock { /// Control - pub ctrl: RW, + pub ctrl: RW, /// Cycle Count #[cfg(not(armv6m))] pub cyccnt: RW, @@ -51,21 +50,6 @@ pub struct RegisterBlock { pub lsr: RO, } -bitfield! { - /// Control register. - #[repr(C)] - #[derive(Copy, Clone)] - pub struct Ctrl(u32); - cyccntena, set_cyccntena: 0; - pcsamplena, set_pcsamplena: 12; - exctrcena, set_exctrcena: 16; - noprfcnt, _: 24; - nocyccnt, _: 25; - noexttrig, _: 26; - notrcpkt, _: 27; - u8, numcomp, _: 31, 28; -} - /// Comparator #[repr(C)] pub struct Comparator { @@ -74,66 +58,58 @@ pub struct Comparator { /// Comparator Mask pub mask: RW, /// Comparator Function - pub function: RW, + pub function: RW, reserved: u32, } -bitfield! { - #[repr(C)] - #[derive(Copy, Clone)] - /// Comparator FUNCTIONn register. - /// - /// See C1.8.17 "Comparator Function registers, DWT_FUNCTIONn" - pub struct Function(u32); - u8, function, set_function: 3, 0; - emitrange, set_emitrange: 5; - cycmatch, set_cycmatch: 7; - datavmatch, set_datavmatch: 8; - lnk1ena, set_lnk1ena: 9; - u8, datavsize, set_datavsize: 11, 10; - u8, datavaddr0, set_datavaddr0: 15, 12; - u8, datavaddr1, set_datavaddr1: 19, 16; - matched, _: 24; -} +// DWT CTRL register fields +const NUMCOMP_OFFSET: u32 = 28; +const NOTRCPKT: u32 = 1 << 27; +const NOEXTTRIG: u32 = 1 << 26; +const NOCYCCNT: u32 = 1 << 25; +const NOPRFCNT: u32 = 1 << 24; +const CYCCNTENA: u32 = 1 << 0; impl DWT { /// Number of comparators implemented /// /// A value of zero indicates no comparator support. #[inline] - pub fn num_comp(&self) -> u8 { - self.ctrl.read().numcomp() + pub fn num_comp() -> u8 { + // NOTE(unsafe) atomic read with no side effects + unsafe { ((*Self::PTR).ctrl.read() >> NUMCOMP_OFFSET) as u8 } } /// Returns `true` if the the implementation supports sampling and exception tracing #[cfg(not(armv6m))] #[inline] - pub fn has_exception_trace(&self) -> bool { - !self.ctrl.read().notrcpkt() + pub fn has_exception_trace() -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Self::PTR).ctrl.read() & NOTRCPKT == 0 } } /// Returns `true` if the implementation includes external match signals #[cfg(not(armv6m))] #[inline] - pub fn has_external_match(&self) -> bool { - !self.ctrl.read().noexttrig() + pub fn has_external_match() -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Self::PTR).ctrl.read() & NOEXTTRIG == 0 } } /// Returns `true` if the implementation supports a cycle counter + #[cfg(not(armv6m))] #[inline] - pub fn has_cycle_counter(&self) -> bool { - #[cfg(not(armv6m))] - return !self.ctrl.read().nocyccnt(); - - #[cfg(armv6m)] - return false; + pub fn has_cycle_counter() -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Self::PTR).ctrl.read() & NOCYCCNT == 0 } } /// Returns `true` if the implementation the profiling counters #[cfg(not(armv6m))] #[inline] - pub fn has_profiling_counter(&self) -> bool { - !self.ctrl.read().noprfcnt() + pub fn has_profiling_counter() -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Self::PTR).ctrl.read() & NOPRFCNT == 0 } } /// Enables the cycle counter @@ -147,67 +123,22 @@ impl DWT { #[cfg(not(armv6m))] #[inline] pub fn enable_cycle_counter(&mut self) { - unsafe { - self.ctrl.modify(|mut r| { - r.set_cyccntena(true); - r - }); - } + unsafe { self.ctrl.modify(|r| r | CYCCNTENA) } } /// Disables the cycle counter #[cfg(not(armv6m))] #[inline] pub fn disable_cycle_counter(&mut self) { - unsafe { - self.ctrl.modify(|mut r| { - r.set_cyccntena(false); - r - }); - } + unsafe { self.ctrl.modify(|r| r & !CYCCNTENA) } } /// Returns `true` if the cycle counter is enabled #[cfg(not(armv6m))] #[inline] - pub fn cycle_counter_enabled(&self) -> bool { - self.ctrl.read().cyccntena() - } - - /// Enables exception tracing - #[cfg(not(armv6m))] - #[inline] - pub fn enable_exception_tracing(&mut self) { - unsafe { - self.ctrl.modify(|mut r| { - r.set_exctrcena(true); - r - }); - } - } - - /// Disables exception tracing - #[cfg(not(armv6m))] - #[inline] - pub fn disable_exception_tracing(&mut self) { - unsafe { - self.ctrl.modify(|mut r| { - r.set_exctrcena(false); - r - }); - } - } - - /// Whether to periodically generate PC samples - #[cfg(not(armv6m))] - #[inline] - pub fn enable_pc_samples(&mut self, bit: bool) { - unsafe { - self.ctrl.modify(|mut r| { - r.set_pcsamplena(bit); - r - }); - } + pub fn cycle_counter_enabled() -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Self::PTR).ctrl.read() & CYCCNTENA != 0 } } /// Returns the current clock cycle count @@ -335,173 +266,3 @@ impl DWT { unsafe { self.foldcnt.write(count as u32) } } } - -/// Whether the comparator should match on read, write or read/write operations. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub enum AccessType { - /// Generate packet only when matched address is read from. - ReadOnly, - /// Generate packet only when matched address is written to. - WriteOnly, - /// Generate packet when matched address is both read from and written to. - ReadWrite, -} - -/// The sequence of packet(s) or events that should be emitted/generated on comparator match. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub enum EmitOption { - /// Emit only trace data value packet. - Data, - /// Emit only trace address packet. - Address, - /// Emit only trace PC value packet - /// - /// *NOTE* only compatible with [AccessType::ReadWrite]. - PC, - /// Emit trace address and data value packets. - AddressData, - /// Emit trace PC value and data value packets. - PCData, - /// Generate a watchpoint debug event. Either halts execution or fires a `DebugMonitor` exception. - /// - /// See more in section "Watchpoint debug event generation" page C1-729. - WatchpointDebugEvent, - /// Generate a `CMPMATCH[N]` event. - /// - /// See more in section "`CMPMATCH[N]` event generation" page C1-730. - CompareMatchEvent, -} - -/// Settings for address matching -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub struct ComparatorAddressSettings { - /// The address to match against. - pub address: u32, - /// The address mask to match against. - pub mask: u32, - /// What sequence of packet(s) to emit on comparator match. - pub emit: EmitOption, - /// Whether to match on read, write or read/write operations. - pub access_type: AccessType, -} - -/// Settings for cycle count matching -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub struct CycleCountSettings { - /// The function selection used. - /// See Table C1-15 for DWT cycle count comparison functions. - pub emit: EmitOption, - /// The cycle count value to compare against. - pub compare: u32, -} - -/// The available functions of a DWT comparator. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -#[non_exhaustive] -pub enum ComparatorFunction { - /// Compare accessed memory addresses. - Address(ComparatorAddressSettings), - /// Compare cycle count & target value. - /// - /// **NOTE**: only supported by comparator 0 and if the HW supports the cycle counter. - /// Check [`DWT::has_cycle_counter`] for support. See C1.8.1 for more details. - CycleCount(CycleCountSettings), -} - -/// Possible error values returned on [Comparator::configure]. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -#[non_exhaustive] -pub enum DwtError { - /// Invalid combination of [AccessType] and [EmitOption]. - InvalidFunction, -} - -impl Comparator { - /// Configure the function of the comparator - #[allow(clippy::missing_inline_in_public_items)] - pub fn configure(&self, settings: ComparatorFunction) -> Result<(), DwtError> { - match settings { - ComparatorFunction::Address(settings) => { - // FUNCTION, EMITRANGE - // See Table C1-14 - let (function, emit_range) = match (&settings.access_type, &settings.emit) { - (AccessType::ReadOnly, EmitOption::Data) => (0b1100, false), - (AccessType::ReadOnly, EmitOption::Address) => (0b1100, true), - (AccessType::ReadOnly, EmitOption::AddressData) => (0b1110, true), - (AccessType::ReadOnly, EmitOption::PCData) => (0b1110, false), - (AccessType::ReadOnly, EmitOption::WatchpointDebugEvent) => (0b0101, false), - (AccessType::ReadOnly, EmitOption::CompareMatchEvent) => (0b1001, false), - - (AccessType::WriteOnly, EmitOption::Data) => (0b1101, false), - (AccessType::WriteOnly, EmitOption::Address) => (0b1101, true), - (AccessType::WriteOnly, EmitOption::AddressData) => (0b1111, true), - (AccessType::WriteOnly, EmitOption::PCData) => (0b1111, false), - (AccessType::WriteOnly, EmitOption::WatchpointDebugEvent) => (0b0110, false), - (AccessType::WriteOnly, EmitOption::CompareMatchEvent) => (0b1010, false), - - (AccessType::ReadWrite, EmitOption::Data) => (0b0010, false), - (AccessType::ReadWrite, EmitOption::Address) => (0b0001, true), - (AccessType::ReadWrite, EmitOption::AddressData) => (0b0010, true), - (AccessType::ReadWrite, EmitOption::PCData) => (0b0011, false), - (AccessType::ReadWrite, EmitOption::WatchpointDebugEvent) => (0b0111, false), - (AccessType::ReadWrite, EmitOption::CompareMatchEvent) => (0b1011, false), - - (AccessType::ReadWrite, EmitOption::PC) => (0b0001, false), - (_, EmitOption::PC) => return Err(DwtError::InvalidFunction), - }; - - unsafe { - self.function.modify(|mut r| { - r.set_function(function); - r.set_emitrange(emit_range); - // don't compare data value - r.set_datavmatch(false); - // don't compare cycle counter value - // NOTE: only needed for comparator 0, but is SBZP. - r.set_cycmatch(false); - // SBZ as needed, see Page 784/C1-724 - r.set_datavsize(0); - r.set_datavaddr0(0); - r.set_datavaddr1(0); - - r - }); - - self.comp.write(settings.address); - self.mask.write(settings.mask); - } - } - ComparatorFunction::CycleCount(settings) => { - let function = match &settings.emit { - EmitOption::PCData => 0b0001, - EmitOption::WatchpointDebugEvent => 0b0100, - EmitOption::CompareMatchEvent => 0b1000, - _ => return Err(DwtError::InvalidFunction), - }; - - unsafe { - self.function.modify(|mut r| { - r.set_function(function); - // emit_range is N/A for cycle count compare - r.set_emitrange(false); - // don't compare data - r.set_datavmatch(false); - // compare cyccnt - r.set_cycmatch(true); - // SBZ as needed, see Page 784/C1-724 - r.set_datavsize(0); - r.set_datavaddr0(0); - r.set_datavaddr1(0); - - r - }); - - self.comp.write(settings.compare); - self.mask.write(0); // SBZ, see Page 784/C1-724 - } - } - } - - Ok(()) - } -} diff --git a/cortex-m/src/peripheral/itm.rs b/cortex-m/src/peripheral/itm.rs index 7291ae06..c0d560f5 100644 --- a/cortex-m/src/peripheral/itm.rs +++ b/cortex-m/src/peripheral/itm.rs @@ -7,12 +7,6 @@ use core::ptr; use volatile_register::{RO, RW, WO}; -use crate::peripheral::ITM; -use bitfield::bitfield; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - /// Register block #[repr(C)] pub struct RegisterBlock { @@ -26,7 +20,7 @@ pub struct RegisterBlock { pub tpr: RW, reserved2: [u32; 15], /// Trace Control - pub tcr: RW, + pub tcr: RW, reserved3: [u32; 75], /// Lock Access pub lar: WO, @@ -34,22 +28,6 @@ pub struct RegisterBlock { pub lsr: RO, } -bitfield! { - /// Trace Control Register. - #[repr(C)] - #[derive(Copy, Clone)] - pub struct Tcr(u32); - itmena, set_itmena: 0; - tsena, set_tsena: 1; - syncena, set_synena: 2; - txena, set_txena: 3; - swoena, set_swoena: 4; - u8, tsprescale, set_tsprescale: 9, 8; - u8, gtsfreq, set_gtsfreq: 11, 10; - u8, tracebusid, set_tracebusid: 22, 16; - busy, _: 23; -} - /// Stimulus Port pub struct Stim { register: UnsafeCell, @@ -91,126 +69,3 @@ impl Stim { unsafe { ptr::read_volatile(self.register.get()) & 0b11 != 0 } } } - -/// The possible local timestamp options. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum LocalTimestampOptions { - /// Disable local timestamps. - Disabled, - /// Enable local timestamps and use no prescaling. - Enabled, - /// Enable local timestamps and set the prescaler to divide the - /// reference clock by 4. - EnabledDiv4, - /// Enable local timestamps and set the prescaler to divide the - /// reference clock by 16. - EnabledDiv16, - /// Enable local timestamps and set the prescaler to divide the - /// reference clock by 64. - EnabledDiv64, -} - -#[cfg(feature = "std")] -impl core::convert::TryFrom for LocalTimestampOptions { - type Error = (); - - /// Converts an integer value to an enabled [LocalTimestampOptions] - /// variant. Accepted values are: 1, 4, 16, 64. Any other value - /// yields `Err(())`. - #[inline] - fn try_from(value: u8) -> Result { - match value { - 1 => Ok(Self::Enabled), - 4 => Ok(Self::EnabledDiv4), - 16 => Ok(Self::EnabledDiv16), - 64 => Ok(Self::EnabledDiv64), - _ => Err(()), - } - } -} - -/// The possible global timestamp options. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub enum GlobalTimestampOptions { - /// Disable global timestamps. - Disabled, - /// Generate a global timestamp approximately every 128 cycles. - Every128Cycles, - /// Generate a global timestamp approximately every 8921 cycles. - Every8192Cycles, - /// Generate a global timestamp after every packet, if the output FIFO is empty. - EveryPacket, -} - -/// The possible clock sources for timestamp counters. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub enum TimestampClkSrc { - /// Clock timestamp counters using the system processor clock. - SystemClock, - /// Clock timestamp counters using the asynchronous clock from the - /// TPIU interface. - /// - /// NOTE: The timestamp counter is held in reset while the output - /// line is idle. - AsyncTPIU, -} - -/// Available settings for the ITM peripheral. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub struct ITMSettings { - /// Whether to enable ITM. - pub enable: bool, - /// Whether DWT packets should be forwarded to ITM. - pub forward_dwt: bool, - /// The local timestamp options that should be applied. - pub local_timestamps: LocalTimestampOptions, - /// The global timestamp options that should be applied. - pub global_timestamps: GlobalTimestampOptions, - /// The trace bus ID to use when multi-trace sources are in use. - /// `None` specifies that only a single trace source is in use and - /// has the same effect as `Some(0)`. - pub bus_id: Option, - /// The clock that should increase timestamp counters. - pub timestamp_clk_src: TimestampClkSrc, -} - -impl ITM { - /// Removes the software lock on the ITM. - #[inline] - pub fn unlock(&mut self) { - // NOTE(unsafe) atomic write to a stateless, write-only register - unsafe { self.lar.write(0xC5AC_CE55) } - } - - /// Configures the ITM with the passed [ITMSettings]. - #[inline] - pub fn configure(&mut self, settings: ITMSettings) { - unsafe { - self.tcr.modify(|mut r| { - r.set_itmena(settings.enable); - r.set_tsena(settings.local_timestamps != LocalTimestampOptions::Disabled); - r.set_txena(settings.forward_dwt); - r.set_tsprescale(match settings.local_timestamps { - LocalTimestampOptions::Disabled | LocalTimestampOptions::Enabled => 0b00, - LocalTimestampOptions::EnabledDiv4 => 0b10, - LocalTimestampOptions::EnabledDiv16 => 0b10, - LocalTimestampOptions::EnabledDiv64 => 0b11, - }); - r.set_gtsfreq(match settings.global_timestamps { - GlobalTimestampOptions::Disabled => 0b00, - GlobalTimestampOptions::Every128Cycles => 0b01, - GlobalTimestampOptions::Every8192Cycles => 0b10, - GlobalTimestampOptions::EveryPacket => 0b11, - }); - r.set_swoena(match settings.timestamp_clk_src { - TimestampClkSrc::SystemClock => false, - TimestampClkSrc::AsyncTPIU => true, - }); - r.set_tracebusid(settings.bus_id.unwrap_or(0)); - - r - }); - } - } -} diff --git a/cortex-m/src/peripheral/mod.rs b/cortex-m/src/peripheral/mod.rs index 8b610a7b..4fae295a 100644 --- a/cortex-m/src/peripheral/mod.rs +++ b/cortex-m/src/peripheral/mod.rs @@ -60,6 +60,8 @@ use core::marker::PhantomData; use core::ops; +use crate::interrupt; + #[cfg(feature = "cm7")] pub mod ac; #[cfg(not(armv6m))] @@ -163,7 +165,7 @@ impl Peripherals { /// Returns all the core peripherals *once* #[inline] pub fn take() -> Option { - critical_section::with(|_| { + interrupt::free(|_| { if unsafe { TAKEN } { None } else { @@ -242,10 +244,16 @@ unsafe impl Send for AC {} impl AC { /// Pointer to the register block pub const PTR: *const self::ac::RegisterBlock = 0xE000_EF90 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const self::ac::RegisterBlock { + Self::PTR + } } /// Cache and branch predictor maintenance operations -#[allow(clippy::upper_case_acronyms)] pub struct CBP { _marker: PhantomData<*const ()>, } @@ -263,6 +271,13 @@ impl CBP { /// Pointer to the register block pub const PTR: *const self::cbp::RegisterBlock = 0xE000_EF50 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const self::cbp::RegisterBlock { + Self::PTR + } } #[cfg(not(armv6m))] @@ -276,7 +291,6 @@ impl ops::Deref for CBP { } /// CPUID -#[allow(clippy::upper_case_acronyms)] pub struct CPUID { _marker: PhantomData<*const ()>, } @@ -286,6 +300,13 @@ unsafe impl Send for CPUID {} impl CPUID { /// Pointer to the register block pub const PTR: *const self::cpuid::RegisterBlock = 0xE000_ED00 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const self::cpuid::RegisterBlock { + Self::PTR + } } impl ops::Deref for CPUID { @@ -298,7 +319,6 @@ impl ops::Deref for CPUID { } /// Debug Control Block -#[allow(clippy::upper_case_acronyms)] pub struct DCB { _marker: PhantomData<*const ()>, } @@ -308,6 +328,13 @@ unsafe impl Send for DCB {} impl DCB { /// Pointer to the register block pub const PTR: *const dcb::RegisterBlock = 0xE000_EDF0 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const dcb::RegisterBlock { + Self::PTR + } } impl ops::Deref for DCB { @@ -320,7 +347,6 @@ impl ops::Deref for DCB { } /// Data Watchpoint and Trace unit -#[allow(clippy::upper_case_acronyms)] pub struct DWT { _marker: PhantomData<*const ()>, } @@ -330,6 +356,13 @@ unsafe impl Send for DWT {} impl DWT { /// Pointer to the register block pub const PTR: *const dwt::RegisterBlock = 0xE000_1000 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const dwt::RegisterBlock { + Self::PTR + } } impl ops::Deref for DWT { @@ -342,7 +375,6 @@ impl ops::Deref for DWT { } /// Flash Patch and Breakpoint unit -#[allow(clippy::upper_case_acronyms)] pub struct FPB { _marker: PhantomData<*const ()>, } @@ -353,6 +385,13 @@ unsafe impl Send for FPB {} impl FPB { /// Pointer to the register block pub const PTR: *const fpb::RegisterBlock = 0xE000_2000 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const fpb::RegisterBlock { + Self::PTR + } } #[cfg(not(armv6m))] @@ -366,7 +405,6 @@ impl ops::Deref for FPB { } /// Floating Point Unit -#[allow(clippy::upper_case_acronyms)] pub struct FPU { _marker: PhantomData<*const ()>, } @@ -377,6 +415,13 @@ unsafe impl Send for FPU {} impl FPU { /// Pointer to the register block pub const PTR: *const fpu::RegisterBlock = 0xE000_EF30 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const fpu::RegisterBlock { + Self::PTR + } } #[cfg(any(has_fpu, native))] @@ -395,7 +440,6 @@ impl ops::Deref for FPU { /// `actlr`. It's called the "implementation control block" in the ARMv8-M /// standard, but earlier standards contained the registers, just without a /// name. -#[allow(clippy::upper_case_acronyms)] pub struct ICB { _marker: PhantomData<*const ()>, } @@ -405,6 +449,13 @@ unsafe impl Send for ICB {} impl ICB { /// Pointer to the register block pub const PTR: *mut icb::RegisterBlock = 0xE000_E004 as *mut _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *mut icb::RegisterBlock { + Self::PTR + } } impl ops::Deref for ICB { @@ -424,7 +475,6 @@ impl ops::DerefMut for ICB { } /// Instrumentation Trace Macrocell -#[allow(clippy::upper_case_acronyms)] pub struct ITM { _marker: PhantomData<*const ()>, } @@ -435,6 +485,13 @@ unsafe impl Send for ITM {} impl ITM { /// Pointer to the register block pub const PTR: *mut itm::RegisterBlock = 0xE000_0000 as *mut _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *mut itm::RegisterBlock { + Self::PTR + } } #[cfg(all(not(armv6m), not(armv8m_base)))] @@ -456,7 +513,6 @@ impl ops::DerefMut for ITM { } /// Memory Protection Unit -#[allow(clippy::upper_case_acronyms)] pub struct MPU { _marker: PhantomData<*const ()>, } @@ -466,6 +522,13 @@ unsafe impl Send for MPU {} impl MPU { /// Pointer to the register block pub const PTR: *const mpu::RegisterBlock = 0xE000_ED90 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const mpu::RegisterBlock { + Self::PTR + } } impl ops::Deref for MPU { @@ -478,7 +541,6 @@ impl ops::Deref for MPU { } /// Nested Vector Interrupt Controller -#[allow(clippy::upper_case_acronyms)] pub struct NVIC { _marker: PhantomData<*const ()>, } @@ -488,6 +550,13 @@ unsafe impl Send for NVIC {} impl NVIC { /// Pointer to the register block pub const PTR: *const nvic::RegisterBlock = 0xE000_E100 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const nvic::RegisterBlock { + Self::PTR + } } impl ops::Deref for NVIC { @@ -500,7 +569,6 @@ impl ops::Deref for NVIC { } /// Security Attribution Unit -#[allow(clippy::upper_case_acronyms)] pub struct SAU { _marker: PhantomData<*const ()>, } @@ -511,6 +579,13 @@ unsafe impl Send for SAU {} impl SAU { /// Pointer to the register block pub const PTR: *const sau::RegisterBlock = 0xE000_EDD0 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const sau::RegisterBlock { + Self::PTR + } } #[cfg(armv8m)] @@ -524,7 +599,6 @@ impl ops::Deref for SAU { } /// System Control Block -#[allow(clippy::upper_case_acronyms)] pub struct SCB { _marker: PhantomData<*const ()>, } @@ -534,6 +608,13 @@ unsafe impl Send for SCB {} impl SCB { /// Pointer to the register block pub const PTR: *const scb::RegisterBlock = 0xE000_ED04 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const scb::RegisterBlock { + Self::PTR + } } impl ops::Deref for SCB { @@ -546,7 +627,6 @@ impl ops::Deref for SCB { } /// SysTick: System Timer -#[allow(clippy::upper_case_acronyms)] pub struct SYST { _marker: PhantomData<*const ()>, } @@ -556,6 +636,13 @@ unsafe impl Send for SYST {} impl SYST { /// Pointer to the register block pub const PTR: *const syst::RegisterBlock = 0xE000_E010 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const syst::RegisterBlock { + Self::PTR + } } impl ops::Deref for SYST { @@ -568,7 +655,6 @@ impl ops::Deref for SYST { } /// Trace Port Interface Unit -#[allow(clippy::upper_case_acronyms)] pub struct TPIU { _marker: PhantomData<*const ()>, } @@ -579,6 +665,13 @@ unsafe impl Send for TPIU {} impl TPIU { /// Pointer to the register block pub const PTR: *const tpiu::RegisterBlock = 0xE004_0000 as *const _; + + /// Returns a pointer to the register block + #[inline(always)] + #[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")] + pub const fn ptr() -> *const tpiu::RegisterBlock { + Self::PTR + } } #[cfg(not(armv6m))] diff --git a/cortex-m/src/peripheral/nvic.rs b/cortex-m/src/peripheral/nvic.rs index fccd6a2c..57fa94b7 100644 --- a/cortex-m/src/peripheral/nvic.rs +++ b/cortex-m/src/peripheral/nvic.rs @@ -36,15 +36,7 @@ pub struct RegisterBlock { #[cfg(armv6m)] _reserved4: [u32; 16], - _reserved5: [u32; 16], - - #[cfg(armv8m)] - /// Interrupt Target Non-secure (only present on Arm v8-M) - pub itns: [RW; 16], - #[cfg(not(armv8m))] - _reserved6: [u32; 16], - - _reserved7: [u32; 16], + _reserved5: [u32; 48], /// Interrupt Priority /// @@ -75,7 +67,7 @@ pub struct RegisterBlock { pub ipr: [RW; 8], #[cfg(not(armv6m))] - _reserved8: [u32; 580], + _reserved6: [u32; 580], /// Software Trigger Interrupt #[cfg(not(armv6m))] @@ -94,14 +86,15 @@ impl NVIC { /// [`NVIC::pend`]: #method.pend #[cfg(not(armv6m))] #[inline] - pub fn request(interrupt: I) + pub fn request(&mut self, interrupt: I) where I: InterruptNumber, { let nr = interrupt.number(); - // NOTE(ptr) this is a write to a stateless register - unsafe { (*Self::PTR).stir.write(u32::from(nr)) } + unsafe { + self.stir.write(u32::from(nr)); + } } /// Disables `interrupt` diff --git a/cortex-m/src/peripheral/sau.rs b/cortex-m/src/peripheral/sau.rs index 6b8477f3..da91aca9 100644 --- a/cortex-m/src/peripheral/sau.rs +++ b/cortex-m/src/peripheral/sau.rs @@ -7,6 +7,7 @@ //! //! For reference please check the section B8.3 of the Armv8-M Architecture Reference Manual. +use crate::interrupt; use crate::peripheral::SAU; use bitfield::bitfield; use volatile_register::{RO, RW}; @@ -161,7 +162,7 @@ impl SAU { /// This function is executed under a critical section to prevent having inconsistent results. #[inline] pub fn set_region(&mut self, region_number: u8, region: SauRegion) -> Result<(), SauError> { - critical_section::with(|_| { + interrupt::free(|_| { let base_address = region.base_address; let limit_address = region.limit_address; let attribute = region.attribute; @@ -214,7 +215,7 @@ impl SAU { /// This function is executed under a critical section to prevent having inconsistent results. #[inline] pub fn get_region(&mut self, region_number: u8) -> Result { - critical_section::with(|_| { + interrupt::free(|_| { if region_number >= self.region_numbers() { Err(SauError::RegionNumberTooBig) } else { diff --git a/cortex-m/src/peripheral/scb.rs b/cortex-m/src/peripheral/scb.rs index 46eb3a4e..ecf98e5a 100644 --- a/cortex-m/src/peripheral/scb.rs +++ b/cortex-m/src/peripheral/scb.rs @@ -170,28 +170,10 @@ impl SCB { /// Returns the active exception number #[inline] pub fn vect_active() -> VectActive { - let icsr = - unsafe { ptr::read_volatile(&(*SCB::PTR).icsr as *const _ as *const u32) } & 0x1FF; + let icsr = unsafe { ptr::read(&(*SCB::PTR).icsr as *const _ as *const u32) }; - match icsr as u16 { - 0 => VectActive::ThreadMode, - 2 => VectActive::Exception(Exception::NonMaskableInt), - 3 => VectActive::Exception(Exception::HardFault), - #[cfg(not(armv6m))] - 4 => VectActive::Exception(Exception::MemoryManagement), - #[cfg(not(armv6m))] - 5 => VectActive::Exception(Exception::BusFault), - #[cfg(not(armv6m))] - 6 => VectActive::Exception(Exception::UsageFault), - #[cfg(any(armv8m, native))] - 7 => VectActive::Exception(Exception::SecureFault), - 11 => VectActive::Exception(Exception::SVCall), - #[cfg(not(armv6m))] - 12 => VectActive::Exception(Exception::DebugMonitor), - 14 => VectActive::Exception(Exception::PendSV), - 15 => VectActive::Exception(Exception::SysTick), - irqn => VectActive::Interrupt { irqn: irqn - 16 }, - } + // NOTE(unsafe): Assume correctly selected target. + unsafe { VectActive::from(icsr as u8).unwrap_unchecked() } } } @@ -275,15 +257,15 @@ pub enum VectActive { /// Device specific exception (external interrupts) Interrupt { - /// Interrupt number. This number is always within half open range `[0, 512)` (9 bit) - irqn: u16, + /// Interrupt number. This number is always within half open range `[0, 240)` + irqn: u8, }, } impl VectActive { - /// Converts a vector number into `VectActive` + /// Converts a `byte` into `VectActive` #[inline] - pub fn from(vect_active: u16) -> Option { + pub fn from(vect_active: u8) -> Option { Some(match vect_active { 0 => VectActive::ThreadMode, 2 => VectActive::Exception(Exception::NonMaskableInt), @@ -301,7 +283,7 @@ impl VectActive { 12 => VectActive::Exception(Exception::DebugMonitor), 14 => VectActive::Exception(Exception::PendSV), 15 => VectActive::Exception(Exception::SysTick), - irqn if (16..512).contains(&irqn) => VectActive::Interrupt { irqn: irqn - 16 }, + irqn if irqn >= 16 => VectActive::Interrupt { irqn: irqn - 16 }, _ => return None, }) } @@ -362,7 +344,7 @@ impl SCB { let mut cbp = unsafe { CBP::new() }; // Disable I-cache - // NOTE(unsafe): We have synchronized access by &mut self + // NOTE(unsafe): We have synchronised access by &mut self unsafe { self.ccr.modify(|r| r & !SCB_CCR_IC_MASK) }; // Invalidate I-cache @@ -435,7 +417,7 @@ impl SCB { } // Turn off the D-cache - // NOTE(unsafe): We have synchronized access by &mut self + // NOTE(unsafe): We have synchronised access by &mut self unsafe { self.ccr.modify(|r| r & !SCB_CCR_DC_MASK) }; // Clean and invalidate whatever was left in it @@ -664,7 +646,10 @@ impl SCB { /// a runtime-dependent `panic!()` call. #[inline] pub unsafe fn invalidate_dcache_by_slice(&mut self, slice: &mut [T]) { - self.invalidate_dcache_by_address(slice.as_ptr() as usize, core::mem::size_of_val(slice)); + self.invalidate_dcache_by_address( + slice.as_ptr() as usize, + slice.len() * core::mem::size_of::(), + ); } /// Cleans D-cache by address. @@ -747,7 +732,10 @@ impl SCB { /// to main memory, overwriting whatever was in main memory. #[inline] pub fn clean_dcache_by_slice(&mut self, slice: &[T]) { - self.clean_dcache_by_address(slice.as_ptr() as usize, core::mem::size_of_val(slice)); + self.clean_dcache_by_address( + slice.as_ptr() as usize, + slice.len() * core::mem::size_of::(), + ); } /// Cleans and invalidates D-cache by address. @@ -832,26 +820,6 @@ impl SCB { } } -const SCB_SCR_SEVONPEND: u32 = 0x1 << 4; - -impl SCB { - /// Set the SEVONPEND bit in the SCR register - #[inline] - pub fn set_sevonpend(&mut self) { - unsafe { - self.scr.modify(|scr| scr | SCB_SCR_SEVONPEND); - } - } - - /// Clear the SEVONPEND bit in the SCR register - #[inline] - pub fn clear_sevonpend(&mut self) { - unsafe { - self.scr.modify(|scr| scr & !SCB_SCR_SEVONPEND); - } - } -} - const SCB_AIRCR_VECTKEY: u32 = 0x05FA << 16; const SCB_AIRCR_PRIGROUP_MASK: u32 = 0x7 << 8; const SCB_AIRCR_SYSRESETREQ: u32 = 1 << 2; diff --git a/cortex-m/src/peripheral/syst.rs b/cortex-m/src/peripheral/syst.rs index 9d6f2919..345acc2f 100644 --- a/cortex-m/src/peripheral/syst.rs +++ b/cortex-m/src/peripheral/syst.rs @@ -1,18 +1,4 @@ //! SysTick: System Timer -//! -//! # Example -//! -//! ```no_run -//! use cortex_m::peripheral::{Peripherals, SYST}; -//! -//! let core_periph = cortex_m::peripheral::Peripherals::take().unwrap(); -//! let mut syst = core_periph.SYST; -//! syst.set_reload(0xffffff); -//! syst.clear_current(); -//! syst.enable_counter(); -//! -//! let syst_value: u32 = SYST::get_current(); -//! ``` use volatile_register::{RO, RW}; @@ -53,7 +39,7 @@ const SYST_CALIB_NOREF: u32 = 1 << 31; impl SYST { /// Clears current value to 0 /// - /// After calling `clear_current()`, the next call to `has_wrapped()`, unless called after the reload time (if the counter is enabled), will return `false`. + /// After calling `clear_current()`, the next call to `has_wrapped()` will return `false`. #[inline] pub fn clear_current(&mut self) { unsafe { self.cvr.write(0) } diff --git a/cortex-m/src/peripheral/tpiu.rs b/cortex-m/src/peripheral/tpiu.rs index 14dd35ca..11cb79e9 100644 --- a/cortex-m/src/peripheral/tpiu.rs +++ b/cortex-m/src/peripheral/tpiu.rs @@ -4,9 +4,6 @@ use volatile_register::{RO, RW, WO}; -use crate::peripheral::TPIU; -use bitfield::bitfield; - /// Register block #[repr(C)] pub struct RegisterBlock { @@ -19,10 +16,10 @@ pub struct RegisterBlock { pub acpr: RW, reserved1: [u32; 55], /// Selected Pin Control - pub sppr: RW, + pub sppr: RW, reserved2: [u32; 132], /// Formatter and Flush Control - pub ffcr: RW, + pub ffcr: RW, reserved3: [u32; 810], /// Lock Access pub lar: WO, @@ -30,131 +27,5 @@ pub struct RegisterBlock { pub lsr: RO, reserved4: [u32; 4], /// TPIU Type - pub _type: RO, -} - -bitfield! { - /// Formatter and flush control register. - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Ffcr(u32); - enfcont, set_enfcont: 1; -} - -bitfield! { - /// TPIU Type Register. - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Type(u32); - u8, fifosz, _: 8, 6; - ptinvalid, _: 9; - mancvalid, _: 10; - nrzvalid, _: 11; -} - -bitfield! { - /// Selected pin protocol register. - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Sppr(u32); - u8, txmode, set_txmode: 1, 0; -} - -/// The available protocols for the trace output. -#[repr(u8)] -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub enum TraceProtocol { - /// Parallel trace port mode - Parallel = 0b00, - /// Asynchronous SWO, using Manchester encoding - AsyncSWOManchester = 0b01, - /// Asynchronous SWO, using NRZ encoding - AsyncSWONRZ = 0b10, -} -impl core::convert::TryFrom for TraceProtocol { - type Error = (); - - /// Tries to convert from a `TXMODE` field value. Fails if the set mode is - /// unknown (and thus unpredictable). - #[inline] - fn try_from(value: u8) -> Result { - match value { - x if x == Self::Parallel as u8 => Ok(Self::Parallel), - x if x == Self::AsyncSWOManchester as u8 => Ok(Self::AsyncSWOManchester), - x if x == Self::AsyncSWONRZ as u8 => Ok(Self::AsyncSWONRZ), - _ => Err(()), // unknown and unpredictable mode - } - } -} - -/// The SWO options supported by the TPIU, and the mimimum size of the -/// FIFO output queue for trace data. -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub struct SWOSupports { - /// Whether UART/NRZ encoding is supported for SWO. - pub nrz_encoding: bool, - /// Whether Manchester encoding is supported for SWO. - pub manchester_encoding: bool, - /// Whether parallel trace port operation is supported. - pub parallel_operation: bool, - /// The minimum implemented FIFO queue size of the TPIU for trace data. - pub min_queue_size: u8, -} - -impl TPIU { - /// Sets the prescaler value for a wanted baud rate of the Serial - /// Wire Output (SWO) in relation to a given asynchronous refernce - /// clock rate. - #[inline] - pub fn set_swo_baud_rate(&mut self, ref_clk_rate: u32, baud_rate: u32) { - unsafe { - self.acpr.write((ref_clk_rate / baud_rate) - 1); - } - } - - /// The used protocol for the trace output. Return `None` if an - /// unknown (and thus unpredicable mode) is configured by means - /// other than - /// [`trace_output_protocol`](Self::set_trace_output_protocol). - #[inline] - pub fn trace_output_protocol(&self) -> Option { - self.sppr.read().txmode().try_into().ok() - } - - /// Sets the used protocol for the trace output. - #[inline] - pub fn set_trace_output_protocol(&mut self, proto: TraceProtocol) { - unsafe { - self.sppr.modify(|mut r| { - r.set_txmode(proto as u8); - r - }); - } - } - - /// Whether to enable the formatter. If disabled, only ITM and DWT - /// trace sources are passed through. Data from the ETM is - /// discarded. - #[inline] - pub fn enable_continuous_formatting(&mut self, bit: bool) { - unsafe { - self.ffcr.modify(|mut r| { - r.set_enfcont(bit); - r - }); - } - } - - /// Reads the supported trace output modes and the minimum size of - /// the TPIU FIFO queue for trace data. - #[inline] - pub fn swo_supports() -> SWOSupports { - let _type = unsafe { (*Self::PTR)._type.read() }; - SWOSupports { - nrz_encoding: _type.nrzvalid(), - manchester_encoding: _type.mancvalid(), - parallel_operation: !_type.ptinvalid(), - min_queue_size: _type.fifosz(), - } - } + pub _type: RO, } diff --git a/cortex-m/src/prelude.rs b/cortex-m/src/prelude.rs new file mode 100644 index 00000000..bc47cc02 --- /dev/null +++ b/cortex-m/src/prelude.rs @@ -0,0 +1,3 @@ +//! Prelude + +pub use embedded_hal::prelude::*; diff --git a/cortex-m/src/register/apsr.rs b/cortex-m/src/register/apsr.rs index edb87373..e83435ce 100644 --- a/cortex-m/src/register/apsr.rs +++ b/cortex-m/src/register/apsr.rs @@ -1,8 +1,5 @@ //! Application Program Status Register -#[cfg(cortex_m)] -use core::arch::asm; - /// Application Program Status Register #[derive(Clone, Copy, Debug)] pub struct Apsr { @@ -48,10 +45,10 @@ impl Apsr { } /// Reads the CPU register -#[cfg(cortex_m)] +/// +/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. #[inline] pub fn read() -> Apsr { - let bits; - unsafe { asm!("mrs {}, APSR", out(reg) bits, options(nomem, nostack, preserves_flags)) }; + let bits: u32 = call_asm!(__apsr_r() -> u32); Apsr { bits } } diff --git a/cortex-m/src/register/basepri.rs b/cortex-m/src/register/basepri.rs index cffb3791..07084cd2 100644 --- a/cortex-m/src/register/basepri.rs +++ b/cortex-m/src/register/basepri.rs @@ -1,42 +1,24 @@ //! Base Priority Mask Register -#[cfg(cortex_m)] -use core::arch::asm; - /// Reads the CPU register -#[cfg(cortex_m)] #[inline] pub fn read() -> u8 { - let r; - unsafe { asm!("mrs {}, BASEPRI", out(reg) r, options(nomem, nostack, preserves_flags)) }; - r + call_asm!(__basepri_r() -> u8) } /// Writes to the CPU register /// /// **IMPORTANT** If you are using a Cortex-M7 device with revision r0p1 you MUST enable the /// `cm7-r0p1` Cargo feature or this function WILL misbehave. -#[cfg(cortex_m)] #[inline] pub unsafe fn write(basepri: u8) { #[cfg(feature = "cm7-r0p1")] { - asm!( - "mrs {1}, PRIMASK", - "cpsid i", - "tst.w {1}, #1", - "msr BASEPRI, {0}", - "it ne", - "bxne lr", - "cpsie i", - in(reg) basepri, - out(reg) _, - options(nomem, nostack, preserves_flags), - ); + call_asm!(__basepri_w_cm7_r0p1(basepri: u8)); } #[cfg(not(feature = "cm7-r0p1"))] { - asm!("msr BASEPRI, {}", in(reg) basepri, options(nomem, nostack, preserves_flags)); + call_asm!(__basepri_w(basepri: u8)); } } diff --git a/cortex-m/src/register/basepri_max.rs b/cortex-m/src/register/basepri_max.rs index 2881c4fe..cea38383 100644 --- a/cortex-m/src/register/basepri_max.rs +++ b/cortex-m/src/register/basepri_max.rs @@ -1,8 +1,5 @@ //! Base Priority Mask Register (conditional write) -#[cfg(cortex_m)] -use core::arch::asm; - /// Writes to BASEPRI *if* /// /// - `basepri != 0` AND `basepri::read() == 0`, OR @@ -10,31 +7,15 @@ use core::arch::asm; /// /// **IMPORTANT** If you are using a Cortex-M7 device with revision r0p1 you MUST enable the /// `cm7-r0p1` Cargo feature or this function WILL misbehave. -#[cfg(cortex_m)] #[inline] pub fn write(basepri: u8) { #[cfg(feature = "cm7-r0p1")] { - unsafe { - asm!( - "mrs {1}, PRIMASK", - "cpsid i", - "tst.w {1}, #1", - "msr BASEPRI_MAX, {0}", - "it ne", - "bxne lr", - "cpsie i", - in(reg) basepri, - out(reg) _, - options(nomem, nostack, preserves_flags), - ); - } + call_asm!(__basepri_max_cm7_r0p1(basepri: u8)); } #[cfg(not(feature = "cm7-r0p1"))] { - unsafe { - asm!("msr BASEPRI_MAX, {}", in(reg) basepri, options(nomem, nostack, preserves_flags)); - } + call_asm!(__basepri_max(basepri: u8)); } } diff --git a/cortex-m/src/register/control.rs b/cortex-m/src/register/control.rs index d7819139..a991625b 100644 --- a/cortex-m/src/register/control.rs +++ b/cortex-m/src/register/control.rs @@ -1,10 +1,5 @@ //! Control register -#[cfg(cortex_m)] -use core::arch::asm; -#[cfg(cortex_m)] -use core::sync::atomic::{compiler_fence, Ordering}; - /// Control register #[derive(Clone, Copy, Debug)] pub struct Control { @@ -155,29 +150,15 @@ impl Fpca { } /// Reads the CPU register -#[cfg(cortex_m)] #[inline] pub fn read() -> Control { - let bits; - unsafe { asm!("mrs {}, CONTROL", out(reg) bits, options(nomem, nostack, preserves_flags)) }; + let bits: u32 = call_asm!(__control_r() -> u32); Control { bits } } /// Writes to the CPU register. -#[cfg(cortex_m)] #[inline] pub unsafe fn write(control: Control) { let control = control.bits(); - - // ISB is required after writing to CONTROL, - // per ARM architectural requirements (see Application Note 321). - asm!( - "msr CONTROL, {}", - "isb", - in(reg) control, - options(nomem, nostack, preserves_flags), - ); - - // Ensure memory accesses are not reordered around the CONTROL update. - compiler_fence(Ordering::SeqCst); + call_asm!(__control_w(control: u32)); } diff --git a/cortex-m/src/register/faultmask.rs b/cortex-m/src/register/faultmask.rs index 1d327095..e57fa28d 100644 --- a/cortex-m/src/register/faultmask.rs +++ b/cortex-m/src/register/faultmask.rs @@ -1,8 +1,5 @@ //! Fault Mask Register -#[cfg(cortex_m)] -use core::arch::asm; - /// All exceptions are ... #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Faultmask { @@ -27,11 +24,9 @@ impl Faultmask { } /// Reads the CPU register -#[cfg(cortex_m)] #[inline] pub fn read() -> Faultmask { - let r: u32; - unsafe { asm!("mrs {}, FAULTMASK", out(reg) r, options(nomem, nostack, preserves_flags)) }; + let r: u32 = call_asm!(__faultmask_r() -> u32); if r & (1 << 0) == (1 << 0) { Faultmask::Inactive } else { diff --git a/cortex-m/src/register/fpscr.rs b/cortex-m/src/register/fpscr.rs index bffed6cd..68692c73 100644 --- a/cortex-m/src/register/fpscr.rs +++ b/cortex-m/src/register/fpscr.rs @@ -1,7 +1,5 @@ //! Floating-point Status Control Register -use core::arch::asm; - /// Floating-point Status Control Register #[derive(Clone, Copy, Debug)] pub struct Fpscr { @@ -295,8 +293,7 @@ impl RMode { /// Read the FPSCR register #[inline] pub fn read() -> Fpscr { - let r; - unsafe { asm!("vmrs {}, fpscr", out(reg) r, options(nomem, nostack, preserves_flags)) }; + let r: u32 = call_asm!(__fpscr_r() -> u32); Fpscr::from_bits(r) } @@ -304,5 +301,5 @@ pub fn read() -> Fpscr { #[inline] pub unsafe fn write(fpscr: Fpscr) { let fpscr = fpscr.bits(); - asm!("vmsr fpscr, {}", in(reg) fpscr, options(nomem, nostack)); + call_asm!(__fpscr_w(fpscr: u32)); } diff --git a/cortex-m/src/register/lr.rs b/cortex-m/src/register/lr.rs index 5752ff72..0da35d9f 100644 --- a/cortex-m/src/register/lr.rs +++ b/cortex-m/src/register/lr.rs @@ -1,18 +1,21 @@ //! Link register -#[cfg(cortex_m)] -use core::arch::asm; - /// Reads the CPU register /// -/// Note that this function can't be used reliably: The value returned at least depends -/// on whether the compiler chooses to inline the function or not. -#[cfg(cortex_m)] +/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. #[inline] pub fn read() -> u32 { - let r; - unsafe { asm!("mov {}, lr", out(reg) r, options(nomem, nostack, preserves_flags)) }; - r + call_asm!(__lr_r() -> u32) } -// No `write` function for the LR register, as it can't be used soundly. +/// Writes `bits` to the CPU register +/// +/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. +/// +/// # Safety +/// This function can't be used soundly. +#[inline] +#[deprecated = "This function can't be used soundly."] +pub unsafe fn write(bits: u32) { + call_asm!(__lr_w(bits: u32)); +} diff --git a/cortex-m/src/register/mod.rs b/cortex-m/src/register/mod.rs index aee7d213..48d157a5 100644 --- a/cortex-m/src/register/mod.rs +++ b/cortex-m/src/register/mod.rs @@ -56,8 +56,13 @@ pub mod msplim; #[cfg(armv8m_main)] pub mod psplim; +// Accessing these registers requires inline assembly because their contents are tied to the current +// stack frame +#[cfg(feature = "inline-asm")] pub mod apsr; +#[cfg(feature = "inline-asm")] pub mod lr; +#[cfg(feature = "inline-asm")] pub mod pc; diff --git a/cortex-m/src/register/msp.rs b/cortex-m/src/register/msp.rs index 22ce7d97..bccc2ae8 100644 --- a/cortex-m/src/register/msp.rs +++ b/cortex-m/src/register/msp.rs @@ -1,27 +1,16 @@ //! Main Stack Pointer -#[cfg(cortex_m)] -use core::arch::asm; - /// Reads the CPU register -#[cfg(cortex_m)] #[inline] pub fn read() -> u32 { - let r; - unsafe { asm!("mrs {}, MSP", out(reg) r, options(nomem, nostack, preserves_flags)) }; - r + call_asm!(__msp_r() -> u32) } /// Writes `bits` to the CPU register -#[cfg(cortex_m)] #[inline] #[deprecated = "calling this function invokes Undefined Behavior, consider asm::bootstrap as an alternative"] pub unsafe fn write(bits: u32) { - // Technically is writing to the stack pointer "not pushing any data to the stack"? - // In any event, if we don't set `nostack` here, this method is useless as the new - // stack value is immediately mutated by returning. Really this is just not a good - // method and its use is marked as deprecated. - asm!("msr MSP, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); + call_asm!(__msp_w(bits: u32)); } /// Reads the Non-Secure CPU register from Secure state. @@ -30,9 +19,7 @@ pub unsafe fn write(bits: u32) { #[cfg(armv8m)] #[inline] pub fn read_ns() -> u32 { - let r; - unsafe { asm!("mrs {}, MSP_NS", out(reg) r, options(nomem, nostack, preserves_flags)) }; - r + call_asm!(__msp_ns_r() -> u32) } /// Writes `bits` to the Non-Secure CPU register from Secure state. @@ -41,5 +28,5 @@ pub fn read_ns() -> u32 { #[cfg(armv8m)] #[inline] pub unsafe fn write_ns(bits: u32) { - asm!("msr MSP_NS, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); + call_asm!(__msp_ns_w(bits: u32)); } diff --git a/cortex-m/src/register/msplim.rs b/cortex-m/src/register/msplim.rs index 7b45b33a..ac6f9ed6 100644 --- a/cortex-m/src/register/msplim.rs +++ b/cortex-m/src/register/msplim.rs @@ -1,17 +1,13 @@ //! Main Stack Pointer Limit Register -use core::arch::asm; - /// Reads the CPU register #[inline] pub fn read() -> u32 { - let r; - unsafe { asm!("mrs {}, MSPLIM", out(reg) r, options(nomem, nostack, preserves_flags)) }; - r + call_asm!(__msplim_r() -> u32) } /// Writes `bits` to the CPU register #[inline] pub unsafe fn write(bits: u32) { - asm!("msr MSPLIM, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); + call_asm!(__msplim_w(bits: u32)) } diff --git a/cortex-m/src/register/pc.rs b/cortex-m/src/register/pc.rs index 34606641..0b33629a 100644 --- a/cortex-m/src/register/pc.rs +++ b/cortex-m/src/register/pc.rs @@ -1,20 +1,17 @@ //! Program counter -#[cfg(cortex_m)] -use core::arch::asm; - /// Reads the CPU register -#[cfg(cortex_m)] +/// +/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. #[inline] pub fn read() -> u32 { - let r; - unsafe { asm!("mov {}, pc", out(reg) r, options(nomem, nostack, preserves_flags)) }; - r + call_asm!(__pc_r() -> u32) } /// Writes `bits` to the CPU register -#[cfg(cortex_m)] +/// +/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. #[inline] pub unsafe fn write(bits: u32) { - asm!("mov pc, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); + call_asm!(__pc_w(bits: u32)); } diff --git a/cortex-m/src/register/primask.rs b/cortex-m/src/register/primask.rs index 58b8c287..842ca49a 100644 --- a/cortex-m/src/register/primask.rs +++ b/cortex-m/src/register/primask.rs @@ -1,10 +1,5 @@ //! Priority mask register -#[cfg(cortex_m)] -use core::arch::asm; -#[cfg(cortex_m)] -use core::sync::atomic::{compiler_fence, Ordering}; - /// All exceptions with configurable priority are ... #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Primask { @@ -28,42 +23,13 @@ impl Primask { } } -/// Reads the prioritizable interrupt mask -#[cfg(cortex_m)] +/// Reads the CPU register #[inline] pub fn read() -> Primask { - if read_raw() & (1 << 0) == (1 << 0) { + let r: u32 = call_asm!(__primask_r() -> u32); + if r & (1 << 0) == (1 << 0) { Primask::Inactive } else { Primask::Active } } - -/// Reads the entire PRIMASK register -/// Note that bits [31:1] are reserved and UNK (Unknown) -#[cfg(cortex_m)] -#[inline] -pub fn read_raw() -> u32 { - let r: u32; - unsafe { asm!("mrs {}, PRIMASK", out(reg) r, options(nomem, nostack, preserves_flags)) }; - r -} - -/// Writes the entire PRIMASK register -/// Note that bits [31:1] are reserved and SBZP (Should-Be-Zero-or-Preserved) -/// -/// # Safety -/// -/// This method is unsafe as other unsafe code may rely on interrupts remaining disabled, for -/// example during a critical section, and being able to safely re-enable them would lead to -/// undefined behaviour. Do not call this function in a context where interrupts are expected to -/// remain disabled -- for example, in the midst of a critical section or `interrupt::free()` call. -#[cfg(cortex_m)] -#[inline] -pub unsafe fn write_raw(r: u32) { - // Ensure no preceeding memory accesses are reordered to after interrupts are possibly enabled. - compiler_fence(Ordering::SeqCst); - unsafe { asm!("msr PRIMASK, {}", in(reg) r, options(nomem, nostack, preserves_flags)) }; - // Ensure no subsequent memory accesses are reordered to before interrupts are possibly disabled. - compiler_fence(Ordering::SeqCst); -} diff --git a/cortex-m/src/register/psp.rs b/cortex-m/src/register/psp.rs index c8f53b98..0bca22c3 100644 --- a/cortex-m/src/register/psp.rs +++ b/cortex-m/src/register/psp.rs @@ -1,22 +1,13 @@ //! Process Stack Pointer -#[cfg(cortex_m)] -use core::arch::asm; - /// Reads the CPU register -#[cfg(cortex_m)] #[inline] pub fn read() -> u32 { - let r; - unsafe { asm!("mrs {}, PSP", out(reg) r, options(nomem, nostack, preserves_flags)) }; - r + call_asm!(__psp_r() -> u32) } /// Writes `bits` to the CPU register -#[cfg(cortex_m)] #[inline] pub unsafe fn write(bits: u32) { - // See comment on msp_w. Unlike MSP, there are legitimate use-cases for modifying PSP - // if MSP is currently being used as the stack pointer. - asm!("msr PSP, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); + call_asm!(__psp_w(bits: u32)) } diff --git a/cortex-m/src/register/psplim.rs b/cortex-m/src/register/psplim.rs index 832f9c67..8ee1e945 100644 --- a/cortex-m/src/register/psplim.rs +++ b/cortex-m/src/register/psplim.rs @@ -1,17 +1,13 @@ //! Process Stack Pointer Limit Register -use core::arch::asm; - /// Reads the CPU register #[inline] pub fn read() -> u32 { - let r; - unsafe { asm!("mrs {}, PSPLIM", out(reg) r, options(nomem, nostack, preserves_flags)) }; - r + call_asm!(__psplim_r() -> u32) } /// Writes `bits` to the CPU register #[inline] pub unsafe fn write(bits: u32) { - asm!("msr PSPLIM, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); + call_asm!(__psplim_w(bits: u32)) } diff --git a/testsuite/src/main.rs b/testsuite/src/main.rs index d742c160..ed2b1588 100644 --- a/testsuite/src/main.rs +++ b/testsuite/src/main.rs @@ -77,8 +77,8 @@ mod tests { #[test] fn interrupt_free_nesting() { EXCEPTION_FLAG.store(false, Ordering::SeqCst); - cortex_m::interrupt::free(|| { - cortex_m::interrupt::free(|| { + cortex_m::interrupt::free(|_| { + cortex_m::interrupt::free(|_| { cortex_m::peripheral::SCB::set_pendsv(); assert!(!EXCEPTION_FLAG.load(Ordering::SeqCst)); }); diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 0abe8e19..73047eef 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -10,6 +10,6 @@ name = "ci" harness = false [dependencies] -ar = "0.9.0" +ar = "0.8.0" cortex-m = { path = "../cortex-m", features = ["serde", "std"] } serde_json = "1" diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 9d966868..ffcabba4 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1,8 +1,126 @@ //! `cargo xtask` automation. //! //! Please refer to for an explanation of the concept. +//! +//! Also see the docs in `asm.rs`. + +use std::collections::BTreeMap; +use std::env::current_dir; +use std::fs::{self, File}; +use std::path::Path; +use std::process::{Command, Stdio}; + +fn toolchain() -> String { + fs::read_to_string("cortex-m/asm-toolchain") + .unwrap() + .trim() + .to_string() +} + +fn rustc() -> Command { + let mut cmd = Command::new("rustc"); + cmd.arg(format!("+{}", toolchain())); + cmd +} + +fn assemble_really(target: &str, cfgs: &[&str], plugin_lto: bool) { + let mut cmd = rustc(); + + // Set the codegen target. + cmd.arg("--target").arg(target); + // Set all the `--cfg` directives for the target. + cmd.args(cfgs.iter().map(|cfg| format!("--cfg={}", cfg))); + + // We want some level of debuginfo to allow unwinding through the functions. + cmd.arg("-g"); + // We always optimize the assembly shims. There's not really any reason not to. + cmd.arg("-O"); + + // We use LTO on the archive to ensure the (unused) panic handler is removed, preventing + // a linker error when the archives are linked into final crates with two panic handlers. + cmd.arg("-Clto=yes"); + + // rustc will usually add frame pointers by default to aid with debugging, but that is a high + // overhead for the tiny assembly routines. + cmd.arg("-Cforce-frame-pointers=no"); + + // We don't want any system-specific paths to show up since we ship the result to other users. + // Add `--remap-path-prefix $(pwd)=.`. + let mut dir = current_dir() + .unwrap() + .join("cortex-m") + .as_os_str() + .to_os_string(); + dir.push("=."); + cmd.arg("--remap-path-prefix").arg(dir); + + // We let rustc build a single object file, not a staticlib, since the latter pulls in loads of + // code that will never be used (`compiler_builtins` and `core::fmt`, etc.). We build the static + // archive by hand after compiling. + cmd.arg("--emit=obj"); + + if plugin_lto { + // Make artifacts compatible with Linker-Plugin LTO (and incompatible with everything else). + cmd.arg("-Clinker-plugin-lto"); + } + + let file_stub = if plugin_lto { + format!("{}-lto", target) + } else { + target.to_string() + }; + + let obj_file = format!("bin/{}.o", file_stub); + + // Pass output and input file. + cmd.arg("-o").arg(&obj_file); + cmd.arg("asm/lib.rs"); + cmd.current_dir("cortex-m"); + + println!("{:?}", cmd); + let status = cmd.status().unwrap(); + assert!(status.success()); + + let full_obj_file_path = Path::new("cortex-m").join(&obj_file); + + // Archive `target.o` -> `bin/target.a`. + let mut builder = + ar::Builder::new(File::create(format!("cortex-m/bin/{}.a", file_stub)).unwrap()); + + // Use `append`, not `append_path`, to avoid adding any filesystem metadata (modification times, + // etc.). + let file = fs::read(&full_obj_file_path).unwrap(); + builder + .append( + &ar::Header::new(obj_file.as_bytes().to_vec(), file.len() as u64), + &*file, + ) + .unwrap(); + + fs::remove_file(&full_obj_file_path).unwrap(); +} + +fn assemble(target: &str, cfgs: &[&str]) { + assemble_really(target, cfgs, false); + assemble_really(target, cfgs, true); +} -use std::process::Command; +// `--target` -> `--cfg` list (mirrors what `build.rs` does). +static TARGETS: &[(&str, &[&str])] = &[ + ("thumbv6m-none-eabi", &[]), + ("thumbv7m-none-eabi", &["armv7m"]), + ("thumbv7em-none-eabi", &["armv7m", "armv7em"]), + ("thumbv7em-none-eabihf", &["armv7m", "armv7em", "has_fpu"]), + ("thumbv8m.base-none-eabi", &["armv8m", "armv8m_base"]), + ( + "thumbv8m.main-none-eabi", + &["armv7m", "armv8m", "armv8m_main"], + ), + ( + "thumbv8m.main-none-eabihf", + &["armv7m", "armv8m", "armv8m_main", "has_fpu"], + ), +]; pub fn install_targets(targets: &mut dyn Iterator, toolchain: Option<&str>) { let mut rustup = Command::new("rustup"); @@ -16,9 +134,93 @@ pub fn install_targets(targets: &mut dyn Iterator, toolchain: Optio assert!(status.success(), "rustup command failed: {:?}", rustup); } +pub fn assemble_blobs() { + let mut cmd = rustc(); + cmd.arg("-V"); + cmd.stdout(Stdio::null()); + let status = cmd.status().unwrap(); + let toolchain = toolchain(); + + if !status.success() { + println!( + "asm toolchain {} does not seem to be installed. installing it now.", + toolchain + ); + + let mut rustup = Command::new("rustup"); + let status = rustup.arg("install").arg(&toolchain).status().unwrap(); + assert!(status.success(), "rustup command failed: {:?}", rustup); + } + + install_targets( + &mut TARGETS.iter().map(|(target, _)| *target), + Some(&*toolchain), + ); + + for (target, cfgs) in TARGETS { + println!("building artifacts for {}", target); + assemble(target, cfgs); + } +} + +pub fn check_blobs() { + // Load each `.a` file in `bin` into memory. + let mut files_before = BTreeMap::new(); + for entry in fs::read_dir("cortex-m/bin").unwrap() { + let entry = entry.unwrap(); + if entry.path().extension().unwrap() == "a" { + files_before.insert( + entry + .path() + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(), + fs::read(entry.path()).unwrap(), + ); + } + } + + assemble_blobs(); + + let mut files_after = BTreeMap::new(); + for entry in fs::read_dir("cortex-m/bin").unwrap() { + let entry = entry.unwrap(); + if entry.path().extension().unwrap() == "a" { + files_after.insert( + entry + .path() + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(), + fs::read(entry.path()).unwrap(), + ); + } + } + + // Ensure they contain the same files. + let before = files_before.keys().collect::>(); + let after = files_after.keys().collect::>(); + assert_eq!(before, after); + + for ((file, before), (_, after)) in files_before.iter().zip(files_after.iter()) { + if before != after { + panic!( + "{} is not up-to-date, please run `cargo xtask assemble`", + file + ); + } + } + + println!("Blobs identical."); +} + // Check that serde and PartialOrd works with VectActive pub fn check_host_side() { - use cortex_m::peripheral::{itm::LocalTimestampOptions, scb::VectActive}; + use cortex_m::peripheral::scb::VectActive; // check serde { @@ -27,12 +229,6 @@ pub fn check_host_side() { let deser_v: VectActive = serde_json::from_str(&json).expect("Failed to deserialize VectActive"); assert_eq!(deser_v, v); - - let lts = LocalTimestampOptions::EnabledDiv4; - let json = serde_json::to_string(<s).expect("Failed to serialize LocalTimestampOptions"); - let deser_lts: LocalTimestampOptions = - serde_json::from_str(&json).expect("Failed to deserilaize LocalTimestampOptions"); - assert_eq!(deser_lts, lts); } // check PartialOrd @@ -41,15 +237,4 @@ pub fn check_host_side() { let b = VectActive::from(20).unwrap(); assert!(a < b); } - - // check TryFrom - { - use core::convert::TryInto; - use std::convert::TryFrom; - - let lts: LocalTimestampOptions = (16_u8).try_into().unwrap(); - assert_eq!(lts, LocalTimestampOptions::EnabledDiv16); - - assert!(LocalTimestampOptions::try_from(42).is_err()); - } } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 4673a455..26dce31b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,14 +1,18 @@ use std::{env, process}; -use xtask::check_host_side; +use xtask::{assemble_blobs, check_blobs, check_host_side}; fn main() { let subcommand = env::args().nth(1); match subcommand.as_deref() { + Some("assemble") => assemble_blobs(), + Some("check-blobs") => check_blobs(), Some("check-host-side") => check_host_side(), _ => { eprintln!("usage: cargo xtask "); eprintln!(); eprintln!("subcommands:"); + eprintln!(" assemble Reassemble the pre-built artifacts"); + eprintln!(" check-blobs Check that the pre-built artifacts are up-to-date and reproducible"); eprintln!(" check-host-side Build the crate in a non-Cortex-M host application and check host side usage of certain types"); process::exit(1); } diff --git a/xtask/tests/ci.rs b/xtask/tests/ci.rs index 3c3ef990..6e4023d1 100644 --- a/xtask/tests/ci.rs +++ b/xtask/tests/ci.rs @@ -1,6 +1,6 @@ use std::process::Command; use std::{env, str}; -use xtask::{check_host_side, install_targets}; +use xtask::{check_blobs, check_host_side, install_targets}; /// List of all compilation targets we support. /// @@ -27,16 +27,9 @@ static NON_BASE_TARGETS: &[&str] = &[ fn build(package: &str, target: &str, features: &[&str]) { println!("building {} for {} {:?}", package, target, features); let mut cargo = Command::new("cargo"); - cargo.args(["build", "-p", package, "--target", target]); + cargo.args(&["build", "-p", package, "--target", target]); for feat in features { - cargo.args(["--features", *feat]); - } - - // A `critical_section` implementation is always needed. - if package == "cortex-m" { - cargo.args(["--features", "critical-section-single-core"]); - } else { - cargo.args(["--features", "cortex-m/critical-section-single-core"]); + cargo.args(&["--features", *feat]); } // Cargo features don't work right when invoked from the workspace root, so change to the @@ -51,13 +44,13 @@ fn build(package: &str, target: &str, features: &[&str]) { #[rustfmt::skip] static PACKAGE_FEATURES: &[(&str, &[&str], &[&str])] = &[ - ("cortex-m", ALL_TARGETS, &["cm7-r0p1"]), + ("cortex-m", ALL_TARGETS, &["inline-asm", "cm7-r0p1", "critical-section-single-core"]), // no `linker-plugin-lto` since it's experimental ("cortex-m-semihosting", ALL_TARGETS, &["no-semihosting", "jlink-quirks"]), ("panic-semihosting", ALL_TARGETS, &["exit", "jlink-quirks"]), ("panic-itm", NON_BASE_TARGETS, &[]), ]; -fn check_crates_build(_is_nightly: bool) { +fn check_crates_build(is_nightly: bool, is_msrv: bool) { // Build all crates for each supported target. for (package, targets, all_features) in PACKAGE_FEATURES { for target in *targets { @@ -65,8 +58,11 @@ fn check_crates_build(_is_nightly: bool) { // Relies on all crates in this repo to use the same convention. let should_use_feature = |feat: &str| { match feat { + // This is nightly-only, so don't use it on stable. + "inline-asm" => is_nightly, // This only affects thumbv7em targets. "cm7-r0p1" => target.starts_with("thumbv7em"), + _ => true, } }; @@ -77,7 +73,7 @@ fn check_crates_build(_is_nightly: bool) { let used_features = &*all_features .iter() .copied() - .filter(|feat| should_use_feature(feat)) + .filter(|feat| should_use_feature(*feat)) .collect::>(); // (note: we don't test with default features disabled, since we don't use them yet) @@ -102,10 +98,14 @@ fn main() { install_targets(&mut ALL_TARGETS.iter().cloned(), None); + // Check that the ASM blobs are up-to-date. + check_blobs(); + let output = Command::new("rustc").arg("-V").output().unwrap(); let is_nightly = str::from_utf8(&output.stdout).unwrap().contains("nightly"); + let is_msrv = str::from_utf8(&output.stdout).unwrap().contains("1.59"); - check_crates_build(is_nightly); + check_crates_build(is_nightly, is_msrv); // Check host-side applications of the crate. check_host_side();