Skip to content
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
19 changes: 17 additions & 2 deletions .github/workflows/clippy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,22 @@ on:
jobs:
clippy:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- rust: 1.54
features: ''
- rust: 1.63
features: ''
- rust: 1.63
features: 'std'
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo clippy
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: ${{matrix.rust}}
components: clippy
override: true
- name: Clippy check
run: cargo clippy --features "${{matrix.features}}"
17 changes: 16 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,22 @@ on:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- rust: 1.54
features: ''
- rust: 1.63
features: ''
- rust: 1.63
features: 'std'
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: ${{matrix.rust}}
components: clippy
override: true
- name: Test
run: cargo test
run: cargo test --features "${{matrix.features}}"
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "critical-section"
version = "1.0.0"
edition = "2021"
edition = "2018"
description = "Cross-platform critical section"
repository = "https://github.com/rust-embedded/critical-section"
readme = "README.md"
Expand All @@ -14,6 +14,11 @@ categories = [

[features]

# Enable a critical-section implementation for platforms supporting `std`, based on `std::sync::Mutex`.
# If you enable this, the `critical-section` crate itself provides the implementation,
# you don't have to get another crate to to do it.
std = ["restore-state-bool"]

# Set the RestoreState size.
# The crate supplying the critical section implementation can set ONE of them.
# Other crates MUST NOT set any of these.
Expand Down
56 changes: 50 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ This crate solves the problem by providing this missing universal API.
- It provides functions `acquire`, `release` and `with` that libraries can directly use.
- It provides a way for any crate to supply an implementation. This allows "target support" crates such as architecture crates (`cortex-m`, `riscv`), RTOS bindings, or HALs for multicore chips to supply the correct implementation so that all the crates in the dependency tree automatically use it.

## Usage
## Usage in `no-std` binaries.

First, add a dependency on a crate providing a critical section implementation. Enable the `critical-section-*` Cargo feature if required by the crate.

Implementations are typically provided by either architecture-support crates (`cortex-m`, `riscv`, etc), or HAL crates.

For example, for single-core Cortex-M targets, you can use:

```toml
[dependencies]
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]}
```

Expand All @@ -55,14 +60,27 @@ critical_section::with(|cs| {
MY_VALUE.borrow(cs).set(42);
});

# struct MyCriticalSection;
# critical_section::set_impl!(MyCriticalSection);
# unsafe impl critical_section::Impl for MyCriticalSection {
# unsafe fn acquire() -> () {}
# unsafe fn release(token: ()) {}
# #[cfg(not(feature = "std"))] // needed for `cargo test --features std`
# mod no_std {
# struct MyCriticalSection;
# critical_section::set_impl!(MyCriticalSection);
# unsafe impl critical_section::Impl for MyCriticalSection {
# unsafe fn acquire() -> () {}
# unsafe fn release(token: ()) {}
# }
# }
```

## Usage in `std` binaries.

Add the `critical-section` dependency to `Cargo.toml` enabling the `std` feature. This makes the `critical-section` crate itself
provide an implementation based on `std::sync::Mutex`, so you don't have to add any other dependency.

```toml
[dependencies]
critical-section = { version = "1.1", features = ["std"]}
```

## Usage in libraries

If you're writing a library intended to be portable across many targets, simply add a dependency on `critical-section`
Expand All @@ -73,6 +91,20 @@ This has to be done by the end user, enabling the correct implementation for the

**Do not** enable any Cargo feature in `critical-section`.

## Usage in `std` tests for `no-std` libraries.

If you want to run `std`-using tests in otherwise `no-std` libraries, enable the `std` feature in `dev-dependencies` only.
This way the main target will use the `no-std` implementation chosen by the end-user's binary, and only the test targets
will use the `std` implementation.

```toml
[dependencies]
critical-section = "1.1"

[dev-dependencies]
critical-section = { version = "1.1", features = ["std"]}
```

## Providing an implementation

Crates adding support for a particular architecture, chip or operating system should provide a critical section implementation.
Expand All @@ -93,6 +125,8 @@ critical-section = { version = "1.0", optional = true }
Then, provide the critical implementation like this:

```rust
# #[cfg(not(feature = "std"))] // needed for `cargo test --features std`
# mod no_std {
// This is a type alias for the enabled `restore-state-*` feature.
// For example, it is `bool` if you enable `restore-state-bool`.
use critical_section::RawRestoreState;
Expand All @@ -109,6 +143,7 @@ unsafe impl critical_section::Impl for MyCriticalSection {
// TODO
}
}
# }
```

## Troubleshooting
Expand Down Expand Up @@ -160,6 +195,15 @@ be generic.
- It would allow mixing different critical section implementations in the same program,
which would be unsound.

## Minimum Supported Rust Version (MSRV)

This crate is guaranteed to compile on the following Rust versions:

- If the `std` feature is not enabled: stable Rust 1.54 and up.
- If the `std` feature is enabled: stable Rust 1.63 and up.

It might compile with older versions but that may change in any new patch release.

## License

This work is licensed under either of
Expand Down
9 changes: 6 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#![no_std]
#![cfg_attr(not(feature = "std"), no_std)]
#![doc = include_str!("../README.md")]

mod mutex;
#[cfg(feature = "std")]
mod std;

use core::marker::PhantomData;

Expand Down Expand Up @@ -116,7 +118,6 @@ pub type RawRestoreState = RawRestoreStateInner;
///
/// User code uses [`RestoreState`] opaquely, critical section implementations
/// use [`RawRestoreState`] so that they can use the inner value.
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct RestoreState(RawRestoreState);

Expand Down Expand Up @@ -238,6 +239,8 @@ pub unsafe trait Impl {
/// # Example
///
/// ```
/// # #[cfg(not(feature = "std"))] // needed for `cargo test --features std`
/// # mod no_std {
/// use critical_section::RawRestoreState;
///
/// struct MyCriticalSection;
Expand All @@ -252,7 +255,7 @@ pub unsafe trait Impl {
/// // ...
/// }
/// }
///
/// # }
#[macro_export]
macro_rules! set_impl {
($t: ty) => {
Expand Down
49 changes: 49 additions & 0 deletions src/std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::cell::Cell;
use std::mem::MaybeUninit;
use std::sync::{Mutex, MutexGuard};

static GLOBAL_MUTEX: Mutex<()> = Mutex::new(());

// This is initialized if a thread has acquired the CS, uninitialized otherwise.
static mut GLOBAL_GUARD: MaybeUninit<MutexGuard<'static, ()>> = MaybeUninit::uninit();

std::thread_local!(static IS_LOCKED: Cell<bool> = Cell::new(false));

struct StdCriticalSection;
crate::set_impl!(StdCriticalSection);

unsafe impl crate::Impl for StdCriticalSection {
unsafe fn acquire() -> bool {
// Allow reentrancy by checking thread local state
IS_LOCKED.with(|l| {
if l.get() {
// CS already acquired in the current thread.
return true;
}

// Note: it is fine to set this flag *before* acquiring the mutex because it's thread local.
// No other thread can see its value, there's no potential for races.
// This way, we hold the mutex for slightly less time.
l.set(true);

// Not acquired in the current thread, acquire it.
let guard = GLOBAL_MUTEX.lock().unwrap();
GLOBAL_GUARD.write(guard);
false
})
}

unsafe fn release(nested_cs: bool) {
if !nested_cs {
// SAFETY: As per the acquire/release safety contract, release can only be called
// if the critical section is acquired in the current thread,
// in which case we know the GLOBAL_GUARD is initialized.
GLOBAL_GUARD.assume_init_drop();

// Note: it is fine to clear this flag *after* releasing the mutex because it's thread local.
// No other thread can see its value, there's no potential for races.
// This way, we hold the mutex for slightly less time.
IS_LOCKED.with(|l| l.set(false));
}
}
}