251 lines
10 KiB
Markdown
251 lines
10 KiB
Markdown
# critical-section
|
|
[](https://crates.io/crates/critical-section)
|
|
[](https://crates.io/crates/critical-section)
|
|
[](https://docs.rs/critical-section)
|
|
|
|
This project is developed and maintained by the [HAL team][team].
|
|
|
|
A critical section that works everywhere!
|
|
|
|
When writing software for embedded systems, it's common to use a "critical section"
|
|
as a basic primitive to control concurrency. A critical section is essentially a
|
|
mutex global to the whole process, that can be acquired by only one thread at a time.
|
|
This can be used to protect data behind mutexes, to [emulate atomics](https://crates.io/crates/portable-atomic) in
|
|
targets that don't support them, etc.
|
|
|
|
There's a wide range of possible implementations depending on the execution environment:
|
|
- For bare-metal single core, disabling interrupts in the current (only) core.
|
|
- For bare-metal multicore, disabling interrupts in the current core and acquiring a hardware spinlock to prevent other cores from entering a critical section concurrently.
|
|
- For bare-metal using a RTOS, using library functions for acquiring a critical section, often named "scheduler lock" or "kernel lock".
|
|
- For bare-metal running in non-privileged mode, calling some system call is usually needed.
|
|
- For `std` targets, acquiring a global `std::sync::Mutex`.
|
|
|
|
Libraries often need to use critical sections, but there's no universal API for this in `core`. This leads
|
|
library authors to hard-code them for their target, or at best add some `cfg`s to support a few targets.
|
|
This doesn't scale since there are many targets out there, and in the general case it's impossible to know
|
|
which critical section implementation is needed from the Rust target alone. For example, the `thumbv7em-none-eabi` target
|
|
could be cases 1-4 from the above list.
|
|
|
|
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 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, HAL crates, and OS/RTOS bindings, including:
|
|
|
|
* The [`cortex-m`] crate provides an implementation for all single-core Cortex-M microcontrollers via its `critical-section-single-core` feature
|
|
* The [`riscv`] crate provides an implementation for all single-hart RISC-V microcontrollers via its `critical-section-single-hart` feature
|
|
* The [`msp430`] crate provides an implementation for all MSP430 microcontrollers via its `critical-section-single-core` feature
|
|
* The [`rp2040-hal`] crate provides a multi-core-safe critical section for the RP2040 microcontroller via its `critical-section-impl` feature
|
|
* The [`avr-device`] crate provides an implementation for all AVR microcontrollers via its `critical-section-impl` feature
|
|
* The [`esp-hal-common`] crate provides an implementation for ESP32 microcontrollers which is used by the ESP HALs
|
|
* The [`embassy-rp`] crate provides a multi-core-safe critical section for the RP2040 microcontroller via its `critical-section-impl` feature
|
|
* The [`nrf-softdevice`] crate provides a critical section that's compatible with the nRF soft-device firmware via its `critical-section-impl` feature
|
|
|
|
[`cortex-m`]: https://crates.io/crates/cortex-m
|
|
[`riscv`]: https://crates.io/crates/riscv
|
|
[`msp430`]: https://crates.io/crates/msp430
|
|
[`rp2040-hal`]: https://crates.io/crates/rp2040-hal
|
|
[`avr-device`]: https://crates.io/crates/avr-device
|
|
[`esp-hal-common`]: https://crates.io/crates/esp-hal-common
|
|
[`embassy-rp`]: https://docs.embassy.dev/embassy-rp
|
|
[`nrf-softdevice`]: https://docs.embassy.dev/nrf-softdevice
|
|
|
|
For example, for single-core Cortex-M targets, you can use:
|
|
|
|
```toml
|
|
[dependencies]
|
|
cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]}
|
|
```
|
|
|
|
Then you can use `critical_section::with()`.
|
|
|
|
```rust
|
|
use core::cell::Cell;
|
|
use critical_section::Mutex;
|
|
|
|
static MY_VALUE: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
|
|
|
|
critical_section::with(|cs| {
|
|
// This code runs within a critical section.
|
|
|
|
// `cs` is a token that you can use to "prove" that to some API,
|
|
// for example to a `Mutex`:
|
|
MY_VALUE.borrow(cs).set(42);
|
|
});
|
|
|
|
# #[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`
|
|
and use `critical_section::free` and/or `Mutex` as usual.
|
|
|
|
**Do not** add any dependency supplying a critical section implementation. Do not enable any `critical-section-*` Cargo feature.
|
|
This has to be done by the end user, enabling the correct implementation for their target.
|
|
|
|
**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.
|
|
It is **strongly recommended** to gate the implementation behind a feature, so the user can still use another implementation
|
|
if needed (having two implementations in the same binary will cause linking to fail).
|
|
|
|
Add the dependency, and a `critical-section-*` feature to your `Cargo.toml`:
|
|
|
|
```toml
|
|
[features]
|
|
# Enable critical section implementation that does "foo"
|
|
critical-section-foo = ["critical-section/restore-state-bool"]
|
|
|
|
[dependencies]
|
|
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;
|
|
|
|
struct MyCriticalSection;
|
|
critical_section::set_impl!(MyCriticalSection);
|
|
|
|
unsafe impl critical_section::Impl for MyCriticalSection {
|
|
unsafe fn acquire() -> RawRestoreState {
|
|
// TODO
|
|
}
|
|
|
|
unsafe fn release(token: RawRestoreState) {
|
|
// TODO
|
|
}
|
|
}
|
|
# }
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Undefined reference errors
|
|
|
|
If you get an error like these:
|
|
|
|
```not_rust
|
|
undefined reference to `_critical_section_1_0_acquire'
|
|
undefined reference to `_critical_section_1_0_release'
|
|
```
|
|
|
|
it is because you (or a library) are using `critical_section::with` without providing a critical section implementation.
|
|
Make sure you're depending on a crate providing the implementation, and have enabled the `critical-section-*` feature in it if required. See the `Usage` section above.
|
|
|
|
The error can also be caused by having the dependency but never `use`ing it. This can be fixed by adding a dummy `use`:
|
|
|
|
```rust,ignore
|
|
use the_cs_impl_crate as _;
|
|
```
|
|
|
|
### Duplicate symbol errors
|
|
|
|
If you get errors like these:
|
|
|
|
```not_rust
|
|
error: symbol `_critical_section_1_0_acquire` is already defined
|
|
```
|
|
|
|
it is because you have two crates trying to provide a critical section implementation. You can only
|
|
have one implementation in a program.
|
|
|
|
You can use `cargo tree --format '{p} {f}'` to view all dependencies and their enabled features. Make sure
|
|
that in the whole dependency tree, exactly one implementation is provided.
|
|
|
|
Check for multiple versions of the same crate as well. For example, check the `critical-section-single-core`
|
|
feature is not enabled for both `cortex-m` 0.7 and 0.8.
|
|
|
|
## Why not generics?
|
|
|
|
An alternative solution would be to use a `CriticalSection` trait, and make all
|
|
code that needs acquiring the critical section generic over it. This has a few problems:
|
|
|
|
- It would require passing it as a generic param to a very big amount of code, which
|
|
would be quite unergonomic.
|
|
- It's common to put `Mutex`es in `static` variables, and `static`s can't
|
|
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.
|
|
|
|
See [here](docs/msrv.md) for details on how the MSRV may be upgraded.
|
|
|
|
## License
|
|
|
|
This work is licensed under either of
|
|
|
|
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
<http://www.apache.org/licenses/LICENSE-2.0>)
|
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
|
|
|
at your option.
|
|
|
|
## Contribution
|
|
|
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
|
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
|
dual licensed as above, without any additional terms or conditions.
|
|
|
|
## Code of Conduct
|
|
|
|
Contribution to this crate is organized under the terms of the [Rust Code of
|
|
Conduct][CoC], the maintainer of this crate, the [HAL team][team], promises
|
|
to intervene to uphold that code of conduct.
|
|
|
|
[CoC]: CODE_OF_CONDUCT.md
|
|
[team]: https://github.com/rust-embedded/wg#the-hal-team
|