diff options
99 files changed, 1418 insertions, 1353 deletions
diff --git a/.github/bors.toml b/.github/bors.toml index 4402e95..17cef85 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -3,11 +3,13 @@ delete_merged_branches = true required_approvals = 1 status = [ "ci-linux (stable)", - "ci-linux (1.42.0)", + "ci-linux (1.59.0)", "rt-ci-linux (stable)", - "rt-ci-linux (1.42.0)", + "rt-ci-linux (1.59.0)", "rt-ci-other-os (macOS-latest)", "rt-ci-other-os (windows-latest)", + "hil-qemu", + "hil-compile-rtt", "rustfmt", "clippy", ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8caebd0..0d9b2b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,19 +16,19 @@ jobs: include: # Test MSRV - - rust: 1.42.0 + - rust: 1.59.0 # Test nightly but don't fail - rust: nightly experimental: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true - name: Run tests - run: cargo test --all --exclude cortex-m-rt + run: cargo test --all --exclude cortex-m-rt --exclude testsuite --features cortex-m/critical-section-single-core # FIXME: test on macOS and Windows diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 5a76037..ecfd0b9 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -8,11 +8,11 @@ jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 if: github.event_name == 'pull_request_target' with: ref: refs/pull/${{ github.event.number }}/head - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 if: github.event_name != 'pull_request_target' - uses: actions-rs/toolchain@v1 with: @@ -23,4 +23,4 @@ jobs: - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all + args: --all --features cortex-m/critical-section-single-core diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 24b547d..866143a 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -9,15 +9,15 @@ jobs: ci-linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Run tests - run: cargo test --all --exclude cortex-m-rt - - uses: imjohnbo/issue-bot@v2 + run: cargo test --all --exclude cortex-m-rt --exclude testsuite + - uses: imjohnbo/issue-bot@v3 if: failure() with: title: CI Failure @@ -36,7 +36,7 @@ jobs: run: working-directory: cortex-m-rt steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -62,7 +62,7 @@ jobs: run: TARGET=thumbv8m.main-none-eabi TRAVIS_RUST_VERSION=stable bash ci/script.sh - name: Run CI script for thumbv8m.main-none-eabihf under stable run: TARGET=thumbv8m.main-none-eabihf TRAVIS_RUST_VERSION=stable bash ci/script.sh - - uses: imjohnbo/issue-bot@v2 + - uses: imjohnbo/issue-bot@v3 if: failure() with: title: CI Failure diff --git a/.github/workflows/on-target.yml b/.github/workflows/on-target.yml new file mode 100644 index 0000000..20121ee --- /dev/null +++ b/.github/workflows/on-target.yml @@ -0,0 +1,83 @@ +on: + push: + branches: [ staging, trying, master ] + pull_request: + # allows manual triggering + workflow_dispatch: + +name: cortex-m on-target tests + +jobs: + + hil-qemu: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + target: thumbv7m-none-eabi + - name: Build testsuite + env: + RUSTFLAGS: -C link-arg=-Tlink.x -D warnings + run: cargo build -p testsuite --target thumbv7m-none-eabi --features semihosting,cortex-m/critical-section-single-core + - name: Install QEMU + run: sudo apt-get update && sudo apt-get install qemu qemu-system-arm + - name: Run testsuite + run: | + qemu-system-arm \ + -cpu cortex-m3 \ + -machine lm3s6965evb \ + -nographic \ + -semihosting-config enable=on,target=native \ + -kernel target/thumbv7m-none-eabi/debug/testsuite + + hil-compile-rtt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + target: thumbv6m-none-eabi + - name: Modify linkerfile + run: | + sed -i 's/FLASH : ORIGIN = 0x00000000, LENGTH = 256K/FLASH : ORIGIN = 0x8000000, LENGTH = 128K/g' memory.x + sed -i 's/RAM : ORIGIN = 0x20000000, LENGTH = 64K/RAM : ORIGIN = 0x20000000, LENGTH = 16K/g' memory.x + - name: Build testsuite + env: + RUSTFLAGS: -C link-arg=-Tlink.x -D warnings + run: cargo build -p testsuite --target thumbv6m-none-eabi --features rtt,cortex-m/critical-section-single-core + - name: Upload testsuite binaries + uses: actions/upload-artifact@v3 + with: + name: testsuite-bin + if-no-files-found: error + retention-days: 1 + path: target/thumbv6m-none-eabi/debug/testsuite + + hil-stm32: + runs-on: self-hosted + needs: + - hil-compile-rtt + steps: + - uses: actions/checkout@v3 + - name: Display probe-run version + run: probe-run --version + - name: List probes + run: probe-run --list-probes + - uses: actions/download-artifact@v3 + with: + name: testsuite-bin + path: testsuite-bin + - name: Run on-target tests + timeout-minutes: 5 + run: | + probe-run \ + --chip STM32F070RBTx \ + --connect-under-reset \ + testsuite-bin/testsuite diff --git a/.github/workflows/rt-ci.yml b/.github/workflows/rt-ci.yml index 8b95612..d46e48a 100644 --- a/.github/workflows/rt-ci.yml +++ b/.github/workflows/rt-ci.yml @@ -11,8 +11,7 @@ jobs: continue-on-error: ${{ matrix.experimental || false }} strategy: matrix: - # All generated code should be running on stable now - rust: [nightly, stable, 1.42.0] + rust: [nightly, stable, 1.59.0] include: # Nightly is only for reference and allowed to fail @@ -22,7 +21,7 @@ jobs: run: working-directory: cortex-m-rt steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -61,7 +60,7 @@ jobs: run: working-directory: cortex-m-rt steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -70,18 +69,18 @@ jobs: - name: Install all Rust targets run: rustup target install thumbv6m-none-eabi thumbv7m-none-eabi thumbv7em-none-eabi thumbv7em-none-eabihf thumbv8m.base-none-eabi thumbv8m.main-none-eabi thumbv8m.main-none-eabihf - name: Build examples for thumbv6m-none-eabi - run: cargo build --target=thumbv6m-none-eabi --examples + run: cargo build --target=thumbv6m-none-eabi --features cortex-m/critical-section-single-core --examples - name: Build examples for thumbv7m-none-eabi - run: cargo build --target=thumbv7m-none-eabi --examples + run: cargo build --target=thumbv7m-none-eabi --features cortex-m/critical-section-single-core --examples - name: Build examples for thumbv7em-none-eabi - run: cargo build --target=thumbv7em-none-eabi --examples + run: cargo build --target=thumbv7em-none-eabi --features cortex-m/critical-section-single-core --examples - name: Build examples for thumbv7em-none-eabihf - run: cargo build --target=thumbv7em-none-eabihf --examples + run: cargo build --target=thumbv7em-none-eabihf --features cortex-m/critical-section-single-core --examples - name: Build examples for thumbv8m.base-none-eabi - run: cargo build --target=thumbv8m.base-none-eabi --examples + run: cargo build --target=thumbv8m.base-none-eabi --features cortex-m/critical-section-single-core --examples - name: Build examples for thumbv8m.main-none-eabi - run: cargo build --target=thumbv8m.main-none-eabi --examples + run: cargo build --target=thumbv8m.main-none-eabi --features cortex-m/critical-section-single-core --examples - name: Build examples for thumbv8m.main-none-eabihf - run: cargo build --target=thumbv8m.main-none-eabihf --examples + run: cargo build --target=thumbv8m.main-none-eabihf --features cortex-m/critical-section-single-core --examples - name: Build crate for host OS run: cargo build diff --git a/.github/workflows/rustfmt.yml b/.github/workflows/rustfmt.yml index bd5997c..c29106e 100644 --- a/.github/workflows/rustfmt.yml +++ b/.github/workflows/rustfmt.yml @@ -10,7 +10,7 @@ jobs: name: rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal diff --git a/CHANGELOG.md b/CHANGELOG.md index 421dce7..ebcd2c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,12 +17,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - 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) ### 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) + +### Changed +- Inline assembly is now always used, requiring Rust 1.59. ### Removed - removed all peripherals `ptr()` functions in favor of the associated constant `PTR` (#385). +- removed `inline-asm` feature which is now always enabled ## [v0.7.4] - 2021-12-31 @@ -12,11 +12,12 @@ name = "cortex-m" readme = "README.md" repository = "https://github.com/rust-embedded/cortex-m" version = "0.7.4" -edition = "2018" +edition = "2021" +rust-version = "1.59" links = "cortex-m" # prevent multiple versions of this crate to be linked together [dependencies] -bare-metal = "1" +critical-section = "1.0.0" volatile-register = "0.2.0" bitfield = "0.13.2" embedded-hal = "0.2.4" @@ -29,17 +30,20 @@ optional = true [features] cm7 = [] cm7-r0p1 = ["cm7"] -inline-asm = [] linker-plugin-lto = [] std = [] +critical-section-single-core = ["critical-section/restore-state-bool"] [workspace] members = [ - "xtask", "cortex-m-rt", "cortex-m-semihosting", + "panic-itm", "panic-semihosting", - "panic-itm" + "testsuite", + "testsuite/minitest", + "testsuite/minitest/macros", + "xtask", ] [package.metadata.docs.rs] @@ -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.42 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.59 and up. It might compile with older versions but that may change in any new patch release. ## License diff --git a/asm-toolchain b/asm-toolchain deleted file mode 100644 index cc5dbb2..0000000 --- a/asm-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2021-12-16 diff --git a/asm/inline.rs b/asm/inline.rs deleted file mode 100644 index bbc04d2..0000000 --- a/asm/inline.rs +++ /dev/null @@ -1,448 +0,0 @@ -//! 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(nomem, nostack, preserves_flags)); - compiler_fence(Ordering::SeqCst); -} - -#[inline(always)] -pub unsafe fn __dsb() { - compiler_fence(Ordering::SeqCst); - asm!("dsb", options(nomem, nostack, preserves_flags)); - compiler_fence(Ordering::SeqCst); -} - -#[inline(always)] -pub unsafe fn __isb() { - compiler_fence(Ordering::SeqCst); - asm!("isb", options(nomem, 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/asm/lib.rs b/asm/lib.rs deleted file mode 100644 index 48f3dc2..0000000 --- a/asm/lib.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! 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/bin/thumbv6m-none-eabi-lto.a b/bin/thumbv6m-none-eabi-lto.a Binary files differdeleted file mode 100644 index a203d7a..0000000 --- a/bin/thumbv6m-none-eabi-lto.a +++ /dev/null diff --git a/bin/thumbv6m-none-eabi.a b/bin/thumbv6m-none-eabi.a Binary files differdeleted file mode 100644 index 9640a69..0000000 --- a/bin/thumbv6m-none-eabi.a +++ /dev/null diff --git a/bin/thumbv7em-none-eabi-lto.a b/bin/thumbv7em-none-eabi-lto.a Binary files differdeleted file mode 100644 index b34ac64..0000000 --- a/bin/thumbv7em-none-eabi-lto.a +++ /dev/null diff --git a/bin/thumbv7em-none-eabi.a b/bin/thumbv7em-none-eabi.a Binary files differdeleted file mode 100644 index 88acbdd..0000000 --- a/bin/thumbv7em-none-eabi.a +++ /dev/null diff --git a/bin/thumbv7em-none-eabihf-lto.a b/bin/thumbv7em-none-eabihf-lto.a Binary files differdeleted file mode 100644 index 6de94bb..0000000 --- a/bin/thumbv7em-none-eabihf-lto.a +++ /dev/null diff --git a/bin/thumbv7em-none-eabihf.a b/bin/thumbv7em-none-eabihf.a Binary files differdeleted file mode 100644 index cf91a7a..0000000 --- a/bin/thumbv7em-none-eabihf.a +++ /dev/null diff --git a/bin/thumbv7m-none-eabi-lto.a b/bin/thumbv7m-none-eabi-lto.a Binary files differdeleted file mode 100644 index 7f677a9..0000000 --- a/bin/thumbv7m-none-eabi-lto.a +++ /dev/null diff --git a/bin/thumbv7m-none-eabi.a b/bin/thumbv7m-none-eabi.a Binary files differdeleted file mode 100644 index ff4bf21..0000000 --- a/bin/thumbv7m-none-eabi.a +++ /dev/null diff --git a/bin/thumbv8m.base-none-eabi-lto.a b/bin/thumbv8m.base-none-eabi-lto.a Binary files differdeleted file mode 100644 index f62acaf..0000000 --- a/bin/thumbv8m.base-none-eabi-lto.a +++ /dev/null diff --git a/bin/thumbv8m.base-none-eabi.a b/bin/thumbv8m.base-none-eabi.a Binary files differdeleted file mode 100644 index c0cc96c..0000000 --- a/bin/thumbv8m.base-none-eabi.a +++ /dev/null diff --git a/bin/thumbv8m.main-none-eabi-lto.a b/bin/thumbv8m.main-none-eabi-lto.a Binary files differdeleted file mode 100644 index 1a51515..0000000 --- a/bin/thumbv8m.main-none-eabi-lto.a +++ /dev/null diff --git a/bin/thumbv8m.main-none-eabi.a b/bin/thumbv8m.main-none-eabi.a Binary files differdeleted file mode 100644 index d017a15..0000000 --- a/bin/thumbv8m.main-none-eabi.a +++ /dev/null diff --git a/bin/thumbv8m.main-none-eabihf-lto.a b/bin/thumbv8m.main-none-eabihf-lto.a Binary files differdeleted file mode 100644 index fd3dc92..0000000 --- a/bin/thumbv8m.main-none-eabihf-lto.a +++ /dev/null diff --git a/bin/thumbv8m.main-none-eabihf.a b/bin/thumbv8m.main-none-eabihf.a Binary files differdeleted file mode 100644 index 223ff1d..0000000 --- a/bin/thumbv8m.main-none-eabihf.a +++ /dev/null @@ -1,33 +1,13 @@ -use std::path::PathBuf; -use std::{env, fs}; +use std::env; 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()); - } - if target.starts_with("thumbv6m-") { println!("cargo:rustc-cfg=cortex_m"); println!("cargo:rustc-cfg=armv6m"); @@ -37,7 +17,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"); // (not currently used) + println!("cargo:rustc-cfg=armv7em"); } else if target.starts_with("thumbv8m.base") { println!("cargo:rustc-cfg=cortex_m"); println!("cargo:rustc-cfg=armv8m"); diff --git a/cortex-m-rt/CHANGELOG.md b/cortex-m-rt/CHANGELOG.md index c66b5c0..0ee0510 100644 --- a/cortex-m-rt/CHANGELOG.md +++ b/cortex-m-rt/CHANGELOG.md @@ -7,11 +7,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [v0.7.2] + +- MSRV is now Rust 1.59. +- Moved precompiled assembly blobs to `global_asm!`, requiring Rust 1.59. +- Add new `set_vtor` and `set-sp` features to conditionally set the VTOR and SP + registers at device reset ([#423]). +- Allow (unstable) `naked` attribute on interrupt handlers and `pre_init`. + ## Fixes - Fix `cortex_m_rt::exception` macro no longer being usable fully-qualified ([#414]) +- Fix veneer limit position in linker script ([#434]). [#414]: https://github.com/rust-embedded/cortex-m/issues/414 +[#423]: https://github.com/rust-embedded/cortex-m/issues/423 +[#434]: https://github.com/rust-embedded/cortex-m/issues/434 ## Notes @@ -591,7 +602,8 @@ section size addr Initial release -[Unreleased]: https://github.com/rust-embedded/cortex-m-rt/compare/v0.7.1...HEAD +[Unreleased]: https://github.com/rust-embedded/cortex-m/compare/c-m-rt-v0.7.2...HEAD +[v0.7.2]: https://github.com/rust-embedded/cortex-m/compare/c-m-rt-v0.7.1...c-m-rt-v0.7.2 [v0.7.1]: https://github.com/rust-embedded/cortex-m-rt/compare/v0.7.0...v0.7.1 [v0.7.0]: https://github.com/rust-embedded/cortex-m-rt/compare/v0.6.11...v0.7.0 [v0.6.15]: https://github.com/rust-embedded/cortex-m-rt/compare/v0.6.14...v0.6.15 diff --git a/cortex-m-rt/Cargo.toml b/cortex-m-rt/Cargo.toml index 5289057..e6ea8c8 100644 --- a/cortex-m-rt/Cargo.toml +++ b/cortex-m-rt/Cargo.toml @@ -12,10 +12,11 @@ license = "MIT OR Apache-2.0" name = "cortex-m-rt" readme = "README.md" repository = "https://github.com/rust-embedded/cortex-m" -version = "0.7.1" +version = "0.7.2" autoexamples = true links = "cortex-m-rt" # Prevent multiple versions of cortex-m-rt being linked -edition = "2018" +edition = "2021" +rust-version = "1.59" [dependencies] cortex-m-rt-macros = { path = "macros", version = "=0.7.0" } @@ -42,6 +43,8 @@ required-features = ["device"] [features] device = [] +set-sp = [] +set-vtor = [] [package.metadata.docs.rs] features = ["device"] diff --git a/cortex-m-rt/README.md b/cortex-m-rt/README.md index 34b0f17..b62dbb5 100644 --- a/cortex-m-rt/README.md +++ b/cortex-m-rt/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.42.0 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.59.0 and up. It *might* compile with older versions but that may change in any new patch release. # License diff --git a/cortex-m-rt/asm.S b/cortex-m-rt/asm.S deleted file mode 100644 index 0d078b3..0000000 --- a/cortex-m-rt/asm.S +++ /dev/null @@ -1,113 +0,0 @@ - .cfi_sections .debug_frame - - # Notes for function attributes: - # .type and .thumb_func are _both_ required, otherwise the Thumb mode bit - # will not be set and an invalid vector table is generated. - # LLD requires that section flags are set explicitly. - - .section .HardFaultTrampoline, "ax" - .global HardFaultTrampoline - .type HardFaultTrampoline,%function - .thumb_func - .cfi_startproc - # HardFault exceptions are bounced through this trampoline which grabs the - # stack pointer at the time of the exception and passes it to the user's - # HardFault handler in r0. -HardFaultTrampoline: - # Depending on the stack mode in EXC_RETURN, fetch stack pointer from - # PSP or MSP. - mov r0, lr - mov r1, #4 - tst r0, r1 - bne 0f - mrs r0, MSP - b HardFault -0: - mrs r0, PSP - b HardFault - .cfi_endproc - .size HardFaultTrampoline, . - HardFaultTrampoline - - .section .Reset, "ax" - .global Reset - .type Reset,%function - .thumb_func - .cfi_startproc - # Main entry point after reset. This jumps to the user __pre_init function, - # which cannot be called from Rust code without invoking UB, then - # initialises RAM. If the target has an FPU, it is enabled. Finally, jumps - # to the user main function. -Reset: - # ARMv6-M does not initialise LR, but many tools expect it to be 0xFFFF_FFFF - # when reaching the first call frame, so we set it at startup. - # ARMv7-M and above initialise LR to 0xFFFF_FFFF at reset. - ldr r4,=0xffffffff - mov lr,r4 - - # Run user pre-init code, which must be executed immediately after startup, - # before the potentially time-consuming memory initialisation takes place. - # Example use cases include disabling default watchdogs or enabling RAM. - bl __pre_init - - # Restore LR after calling __pre_init (r4 is preserved by subroutines). - mov lr,r4 - - # Initialise .bss memory. `__sbss` and `__ebss` come from the linker script. - ldr r0,=__sbss - ldr r1,=__ebss - mov r2,#0 -0: - cmp r1, r0 - beq 1f - stm r0!, {r2} - b 0b -1: - - # Initialise .data memory. `__sdata`, `__sidata`, and `__edata` come from the - # linker script. Copy from r2 into r0 until r0 reaches r1. - ldr r0,=__sdata - ldr r1,=__edata - ldr r2,=__sidata -2: - cmp r1, r0 - beq 3f - # load 1 word from r2 to r3, inc r2 - ldm r2!, {r3} - # store 1 word from r3 to r0, inc r0 - stm r0!, {r3} - b 2b -3: - -#ifdef HAS_FPU - # Conditionally enable the FPU. - # Address of SCB.CPACR. - ldr r0, =0xE000ED88 - # Enable access to CP10 and CP11 from both privileged and unprivileged mode. - ldr r1, =(0b1111 << 20) - # RMW. - ldr r2, [r0] - orr r2, r2, r1 - str r2, [r0] - # Barrier is required on some processors. - dsb - isb -#endif - -4: - # Preserve `lr` and emit debuginfo that lets external tools restore it. - # This fixes unwinding past the `Reset` handler. - # See https://sourceware.org/binutils/docs/as/CFI-directives.html for an - # explanation of the directives. -.cfi_def_cfa sp, 0 - push {lr} -.cfi_offset lr, 0 - - # Jump to user main function. We use bl for the extended range, but the - # user main function may not return. - bl main - - # Trap on return. - udf - - .cfi_endproc - .size Reset, . - Reset diff --git a/cortex-m-rt/assemble.sh b/cortex-m-rt/assemble.sh deleted file mode 100755 index 9b1f15c..0000000 --- a/cortex-m-rt/assemble.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash - -set -euxo pipefail - -# cflags taken from cc 1.0.22 - -crate=cortex-m-rt - -# remove existing blobs because otherwise this will append object files to the old blobs -rm -f bin/*.a - -arm-none-eabi-gcc -g -c -march=armv6s-m asm.S -o bin/$crate.o -ar crs bin/thumbv6m-none-eabi.a bin/$crate.o - -arm-none-eabi-gcc -g -c -march=armv7-m asm.S -o bin/$crate.o -ar crs bin/thumbv7m-none-eabi.a bin/$crate.o - -arm-none-eabi-gcc -g -c -march=armv7e-m asm.S -o bin/$crate.o -ar crs bin/thumbv7em-none-eabi.a bin/$crate.o - -arm-none-eabi-gcc -g -c -march=armv7e-m asm.S -DHAS_FPU -o bin/$crate.o -ar crs bin/thumbv7em-none-eabihf.a bin/$crate.o - -arm-none-eabi-gcc -g -c -march=armv8-m.base asm.S -o bin/$crate.o -ar crs bin/thumbv8m.base-none-eabi.a bin/$crate.o - -arm-none-eabi-gcc -g -c -march=armv8-m.main asm.S -o bin/$crate.o -ar crs bin/thumbv8m.main-none-eabi.a bin/$crate.o - -arm-none-eabi-gcc -g -c -march=armv8-m.main -DHAS_FPU asm.S -o bin/$crate.o -ar crs bin/thumbv8m.main-none-eabihf.a bin/$crate.o - -rm bin/$crate.o diff --git a/cortex-m-rt/bin/thumbv6m-none-eabi.a b/cortex-m-rt/bin/thumbv6m-none-eabi.a Binary files differdeleted file mode 100644 index c145cc6..0000000 --- a/cortex-m-rt/bin/thumbv6m-none-eabi.a +++ /dev/null diff --git a/cortex-m-rt/bin/thumbv7em-none-eabi.a b/cortex-m-rt/bin/thumbv7em-none-eabi.a Binary files differdeleted file mode 100644 index 2d6b6a1..0000000 --- a/cortex-m-rt/bin/thumbv7em-none-eabi.a +++ /dev/null diff --git a/cortex-m-rt/bin/thumbv7em-none-eabihf.a b/cortex-m-rt/bin/thumbv7em-none-eabihf.a Binary files differdeleted file mode 100644 index aa765ea..0000000 --- a/cortex-m-rt/bin/thumbv7em-none-eabihf.a +++ /dev/null diff --git a/cortex-m-rt/bin/thumbv7m-none-eabi.a b/cortex-m-rt/bin/thumbv7m-none-eabi.a Binary files differdeleted file mode 100644 index 3d1783c..0000000 --- a/cortex-m-rt/bin/thumbv7m-none-eabi.a +++ /dev/null diff --git a/cortex-m-rt/bin/thumbv8m.base-none-eabi.a b/cortex-m-rt/bin/thumbv8m.base-none-eabi.a Binary files differdeleted file mode 100644 index a9fb434..0000000 --- a/cortex-m-rt/bin/thumbv8m.base-none-eabi.a +++ /dev/null diff --git a/cortex-m-rt/bin/thumbv8m.main-none-eabi.a b/cortex-m-rt/bin/thumbv8m.main-none-eabi.a Binary files differdeleted file mode 100644 index 40a5c51..0000000 --- a/cortex-m-rt/bin/thumbv8m.main-none-eabi.a +++ /dev/null diff --git a/cortex-m-rt/bin/thumbv8m.main-none-eabihf.a b/cortex-m-rt/bin/thumbv8m.main-none-eabihf.a Binary files differdeleted file mode 100644 index 6c523af..0000000 --- a/cortex-m-rt/bin/thumbv8m.main-none-eabihf.a +++ /dev/null diff --git a/cortex-m-rt/build.rs b/cortex-m-rt/build.rs index 96a8560..2b65cdf 100644 --- a/cortex-m-rt/build.rs +++ b/cortex-m-rt/build.rs @@ -1,4 +1,4 @@ -use std::fs::{self, File}; +use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::{env, ffi::OsStr}; @@ -16,15 +16,6 @@ fn main() { .map_or(target.clone(), |stem| stem.to_str().unwrap().to_string()); } - let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - - if target.starts_with("thumbv") { - let lib_path = format!("bin/{}.a", target); - fs::copy(&lib_path, out_dir.join("libcortex-m-rt.a")).unwrap(); - println!("cargo:rustc-link-lib=static=cortex-m-rt"); - println!("cargo:rerun-if-changed={}", lib_path); - } - // Put the linker script somewhere the linker can find it let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); let link_x = include_bytes!("link.x.in"); @@ -69,6 +60,10 @@ INCLUDE device.x"# 240 }; + if target.ends_with("-eabihf") { + println!("cargo:rustc-cfg=has_fpu"); + } + // checking the size of the interrupts portion of the vector table is sub-architecture dependent writeln!( f, diff --git a/cortex-m-rt/check-blobs.sh b/cortex-m-rt/check-blobs.sh deleted file mode 100755 index 166b4a4..0000000 --- a/cortex-m-rt/check-blobs.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -# Checks that the blobs are up to date with the committed assembly files - -set -euxo pipefail - -for lib in bin/*.a; do - filename=$(basename "$lib") - arm-none-eabi-objdump -Cd "$lib" > "bin/${filename%.a}.before" -done - -./assemble.sh - -for lib in bin/*.a; do - filename=$(basename "$lib") - arm-none-eabi-objdump -Cd "$lib" > "bin/${filename%.a}.after" -done - -for cksum in bin/*.after; do - diff -u "$cksum" "${cksum%.after}.before" -done diff --git a/cortex-m-rt/ci/script.sh b/cortex-m-rt/ci/script.sh index 08ff863..2941e48 100755 --- a/cortex-m-rt/ci/script.sh +++ b/cortex-m-rt/ci/script.sh @@ -7,10 +7,13 @@ main() { cargo check --target "$TARGET" --features device + # A `critical_section` implementation is always needed. + needed_features=cortex-m/critical-section-single-core + if [ "$TARGET" = x86_64-unknown-linux-gnu ] && [ "$TRAVIS_RUST_VERSION" = stable ]; then ( cd macros && cargo check && cargo test ) - cargo test --features device --test compiletest + cargo test --features "device,${needed_features}" --test compiletest fi local examples=( @@ -43,20 +46,25 @@ main() { if [ "$TARGET" != x86_64-unknown-linux-gnu ]; then # Only test on stable and nightly, not MSRV. if [ "$TRAVIS_RUST_VERSION" = stable ] || [ "$TRAVIS_RUST_VERSION" = nightly ]; then - RUSTDOCFLAGS="-Cpanic=abort" cargo test --doc + RUSTDOCFLAGS="-Cpanic=abort" cargo test --features "${needed_features}" --doc fi for linker in "${linkers[@]}"; do for ex in "${examples[@]}"; do - cargo rustc --target "$TARGET" --example "$ex" -- $linker - cargo rustc --target "$TARGET" --example "$ex" --release -- $linker + cargo rustc --target "$TARGET" --example "$ex" --features "${needed_features}" -- $linker + cargo rustc --target "$TARGET" --example "$ex" --features "${needed_features}" --release -- $linker done for ex in "${fail_examples[@]}"; do - ! cargo rustc --target "$TARGET" --example "$ex" -- $linker - ! cargo rustc --target "$TARGET" --example "$ex" --release -- $linker + ! cargo rustc --target "$TARGET" --example "$ex" --features "${needed_features}" -- $linker + ! cargo rustc --target "$TARGET" --example "$ex" --features "${needed_features}" --release -- $linker done - cargo rustc --target "$TARGET" --example device --features device -- $linker - cargo rustc --target "$TARGET" --example device --features device --release -- $linker + cargo rustc --target "$TARGET" --example device --features "device,${needed_features}" -- $linker + cargo rustc --target "$TARGET" --example device --features "device,${needed_features}" --release -- $linker + + cargo rustc --target "$TARGET" --example minimal --features "set-sp,${needed_features}" -- $linker + cargo rustc --target "$TARGET" --example minimal --features "set-sp,${needed_features}" --release -- $linker + cargo rustc --target "$TARGET" --example minimal --features "set-vtor,${needed_features}" -- $linker + cargo rustc --target "$TARGET" --example minimal --features "set-vtor,${needed_features}" --release -- $linker done fi @@ -64,17 +72,13 @@ main() { thumbv6m-none-eabi|thumbv7m-none-eabi) for linker in "${linkers[@]}"; do env RUSTFLAGS="$linker -C link-arg=-Tlink.x" cargo run \ - --target "$TARGET" --example qemu | grep "x = 42" + --target "$TARGET" --features "${needed_features}" --example qemu | grep "x = 42" env RUSTFLAGS="$linker -C link-arg=-Tlink.x" cargo run \ - --target "$TARGET" --example qemu --release | grep "x = 42" + --target "$TARGET" --features "${needed_features}" --example qemu --release | grep "x = 42" done ;; esac - - if [ "$TARGET" = x86_64-unknown-linux-gnu ]; then - ./check-blobs.sh - fi } main diff --git a/cortex-m-rt/examples/qemu.rs b/cortex-m-rt/examples/qemu.rs index e903404..a8ffd20 100644 --- a/cortex-m-rt/examples/qemu.rs +++ b/cortex-m-rt/examples/qemu.rs @@ -1,28 +1,24 @@ -// #![feature(stdsimd)] #![no_main] #![no_std] -extern crate cortex_m; -extern crate cortex_m_rt as rt; -extern crate cortex_m_semihosting as semihosting; +use core::fmt::Write; -extern crate panic_halt; - -use cortex_m::asm; -use rt::entry; - -#[entry] +#[cortex_m_rt::entry] fn main() -> ! { - use core::fmt::Write; let x = 42; loop { - asm::nop(); - - // write something through semihosting interface - let mut hstdout = semihosting::hio::hstdout().unwrap(); + let mut hstdout = cortex_m_semihosting::hio::hstdout().unwrap(); write!(hstdout, "x = {}\n", x).unwrap(); - // exit from qemu - semihosting::debug::exit(semihosting::debug::EXIT_SUCCESS); + cortex_m_semihosting::debug::exit(cortex_m_semihosting::debug::EXIT_SUCCESS); + } +} + +// Define a panic handler that uses semihosting to exit immediately, +// so that any panics cause qemu to quit instead of hang. +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + loop { + cortex_m_semihosting::debug::exit(cortex_m_semihosting::debug::EXIT_FAILURE); } } diff --git a/cortex-m-rt/link.x.in b/cortex-m-rt/link.x.in index 92004b7..4461646 100644 --- a/cortex-m-rt/link.x.in +++ b/cortex-m-rt/link.x.in @@ -66,6 +66,8 @@ SECTIONS /* ### Vector table */ .vector_table ORIGIN(FLASH) : { + __vector_table = .; + /* Initial Stack Pointer (SP) value */ LONG(_stack_start); @@ -142,8 +144,12 @@ SECTIONS __veneer_base = .; *(.gnu.sgstubs*) . = ALIGN(32); - __veneer_limit = .; } > FLASH + /* Place `__veneer_limit` outside the `.gnu.sgstubs` section because veneers are + * always inserted last in the section, which would otherwise be _after_ the `__veneer_limit` symbol. + */ + . = ALIGN(32); + __veneer_limit = .; /* ### .bss */ .bss (NOLOAD) : ALIGN(4) diff --git a/cortex-m-rt/macros/Cargo.toml b/cortex-m-rt/macros/Cargo.toml index c73ebc1..e95cc7d 100644 --- a/cortex-m-rt/macros/Cargo.toml +++ b/cortex-m-rt/macros/Cargo.toml @@ -8,7 +8,8 @@ license = "MIT OR Apache-2.0" name = "cortex-m-rt-macros" repository = "https://github.com/rust-embedded/cortex-m" version = "0.7.0" -edition = "2018" +edition = "2021" +rust-version = "1.59" [lib] proc-macro = true diff --git a/cortex-m-rt/src/lib.rs b/cortex-m-rt/src/lib.rs index 752d3d7..6e6bf7e 100644 --- a/cortex-m-rt/src/lib.rs +++ b/cortex-m-rt/src/lib.rs @@ -34,14 +34,14 @@ //! //! This crate expects the user, or some other crate, to provide the memory layout of the target //! device via a linker script named `memory.x`. This section covers the contents of `memory.x` -//! The `memory.x` file is used by during linking by the `link.x` script provided by this crate. +//! The `memory.x` file is used during linking by the `link.x` script provided by this crate. //! //! ### `MEMORY` //! //! The linker script must specify the memory available in the device as, at least, two `MEMORY` //! regions: one named `FLASH` and one named `RAM`. The `.text` and `.rodata` sections of the //! program will be placed in the `FLASH` region, whereas the `.bss` and `.data` sections, as well -//! as the heap,will be placed in the `RAM` region. +//! as the heap, will be placed in the `RAM` region. //! //! ```text //! /* Linker script for the STM32F103C8T6 */ @@ -158,6 +158,19 @@ //! conjunction with crates generated using `svd2rust`. Those *device crates* will populate the //! missing part of the vector table when their `"rt"` feature is enabled. //! +//! ## `set-sp` +//! +//! If this feature is enabled, the stack pointer (SP) is initialised in the reset handler to the +//! `_stack_start` value from the linker script. This is not usually required, but some debuggers +//! do not initialise SP when performing a soft reset, which can lead to stack corruption. +//! +//! ## `set-vtor` +//! +//! If this feature is enabled, the vector table offset register (VTOR) is initialised in the reset +//! handler to the start of the vector table defined in the linker script. This is not usually +//! required, but some bootloaders do not set VTOR before jumping to application code, leading to +//! your main function executing but interrupt handlers not being used. +//! //! # Inspection //! //! This section covers how to inspect a binary that builds on top of `cortex-m-rt`. @@ -309,14 +322,10 @@ //! //! We want to provide a default handler for all the interrupts while still letting the user //! individually override each interrupt handler. In C projects, this is usually accomplished using -//! weak aliases declared in external assembly files. In Rust, we could achieve something similar -//! using `global_asm!`, but that's an unstable feature. -//! -//! A solution that doesn't require `global_asm!` or external assembly files is to use the `PROVIDE` -//! command in a linker script to create the weak aliases. This is the approach that `cortex-m-rt` -//! uses; when the `"device"` feature is enabled `cortex-m-rt`'s linker script (`link.x`) depends on -//! a linker script named `device.x`. The crate that provides `__INTERRUPTS` must also provide this -//! file. +//! weak aliases declared in external assembly files. We use a similar solution via the `PROVIDE` +//! command in the linker script: when the `"device"` feature is enabled, `cortex-m-rt`'s linker +//! script (`link.x`) includes a linker script named `device.x`, which must be provided by +//! whichever crate provides `__INTERRUPTS`. //! //! For our running example the `device.x` linker script looks like this: //! @@ -330,8 +339,8 @@ //! that the core exceptions use unless overridden. //! //! Because this linker script is provided by a dependency of the final application the dependency -//! must contain build script that puts `device.x` somewhere the linker can find. An example of such -//! build script is shown below: +//! must contain a build script that puts `device.x` somewhere the linker can find. An example of +//! such build script is shown below: //! //! ```ignore //! use std::env; @@ -418,7 +427,7 @@ //! //! # Minimum Supported Rust Version (MSRV) //! -//! The MSRV of this release is Rust 1.42.0. +//! The MSRV of this release is Rust 1.59.0. // # Developer notes // @@ -430,16 +439,152 @@ extern crate cortex_m_rt_macros as macros; +#[cfg(cortex_m)] +use core::arch::global_asm; use core::fmt; -use core::sync::atomic::{self, Ordering}; + +// HardFault exceptions are bounced through this trampoline which grabs the stack pointer at +// the time of the exception and passes it to the user's HardFault handler in r0. +// Depending on the stack mode in EXC_RETURN, fetches stack from either MSP or PSP. +#[cfg(cortex_m)] +global_asm!( + ".cfi_sections .debug_frame + .section .HardFaultTrampoline, \"ax\" + .global HardFaultTrampline + .type HardFaultTrampline,%function + .thumb_func + .cfi_startproc + HardFaultTrampoline:", + "mov r0, lr + movs r1, #4 + tst r0, r1 + bne 0f + mrs r0, MSP + b HardFault + 0: + mrs r0, PSP + b HardFault", + ".cfi_endproc + .size HardFaultTrampoline, . - HardFaultTrampoline", +); + +/// Parse cfg attributes inside a global_asm call. +#[cfg(cortex_m)] +macro_rules! cfg_global_asm { + {@inner, [$($x:tt)*], } => { + global_asm!{$($x)*} + }; + (@inner, [$($x:tt)*], #[cfg($meta:meta)] $asm:literal, $($rest:tt)*) => { + #[cfg($meta)] + cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*} + #[cfg(not($meta))] + cfg_global_asm!{@inner, [$($x)*], $($rest)*} + }; + {@inner, [$($x:tt)*], $asm:literal, $($rest:tt)*} => { + cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*} + }; + {$($asms:tt)*} => { + cfg_global_asm!{@inner, [], $($asms)*} + }; +} + +// This reset vector is the initial entry point after a system reset. +// Calls an optional user-provided __pre_init and then initialises RAM. +// If the target has an FPU, it is enabled. +// Finally jumps to the user main function. +#[cfg(cortex_m)] +cfg_global_asm! { + ".cfi_sections .debug_frame + .section .Reset, \"ax\" + .global Reset + .type Reset,%function + .thumb_func", + ".cfi_startproc + Reset:", + + // Ensure LR is loaded with 0xFFFF_FFFF at startup to help debuggers find the first call frame. + // On ARMv6-M LR is not initialised at all, while other platforms should initialise it. + "movs r4, #0 + mvns r4, r4 + mov lr, r4", + + // If enabled, initialise the SP. This is normally initialised by the CPU itself or by a + // bootloader, but some debuggers fail to set it when resetting the target, leading to + // stack corruptions. + #[cfg(feature = "set-sp")] + "ldr r0, =_stack_start + msr msp, r0", + + // If enabled, initialise VTOR to the start of the vector table. This is normally initialised + // by a bootloader when the non-reset value is required, but some bootloaders do not set it, + // leading to frustrating issues where everything seems to work but interrupts are never + // handled. The VTOR register is optional on ARMv6-M, but when not present is RAZ,WI and + // therefore safe to write to. + #[cfg(feature = "set-vtor")] + "ldr r0, =0xe000ed08 + ldr r1, =__vector_table + str r1, [r0]", + + // Run user pre-init code which must be executed immediately after startup, before the + // potentially time-consuming memory initialisation takes place. + // Example use cases include disabling default watchdogs or enabling RAM. + // Reload LR after returning from pre-init (r4 is preserved by subroutines). + "bl __pre_init + mov lr, r4", + + // Initialise .bss memory. `__sbss` and `__ebss` come from the linker script. + "ldr r0, =__sbss + ldr r1, =__ebss + movs r2, #0 + 0: + cmp r1, r0 + beq 1f + stm r0!, {{r2}} + b 0b + 1:", + + // Initialise .data memory. `__sdata`, `__sidata`, and `__edata` come from the linker script. + "ldr r0, =__sdata + ldr r1, =__edata + ldr r2, =__sidata + 2: + cmp r1, r0 + beq 3f + ldm r2!, {{r3}} + stm r0!, {{r3}} + b 2b + 3:", + + // Potentially enable an FPU. + // SCB.CPACR is 0xE000_ED88. + // We enable access to CP10 and CP11 from priviliged and unprivileged mode. + #[cfg(has_fpu)] + "ldr r0, =0xE000ED88 + ldr r1, =(0b1111 << 20) + ldr r2, [r0] + orr r2, r2, r1 + str r2, [r0] + dsb + isb", + + // Push `lr` to the stack for debuggers, to prevent them unwinding past Reset. + // See https://sourceware.org/binutils/docs/as/CFI-directives.html. + ".cfi_def_cfa sp, 0 + push {{lr}} + .cfi_offset lr, 0", + + // Jump to user main function. + // `bl` is used for the extended range, but the user main function should not return, + // so trap on any unexpected return. + "bl main + udf #0", + + ".cfi_endproc + .size Reset, . - Reset", +} /// Attribute to declare an interrupt (AKA device-specific exception) handler /// -/// **IMPORTANT**: If you are using Rust 1.30 this attribute must be used on reachable items (i.e. -/// there must be no private modules between the item and the root of the crate); if the item is in -/// the root of the crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 -/// and newer releases. -/// /// **NOTE**: This attribute is exposed by `cortex-m-rt` only when the `device` feature is enabled. /// However, that export is not meant to be used directly -- using it will result in a compilation /// error. You should instead use the device crate (usually generated using `svd2rust`) re-export of @@ -506,11 +651,6 @@ pub use macros::interrupt; /// Attribute to declare the entry point of the program /// -/// **IMPORTANT**: This attribute must appear exactly *once* in the dependency graph. Also, if you -/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no -/// private modules between the item and the root of the crate); if the item is in the root of the -/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer releases. -/// /// The specified function will be called by the reset handler *after* RAM has been initialized. In /// the case of the `thumbv7em-none-eabihf` target the FPU will also be enabled before the function /// is called. @@ -565,11 +705,6 @@ pub use macros::entry; /// Attribute to declare an exception handler /// -/// **IMPORTANT**: If you are using Rust 1.30 this attribute must be used on reachable items (i.e. -/// there must be no private modules between the item and the root of the crate); if the item is in -/// the root of the crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 -/// and newer releases. -/// /// # Syntax /// /// ``` @@ -681,11 +816,7 @@ pub use macros::exception; /// Attribute to mark which function will be called at the beginning of the reset handler. /// -/// **IMPORTANT**: This attribute can appear at most *once* in the dependency graph. Also, if you -/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no -/// private modules between the item and the root of the crate); if the item is in the root of the -/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer -/// releases. +/// **IMPORTANT**: This attribute can appear at most *once* in the dependency graph. /// /// The function must have the signature of `unsafe fn()`. /// @@ -920,21 +1051,15 @@ pub static __RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; #[cfg_attr(cortex_m, link_section = ".HardFault.default")] #[no_mangle] pub unsafe extern "C" fn HardFault_(ef: &ExceptionFrame) -> ! { - loop { - // add some side effect to prevent this from turning into a UDF instruction - // see rust-lang/rust#28728 for details - atomic::compiler_fence(Ordering::SeqCst); - } + #[allow(clippy::empty_loop)] + loop {} } #[doc(hidden)] #[no_mangle] pub unsafe extern "C" fn DefaultHandler_() -> ! { - loop { - // add some side effect to prevent this from turning into a UDF instruction - // see rust-lang/rust#28728 for details - atomic::compiler_fence(Ordering::SeqCst); - } + #[allow(clippy::empty_loop)] + loop {} } #[doc(hidden)] diff --git a/cortex-m-rt/tests/compile-fail/non-static-resource.rs b/cortex-m-rt/tests/compile-fail/non-static-resource.rs index a603728..95f314b 100644 --- a/cortex-m-rt/tests/compile-fail/non-static-resource.rs +++ b/cortex-m-rt/tests/compile-fail/non-static-resource.rs @@ -21,7 +21,7 @@ fn SVCall() { static mut STAT: u8 = 0; let _stat: &'static mut u8 = STAT; - //~^ ERROR lifetime of reference outlives lifetime of borrowed content + //~^ ERROR lifetime may not live long enough } #[interrupt] @@ -29,7 +29,7 @@ fn UART0() { static mut STAT: u8 = 0; let _stat: &'static mut u8 = STAT; - //~^ ERROR lifetime of reference outlives lifetime of borrowed content + //~^ ERROR lifetime may not live long enough } #[entry] diff --git a/cortex-m-semihosting/CHANGELOG.md b/cortex-m-semihosting/CHANGELOG.md index 0a942cf..76f0694 100644 --- a/cortex-m-semihosting/CHANGELOG.md +++ b/cortex-m-semihosting/CHANGELOG.md @@ -5,6 +5,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [v0.5.0] - 2022-03-01 + +- Always use inline-asm, requiring Rust 1.59. +- Removed inline-asm feature. + ## [v0.4.1] - 2020-10-20 0.4.1 was yanked because the pre-built binaries contain conflicting symbols @@ -141,7 +146,8 @@ change. - Initial release -[Unreleased]: https://github.com/rust-embedded/cortex-m/compare/c-m-sh-v0.4.1...HEAD +[Unreleased]: https://github.com/rust-embedded/cortex-m/compare/c-m-sh-v0.5.0...HEAD +[v0.5.0]: https://github.com/rust-embedded/cortex-m/compare/c-m-sh-v0.4.1...c-m-sh-v0.5.0 [v0.4.1]: https://github.com/rust-embedded/cortex-m/compare/c-m-sh-v0.4.0...c-m-sh-v0.4.1 [v0.4.0]: https://github.com/rust-embedded/cortex-m/compare/c-m-sh-v0.3.5...c-m-sh-v0.4.0 [v0.3.7]: https://github.com/rust-embedded/cortex-m-semihosting/compare/v0.3.6...v0.3.7 diff --git a/cortex-m-semihosting/Cargo.toml b/cortex-m-semihosting/Cargo.toml index 5894029..ac0afa5 100644 --- a/cortex-m-semihosting/Cargo.toml +++ b/cortex-m-semihosting/Cargo.toml @@ -11,13 +11,14 @@ license = "MIT OR Apache-2.0" name = "cortex-m-semihosting" readme = "README.md" repository = "https://github.com/rust-embedded/cortex-m" -version = "0.4.1" -edition = "2018" +version = "0.5.0" +edition = "2021" +rust-version = "1.59" [features] -inline-asm = [] jlink-quirks = [] no-semihosting = [] [dependencies] cortex-m = { path = "..", version = ">= 0.5.8, < 0.8" } +critical-section = "1.0.0" diff --git a/cortex-m-semihosting/README.md b/cortex-m-semihosting/README.md index bfbfb44..6036d4e 100644 --- a/cortex-m-semihosting/README.md +++ b/cortex-m-semihosting/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.33.0 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.59.0 and up. It *might* compile with older versions but that may change in any new patch release. ## License diff --git a/cortex-m-semihosting/bin b/cortex-m-semihosting/bin deleted file mode 120000 index 19f285a..0000000 --- a/cortex-m-semihosting/bin +++ /dev/null @@ -1 +0,0 @@ -../bin
\ No newline at end of file diff --git a/cortex-m-semihosting/build.rs b/cortex-m-semihosting/build.rs index 315035e..ed0d069 100644 --- a/cortex-m-semihosting/build.rs +++ b/cortex-m-semihosting/build.rs @@ -1,23 +1,9 @@ -use std::path::PathBuf; -use std::{env, fs}; +use std::env; fn main() { let target = env::var("TARGET").unwrap(); - let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - let name = env::var("CARGO_PKG_NAME").unwrap(); if target.starts_with("thumbv") { - if env::var_os("CARGO_FEATURE_INLINE_ASM").is_none() { - fs::copy( - format!("bin/{}.a", target), - 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-cfg=thumb"); } } diff --git a/cortex-m-semihosting/src/export.rs b/cortex-m-semihosting/src/export.rs index 0bbd09f..46e70e7 100644 --- a/cortex-m-semihosting/src/export.rs +++ b/cortex-m-semihosting/src/export.rs @@ -2,14 +2,12 @@ use core::fmt::{self, Write}; -use cortex_m::interrupt; - use crate::hio::{self, HostStream}; static mut HSTDOUT: Option<HostStream> = None; pub fn hstdout_str(s: &str) { - let _result = interrupt::free(|_| unsafe { + let _result = critical_section::with(|_| unsafe { if HSTDOUT.is_none() { HSTDOUT = Some(hio::hstdout()?); } @@ -19,7 +17,7 @@ pub fn hstdout_str(s: &str) { } pub fn hstdout_fmt(args: fmt::Arguments) { - let _result = interrupt::free(|_| unsafe { + let _result = critical_section::with(|_| unsafe { if HSTDOUT.is_none() { HSTDOUT = Some(hio::hstdout()?); } @@ -31,7 +29,7 @@ pub fn hstdout_fmt(args: fmt::Arguments) { static mut HSTDERR: Option<HostStream> = None; pub fn hstderr_str(s: &str) { - let _result = interrupt::free(|_| unsafe { + let _result = critical_section::with(|_| unsafe { if HSTDERR.is_none() { HSTDERR = Some(hio::hstderr()?); } @@ -41,7 +39,7 @@ pub fn hstderr_str(s: &str) { } pub fn hstderr_fmt(args: fmt::Arguments) { - let _result = interrupt::free(|_| unsafe { + let _result = critical_section::with(|_| unsafe { if HSTDERR.is_none() { HSTDERR = Some(hio::hstderr()?); } diff --git a/cortex-m-semihosting/src/lib.rs b/cortex-m-semihosting/src/lib.rs index 3bc23ea..8306307 100644 --- a/cortex-m-semihosting/src/lib.rs +++ b/cortex-m-semihosting/src/lib.rs @@ -151,14 +151,6 @@ //! //! # Optional features //! -//! ## `inline-asm` -//! -//! When this feature is enabled semihosting is implemented using inline assembly and -//! compiling this crate requires nightly. -//! -//! When this feature is disabled semihosting is implemented using FFI calls into an external -//! assembly file and compiling this crate works on stable and beta. -//! //! ## `jlink-quirks` //! //! When this feature is enabled, return values above `0xfffffff0` from semihosting operation @@ -191,11 +183,6 @@ pub mod export; pub mod hio; pub mod nr; -#[cfg(all(thumb, not(feature = "inline-asm")))] -extern "C" { - fn __sh_syscall(nr: usize, arg: usize) -> usize; -} - /// Performs a semihosting operation, takes a pointer to an argument block #[inline(always)] pub unsafe fn syscall<T>(nr: usize, arg: &T) -> usize { @@ -206,24 +193,16 @@ pub unsafe fn syscall<T>(nr: usize, arg: &T) -> usize { #[inline(always)] pub unsafe fn syscall1(_nr: usize, _arg: usize) -> usize { match () { - #[cfg(all(thumb, not(feature = "inline-asm"), not(feature = "no-semihosting")))] - () => __sh_syscall(_nr, _arg), - - #[cfg(all(thumb, feature = "inline-asm", not(feature = "no-semihosting")))] + #[cfg(all(thumb, not(feature = "no-semihosting")))] () => { - let mut nr = _nr; - core::arch::asm!( - "bkpt #0xab", - inout("r0") nr, - in("r1") _arg, - options(nomem, nostack, preserves_flags) - ); - nr + use core::arch::asm; + let mut nr = _nr as u32; + let arg = _arg as u32; + asm!("bkpt #0xab", inout("r0") nr, in("r1") arg, options(nostack, preserves_flags)); + nr as usize } - #[cfg(all(thumb, feature = "no-semihosting"))] () => 0, - #[cfg(not(thumb))] () => unimplemented!(), } diff --git a/panic-semihosting/CHANGELOG.md b/panic-semihosting/CHANGELOG.md index 95c3890..1fa6de3 100644 --- a/panic-semihosting/CHANGELOG.md +++ b/panic-semihosting/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [v0.6.0] - 2022-03-01 + +- Always use inline-asm, requiring Rust 1.59. +- Remove inline-asm feature. + ## [v0.5.6] - 2020-11-14 - Fix update to docs.rs to build for an embedded target @@ -69,7 +74,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Initial release -[Unreleased]: https://github.com/rust-embedded/panic-semihosting/compare/p-sh-v0.5.6...HEAD +[Unreleased]: https://github.com/rust-embedded/panic-semihosting/compare/p-sh-v0.6.0...HEAD +[v0.6.0]: https://github.com/rust-embedded/cortex-m/compare/p-sh-v0.5.6...p-sh-v0.6.0 [v0.5.6]: https://github.com/rust-embedded/cortex-m/compare/p-sh-v0.5.5...p-sh-v0.5.6 [v0.5.5]: https://github.com/rust-embedded/cortex-m/compare/p-sh-v0.5.4...p-sh-v0.5.5 [v0.5.4]: https://github.com/rust-embedded/cortex-m/compare/p-sh-v0.5.3...p-sh-v0.5.4 diff --git a/panic-semihosting/Cargo.toml b/panic-semihosting/Cargo.toml index 46a3d2f..f096614 100644 --- a/panic-semihosting/Cargo.toml +++ b/panic-semihosting/Cargo.toml @@ -10,15 +10,16 @@ keywords = ["panic-handler", "panic-impl", "panic", "semihosting"] license = "MIT OR Apache-2.0" name = "panic-semihosting" repository = "https://github.com/rust-embedded/cortex-m" -version = "0.5.6" +version = "0.6.0" +rust-version = "1.59" +edition = "2021" [dependencies] cortex-m = { path = "..", version = ">= 0.5.6, < 0.8" } -cortex-m-semihosting = { path = "../cortex-m-semihosting", version = ">= 0.3, < 0.5" } +cortex-m-semihosting = { path = "../cortex-m-semihosting", version = ">= 0.5.0, < 0.6" } [features] exit = [] -inline-asm = ["cortex-m-semihosting/inline-asm", "cortex-m/inline-asm"] jlink-quirks = ["cortex-m-semihosting/jlink-quirks"] [package.metadata.docs.rs] diff --git a/panic-semihosting/README.md b/panic-semihosting/README.md index baacf1a..f8057d3 100644 --- a/panic-semihosting/README.md +++ b/panic-semihosting/README.md @@ -8,7 +8,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.32.0 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.59.0 and up. It *might* compile with older versions but that may change in any new patch release. ## License diff --git a/panic-semihosting/src/lib.rs b/panic-semihosting/src/lib.rs index 1db7b72..1d7379e 100644 --- a/panic-semihosting/src/lib.rs +++ b/panic-semihosting/src/lib.rs @@ -47,14 +47,6 @@ //! //! We discourage using this feature when the program will run on hardware as the exit call can //! leave the hardware debugger in an inconsistent state. -//! -//! ## `inline-asm` -//! -//! When this feature is enabled semihosting is implemented using inline assembly (`asm!`) and -//! compiling this crate requires nightly. -//! -//! When this feature is disabled semihosting is implemented using FFI calls into an external -//! assembly file and compiling this crate works on stable and beta. #![cfg(all(target_arch = "arm", target_os = "none"))] #![deny(missing_docs)] @@ -1,18 +1,17 @@ //! Miscellaneous assembly instructions -// 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; +#[cfg(cortex_m)] +use core::arch::asm; +use core::sync::atomic::{compiler_fence, Ordering}; /// Puts the processor in Debug state. Debuggers can pick this up as a "breakpoint". /// /// **NOTE** calling `bkpt` when the processor is not connected to a debugger will cause an /// exception. +#[cfg(cortex_m)] #[inline(always)] pub fn bkpt() { - call_asm!(__bkpt()); + unsafe { asm!("bkpt", options(nomem, nostack, preserves_flags)) }; } /// Blocks the program for *at least* `cycles` CPU cycles. @@ -24,50 +23,80 @@ pub fn bkpt() { /// 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)] #[inline] pub fn delay(cycles: u32) { - call_asm!(__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!( + // 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), + ) + }; } /// A no-operation. Useful to prevent delay loops from being optimized away. -#[inline] +#[inline(always)] pub fn nop() { - call_asm!(__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)) + }; } /// Generate an Undefined Instruction exception. /// /// Can be used as a stable alternative to `core::intrinsics::abort`. -#[inline] +#[cfg(cortex_m)] +#[inline(always)] pub fn udf() -> ! { - call_asm!(__udf() -> !) + unsafe { asm!("udf #0", options(noreturn, nomem, nostack, preserves_flags)) }; } /// Wait For Event -#[inline] +#[cfg(cortex_m)] +#[inline(always)] pub fn wfe() { - call_asm!(__wfe()) + unsafe { asm!("wfe", options(nomem, nostack, preserves_flags)) }; } /// Wait For Interrupt -#[inline] +#[cfg(cortex_m)] +#[inline(always)] pub fn wfi() { - call_asm!(__wfi()) + unsafe { asm!("wfi", options(nomem, nostack, preserves_flags)) }; } /// Send Event -#[inline] +#[cfg(cortex_m)] +#[inline(always)] pub fn sev() { - call_asm!(__sev()) + unsafe { asm!("sev", options(nomem, nostack, preserves_flags)) }; } /// 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] +#[inline(always)] pub fn isb() { - call_asm!(__isb()) + compiler_fence(Ordering::SeqCst); + #[cfg(cortex_m)] + unsafe { + asm!("isb", options(nomem, nostack, preserves_flags)) + }; + compiler_fence(Ordering::SeqCst); } /// Data Synchronization Barrier @@ -77,9 +106,14 @@ 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] +#[inline(always)] pub fn dsb() { - call_asm!(__dsb()) + compiler_fence(Ordering::SeqCst); + #[cfg(cortex_m)] + unsafe { + asm!("dsb", options(nomem, nostack, preserves_flags)) + }; + compiler_fence(Ordering::SeqCst); } /// Data Memory Barrier @@ -87,9 +121,14 @@ 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] +#[inline(always)] pub fn dmb() { - call_asm!(__dmb()) + compiler_fence(Ordering::SeqCst); + #[cfg(cortex_m)] + unsafe { + asm!("dmb", options(nomem, nostack, preserves_flags)) + }; + compiler_fence(Ordering::SeqCst); } /// Test Target @@ -97,13 +136,20 @@ 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] +#[inline(always)] #[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 addr = addr as u32; - call_asm!(__tt(addr: u32) -> u32) + let mut target = addr as u32; + unsafe { + asm!( + "tt {target}, {target}", + target = inout(reg) target, + options(nomem, nostack, preserves_flags), + ) + }; + target } /// Test Target Unprivileged @@ -112,13 +158,20 @@ 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] +#[inline(always)] #[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 addr = addr as u32; - call_asm!(__ttt(addr: u32) -> u32) + let mut target = addr as u32; + unsafe { + asm!( + "ttt {target}, {target}", + target = inout(reg) target, + options(nomem, nostack, preserves_flags), + ) + }; + target } /// Test Target Alternate Domain @@ -128,13 +181,20 @@ 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] +#[inline(always)] #[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 addr = addr as u32; - call_asm!(__tta(addr: u32) -> u32) + let mut target = addr as u32; + unsafe { + asm!( + "tta {target}, {target}", + target = inout(reg) target, + options(nomem, nostack, preserves_flags), + ) + }; + target } /// Test Target Alternate Domain Unprivileged @@ -144,31 +204,40 @@ 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] +#[inline(always)] #[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 addr = addr as u32; - call_asm!(__ttat(addr: u32) -> u32) + let mut target = addr as u32; + unsafe { + asm!( + "ttat {target}, {target}", + target = inout(reg) target, + options(nomem, nostack, preserves_flags), + ) + }; + target } /// 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] +#[inline(always)] #[cfg(armv8m)] pub unsafe fn bx_ns(addr: u32) { - call_asm!(__bxns(addr: u32)); + asm!("bxns {}", in(reg) addr, options(nomem, nostack, preserves_flags)); } /// Semihosting syscall. /// /// This method is used by cortex-m-semihosting to provide semihosting syscalls. -#[inline] -pub unsafe fn semihosting_syscall(nr: u32, arg: u32) -> u32 { - call_asm!(__sh_syscall(nr: u32, arg: u32) -> u32) +#[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 } /// Bootstrap. @@ -181,12 +250,27 @@ pub unsafe fn semihosting_syscall(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; - call_asm!(__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), + ); } /// Bootload. @@ -201,6 +285,7 @@ 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/src/call_asm.rs b/src/call_asm.rs deleted file mode 100644 index 295277f..0000000 --- a/src/call_asm.rs +++ /dev/null @@ -1,24 +0,0 @@ -/// 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/src/cmse.rs b/src/cmse.rs index 36d7447..7826bb8 100644 --- a/src/cmse.rs +++ b/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<u8> { - if self.tt_resp.srvalid() { - // Cast is safe as SREGION field is defined on 8 bits. - Some(self.tt_resp.sregion() as u8) + if self.tt_resp.mrvalid() { + // Cast is safe as MREGION field is defined on 8 bits. + Some(self.tt_resp.mregion() as u8) } else { None } diff --git a/src/critical_section.rs b/src/critical_section.rs new file mode 100644 index 0000000..e3d57d1 --- /dev/null +++ b/src/critical_section.rs @@ -0,0 +1,22 @@ +use critical_section::{set_impl, Impl, RawRestoreState}; + +use crate::interrupt; +use crate::register::primask; + +struct SingleCoreCriticalSection; +set_impl!(SingleCoreCriticalSection); + +unsafe impl Impl for SingleCoreCriticalSection { + unsafe fn acquire() -> RawRestoreState { + let was_active = primask::read().is_active(); + interrupt::disable(); + was_active + } + + 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/src/interrupt.rs b/src/interrupt.rs index 68719ec..f6ce990 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -1,6 +1,9 @@ //! Interrupts -pub use bare_metal::{CriticalSection, Mutex}; +#[cfg(cortex_m)] +use core::arch::asm; +#[cfg(cortex_m)] +use core::sync::atomic::{compiler_fence, Ordering}; /// Trait for enums of external interrupt numbers. /// @@ -23,36 +26,52 @@ pub unsafe trait InterruptNumber: Copy { fn number(self) -> u16; } -/// Disables all interrupts +/// Disables all interrupts in the current core. +#[cfg(cortex_m)] #[inline] pub fn disable() { - call_asm!(__cpsid()); + unsafe { + asm!("cpsid i", options(nomem, nostack, preserves_flags)); + } + + // Ensure no subsequent memory accesses are reordered to before interrupts are disabled. + compiler_fence(Ordering::SeqCst); } -/// Enables all the interrupts +/// Enables all the interrupts in the current core. /// /// # Safety /// -/// - Do not call this function inside an `interrupt::free` critical section +/// - Do not call this function inside a critical section. +#[cfg(cortex_m)] #[inline] pub unsafe fn enable() { - call_asm!(__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)); } -/// Execute closure `f` in an interrupt-free context. +/// Execute closure `f` with interrupts disabled in the current core. /// -/// This as also known as a "critical section". +/// This method does not synchronise 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)] #[inline] pub fn free<F, R>(f: F) -> R where - F: FnOnce(&CriticalSection) -> R, + F: FnOnce() -> R, { let primask = crate::register::primask::read(); // disable interrupts disable(); - let r = f(unsafe { &CriticalSection::new() }); + let r = f(); // If the interrupts were active before our `disable` call, then re-enable // them. Otherwise, keep them disabled @@ -62,3 +81,15 @@ where 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>(_: F) -> R +where + F: FnOnce() -> R, +{ + panic!("cortex_m::interrupt::free() is only functional on cortex-m platforms"); +} @@ -9,20 +9,15 @@ //! //! # Optional features //! -//! ## `inline-asm` +//! ## `critical-section-single-core` //! -//! 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: +//! This feature enables a [`critical-section`](https://github.com/rust-embedded/critical-section) +//! implementation suitable for single-core targets, based on disabling interrupts globally. //! -//! - 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 nightly toolchain. +//! 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 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. //! //! ## `cm7-r0p1` //! @@ -30,32 +25,11 @@ //! 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.42 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. -#![cfg_attr(feature = "inline-asm", feature(asm))] #![deny(missing_docs)] #![no_std] #![allow(clippy::identity_op)] @@ -79,11 +53,6 @@ // 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; @@ -95,7 +64,16 @@ 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/src/macros.rs b/src/macros.rs index 512c932..2cf4f89 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -31,7 +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 /// the debugger and is not referencable from other code. See example below. @@ -62,7 +65,7 @@ macro_rules! iprintln { #[macro_export] macro_rules! singleton { ($name:ident: $ty:ty = $expr:expr) => { - $crate::interrupt::free(|_| { + $crate::_export::critical_section::with(|_| { // 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`... diff --git a/src/peripheral/dwt.rs b/src/peripheral/dwt.rs index c5f7bc9..72575d3 100644 --- a/src/peripheral/dwt.rs +++ b/src/peripheral/dwt.rs @@ -155,6 +155,18 @@ impl DWT { } } + /// 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 + }); + } + } + /// Returns `true` if the cycle counter is enabled #[cfg(not(armv6m))] #[inline] diff --git a/src/peripheral/mod.rs b/src/peripheral/mod.rs index af922b1..bf18151 100644 --- a/src/peripheral/mod.rs +++ b/src/peripheral/mod.rs @@ -60,8 +60,6 @@ use core::marker::PhantomData; use core::ops; -use crate::interrupt; - #[cfg(cm7)] pub mod ac; #[cfg(not(armv6m))] @@ -165,7 +163,7 @@ impl Peripherals { /// Returns all the core peripherals *once* #[inline] pub fn take() -> Option<Self> { - interrupt::free(|_| { + critical_section::with(|_| { if unsafe { TAKEN } { None } else { diff --git a/src/peripheral/sau.rs b/src/peripheral/sau.rs index da91aca..6b8477f 100644 --- a/src/peripheral/sau.rs +++ b/src/peripheral/sau.rs @@ -7,7 +7,6 @@ //! //! 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}; @@ -162,7 +161,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> { - interrupt::free(|_| { + critical_section::with(|_| { let base_address = region.base_address; let limit_address = region.limit_address; let attribute = region.attribute; @@ -215,7 +214,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<SauRegion, SauError> { - interrupt::free(|_| { + critical_section::with(|_| { if region_number >= self.region_numbers() { Err(SauError::RegionNumberTooBig) } else { diff --git a/src/peripheral/tpiu.rs b/src/peripheral/tpiu.rs index 0762495..14dd35c 100644 --- a/src/peripheral/tpiu.rs +++ b/src/peripheral/tpiu.rs @@ -118,7 +118,6 @@ impl TPIU { /// [`trace_output_protocol`](Self::set_trace_output_protocol). #[inline] pub fn trace_output_protocol(&self) -> Option<TraceProtocol> { - use core::convert::TryInto; self.sppr.read().txmode().try_into().ok() } diff --git a/src/prelude.rs b/src/prelude.rs deleted file mode 100644 index bc47cc0..0000000 --- a/src/prelude.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Prelude - -pub use embedded_hal::prelude::*; diff --git a/src/register/apsr.rs b/src/register/apsr.rs index e83435c..edb8737 100644 --- a/src/register/apsr.rs +++ b/src/register/apsr.rs @@ -1,5 +1,8 @@ //! Application Program Status Register +#[cfg(cortex_m)] +use core::arch::asm; + /// Application Program Status Register #[derive(Clone, Copy, Debug)] pub struct Apsr { @@ -45,10 +48,10 @@ impl Apsr { } /// Reads the CPU register -/// -/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. +#[cfg(cortex_m)] #[inline] pub fn read() -> Apsr { - let bits: u32 = call_asm!(__apsr_r() -> u32); + let bits; + unsafe { asm!("mrs {}, APSR", out(reg) bits, options(nomem, nostack, preserves_flags)) }; Apsr { bits } } diff --git a/src/register/basepri.rs b/src/register/basepri.rs index 07084cd..cffb379 100644 --- a/src/register/basepri.rs +++ b/src/register/basepri.rs @@ -1,24 +1,42 @@ //! Base Priority Mask Register +#[cfg(cortex_m)] +use core::arch::asm; + /// Reads the CPU register +#[cfg(cortex_m)] #[inline] pub fn read() -> u8 { - call_asm!(__basepri_r() -> u8) + let r; + unsafe { asm!("mrs {}, BASEPRI", out(reg) r, options(nomem, nostack, preserves_flags)) }; + r } /// 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")] { - call_asm!(__basepri_w_cm7_r0p1(basepri: u8)); + 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), + ); } #[cfg(not(feature = "cm7-r0p1"))] { - call_asm!(__basepri_w(basepri: u8)); + asm!("msr BASEPRI, {}", in(reg) basepri, options(nomem, nostack, preserves_flags)); } } diff --git a/src/register/basepri_max.rs b/src/register/basepri_max.rs index cea3838..2881c4f 100644 --- a/src/register/basepri_max.rs +++ b/src/register/basepri_max.rs @@ -1,5 +1,8 @@ //! Base Priority Mask Register (conditional write) +#[cfg(cortex_m)] +use core::arch::asm; + /// Writes to BASEPRI *if* /// /// - `basepri != 0` AND `basepri::read() == 0`, OR @@ -7,15 +10,31 @@ /// /// **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")] { - call_asm!(__basepri_max_cm7_r0p1(basepri: u8)); + 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), + ); + } } #[cfg(not(feature = "cm7-r0p1"))] { - call_asm!(__basepri_max(basepri: u8)); + unsafe { + asm!("msr BASEPRI_MAX, {}", in(reg) basepri, options(nomem, nostack, preserves_flags)); + } } } diff --git a/src/register/control.rs b/src/register/control.rs index a991625..d781913 100644 --- a/src/register/control.rs +++ b/src/register/control.rs @@ -1,5 +1,10 @@ //! 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 { @@ -150,15 +155,29 @@ impl Fpca { } /// Reads the CPU register +#[cfg(cortex_m)] #[inline] pub fn read() -> Control { - let bits: u32 = call_asm!(__control_r() -> u32); + let bits; + unsafe { asm!("mrs {}, CONTROL", out(reg) bits, options(nomem, nostack, preserves_flags)) }; Control { bits } } /// Writes to the CPU register. +#[cfg(cortex_m)] #[inline] pub unsafe fn write(control: Control) { let control = control.bits(); - call_asm!(__control_w(control: u32)); + + // 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); } diff --git a/src/register/faultmask.rs b/src/register/faultmask.rs index e57fa28..1d32709 100644 --- a/src/register/faultmask.rs +++ b/src/register/faultmask.rs @@ -1,5 +1,8 @@ //! Fault Mask Register +#[cfg(cortex_m)] +use core::arch::asm; + /// All exceptions are ... #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Faultmask { @@ -24,9 +27,11 @@ impl Faultmask { } /// Reads the CPU register +#[cfg(cortex_m)] #[inline] pub fn read() -> Faultmask { - let r: u32 = call_asm!(__faultmask_r() -> u32); + let r: u32; + unsafe { asm!("mrs {}, FAULTMASK", out(reg) r, options(nomem, nostack, preserves_flags)) }; if r & (1 << 0) == (1 << 0) { Faultmask::Inactive } else { diff --git a/src/register/fpscr.rs b/src/register/fpscr.rs index 68692c7..bffed6c 100644 --- a/src/register/fpscr.rs +++ b/src/register/fpscr.rs @@ -1,5 +1,7 @@ //! Floating-point Status Control Register +use core::arch::asm; + /// Floating-point Status Control Register #[derive(Clone, Copy, Debug)] pub struct Fpscr { @@ -293,7 +295,8 @@ impl RMode { /// Read the FPSCR register #[inline] pub fn read() -> Fpscr { - let r: u32 = call_asm!(__fpscr_r() -> u32); + let r; + unsafe { asm!("vmrs {}, fpscr", out(reg) r, options(nomem, nostack, preserves_flags)) }; Fpscr::from_bits(r) } @@ -301,5 +304,5 @@ pub fn read() -> Fpscr { #[inline] pub unsafe fn write(fpscr: Fpscr) { let fpscr = fpscr.bits(); - call_asm!(__fpscr_w(fpscr: u32)); + asm!("vmsr fpscr, {}", in(reg) fpscr, options(nomem, nostack)); } diff --git a/src/register/lr.rs b/src/register/lr.rs index 1aa546c..02708ae 100644 --- a/src/register/lr.rs +++ b/src/register/lr.rs @@ -1,17 +1,20 @@ //! Link register +#[cfg(cortex_m)] +use core::arch::asm; + /// Reads the CPU register -/// -/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. +#[cfg(cortex_m)] #[inline] pub fn read() -> u32 { - call_asm!(__lr_r() -> u32) + let r; + unsafe { asm!("mov {}, lr", out(reg) r, options(nomem, nostack, preserves_flags)) }; + r } /// Writes `bits` to the CPU register -/// -/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. +#[cfg(cortex_m)] #[inline] pub unsafe fn write(bits: u32) { - call_asm!(__lr_w(bits: u32)); + asm!("mov lr, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); } diff --git a/src/register/mod.rs b/src/register/mod.rs index 48d157a..aee7d21 100644 --- a/src/register/mod.rs +++ b/src/register/mod.rs @@ -56,13 +56,8 @@ 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/src/register/msp.rs b/src/register/msp.rs index bccc2ae..22ce7d9 100644 --- a/src/register/msp.rs +++ b/src/register/msp.rs @@ -1,16 +1,27 @@ //! Main Stack Pointer +#[cfg(cortex_m)] +use core::arch::asm; + /// Reads the CPU register +#[cfg(cortex_m)] #[inline] pub fn read() -> u32 { - call_asm!(__msp_r() -> u32) + let r; + unsafe { asm!("mrs {}, MSP", out(reg) r, options(nomem, nostack, preserves_flags)) }; + r } /// 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) { - call_asm!(__msp_w(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)); } /// Reads the Non-Secure CPU register from Secure state. @@ -19,7 +30,9 @@ pub unsafe fn write(bits: u32) { #[cfg(armv8m)] #[inline] pub fn read_ns() -> u32 { - call_asm!(__msp_ns_r() -> u32) + let r; + unsafe { asm!("mrs {}, MSP_NS", out(reg) r, options(nomem, nostack, preserves_flags)) }; + r } /// Writes `bits` to the Non-Secure CPU register from Secure state. @@ -28,5 +41,5 @@ pub fn read_ns() -> u32 { #[cfg(armv8m)] #[inline] pub unsafe fn write_ns(bits: u32) { - call_asm!(__msp_ns_w(bits: u32)); + asm!("msr MSP_NS, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); } diff --git a/src/register/msplim.rs b/src/register/msplim.rs index ac6f9ed..7b45b33 100644 --- a/src/register/msplim.rs +++ b/src/register/msplim.rs @@ -1,13 +1,17 @@ //! Main Stack Pointer Limit Register +use core::arch::asm; + /// Reads the CPU register #[inline] pub fn read() -> u32 { - call_asm!(__msplim_r() -> u32) + let r; + unsafe { asm!("mrs {}, MSPLIM", out(reg) r, options(nomem, nostack, preserves_flags)) }; + r } /// Writes `bits` to the CPU register #[inline] pub unsafe fn write(bits: u32) { - call_asm!(__msplim_w(bits: u32)) + asm!("msr MSPLIM, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); } diff --git a/src/register/pc.rs b/src/register/pc.rs index 0b33629..3460664 100644 --- a/src/register/pc.rs +++ b/src/register/pc.rs @@ -1,17 +1,20 @@ //! Program counter +#[cfg(cortex_m)] +use core::arch::asm; + /// Reads the CPU register -/// -/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. +#[cfg(cortex_m)] #[inline] pub fn read() -> u32 { - call_asm!(__pc_r() -> u32) + let r; + unsafe { asm!("mov {}, pc", out(reg) r, options(nomem, nostack, preserves_flags)) }; + r } /// Writes `bits` to the CPU register -/// -/// **NOTE** This function is available if `cortex-m` is built with the `"inline-asm"` feature. +#[cfg(cortex_m)] #[inline] pub unsafe fn write(bits: u32) { - call_asm!(__pc_w(bits: u32)); + asm!("mov pc, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); } diff --git a/src/register/primask.rs b/src/register/primask.rs index 842ca49..e95276f 100644 --- a/src/register/primask.rs +++ b/src/register/primask.rs @@ -1,5 +1,8 @@ //! Priority mask register +#[cfg(cortex_m)] +use core::arch::asm; + /// All exceptions with configurable priority are ... #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Primask { @@ -24,9 +27,11 @@ impl Primask { } /// Reads the CPU register +#[cfg(cortex_m)] #[inline] pub fn read() -> Primask { - let r: u32 = call_asm!(__primask_r() -> u32); + let r: u32; + unsafe { asm!("mrs {}, PRIMASK", out(reg) r, options(nomem, nostack, preserves_flags)) }; if r & (1 << 0) == (1 << 0) { Primask::Inactive } else { diff --git a/src/register/psp.rs b/src/register/psp.rs index 0bca22c..c8f53b9 100644 --- a/src/register/psp.rs +++ b/src/register/psp.rs @@ -1,13 +1,22 @@ //! Process Stack Pointer +#[cfg(cortex_m)] +use core::arch::asm; + /// Reads the CPU register +#[cfg(cortex_m)] #[inline] pub fn read() -> u32 { - call_asm!(__psp_r() -> u32) + let r; + unsafe { asm!("mrs {}, PSP", out(reg) r, options(nomem, nostack, preserves_flags)) }; + r } /// Writes `bits` to the CPU register +#[cfg(cortex_m)] #[inline] pub unsafe fn write(bits: u32) { - call_asm!(__psp_w(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)); } diff --git a/src/register/psplim.rs b/src/register/psplim.rs index 8ee1e94..832f9c6 100644 --- a/src/register/psplim.rs +++ b/src/register/psplim.rs @@ -1,13 +1,17 @@ //! Process Stack Pointer Limit Register +use core::arch::asm; + /// Reads the CPU register #[inline] pub fn read() -> u32 { - call_asm!(__psplim_r() -> u32) + let r; + unsafe { asm!("mrs {}, PSPLIM", out(reg) r, options(nomem, nostack, preserves_flags)) }; + r } /// Writes `bits` to the CPU register #[inline] pub unsafe fn write(bits: u32) { - call_asm!(__psplim_w(bits: u32)) + asm!("msr PSPLIM, {}", in(reg) bits, options(nomem, nostack, preserves_flags)); } diff --git a/testsuite/.cargo/config.toml b/testsuite/.cargo/config.toml new file mode 100644 index 0000000..cce98a9 --- /dev/null +++ b/testsuite/.cargo/config.toml @@ -0,0 +1,6 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = ["-C", "link-arg=-Tlink.x"] +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" + +[build] +target = "thumbv7m-none-eabi" diff --git a/testsuite/Cargo.toml b/testsuite/Cargo.toml new file mode 100644 index 0000000..be3e43d --- /dev/null +++ b/testsuite/Cargo.toml @@ -0,0 +1,24 @@ +[package] +authors = ["The Cortex-M Team <cortex-m@teams.rust-embedded.org>"] +name = "testsuite" +publish = false +edition = "2018" +version = "0.1.0" + +[features] +rtt = ["rtt-target", "minitest/rtt"] +semihosting = ["cortex-m-semihosting", "minitest/semihosting"] + +[dependencies] +cortex-m-rt.path = "../cortex-m-rt" +cortex-m.path = ".." +minitest.path = "minitest" +critical-section = "1.0.0" + +[dependencies.rtt-target] +version = "0.3.1" +optional = true + +[dependencies.cortex-m-semihosting] +path = "../cortex-m-semihosting" +optional = true diff --git a/testsuite/README.md b/testsuite/README.md new file mode 100644 index 0000000..c11d850 --- /dev/null +++ b/testsuite/README.md @@ -0,0 +1,69 @@ +# Testsuite + +This workspace contains tests that run on physical and simulated Cortex-M CPUs. + +## Building + +Exactly one of these features are required: + +* `semihosting` Use semihosting for logging, this is used for QEMU. +* `rtt` Use RTT for logging, this is used with physical cortex-m CPUs. + +Assuming you are at the root of the repository you can build like this: + +```console +$ cd testsuite +$ cargo build --features semihosting + Compiling testsuite v0.1.0 (cortex-m/testsuite) + Finished dev [unoptimized + debuginfo] target(s) in 0.08 +``` + +## Running with QEMU + +The runner is already configured for QEMU in `testsuite/.cargo/config.toml`. +Use the `semihosting` feature for logging, QEMU does not have native support for RTT. + +For more information on QEMU reference the QEMU section in [The Embedded Rust Book]. + +```console +$ cd testsuite +$ cargo run --features semihosting + Finished dev [unoptimized + debuginfo] target(s) in 0.01s + Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel /cortex-m/target/thumbv7m-none-eabi/debug/testsuite` +Timer with period zero, disabling +Hello world! +(1/1) running `double_take`... +all tests passed! +``` + +## Running with Physical Hardware + +No implementation-specific features are tested right now; any physical `thumbv7m` target should work. + +Tests are executed with [probe-run](https://github.com/knurling-rs/probe-run). + +* Update `memory.x` in the root of the repository to match your target memory layout. +* Change the `probe-run` chip argument to match your chip, supported chips can be found with `probe-run --list-chips` +* Change the target to match your CPU + +```console +$ sed -i 's/FLASH : ORIGIN = 0x00000000, LENGTH = 256K/FLASH : ORIGIN = 0x8000000, LENGTH = 256K/g' memory.x +$ cd testsuite +$ cargo build --target thumbv7em-none-eabi --features rtt + Compiling minitest v0.1.0 (/cortex-m/testsuite/minitest) + Compiling testsuite v0.1.0 (/cortex-m/testsuite) + Finished dev [unoptimized + debuginfo] target(s) in 0.16s +$ probe-run --chip STM32WLE5JCIx --connect-under-reset ../target/thumbv7em-none-eabi/debug/testsuite +(HOST) INFO flashing program (19 pages / 19.00 KiB) +(HOST) INFO success! +──────────────────────────────────────────────────────────────────────────────── +Hello world! +(1/2) running `double_take`... +(2/2) running `cycle_count`... +all tests passed! +──────────────────────────────────────────────────────────────────────────────── +(HOST) INFO device halted without error +``` + +[The Embedded Rust Book]: https://docs.rust-embedded.org/book/start/qemu.html +[probe-run]: https://github.com/knurling-rs/probe-run diff --git a/testsuite/build.rs b/testsuite/build.rs new file mode 100644 index 0000000..c0662b9 --- /dev/null +++ b/testsuite/build.rs @@ -0,0 +1,18 @@ +fn main() { + let target = std::env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); + } else if target.starts_with("thumbv7m-") { + println!("cargo:rustc-cfg=armv7m"); + } else if target.starts_with("thumbv7em-") { + println!("cargo:rustc-cfg=armv7m"); + println!("cargo:rustc-cfg=armv7em"); // (not currently used) + } else if target.starts_with("thumbv8m.base") { + println!("cargo:rustc-cfg=armv8m"); + println!("cargo:rustc-cfg=armv8m_base"); + } else if target.starts_with("thumbv8m.main") { + println!("cargo:rustc-cfg=armv8m"); + println!("cargo:rustc-cfg=armv8m_main"); + } +} diff --git a/testsuite/minitest/Cargo.toml b/testsuite/minitest/Cargo.toml new file mode 100644 index 0000000..bf2c2eb --- /dev/null +++ b/testsuite/minitest/Cargo.toml @@ -0,0 +1,23 @@ +[package] +authors = ["The Cortex-M Team <cortex-m@teams.rust-embedded.org>"] +name = "minitest" +publish = false +edition = "2018" +version = "0.1.0" + +[features] +semihosting = ["cortex-m-semihosting", "minitest-macros/semihosting"] +rtt = ["rtt-target", "minitest-macros/rtt"] + +[dependencies] +cortex-m.path = "../.." +cortex-m-rt.path = "../../cortex-m-rt" +minitest-macros.path = "macros" + +[dependencies.rtt-target] +version = "0.3.1" +optional = true + +[dependencies.cortex-m-semihosting] +path = "../../cortex-m-semihosting" +optional = true diff --git a/testsuite/minitest/README.md b/testsuite/minitest/README.md new file mode 100644 index 0000000..0a456a8 --- /dev/null +++ b/testsuite/minitest/README.md @@ -0,0 +1,7 @@ +# mini-test + +This is an embedded test framework forked from knurling's excellent [`defmt-test`] crate. + +This even more minimal than [`defmt-test`] to allow for for testing of this crate without dependency cycles. + +[`defmt-test`]: https://crates.io/crates/defmt-test/ diff --git a/testsuite/minitest/macros/Cargo.toml b/testsuite/minitest/macros/Cargo.toml new file mode 100644 index 0000000..077e316 --- /dev/null +++ b/testsuite/minitest/macros/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["The Cortex-M Team <cortex-m@teams.rust-embedded.org>"] +name = "minitest-macros" +publish = false +edition = "2018" +version = "0.1.0" + +[lib] +proc-macro = true + +[features] +semihosting = [] +rtt = [] + +[dependencies] +proc-macro2 = "1.0.29" +quote = "1.0.10" +syn = { version = "1.0.80", features = ["extra-traits", "full"] } diff --git a/testsuite/minitest/macros/src/lib.rs b/testsuite/minitest/macros/src/lib.rs new file mode 100644 index 0000000..e8a1087 --- /dev/null +++ b/testsuite/minitest/macros/src/lib.rs @@ -0,0 +1,331 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{format_ident, quote, quote_spanned}; +use syn::{parse, spanned::Spanned, Attribute, Item, ItemFn, ItemMod, ReturnType, Type}; + +#[proc_macro_attribute] +pub fn tests(args: TokenStream, input: TokenStream) -> TokenStream { + match tests_impl(args, input) { + Ok(ts) => ts, + Err(e) => e.to_compile_error().into(), + } +} + +fn tests_impl(args: TokenStream, input: TokenStream) -> parse::Result<TokenStream> { + if !args.is_empty() { + return Err(parse::Error::new( + Span::call_site(), + "`#[test]` attribute takes no arguments", + )); + } + + let module: ItemMod = syn::parse(input)?; + + let items = if let Some(content) = module.content { + content.1 + } else { + return Err(parse::Error::new( + module.span(), + "module must be inline (e.g. `mod foo {}`)", + )); + }; + + let mut init = None; + let mut tests = vec![]; + let mut untouched_tokens = vec![]; + for item in items { + match item { + Item::Fn(mut f) => { + let mut test_kind = None; + let mut should_error = false; + + f.attrs.retain(|attr| { + if attr.path.is_ident("init") { + test_kind = Some(Attr::Init); + false + } else if attr.path.is_ident("test") { + test_kind = Some(Attr::Test); + false + } else if attr.path.is_ident("should_error") { + should_error = true; + false + } else { + true + } + }); + + let attr = match test_kind { + Some(it) => it, + None => { + return Err(parse::Error::new( + f.span(), + "function requires `#[init]` or `#[test]` attribute", + )); + } + }; + + match attr { + Attr::Init => { + if init.is_some() { + return Err(parse::Error::new( + f.sig.ident.span(), + "only a single `#[init]` function can be defined", + )); + } + + if should_error { + return Err(parse::Error::new( + f.sig.ident.span(), + "`#[should_error]` is not allowed on the `#[init]` function", + )); + } + + if check_fn_sig(&f.sig).is_err() || !f.sig.inputs.is_empty() { + return Err(parse::Error::new( + f.sig.ident.span(), + "`#[init]` function must have signature `fn() [-> Type]` (the return type is optional)", + )); + } + + let state = match &f.sig.output { + ReturnType::Default => None, + ReturnType::Type(.., ty) => Some(ty.clone()), + }; + + init = Some(Init { func: f, state }); + } + + Attr::Test => { + if check_fn_sig(&f.sig).is_err() || f.sig.inputs.len() > 1 { + return Err(parse::Error::new( + f.sig.ident.span(), + "`#[test]` function must have signature `fn([&mut Type])` (parameter is optional)", + )); + } + + let input = if f.sig.inputs.len() == 1 { + let arg = &f.sig.inputs[0]; + + // NOTE we cannot check the argument type matches `init.state` at this + // point + if let Some(ty) = get_mutable_reference_type(arg).cloned() { + Some(Input { ty }) + } else { + // was not `&mut T` + return Err(parse::Error::new( + arg.span(), + "parameter must be a mutable reference (`&mut $Type`)", + )); + } + } else { + None + }; + + tests.push(Test { + cfgs: extract_cfgs(&f.attrs), + func: f, + input, + should_error, + }) + } + } + } + + _ => { + untouched_tokens.push(item); + } + } + } + + let krate = format_ident!("minitest"); + let ident = module.ident; + let mut state_ty = None; + let (init_fn, init_expr) = if let Some(init) = init { + let init_func = &init.func; + let init_ident = &init.func.sig.ident; + state_ty = init.state; + + ( + Some(quote!(#init_func)), + Some(quote!(#[allow(dead_code)] let mut state = #init_ident();)), + ) + } else { + (None, None) + }; + + let mut unit_test_calls = vec![]; + for test in &tests { + let should_error = test.should_error; + let ident = &test.func.sig.ident; + let span = test.func.sig.ident.span(); + let call = if let Some(input) = test.input.as_ref() { + if let Some(state) = &state_ty { + if input.ty != **state { + return Err(parse::Error::new( + input.ty.span(), + "this type must match `#[init]`s return type", + )); + } + } else { + return Err(parse::Error::new( + span, + "no state was initialized by `#[init]`; signature must be `fn()`", + )); + } + + quote!(#ident(&mut state)) + } else { + quote!(#ident()) + }; + unit_test_calls.push(quote!( + #krate::export::check_outcome(#call, #should_error); + )); + } + + let test_functions = tests.iter().map(|test| &test.func); + let test_cfgs = tests.iter().map(|test| &test.cfgs); + let declare_test_count = { + let test_cfgs = test_cfgs.clone(); + quote!( + // We can't evaluate `#[cfg]`s in the macro, but this works too. + const __MINITEST_COUNT: usize = { + let mut counter = 0; + #( + #(#test_cfgs)* + { counter += 1; } + )* + counter + }; + ) + }; + + #[cfg(feature = "rtt")] + let init_logging = quote!({ + let channels = ::rtt_target::rtt_init! { + up: { + 0: { + size: 256 + mode: BlockIfFull + name: "minitest" + } + } + }; + unsafe { + ::rtt_target::set_print_channel_cs( + channels.up.0, + &((|arg, f| ::critical_section::with(|_| f(arg))) + as ::rtt_target::CriticalSectionFunc), + ); + } + }); + + #[cfg(not(feature = "rtt"))] + let init_logging = quote!({}); + + let unit_test_progress = tests + .iter() + .map(|test| { + let message = format!("({{}}/{{}}) running `{}`...", test.func.sig.ident); + quote_spanned! { + test.func.sig.ident.span() => #krate::log!(#message, __minitest_number, __MINITEST_COUNT); + } + }) + .collect::<Vec<_>>(); + Ok(quote!(mod #ident { + #(#untouched_tokens)* + #[cortex_m_rt::entry] + fn __minitest_entry() -> ! { + #init_logging + #declare_test_count + #init_expr + + let mut __minitest_number: usize = 1; + #( + #(#test_cfgs)* + { + #unit_test_progress + #unit_test_calls + __minitest_number += 1; + } + )* + + #krate::log!("all tests passed!"); + #krate::exit() + } + + #init_fn + + #( + #test_functions + )* + }) + .into()) +} + +#[derive(Clone, Copy)] +enum Attr { + Init, + Test, +} + +struct Init { + func: ItemFn, + state: Option<Box<Type>>, +} + +struct Test { + func: ItemFn, + cfgs: Vec<Attribute>, + input: Option<Input>, + should_error: bool, +} + +struct Input { + ty: Type, +} + +// NOTE doesn't check the parameters or the return type +fn check_fn_sig(sig: &syn::Signature) -> Result<(), ()> { + if sig.constness.is_none() + && sig.asyncness.is_none() + && sig.unsafety.is_none() + && sig.abi.is_none() + && sig.generics.params.is_empty() + && sig.generics.where_clause.is_none() + && sig.variadic.is_none() + { + Ok(()) + } else { + Err(()) + } +} + +fn get_mutable_reference_type(arg: &syn::FnArg) -> Option<&Type> { + if let syn::FnArg::Typed(pat) = arg { + if let syn::Type::Reference(refty) = &*pat.ty { + if refty.mutability.is_some() { + Some(&refty.elem) + } else { + None + } + } else { + None + } + } else { + None + } +} + +fn extract_cfgs(attrs: &[Attribute]) -> Vec<Attribute> { + let mut cfgs = vec![]; + + for attr in attrs { + if attr.path.is_ident("cfg") { + cfgs.push(attr.clone()); + } + } + + cfgs +} diff --git a/testsuite/minitest/src/export.rs b/testsuite/minitest/src/export.rs new file mode 100644 index 0000000..4b04fda --- /dev/null +++ b/testsuite/minitest/src/export.rs @@ -0,0 +1,13 @@ +use crate::TestOutcome; +use cortex_m_rt as _; + +pub fn check_outcome<T: TestOutcome>(outcome: T, should_error: bool) { + if outcome.is_success() == should_error { + let note: &str = if should_error { + "`#[should_error]` " + } else { + "" + }; + panic!("{}test failed with outcome: {:?}", note, outcome); + } +} diff --git a/testsuite/minitest/src/lib.rs b/testsuite/minitest/src/lib.rs new file mode 100644 index 0000000..d98fb64 --- /dev/null +++ b/testsuite/minitest/src/lib.rs @@ -0,0 +1,70 @@ +#![no_std] + +use core::fmt::Debug; +pub use minitest_macros::tests; + +/// Private implementation details used by the proc macro. +#[doc(hidden)] +pub mod export; + +mod sealed { + pub trait Sealed {} + impl Sealed for () {} + impl<T, E> Sealed for Result<T, E> {} +} + +/// Indicates whether a test succeeded or failed. +/// +/// This is comparable to the `Termination` trait in libstd, except stable and tailored towards the +/// needs of defmt-test. It is implemented for `()`, which always indicates success, and `Result`, +/// where `Ok` indicates success. +pub trait TestOutcome: Debug + sealed::Sealed { + fn is_success(&self) -> bool; +} + +impl TestOutcome for () { + fn is_success(&self) -> bool { + true + } +} + +impl<T: Debug, E: Debug> TestOutcome for Result<T, E> { + fn is_success(&self) -> bool { + self.is_ok() + } +} + +#[macro_export] +macro_rules! log { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "semihosting")] + ::cortex_m_semihosting::hprintln!($s $(, $x)*); + #[cfg(feature = "rtt")] + ::rtt_target::rprintln!($s $(, $x)*); + #[cfg(not(any(feature = "semihosting", feature="rtt")))] + let _ = ($( & $x ),*); + } + }; +} + +/// Stop all tests without failure. +pub fn exit() -> ! { + #[cfg(feature = "rtt")] + cortex_m::asm::bkpt(); + #[cfg(feature = "semihosting")] + cortex_m_semihosting::debug::exit(cortex_m_semihosting::debug::EXIT_SUCCESS); + + unreachable!() +} + +/// Stop all tests and report a failure. +pub fn fail() -> ! { + #[cfg(feature = "rtt")] + cortex_m::asm::udf(); + #[cfg(feature = "semihosting")] + cortex_m_semihosting::debug::exit(cortex_m_semihosting::debug::EXIT_FAILURE); + + #[cfg(not(feature = "rtt"))] + unreachable!() +} diff --git a/testsuite/src/main.rs b/testsuite/src/main.rs new file mode 100644 index 0000000..46ab629 --- /dev/null +++ b/testsuite/src/main.rs @@ -0,0 +1,54 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; + +#[cfg(target_env = "")] // appease clippy +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + cortex_m::interrupt::disable(); + minitest::log!("{}", info); + minitest::fail() +} + +#[minitest::tests] +mod tests { + use minitest::log; + + #[init] + fn init() -> cortex_m::Peripherals { + log!("Hello world!"); + cortex_m::Peripherals::take().unwrap() + } + + #[test] + fn double_take() { + assert!(cortex_m::Peripherals::take().is_none()); + } + + #[test] + #[cfg(not(feature = "semihosting"))] // QEMU does not model the cycle counter + fn cycle_count(p: &mut cortex_m::Peripherals) { + #[cfg(not(armv6m))] + { + use cortex_m::peripheral::DWT; + + assert!(p.DWT.has_cycle_counter()); + + p.DCB.enable_trace(); + p.DWT.disable_cycle_counter(); + + const TEST_COUNT: u32 = 0x5555_AAAA; + p.DWT.set_cycle_count(TEST_COUNT); + assert_eq!(DWT::cycle_count(), TEST_COUNT); + + p.DWT.enable_cycle_counter(); + assert!(DWT::cycle_count() > TEST_COUNT); + } + + #[cfg(armv6m)] + { + assert!(!p.DWT.has_cycle_counter()); + } + } +} diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index f6a57b3..9d96686 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1,117 +1,8 @@ //! `cargo xtask` automation. //! //! Please refer to <https://github.com/matklad/cargo-xtask/> 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::process::{Command, Stdio}; - -fn toolchain() -> String { - fs::read_to_string("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().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"); - - println!("{:?}", cmd); - let status = cmd.status().unwrap(); - assert!(status.success()); - - // Archive `target.o` -> `bin/target.a`. - let mut builder = ar::Builder::new(File::create(format!("bin/{}.a", file_stub)).unwrap()); - - // Use `append`, not `append_path`, to avoid adding any filesystem metadata (modification times, - // etc.). - let file = fs::read(&obj_file).unwrap(); - builder - .append( - &ar::Header::new(obj_file.as_bytes().to_vec(), file.len() as u64), - &*file, - ) - .unwrap(); - - fs::remove_file(&obj_file).unwrap(); -} - -fn assemble(target: &str, cfgs: &[&str]) { - assemble_really(target, cfgs, false); - assemble_really(target, cfgs, true); -} - -// `--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"], - ), -]; +use std::process::Command; pub fn install_targets(targets: &mut dyn Iterator<Item = &str>, toolchain: Option<&str>) { let mut rustup = Command::new("rustup"); @@ -125,90 +16,6 @@ pub fn install_targets(targets: &mut dyn Iterator<Item = &str>, 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("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("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::<Vec<_>>(); - let after = files_after.keys().collect::<Vec<_>>(); - 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}; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 26dce31..4673a45 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,18 +1,14 @@ use std::{env, process}; -use xtask::{assemble_blobs, check_blobs, check_host_side}; +use xtask::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 <subcommand>"); 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 37466e9..1dc4754 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_blobs, check_host_side, install_targets}; +use xtask::{check_host_side, install_targets}; /// List of all compilation targets we support. /// @@ -32,6 +32,13 @@ fn build(package: &str, target: &str, features: &[&str]) { 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 features don't work right when invoked from the workspace root, so change to the // package's directory when necessary. if package != "cortex-m" { @@ -44,13 +51,13 @@ fn build(package: &str, target: &str, features: &[&str]) { #[rustfmt::skip] static PACKAGE_FEATURES: &[(&str, &[&str], &[&str])] = &[ - ("cortex-m", ALL_TARGETS, &["inline-asm", "cm7-r0p1"]), // no `linker-plugin-lto` since it's experimental - ("cortex-m-semihosting", ALL_TARGETS, &["inline-asm", "no-semihosting", "jlink-quirks"]), - ("panic-semihosting", ALL_TARGETS, &["inline-asm", "exit", "jlink-quirks"]), + ("cortex-m", ALL_TARGETS, &["cm7-r0p1"]), + ("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) { // Build all crates for each supported target. for (package, targets, all_features) in PACKAGE_FEATURES { for target in *targets { @@ -58,11 +65,8 @@ 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, } }; @@ -98,9 +102,6 @@ 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"); |