Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interrupt: Support no-std pre-v6 ARM targets #28

Merged
merged 2 commits into from
Aug 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Note: In this file, do not use the hard wrap in the middle of a sentence for com

## [Unreleased]

- Support atomic CAS on no-std pre-v6 ARM targets (e.g., thumbv4t-none-eabi) under unsafe cfg `portable_atomic_unsafe_assume_single_core`. ([#28](https://github.com/taiki-e/portable-atomic/pull/28))

## [0.3.11] - 2022-08-12

- Always provide atomic CAS for MSP430 and AVR. ([#31](https://github.com/taiki-e/portable-atomic/pull/31))
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Portable atomic types including support for 128-bit atomics, atomic float, etc.
- Provide `AtomicF32` and `AtomicF64`. (optional)
<!-- - Provide generic `Atomic<T>` type. (optional) -->
- Provide atomic load/store for targets where atomic is not available at all in the standard library. (RISC-V without A-extension, MSP430, AVR)
- Provide atomic CAS for targets where atomic CAS is not available in the standard library. (thumbv6m, RISC-V without A-extension, MSP430, AVR) (optional and [single-core only](#optional-cfg) for ARM and RISC-V, always enabled for MSP430 and AVR)
- Provide atomic CAS for targets where atomic CAS is not available in the standard library. (thumbv6m, pre-v6 ARM, RISC-V without A-extension, MSP430, AVR) (optional and [single-core only](#optional-cfg) for ARM and RISC-V, always enabled for MSP430 and AVR)
- Provide equivalents on the target that the standard library's atomic-related APIs cause LLVM errors. (fence/compiler_fence on MSP430)
- Provide stable equivalents of the standard library atomic types' unstable APIs, such as [`AtomicPtr::fetch_*`](https://github.com/rust-lang/rust/issues/99108), [`AtomicBool::fetch_not`](https://github.com/rust-lang/rust/issues/98485).
- Make features that require newer compilers, such as [fetch_max](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicUsize.html#method.fetch_max), [fetch_min](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicUsize.html#method.fetch_min), [fetch_update](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicPtr.html#method.fetch_update), and [stronger CAS failure ordering](https://github.com/rust-lang/rust/pull/98383) available on Rust 1.34+.
Expand Down Expand Up @@ -74,12 +74,14 @@ See [this list](https://github.com/taiki-e/portable-atomic/issues/10#issuecommen
- Enabling this cfg for multi-core systems is always **unsound**.
- This uses privileged instructions to disable interrupts, so it usually doesn't work on unprivileged mode.
Enabling this cfg in an environment where privileged instructions are not available is also usually considered **unsound**, although the details are system-dependent.
- On pre-v6 ARM, this currently disables only IRQs.
Enabling this cfg in an environment where FIQs must also be disabled is also considered **unsound**.

This is intentionally not an optional feature. (If this is an optional feature, dependencies can implicitly enable the feature, resulting in the use of unsound code without the end-user being aware of it.)

Enabling this cfg for targets that have atomic CAS will result in a compile error.

ARMv6-M (thumbv6m), RISC-V without A-extension are currently supported. See [#26] for support of no-std pre-v6 ARM and multi-core systems.
ARMv6-M (thumbv6m), pre-v6 ARM (e.g., thumbv4t), RISC-V without A-extension are currently supported. See [#26] for support of multi-core systems.

Since all MSP430 and AVR are single-core, we always provide atomic CAS for them without this cfg.

Expand Down
44 changes: 44 additions & 0 deletions src/imp/interrupt/armv4t.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Refs: https://developer.arm.com/documentation/ddi0406/cb/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-ARM-core-registers/Program-Status-Registers--PSRs-?lang=en#CIHJBHJA

#[cfg(not(portable_atomic_no_asm))]
use core::arch::asm;

#[derive(Clone, Copy)]
pub(super) struct State(u32);

/// Disables interrupts and returns the previous interrupt state.
#[inline]
#[instruction_set(arm::a32)]
pub(super) fn disable() -> State {
let cpsr: u32;
// SAFETY: reading CPSR and disabling interrupts are safe.
// (see module-level comments of interrupt/mod.rs on the safety of using privileged instructions)
unsafe {
// Do not use `nomem` and `readonly` because prevent subsequent memory accesses from being reordered before interrupts are disabled.
asm!(
"mrs {0}, cpsr",
// We disable only IRQs. See also https://github.com/taiki-e/portable-atomic/pull/28#issuecomment-1214146912.
"orr {1}, {0}, 0x80",
"msr cpsr_c, {1}",
out(reg) cpsr,
out(reg) _,
options(nostack),
);
}
State(cpsr)
}

/// Restores the previous interrupt state.
#[inline]
#[instruction_set(arm::a32)]
pub(super) unsafe fn restore(State(prev): State) {
// SAFETY: the caller must guarantee that the state was retrieved by the previous `disable`,
unsafe {
// Do not use `nomem` and `readonly` because prevent preceding memory accesses from being reordered after interrupts are enabled.
asm!(
"msr cpsr_c, {0}",
in(reg) prev,
options(nostack),
);
}
}
117 changes: 105 additions & 12 deletions src/imp/interrupt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,19 @@
use super::msp430 as atomic;
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
use super::riscv as atomic;
// On pre-v6 ARM, we cannot use core::sync::atomic here because they call the
// `__sync_*` builtins for non-relaxed loads and stores.
#[cfg(portable_atomic_armv6m)]
use core::sync::atomic;

#[cfg_attr(portable_atomic_armv6m, path = "armv6m.rs")]
#[cfg_attr(
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
),
path = "armv4t.rs"
)]
#[cfg_attr(target_arch = "avr", path = "avr.rs")]
#[cfg_attr(target_arch = "msp430", path = "msp430.rs")]
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), path = "riscv.rs")]
Expand Down Expand Up @@ -106,12 +115,24 @@ impl AtomicBool {
crate::utils::assert_load_ordering(order);
#[deny(unreachable_patterns)]
match () {
#[cfg(not(target_arch = "avr"))]
#[cfg(not(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
)
)))]
// SAFETY: any data races are prevented by atomic intrinsics (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
() => unsafe { (*(self as *const Self as *const atomic::AtomicBool)).load(order) },
#[cfg(target_arch = "avr")]
#[cfg(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
)
))]
// SAFETY: any data races are prevented by disabling interrupts (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
Expand All @@ -124,14 +145,26 @@ impl AtomicBool {
crate::utils::assert_store_ordering(order);
#[deny(unreachable_patterns)]
match () {
#[cfg(not(target_arch = "avr"))]
#[cfg(not(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
)
)))]
// SAFETY: any data races are prevented by atomic intrinsics (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
() => unsafe {
(*(self as *const Self as *const atomic::AtomicBool)).store(val, order);
},
#[cfg(target_arch = "avr")]
#[cfg(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
)
))]
// SAFETY: any data races are prevented by disabling interrupts (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
Expand Down Expand Up @@ -278,12 +311,24 @@ impl<T> AtomicPtr<T> {
crate::utils::assert_load_ordering(order);
#[deny(unreachable_patterns)]
match () {
#[cfg(not(target_arch = "avr"))]
#[cfg(not(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
)
)))]
// SAFETY: any data races are prevented by atomic intrinsics (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
() => unsafe { (*(self as *const Self as *const atomic::AtomicPtr<T>)).load(order) },
#[cfg(target_arch = "avr")]
#[cfg(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
)
))]
// SAFETY: any data races are prevented by disabling interrupts (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
Expand All @@ -296,14 +341,26 @@ impl<T> AtomicPtr<T> {
crate::utils::assert_store_ordering(order);
#[deny(unreachable_patterns)]
match () {
#[cfg(not(target_arch = "avr"))]
#[cfg(not(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
)
)))]
// SAFETY: any data races are prevented by atomic intrinsics (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
() => unsafe {
(*(self as *const Self as *const atomic::AtomicPtr<T>)).store(ptr, order);
},
#[cfg(target_arch = "avr")]
#[cfg(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
)
))]
// SAFETY: any data races are prevented by disabling interrupts (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
Expand Down Expand Up @@ -402,14 +459,32 @@ macro_rules! atomic_int {
crate::utils::assert_load_ordering(order);
#[deny(unreachable_patterns)]
match () {
#[cfg(not(target_arch = "avr"))]
#[cfg(not(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(
target_feature = "v6",
portable_atomic_target_feature = "v6"
))
)
)))]
// SAFETY: any data races are prevented by atomic intrinsics (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
() => unsafe {
(*(self as *const Self as *const atomic::$atomic_type)).load(order)
},
#[cfg(target_arch = "avr")]
#[cfg(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(
target_feature = "v6",
portable_atomic_target_feature = "v6"
))
)
))]
// SAFETY: any data races are prevented by disabling interrupts (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
Expand All @@ -422,14 +497,32 @@ macro_rules! atomic_int {
crate::utils::assert_store_ordering(order);
#[deny(unreachable_patterns)]
match () {
#[cfg(not(target_arch = "avr"))]
#[cfg(not(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(
target_feature = "v6",
portable_atomic_target_feature = "v6"
))
)
)))]
// SAFETY: any data races are prevented by atomic intrinsics (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
() => unsafe {
(*(self as *const Self as *const atomic::$atomic_type)).store(val, order);
},
#[cfg(target_arch = "avr")]
#[cfg(any(
target_arch = "avr",
all(
target_arch = "arm",
not(any(
target_feature = "v6",
portable_atomic_target_feature = "v6"
))
)
))]
// SAFETY: any data races are prevented by disabling interrupts (see
// module-level comments) and the raw pointer is valid because we got it
// from a reference.
Expand Down
4 changes: 4 additions & 0 deletions src/imp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ mod fallback;
)]
#[cfg(any(
portable_atomic_armv6m,
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
),
target_arch = "avr",
target_arch = "msp430",
target_arch = "riscv32",
Expand Down
25 changes: 23 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Portable atomic types including support for 128-bit atomics, atomic float, etc.
- Provide `AtomicF32` and `AtomicF64`. (optional)
<!-- - Provide generic `Atomic<T>` type. (optional) -->
- Provide atomic load/store for targets where atomic is not available at all in the standard library. (RISC-V without A-extension, MSP430, AVR)
- Provide atomic CAS for targets where atomic CAS is not available in the standard library. (thumbv6m, RISC-V without A-extension, MSP430, AVR) (optional and [single-core only](#optional-cfg) for ARM and RISC-V, always enabled for MSP430 and AVR)
- Provide atomic CAS for targets where atomic CAS is not available in the standard library. (thumbv6m, pre-v6 ARM, RISC-V without A-extension, MSP430, AVR) (optional and [single-core only](#optional-cfg) for ARM and RISC-V, always enabled for MSP430 and AVR)
- Provide equivalents on the target that the standard library's atomic-related APIs cause LLVM errors. (fence/compiler_fence on MSP430)
- Provide stable equivalents of the standard library atomic types' unstable APIs, such as [`AtomicPtr::fetch_*`](https://github.com/rust-lang/rust/issues/99108), [`AtomicBool::fetch_not`](https://github.com/rust-lang/rust/issues/98485).
- Make features that require newer compilers, such as [fetch_max](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicUsize.html#method.fetch_max), [fetch_min](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicUsize.html#method.fetch_min), [fetch_update](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicPtr.html#method.fetch_update), and [stronger CAS failure ordering](https://github.com/rust-lang/rust/pull/98383) available on Rust 1.34+.
Expand Down Expand Up @@ -66,12 +66,14 @@ See [this list](https://github.com/taiki-e/portable-atomic/issues/10#issuecommen
- Enabling this cfg for multi-core systems is always **unsound**.
- This uses privileged instructions to disable interrupts, so it usually doesn't work on unprivileged mode.
Enabling this cfg in an environment where privileged instructions are not available is also usually considered **unsound**, although the details are system-dependent.
- On pre-v6 ARM, this currently disables only IRQs.
Enabling this cfg in an environment where FIQs must also be disabled is also considered **unsound**.

This is intentionally not an optional feature. (If this is an optional feature, dependencies can implicitly enable the feature, resulting in the use of unsound code without the end-user being aware of it.)

Enabling this cfg for targets that have atomic CAS will result in a compile error.

ARMv6-M (thumbv6m), RISC-V without A-extension are currently supported. See [#26] for support of no-std pre-v6 ARM and multi-core systems.
ARMv6-M (thumbv6m), pre-v6 ARM (e.g., thumbv4t), RISC-V without A-extension are currently supported. See [#26] for support of multi-core systems.

Since all MSP430 and AVR are single-core, we always provide atomic CAS for them without this cfg.

Expand Down Expand Up @@ -176,6 +178,17 @@ See [this list](https://github.com/taiki-e/portable-atomic/issues/10#issuecommen
),
feature(asm_experimental_arch)
)]
// non-Linux armv4t (tier 3)
#![cfg_attr(
all(
portable_atomic_nightly,
target_arch = "arm",
not(target_has_atomic = "ptr"),
not(any(target_feature = "v6", portable_atomic_target_feature = "v6")),
any(test, portable_atomic_unsafe_assume_single_core)
),
feature(isa_attribute)
)]
// Old nightly only
// These features are already stable or have already been removed from compilers,
// and can safely be enabled for old nightly as long as version detection works.
Expand Down Expand Up @@ -246,6 +259,10 @@ compile_error!(
not(portable_atomic_no_atomic_cas),
not(any(
portable_atomic_armv6m,
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
),
all(
any(target_arch = "riscv32", target_arch = "riscv64"),
portable_atomic_no_atomic_cas
Expand All @@ -260,6 +277,10 @@ compile_error!(
target_has_atomic = "ptr",
not(any(
portable_atomic_armv6m,
all(
target_arch = "arm",
not(any(target_feature = "v6", portable_atomic_target_feature = "v6"))
),
all(
any(target_arch = "riscv32", target_arch = "riscv64"),
not(target_has_atomic = "ptr")
Expand Down
8 changes: 8 additions & 0 deletions tests/gba/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[build]
target-dir = "../../target"

[target.thumbv4t-none-eabi]
runner = "mgba -l 4"

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = ["-C", "link-arg=-Tlinker.ld"]
20 changes: 20 additions & 0 deletions tests/gba/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "gba-test"
version = "0.0.0"
edition = "2021"
publish = false

[workspace]
resolver = "2"

[dependencies]
portable-atomic = { path = "../.." }

gba = "0.5"
paste = "1"

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
Loading