Skip to content

Commit de61182

Browse files
authored
Merge pull request #576 from sw/cs-msr
Optimize critical_section: use MSR instruction
2 parents b02ec57 + e451dc3 commit de61182

File tree

6 files changed

+88
-19
lines changed

6 files changed

+88
-19
lines changed

cortex-m/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ cm7 = []
3333
cm7-r0p1 = ["cm7"]
3434
linker-plugin-lto = []
3535
std = []
36-
critical-section-single-core = ["critical-section/restore-state-bool"]
36+
critical-section-single-core = ["critical-section/restore-state-u32"]
3737

3838
[package.metadata.docs.rs]
3939
targets = [

cortex-m/src/critical_section.rs

+8-9
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,17 @@ set_impl!(SingleCoreCriticalSection);
88

99
unsafe impl Impl for SingleCoreCriticalSection {
1010
unsafe fn acquire() -> RawRestoreState {
11-
let was_active = primask::read().is_active();
11+
// Backup previous state of PRIMASK register. We access the entire register directly as a
12+
// u32 instead of using the primask::read() function to minimize the number of processor
13+
// cycles during which interrupts are disabled.
14+
let restore_state = primask::read_raw();
1215
// NOTE: Fence guarantees are provided by interrupt::disable(), which performs a `compiler_fence(SeqCst)`.
1316
interrupt::disable();
14-
was_active
17+
restore_state
1518
}
1619

17-
unsafe fn release(was_active: RawRestoreState) {
18-
// Only re-enable interrupts if they were enabled before the critical section.
19-
if was_active {
20-
// NOTE: Fence guarantees are provided by interrupt::enable(), which performs a
21-
// `compiler_fence(SeqCst)`.
22-
interrupt::enable()
23-
}
20+
unsafe fn release(restore_state: RawRestoreState) {
21+
// NOTE: Fence guarantees are provided by primask::write_raw(), which performs a `compiler_fence(SeqCst)`.
22+
primask::write_raw(restore_state);
2423
}
2524
}

cortex-m/src/interrupt.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,18 @@ pub fn free<F, R>(f: F) -> R
6666
where
6767
F: FnOnce() -> R,
6868
{
69-
let primask = crate::register::primask::read();
69+
// Backup previous state of PRIMASK register. We access the entire register directly as a
70+
// u32 instead of using the primask::read() function to minimize the number of processor
71+
// cycles during which interrupts are disabled.
72+
let primask = crate::register::primask::read_raw();
7073

7174
// disable interrupts
7275
disable();
7376

7477
let r = f();
7578

76-
// If the interrupts were active before our `disable` call, then re-enable
77-
// them. Otherwise, keep them disabled
78-
if primask.is_active() {
79-
unsafe { enable() }
79+
unsafe {
80+
crate::register::primask::write_raw(primask);
8081
}
8182

8283
r

cortex-m/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
//! or critical sections are managed as part of an RTOS. In these cases, you should use
2020
//! a target-specific implementation instead, typically provided by a HAL or RTOS crate.
2121
//!
22+
//! The critical section has been optimized to block interrupts for as few cycles as possible,
23+
//! but -- due to `critical-section` implementation details -- incurs branches in a normal build
24+
//! configuration. For minimal interrupt latency, you can achieve inlining by enabling
25+
//! [linker-plugin-based LTO](https://doc.rust-lang.org/rustc/linker-plugin-lto.html).
26+
//!
2227
//! ## `cm7-r0p1`
2328
//!
2429
//! This feature enables workarounds for errata found on Cortex-M7 chips with revision r0p1. Some

cortex-m/src/register/primask.rs

+33-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
33
#[cfg(cortex_m)]
44
use core::arch::asm;
5+
#[cfg(cortex_m)]
6+
use core::sync::atomic::{compiler_fence, Ordering};
57

68
/// All exceptions with configurable priority are ...
79
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -26,15 +28,42 @@ impl Primask {
2628
}
2729
}
2830

29-
/// Reads the CPU register
31+
/// Reads the prioritizable interrupt mask
3032
#[cfg(cortex_m)]
3133
#[inline]
3234
pub fn read() -> Primask {
33-
let r: u32;
34-
unsafe { asm!("mrs {}, PRIMASK", out(reg) r, options(nomem, nostack, preserves_flags)) };
35-
if r & (1 << 0) == (1 << 0) {
35+
if read_raw() & (1 << 0) == (1 << 0) {
3636
Primask::Inactive
3737
} else {
3838
Primask::Active
3939
}
4040
}
41+
42+
/// Reads the entire PRIMASK register
43+
/// Note that bits [31:1] are reserved and UNK (Unknown)
44+
#[cfg(cortex_m)]
45+
#[inline]
46+
pub fn read_raw() -> u32 {
47+
let r: u32;
48+
unsafe { asm!("mrs {}, PRIMASK", out(reg) r, options(nomem, nostack, preserves_flags)) };
49+
r
50+
}
51+
52+
/// Writes the entire PRIMASK register
53+
/// Note that bits [31:1] are reserved and SBZP (Should-Be-Zero-or-Preserved)
54+
///
55+
/// # Safety
56+
///
57+
/// This method is unsafe as other unsafe code may rely on interrupts remaining disabled, for
58+
/// example during a critical section, and being able to safely re-enable them would lead to
59+
/// undefined behaviour. Do not call this function in a context where interrupts are expected to
60+
/// remain disabled -- for example, in the midst of a critical section or `interrupt::free()` call.
61+
#[cfg(cortex_m)]
62+
#[inline]
63+
pub unsafe fn write_raw(r: u32) {
64+
// Ensure no preceeding memory accesses are reordered to after interrupts are possibly enabled.
65+
compiler_fence(Ordering::SeqCst);
66+
unsafe { asm!("msr PRIMASK, {}", in(reg) r, options(nomem, nostack, preserves_flags)) };
67+
// Ensure no subsequent memory accesses are reordered to before interrupts are possibly disabled.
68+
compiler_fence(Ordering::SeqCst);
69+
}

testsuite/src/main.rs

+35
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![no_std]
33

44
extern crate cortex_m_rt;
5+
use core::sync::atomic::{AtomicBool, Ordering};
56

67
#[cfg(target_env = "")] // appease clippy
78
#[panic_handler]
@@ -11,8 +12,16 @@ fn panic(info: &core::panic::PanicInfo) -> ! {
1112
minitest::fail()
1213
}
1314

15+
static EXCEPTION_FLAG: AtomicBool = AtomicBool::new(false);
16+
17+
#[cortex_m_rt::exception]
18+
fn PendSV() {
19+
EXCEPTION_FLAG.store(true, Ordering::SeqCst);
20+
}
21+
1422
#[minitest::tests]
1523
mod tests {
24+
use crate::{Ordering, EXCEPTION_FLAG};
1625
use minitest::log;
1726

1827
#[init]
@@ -51,4 +60,30 @@ mod tests {
5160
assert!(!p.DWT.has_cycle_counter());
5261
}
5362
}
63+
64+
#[test]
65+
fn critical_section_nesting() {
66+
EXCEPTION_FLAG.store(false, Ordering::SeqCst);
67+
critical_section::with(|_| {
68+
critical_section::with(|_| {
69+
cortex_m::peripheral::SCB::set_pendsv();
70+
assert!(!EXCEPTION_FLAG.load(Ordering::SeqCst));
71+
});
72+
assert!(!EXCEPTION_FLAG.load(Ordering::SeqCst));
73+
});
74+
assert!(EXCEPTION_FLAG.load(Ordering::SeqCst));
75+
}
76+
77+
#[test]
78+
fn interrupt_free_nesting() {
79+
EXCEPTION_FLAG.store(false, Ordering::SeqCst);
80+
cortex_m::interrupt::free(|| {
81+
cortex_m::interrupt::free(|| {
82+
cortex_m::peripheral::SCB::set_pendsv();
83+
assert!(!EXCEPTION_FLAG.load(Ordering::SeqCst));
84+
});
85+
assert!(!EXCEPTION_FLAG.load(Ordering::SeqCst));
86+
});
87+
assert!(EXCEPTION_FLAG.load(Ordering::SeqCst));
88+
}
5489
}

0 commit comments

Comments
 (0)