mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-12-24 02:49:34 +01:00
compile time verified ceilings
This commit is contained in:
parent
6ac2625a75
commit
595404c5ff
8 changed files with 498 additions and 562 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
target/
|
||||
|
|
22
Cargo.toml
22
Cargo.toml
|
@ -4,9 +4,21 @@ build = "build.rs"
|
|||
name = "cortex-m-srp"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = "0.2.0"
|
||||
[build-dependencies]
|
||||
quote = "0.3.15"
|
||||
syn = "0.11.10"
|
||||
|
||||
[dependencies.vcell]
|
||||
features = ["const-fn"]
|
||||
version = "0.1.0"
|
||||
[dependencies]
|
||||
cortex-m = "0.2.2"
|
||||
typenum = "1.7.0"
|
||||
|
||||
[dev-dependencies]
|
||||
compiletest_rs = "0.2.5"
|
||||
|
||||
[features]
|
||||
# Number of priority bits
|
||||
P2 = []
|
||||
P3 = []
|
||||
P4 = []
|
||||
P5 = []
|
||||
default = ["P4"]
|
||||
|
|
103
build.rs
103
build.rs
|
@ -1,9 +1,106 @@
|
|||
#[macro_use]
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use syn::{Ident, IntTy, Lit};
|
||||
|
||||
fn main() {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
let bits = if env::var_os("CARGO_FEATURE_P2").is_some() {
|
||||
2
|
||||
} else if env::var_os("CARGO_FEATURE_P3").is_some() {
|
||||
3
|
||||
} else if env::var_os("CARGO_FEATURE_P4").is_some() {
|
||||
4
|
||||
} else if env::var_os("CARGO_FEATURE_P5").is_some() {
|
||||
5
|
||||
} else {
|
||||
panic!(
|
||||
"Specify the number of priority bits through one of these Cargo \
|
||||
features: P2, P3, P4 or P5"
|
||||
);
|
||||
};
|
||||
|
||||
if target == "thumbv6m-none-eabi" {
|
||||
println!("cargo:rustc-cfg=thumbv6m");
|
||||
let n = Lit::Int(bits, IntTy::Unsuffixed);
|
||||
let mut tokens = vec![];
|
||||
tokens.push(
|
||||
quote! {
|
||||
const PRIORITY_BITS: u8 = #n;
|
||||
},
|
||||
);
|
||||
|
||||
// Ceilings
|
||||
for i in 1..(1 << bits) + 1 {
|
||||
let c = Ident::new(format!("C{}", i));
|
||||
let u = Ident::new(format!("U{}", i));
|
||||
|
||||
tokens.push(
|
||||
quote! {
|
||||
/// Ceiling
|
||||
pub type #c = C<::typenum::#u>;
|
||||
|
||||
unsafe impl Ceiling for #c {}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Priorities
|
||||
for i in 1..(1 << bits) + 1 {
|
||||
let p = Ident::new(format!("P{}", i));
|
||||
let u = Ident::new(format!("U{}", i));
|
||||
|
||||
tokens.push(
|
||||
quote! {
|
||||
/// Priority
|
||||
pub type #p = P<::typenum::#u>;
|
||||
|
||||
unsafe impl Priority for #p {}
|
||||
|
||||
unsafe impl Level for ::typenum::#u {
|
||||
fn hw() -> u8 {
|
||||
logical2hw(::typenum::#u::to_u8())
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// GreaterThanOrEqual
|
||||
for i in 1..(1 << bits) + 1 {
|
||||
for j in 1..(i + 1) {
|
||||
let i = Ident::new(format!("U{}", i));
|
||||
let j = Ident::new(format!("U{}", j));
|
||||
|
||||
tokens.push(
|
||||
quote! {
|
||||
unsafe impl GreaterThanOrEqual<::typenum::#j> for
|
||||
::typenum::#i {}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let u = Ident::new(format!("U{}", (1 << bits)));
|
||||
tokens.push(quote! {
|
||||
#[doc(hidden)]
|
||||
pub type CMAX = C<::typenum::#u>;
|
||||
|
||||
/// Maximum priority level
|
||||
pub type UMAX = ::typenum::#u;
|
||||
});
|
||||
|
||||
let tokens = quote! {
|
||||
#(#tokens)*
|
||||
};
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let mut out = File::create(PathBuf::from(out_dir).join("prio.rs")).unwrap();
|
||||
|
||||
out.write_all(tokens.as_str().as_bytes()).unwrap();
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
//! Safe, run-time checked resources
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::cell::UnsafeCell;
|
||||
|
||||
use cortex_m::interrupt;
|
||||
use cortex_m::register::{basepri, basepri_max};
|
||||
use vcell::VolatileCell;
|
||||
|
||||
use Ceiling;
|
||||
|
||||
unsafe fn acquire(locked: &VolatileCell<bool>, ceiling: u8) -> u8 {
|
||||
assert!(!locked.get(), "resource already locked");
|
||||
let old_basepri = basepri::read();
|
||||
basepri_max::write(ceiling);
|
||||
locked.set(true);
|
||||
old_basepri
|
||||
}
|
||||
|
||||
unsafe fn release(locked: &VolatileCell<bool>, old_basepri: u8) {
|
||||
locked.set(false);
|
||||
basepri::write(old_basepri);
|
||||
}
|
||||
|
||||
/// A totally safe `Resource` that panics on misuse
|
||||
pub struct Resource<T, C> {
|
||||
_marker: PhantomData<C>,
|
||||
data: UnsafeCell<T>,
|
||||
locked: VolatileCell<bool>,
|
||||
}
|
||||
|
||||
impl<T, C> Resource<T, C>
|
||||
where
|
||||
C: Ceiling,
|
||||
{
|
||||
/// Creates a new `Resource` with ceiling `C`
|
||||
pub const fn new(data: T) -> Resource<T, C> {
|
||||
Resource {
|
||||
_marker: PhantomData,
|
||||
data: UnsafeCell::new(data),
|
||||
locked: VolatileCell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Locks the resource, blocking tasks with priority equal or smaller than
|
||||
/// the ceiling `C`
|
||||
pub fn lock<R, F>(&'static self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&T) -> R,
|
||||
{
|
||||
unsafe {
|
||||
let old_basepri = acquire(&self.locked, C::hw_ceiling());
|
||||
::compiler_barrier();
|
||||
let ret = f(&*self.data.get());
|
||||
::compiler_barrier();
|
||||
release(&self.locked, old_basepri);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutably locks the resource, blocking tasks with priority equal or
|
||||
/// smaller than the ceiling `C`
|
||||
pub fn lock_mut<R, F>(&'static self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut T) -> R,
|
||||
{
|
||||
unsafe {
|
||||
let old_basepri = acquire(&self.locked, C::hw_ceiling());
|
||||
::compiler_barrier();
|
||||
let ret = f(&mut *self.data.get());
|
||||
::compiler_barrier();
|
||||
release(&self.locked, old_basepri);
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T, C> Sync for Resource<T, C> {}
|
775
src/lib.rs
775
src/lib.rs
|
@ -1,138 +1,26 @@
|
|||
//! Stack Resource Policy for Cortex-M processors
|
||||
//!
|
||||
//! NOTE ARMv6-M is not fully supported at the moment.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(warnings)]
|
||||
#![feature(asm)]
|
||||
#![feature(const_fn)]
|
||||
#![no_std]
|
||||
|
||||
extern crate cortex_m;
|
||||
extern crate vcell;
|
||||
extern crate typenum;
|
||||
|
||||
pub mod checked;
|
||||
|
||||
use cortex_m::ctxt::Context;
|
||||
use cortex_m::interrupt::{CriticalSection, Nr};
|
||||
use cortex_m::peripheral::{Peripheral, NVIC};
|
||||
#[cfg(not(thumbv6m))]
|
||||
use cortex_m::peripheral::SCB;
|
||||
#[cfg(not(thumbv6m))]
|
||||
use cortex_m::register::{basepri, basepri_max};
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::marker::PhantomData;
|
||||
#[cfg(not(thumbv6m))]
|
||||
use core::ptr;
|
||||
use core::cell::UnsafeCell;
|
||||
|
||||
#[cfg(not(thumbv6m))]
|
||||
// NOTE Only the 4 highest bits of the priority byte (BASEPRI / NVIC.IPR) are
|
||||
// considered when determining priorities.
|
||||
const PRIORITY_BITS: u8 = 4;
|
||||
use cortex_m::interrupt::Nr;
|
||||
use cortex_m::register::{basepri, basepri_max};
|
||||
use typenum::{Cmp, Equal, Greater, Less, Unsigned};
|
||||
|
||||
/// Logical task priority
|
||||
#[cfg(not(thumbv6m))]
|
||||
unsafe fn task_priority() -> u8 {
|
||||
// NOTE(safe) atomic read
|
||||
let nr = match (*SCB.get()).icsr.read() as u8 {
|
||||
n if n >= 16 => n - 16,
|
||||
_ => panic!("not in a task"),
|
||||
};
|
||||
// NOTE(safe) atomic read
|
||||
hardware((*NVIC.get()).ipr[nr as usize].read())
|
||||
}
|
||||
pub use cortex_m::ctxt::Local;
|
||||
#[doc(hidden)]
|
||||
pub use cortex_m::peripheral::NVIC;
|
||||
#[doc(hidden)]
|
||||
pub use cortex_m::interrupt::free;
|
||||
|
||||
#[cfg(all(debug_assertions, not(thumbv6m)))]
|
||||
unsafe fn get_check(logical_ceiling: u8) {
|
||||
let task_priority = task_priority();
|
||||
let system_ceiling = hardware(cortex_m::register::basepri::read());
|
||||
let resource_ceiling = logical_ceiling;
|
||||
|
||||
if resource_ceiling < task_priority {
|
||||
panic!(
|
||||
"bad ceiling value. task priority = {}, \
|
||||
resource ceiling = {}",
|
||||
task_priority,
|
||||
resource_ceiling,
|
||||
);
|
||||
} else if resource_ceiling == task_priority {
|
||||
// OK: safe to access the resource without locking in the
|
||||
// task with highest priority
|
||||
} else if resource_ceiling <= system_ceiling {
|
||||
// OK: use within another resource critical section, where
|
||||
// the locked resource has higher or equal ceiling
|
||||
} else {
|
||||
panic!(
|
||||
"racy access to resource. \
|
||||
task priority = {}, \
|
||||
resource ceiling = {}, \
|
||||
system ceiling = {}",
|
||||
task_priority,
|
||||
resource_ceiling,
|
||||
system_ceiling,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
unsafe fn get_check(_: u8) {}
|
||||
|
||||
#[cfg(all(debug_assertions, not(thumbv6m)))]
|
||||
unsafe fn lock_check(ceiling: u8) {
|
||||
let ceiling = hardware(ceiling);
|
||||
let task_priority = task_priority();
|
||||
|
||||
if task_priority > ceiling {
|
||||
panic!(
|
||||
"bad ceiling value. task_priority = {}, resource_ceiling = {}",
|
||||
task_priority,
|
||||
ceiling,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
unsafe fn lock_check(_: u8) {}
|
||||
|
||||
// XXX Do we need memory / instruction / compiler barriers here?
|
||||
#[cfg(not(thumbv6m))]
|
||||
#[inline(always)]
|
||||
unsafe fn lock<T, R, C, F>(f: F, res: *const T, ceiling: u8) -> R
|
||||
where
|
||||
C: Ceiling,
|
||||
F: FnOnce(&T, C) -> R,
|
||||
{
|
||||
lock_check(ceiling);
|
||||
let old_basepri = basepri::read();
|
||||
basepri_max::write(ceiling);
|
||||
compiler_barrier();
|
||||
let ret = f(&*res, ptr::read(0 as *const _));
|
||||
compiler_barrier();
|
||||
basepri::write(old_basepri);
|
||||
ret
|
||||
}
|
||||
|
||||
// XXX Do we need memory / instruction / compiler barriers here?
|
||||
#[cfg(not(thumbv6m))]
|
||||
#[inline(always)]
|
||||
unsafe fn lock_mut<T, R, C, F>(f: F, res: *mut T, ceiling: u8) -> R
|
||||
where
|
||||
C: Ceiling,
|
||||
F: FnOnce(&mut T, C) -> R,
|
||||
{
|
||||
lock_check(ceiling);
|
||||
let old_basepri = basepri::read();
|
||||
basepri_max::write(ceiling);
|
||||
compiler_barrier();
|
||||
let ret = f(&mut *res, ptr::read(0 as *const _));
|
||||
compiler_barrier();
|
||||
basepri::write(old_basepri);
|
||||
ret
|
||||
}
|
||||
|
||||
fn compiler_barrier() {
|
||||
unsafe {
|
||||
macro_rules! barrier {
|
||||
() => {
|
||||
asm!(""
|
||||
:
|
||||
:
|
||||
|
@ -141,402 +29,339 @@ fn compiler_barrier() {
|
|||
}
|
||||
}
|
||||
|
||||
/// A peripheral as a resource
|
||||
pub struct ResourceP<P, Ceiling>
|
||||
where
|
||||
P: 'static,
|
||||
{
|
||||
_marker: PhantomData<Ceiling>,
|
||||
peripheral: Peripheral<P>,
|
||||
}
|
||||
|
||||
impl<P, C> ResourceP<P, C>
|
||||
where
|
||||
C: CeilingLike,
|
||||
{
|
||||
/// Wraps a `peripheral` into a `Resource`
|
||||
///
|
||||
/// # Unsafety
|
||||
///
|
||||
/// - Must not create two resources that point to the same peripheral
|
||||
/// - The ceiling, `C`, must be picked to prevent two or more tasks from
|
||||
/// concurrently accessing the resource through preemption
|
||||
pub const unsafe fn new(p: Peripheral<P>) -> Self {
|
||||
ResourceP {
|
||||
_marker: PhantomData,
|
||||
peripheral: p,
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrows the resource for the duration of `interrupt::free`
|
||||
pub fn cs_borrow<'cs>(&self, _ctxt: &'cs CriticalSection) -> &'cs P {
|
||||
unsafe { &*self.peripheral.get() }
|
||||
}
|
||||
|
||||
/// Mutably borrows the resource for the duration of `interrupt::free`
|
||||
pub fn cs_borrow_mut<'cs>(
|
||||
&self,
|
||||
_ctxt: &'cs mut CriticalSection,
|
||||
) -> &'cs mut P {
|
||||
unsafe { &mut *self.peripheral.get() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(thumbv6m))]
|
||||
impl<P, C> ResourceP<P, C>
|
||||
where
|
||||
C: Ceiling,
|
||||
{
|
||||
/// Borrows the resource without locking
|
||||
///
|
||||
/// NOTE The system ceiling must be higher than this resource ceiling
|
||||
pub fn borrow<'l, SC>(&'static self, _system_ceiling: &'l SC) -> &'l P
|
||||
where
|
||||
SC: HigherThan<C>,
|
||||
{
|
||||
unsafe { &*self.peripheral.get() }
|
||||
}
|
||||
|
||||
/// Returns an immutable reference to the inner data without locking
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must
|
||||
///
|
||||
/// - Preserve the "reference" rules. Don't create an immutable reference if
|
||||
/// the current task already owns a mutable reference to the data.
|
||||
///
|
||||
/// - adhere to the Stack Resource Policy. You can
|
||||
/// - Access the resource from the highest priority task.
|
||||
/// - Access the resource from within a critical section that sets the
|
||||
/// system ceiling to `C`.
|
||||
pub unsafe fn get(&self) -> &'static P {
|
||||
get_check(C::ceiling());
|
||||
|
||||
&*self.peripheral.get()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner data without locking
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must
|
||||
///
|
||||
/// - Preserve the "reference" rules. Don't create a mutable reference if
|
||||
/// the current task already owns a reference to the data.
|
||||
///
|
||||
/// - adhere to the Stack Resource Policy. You can
|
||||
/// - Access the resource from the highest priority task.
|
||||
/// - Access the resource from within a critical section that sets the
|
||||
/// system ceiling to `C`.
|
||||
pub unsafe fn get_mut(&self) -> &'static mut P {
|
||||
get_check(C::ceiling());
|
||||
|
||||
&mut *self.peripheral.get()
|
||||
}
|
||||
|
||||
/// Locks the resource, preventing tasks with priority lower than `Ceiling`
|
||||
/// from preempting the current task
|
||||
pub fn lock<R, F, Ctxt>(&'static self, _ctxt: &Ctxt, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&P, C) -> R,
|
||||
Ctxt: Context,
|
||||
{
|
||||
unsafe { lock(f, self.peripheral.get(), C::hw_ceiling()) }
|
||||
}
|
||||
|
||||
/// Mutably locks the resource, preventing tasks with priority lower than
|
||||
/// `Ceiling` from preempting the current task
|
||||
pub fn lock_mut<R, F, Ctxt>(&'static self, _ctxt: &mut Ctxt, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut P, C) -> R,
|
||||
Ctxt: Context,
|
||||
{
|
||||
unsafe { lock_mut(f, self.peripheral.get(), C::hw_ceiling()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> ResourceP<P, C0> {
|
||||
/// Borrows the resource without locking
|
||||
pub fn borrow<'ctxt, Ctxt>(&'static self, _ctxt: &'ctxt Ctxt) -> &'ctxt P
|
||||
where
|
||||
Ctxt: Context,
|
||||
{
|
||||
unsafe { &*self.peripheral.get() }
|
||||
}
|
||||
|
||||
/// Mutably borrows the resource without locking
|
||||
pub fn borrow_mut<'ctxt, Ctxt>(
|
||||
&'static self,
|
||||
_ctxt: &'ctxt mut Ctxt,
|
||||
) -> &'ctxt mut P
|
||||
where
|
||||
Ctxt: Context,
|
||||
{
|
||||
unsafe { &mut *self.peripheral.get() }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<P, C> Sync for ResourceP<P, C> {}
|
||||
|
||||
/// A resource
|
||||
pub struct Resource<T, Ceiling> {
|
||||
_marker: PhantomData<Ceiling>,
|
||||
pub struct Resource<T, CEILING> {
|
||||
_ceiling: PhantomData<CEILING>,
|
||||
data: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
impl<T, C> Resource<T, C> {
|
||||
/// Initializes a resource
|
||||
///
|
||||
/// # Unsafety
|
||||
///
|
||||
/// - The ceiling, `C`, must be picked to prevent two or more tasks from
|
||||
/// concurrently accessing the resource through preemption
|
||||
pub const unsafe fn new(data: T) -> Self
|
||||
/// Creates a new resource with ceiling `C`
|
||||
pub const fn new(data: T) -> Self
|
||||
where
|
||||
C: CeilingLike,
|
||||
C: Ceiling,
|
||||
{
|
||||
Resource {
|
||||
_marker: PhantomData,
|
||||
_ceiling: PhantomData,
|
||||
data: UnsafeCell::new(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrows the resource for the duration of `interrupt::free`
|
||||
pub fn cs_borrow<'cs>(&self, _ctxt: &'cs CriticalSection) -> &'cs T {
|
||||
impl<T, CEILING> Resource<T, C<CEILING>>
|
||||
where
|
||||
C<CEILING>: Ceiling,
|
||||
{
|
||||
/// Borrows the resource for the duration of another resource's critical
|
||||
/// section
|
||||
///
|
||||
/// This operation is zero cost and doesn't impose any additional blocking
|
||||
pub fn borrow<'cs, SCEILING>(
|
||||
&'static self,
|
||||
_system_ceiling: &'cs C<SCEILING>,
|
||||
) -> &'cs T
|
||||
where
|
||||
SCEILING: GreaterThanOrEqual<CEILING>,
|
||||
{
|
||||
unsafe { &*self.data.get() }
|
||||
}
|
||||
|
||||
/// Mutably borrows the resource for the duration of `interrupt::free`
|
||||
pub fn cs_borrow_mut<'cs>(
|
||||
&self,
|
||||
_ctxt: &'cs mut CriticalSection,
|
||||
) -> &'cs mut T {
|
||||
unsafe { &mut *self.data.get() }
|
||||
/// Claims the resource at the task with highest priority
|
||||
///
|
||||
/// This operation is zero cost and doesn't impose any additional blocking
|
||||
pub fn claim<'task, PRIORITY>(
|
||||
&'static self,
|
||||
_priority: &'task P<PRIORITY>,
|
||||
) -> &'task T
|
||||
where
|
||||
CEILING: Cmp<PRIORITY, Output = Equal>,
|
||||
P<PRIORITY>: Priority,
|
||||
{
|
||||
unsafe { &*self.data.get() }
|
||||
}
|
||||
|
||||
/// Locks the resource for the duration of the critical section `f`
|
||||
///
|
||||
/// For the duration of the critical section, tasks whose priority level is
|
||||
/// smaller than or equal to the resource `CEILING` will be prevented from
|
||||
/// preempting the current task.
|
||||
pub fn lock<R, PRIORITY, F>(
|
||||
&'static self,
|
||||
_priority: &P<PRIORITY>,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(&T, C<CEILING>) -> R,
|
||||
C<CEILING>: Ceiling,
|
||||
CEILING: Cmp<PRIORITY, Output = Greater> + Cmp<UMAX, Output = Less>
|
||||
+ Level,
|
||||
P<PRIORITY>: Priority,
|
||||
{
|
||||
unsafe {
|
||||
let old_basepri = basepri::read();
|
||||
basepri_max::write(<CEILING>::hw());
|
||||
barrier!();
|
||||
let ret = f(
|
||||
&*self.data.get(),
|
||||
C {
|
||||
_0: (),
|
||||
_marker: PhantomData,
|
||||
},
|
||||
);
|
||||
barrier!();
|
||||
basepri::write(old_basepri);
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(thumbv6m))]
|
||||
impl<T, C> Resource<T, C>
|
||||
unsafe impl<T, CEILING> Sync for Resource<T, CEILING>
|
||||
where
|
||||
CEILING: Ceiling,
|
||||
{
|
||||
}
|
||||
|
||||
/// A hardware peripheral as a resource
|
||||
pub struct Peripheral<P, CEILING>
|
||||
where
|
||||
P: 'static,
|
||||
{
|
||||
peripheral: cortex_m::peripheral::Peripheral<P>,
|
||||
_ceiling: PhantomData<CEILING>,
|
||||
}
|
||||
|
||||
impl<P, C> Peripheral<P, C>
|
||||
where
|
||||
C: Ceiling,
|
||||
{
|
||||
/// Borrows the resource without locking
|
||||
///
|
||||
/// NOTE The system ceiling must be higher than this resource ceiling
|
||||
pub fn borrow<'l, SC>(&'static self, _system_ceiling: &'l SC) -> &'l T
|
||||
where
|
||||
SC: HigherThan<C>,
|
||||
{
|
||||
unsafe { &*self.data.get() }
|
||||
}
|
||||
|
||||
/// Returns an immutable reference to the inner data without locking
|
||||
/// Assigns a ceiling `C` to the `peripheral`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must
|
||||
///
|
||||
/// - Preserve the "reference" rules. Don't create an immutable reference if
|
||||
/// the current task already owns a mutable reference to the data.
|
||||
///
|
||||
/// - adhere to the Stack Resource Policy. You can
|
||||
/// - Access the resource from the highest priority task.
|
||||
/// - Access the resource from within a critical section that sets the
|
||||
/// system ceiling to `C`.
|
||||
pub unsafe fn get(&'static self) -> &'static T {
|
||||
get_check(C::ceiling());
|
||||
|
||||
&*self.data.get()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner data without locking
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must
|
||||
///
|
||||
/// - Preserve the "reference" rules. Don't create a mutable reference if
|
||||
/// the current task already owns a reference to the data.
|
||||
///
|
||||
/// - adhere to the Stack Resource Policy. You can
|
||||
/// - Access the resource from the highest priority task.
|
||||
/// - Access the resource from within a critical section that sets the
|
||||
/// system ceiling to `C`.
|
||||
pub unsafe fn get_mut(&'static self) -> &'static mut T {
|
||||
get_check(C::ceiling());
|
||||
|
||||
&mut *self.data.get()
|
||||
}
|
||||
|
||||
/// Locks the resource, preventing tasks with priority lower than `Ceiling`
|
||||
/// from preempting the current task
|
||||
pub fn lock<F, R, Ctxt>(&'static self, _ctxt: &Ctxt, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&T, C) -> R,
|
||||
Ctxt: Context,
|
||||
{
|
||||
unsafe { lock(f, self.data.get(), C::hw_ceiling()) }
|
||||
}
|
||||
|
||||
/// Mutably locks the resource, preventing tasks with priority lower than
|
||||
/// `Ceiling` from preempting the current task
|
||||
pub fn lock_mut<F, R, Ctxt>(&'static self, _ctxt: &mut Ctxt, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut T, C) -> R,
|
||||
Ctxt: Context,
|
||||
{
|
||||
unsafe { lock_mut(f, self.data.get(), C::hw_ceiling()) }
|
||||
/// You MUST not create two resources that point to the same peripheral
|
||||
pub const unsafe fn new(peripheral: cortex_m::peripheral::Peripheral<P>,)
|
||||
-> Self {
|
||||
Peripheral {
|
||||
_ceiling: PhantomData,
|
||||
peripheral: peripheral,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Resource<T, C0> {
|
||||
/// Borrows the resource without locking
|
||||
pub fn borrow<'ctxt, Ctxt>(&'static self, _ctxt: &'ctxt Ctxt) -> &'ctxt T
|
||||
where
|
||||
Ctxt: Context,
|
||||
{
|
||||
unsafe { &*self.data.get() }
|
||||
}
|
||||
|
||||
/// Mutably borrows the resource without locking
|
||||
pub fn borrow_mut<'ctxt, Ctxt>(
|
||||
&'static self,
|
||||
_ctxt: &'ctxt mut Ctxt,
|
||||
) -> &'ctxt mut T
|
||||
where
|
||||
Ctxt: Context,
|
||||
{
|
||||
unsafe { &mut *self.data.get() }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T, Ceiling> Sync for Resource<T, Ceiling> {}
|
||||
|
||||
/// Maps a hardware priority to a logical priority
|
||||
#[cfg(not(thumbv6m))]
|
||||
fn hardware(priority: u8) -> u8 {
|
||||
16 - (priority >> (8 - PRIORITY_BITS))
|
||||
}
|
||||
|
||||
/// Turns a `logical` priority into a NVIC-style priority
|
||||
///
|
||||
/// With `logical` priorities, `2` has HIGHER priority than `1`.
|
||||
///
|
||||
/// With NVIC priorities, `32` has LOWER priority than `16`. (Also, NVIC
|
||||
/// priorities encode the actual priority in the highest bits of a byte so
|
||||
/// priorities like `1` and `2` aren't actually different)
|
||||
///
|
||||
/// NOTE Input `priority` must be in the range `[1, 16]` (inclusive)
|
||||
#[cfg(not(thumbv6m))]
|
||||
pub fn logical(priority: u8) -> u8 {
|
||||
assert!(priority >= 1 && priority <= 16);
|
||||
|
||||
((1 << PRIORITY_BITS) - priority) << (8 - PRIORITY_BITS)
|
||||
}
|
||||
|
||||
/// Puts `interrupt` in the "to execute" queue
|
||||
///
|
||||
/// This function has no effect if the interrupt was already queued
|
||||
pub fn queue<I>(interrupt: I)
|
||||
impl<Periph, CEILING> Peripheral<Periph, C<CEILING>>
|
||||
where
|
||||
I: Nr,
|
||||
C<CEILING>: Ceiling,
|
||||
{
|
||||
unsafe {
|
||||
// NOTE(safe) atomic write
|
||||
(*NVIC.get()).set_pending(interrupt)
|
||||
/// See [Resource.borrow](./struct.Resource.html#method.borrow)
|
||||
pub fn borrow<'cs, SCEILING>(
|
||||
&'static self,
|
||||
_system_ceiling: &'cs C<SCEILING>,
|
||||
) -> &'cs Periph
|
||||
where
|
||||
SCEILING: GreaterThanOrEqual<CEILING>,
|
||||
{
|
||||
unsafe { &*self.peripheral.get() }
|
||||
}
|
||||
|
||||
/// See [Resource.claim](./struct.Resource.html#method.claim)
|
||||
pub fn claim<'task, PRIORITY>(
|
||||
&'static self,
|
||||
_priority: &'task P<PRIORITY>,
|
||||
) -> &'task Periph
|
||||
where
|
||||
CEILING: Cmp<PRIORITY, Output = Equal>,
|
||||
P<PRIORITY>: Priority,
|
||||
{
|
||||
unsafe { &*self.peripheral.get() }
|
||||
}
|
||||
|
||||
/// See [Resource.lock](./struct.Resource.html#method.lock)
|
||||
pub fn lock<R, PRIORITY, F>(
|
||||
&'static self,
|
||||
_priority: &P<PRIORITY>,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(&Periph, C<CEILING>) -> R,
|
||||
C<CEILING>: Ceiling,
|
||||
CEILING: Cmp<PRIORITY, Output = Greater> + Cmp<UMAX, Output = Less>
|
||||
+ Level,
|
||||
P<PRIORITY>: Priority,
|
||||
{
|
||||
unsafe {
|
||||
let old_basepri = basepri::read();
|
||||
basepri_max::write(<CEILING>::hw());
|
||||
barrier!();
|
||||
let ret = f(
|
||||
&*self.peripheral.get(),
|
||||
C {
|
||||
_0: (),
|
||||
_marker: PhantomData,
|
||||
},
|
||||
);
|
||||
barrier!();
|
||||
basepri::write(old_basepri);
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fake ceiling, indicates that the resource is shared by cooperative tasks
|
||||
pub struct C0 {
|
||||
unsafe impl<T, C> Sync for Peripheral<T, C>
|
||||
where
|
||||
C: Ceiling,
|
||||
{
|
||||
}
|
||||
|
||||
/// Requests the execution of the task `task`
|
||||
pub fn request<T>(task: T)
|
||||
where
|
||||
T: Nr,
|
||||
{
|
||||
let nvic = unsafe { &*NVIC.get() };
|
||||
|
||||
match () {
|
||||
#[cfg(debug_assertions)]
|
||||
() => {
|
||||
let task = unsafe { core::ptr::read(&task) };
|
||||
// NOTE(safe) atomic read
|
||||
assert!(!nvic.is_pending(task),
|
||||
"Task is already in the pending state");
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
() => {}
|
||||
}
|
||||
|
||||
// NOTE(safe) atomic write
|
||||
nvic.set_pending(task);
|
||||
}
|
||||
|
||||
/// A type-level ceiling
|
||||
pub struct C<T> {
|
||||
_0: (),
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// A real ceiling
|
||||
// XXX this should be a "closed" trait
|
||||
#[cfg(not(thumbv6m))]
|
||||
pub unsafe trait Ceiling {
|
||||
/// Returns the logical ceiling as a number
|
||||
fn ceiling() -> u8;
|
||||
|
||||
/// Returns the HW ceiling as a number
|
||||
fn hw_ceiling() -> u8;
|
||||
/// A type-level priority
|
||||
pub struct P<T> {
|
||||
_0: (),
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Usable as a ceiling
|
||||
// XXX this should be a "closed" trait
|
||||
pub unsafe trait CeilingLike {}
|
||||
impl<T> P<T>
|
||||
where
|
||||
T: Level,
|
||||
{
|
||||
pub fn hw() -> u8 {
|
||||
T::hw()
|
||||
}
|
||||
}
|
||||
|
||||
/// This ceiling is lower than `C`
|
||||
// XXX this should be a "closed" trait
|
||||
#[cfg(not(thumbv6m))]
|
||||
pub unsafe trait HigherThan<C> {}
|
||||
/// A valid ceiling
|
||||
///
|
||||
/// DO NOT IMPLEMENT THIS TRAIT YOURSELF
|
||||
pub unsafe trait Ceiling {}
|
||||
|
||||
#[cfg(not(thumbv6m))]
|
||||
macro_rules! ceiling {
|
||||
($ceiling:ident, $logical:expr) => {
|
||||
/// Ceiling
|
||||
pub struct $ceiling {
|
||||
_0: ()
|
||||
}
|
||||
/// Type-level `>=` operator
|
||||
///
|
||||
/// DO NOT IMPLEMENT THIS TRAIT YOURSELF
|
||||
pub unsafe trait GreaterThanOrEqual<RHS> {}
|
||||
|
||||
unsafe impl CeilingLike for $ceiling {}
|
||||
/// Interrupt hardware level
|
||||
///
|
||||
/// DO NOT IMPLEMENT THIS TRAIT YOURSELF
|
||||
pub unsafe trait Level {
|
||||
fn hw() -> u8;
|
||||
}
|
||||
|
||||
unsafe impl Ceiling for $ceiling {
|
||||
#[inline(always)]
|
||||
fn ceiling() -> u8 {
|
||||
$logical
|
||||
/// A valid priority level
|
||||
///
|
||||
/// DO NOT IMPLEMENT THIS TRAIT YOURSELF
|
||||
pub unsafe trait Priority {}
|
||||
|
||||
fn logical2hw(logical: u8) -> u8 {
|
||||
((1 << PRIORITY_BITS) - logical) << (8 - PRIORITY_BITS)
|
||||
}
|
||||
|
||||
/// Priority 0, the lowest priority
|
||||
pub type P0 = P<::typenum::U0>;
|
||||
|
||||
unsafe impl Priority for P0 {}
|
||||
|
||||
/// Declares tasks
|
||||
#[macro_export]
|
||||
macro_rules! tasks {
|
||||
($krate:ident, {
|
||||
$($task:ident: ($interrupt:ident, $Interrupt:ident, $P:ident),)*
|
||||
}) => {
|
||||
fn main() {
|
||||
$crate::free(|_| {
|
||||
init(unsafe { ::core::ptr::read(0x0 as *const $crate::CMAX )});
|
||||
set_priorities();
|
||||
enable_tasks();
|
||||
});
|
||||
|
||||
idle(unsafe { ::core::ptr::read(0x0 as *const P0) });
|
||||
|
||||
fn set_priorities() {
|
||||
// NOTE(safe) this function runs in an interrupt free context
|
||||
let _nvic = unsafe { &*$crate::NVIC.get() };
|
||||
|
||||
$(
|
||||
{
|
||||
let hw = $crate::$P::hw();
|
||||
if hw != 0 {
|
||||
_nvic.set_priority
|
||||
(::$krate::interrupt::Interrupt::$Interrupt,
|
||||
hw,
|
||||
);
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
// TODO freeze the NVIC.IPR register using the MPU, if available
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn hw_ceiling() -> u8 {
|
||||
((1 << PRIORITY_BITS) - $logical) << (8 - PRIORITY_BITS)
|
||||
fn enable_tasks() {
|
||||
// NOTE(safe) this function runs in an interrupt free context
|
||||
let _nvic = unsafe { &*$crate::NVIC.get() };
|
||||
|
||||
$(
|
||||
_nvic.enable(::$krate::interrupt::Interrupt::$Interrupt);
|
||||
)*
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn is_priority<P>()
|
||||
where
|
||||
P: $crate::Priority,
|
||||
{
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[link_section = ".rodata.interrupts"]
|
||||
#[used]
|
||||
static INTERRUPTS: ::$krate::interrupt::Handlers =
|
||||
::$krate::interrupt::Handlers {
|
||||
$(
|
||||
$interrupt: {
|
||||
extern "C" fn $task(
|
||||
task: ::$krate::interrupt::$Interrupt
|
||||
) {
|
||||
is_priority::<$crate::$P>();
|
||||
::$task(
|
||||
task, unsafe {
|
||||
::core::ptr::read(0x0 as *const $crate::$P)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$task
|
||||
},
|
||||
)*
|
||||
..::$krate::interrupt::DEFAULT_HANDLERS
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(thumbv6m)]
|
||||
macro_rules! ceiling {
|
||||
($($tt:tt)*) => {};
|
||||
}
|
||||
|
||||
ceiling!(C1, 1);
|
||||
ceiling!(C2, 2);
|
||||
ceiling!(C3, 3);
|
||||
ceiling!(C4, 4);
|
||||
ceiling!(C5, 5);
|
||||
ceiling!(C6, 6);
|
||||
ceiling!(C7, 7);
|
||||
ceiling!(C8, 8);
|
||||
ceiling!(C9, 9);
|
||||
ceiling!(C10, 10);
|
||||
ceiling!(C11, 11);
|
||||
ceiling!(C12, 12);
|
||||
ceiling!(C13, 13);
|
||||
ceiling!(C14, 14);
|
||||
ceiling!(C15, 15);
|
||||
|
||||
#[cfg(not(thumbv6m))]
|
||||
macro_rules! higher_than {
|
||||
() => {};
|
||||
($lowest:ident, $($higher:ident,)*) => {
|
||||
$(
|
||||
unsafe impl HigherThan<$lowest> for $higher {}
|
||||
)*
|
||||
|
||||
higher_than!($($higher,)*);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(thumbv6m)]
|
||||
macro_rules! higher_than {
|
||||
($($tt:tt)*) => {};
|
||||
}
|
||||
|
||||
higher_than! {
|
||||
C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15,
|
||||
}
|
||||
|
||||
unsafe impl CeilingLike for C0 {}
|
||||
include!(concat!(env!("OUT_DIR"), "/prio.rs"));
|
||||
|
|
16
tests/cfail.rs
Normal file
16
tests/cfail.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
extern crate compiletest_rs as compiletest;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use compiletest::common::Mode;
|
||||
|
||||
#[test]
|
||||
fn cfail() {
|
||||
let mut config = compiletest::default_config();
|
||||
config.mode = Mode::CompileFail;
|
||||
config.src_base = PathBuf::from(format!("tests/cfail"));
|
||||
config.target_rustcflags =
|
||||
Some("-L target/debug -L target/debug/deps ".to_string());
|
||||
|
||||
compiletest::run_tests(&config);
|
||||
}
|
22
tests/cfail/borrow.rs
Normal file
22
tests/cfail/borrow.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
extern crate cortex_m_srp;
|
||||
|
||||
use cortex_m_srp::{C2, C3, C4, P1, Resource};
|
||||
|
||||
static R1: Resource<i32, C3> = Resource::new(0);
|
||||
static R2: Resource<i32, C2> = Resource::new(0);
|
||||
static R3: Resource<i32, C3> = Resource::new(0);
|
||||
static R4: Resource<i32, C4> = Resource::new(0);
|
||||
|
||||
fn j1(prio: P1) {
|
||||
R1.lock(&prio, |r1, c3| {
|
||||
// CAN borrow a resource with ceiling C when the system ceiling SC > C
|
||||
let r2 = R2.borrow(&c3);
|
||||
|
||||
// CAN borrow a resource with ceiling C when the system ceiling SC == C
|
||||
let r3 = R3.borrow(&c3);
|
||||
|
||||
// CAN'T borrow a resource with ceiling C when the system ceiling SC < C
|
||||
let r4 = R4.borrow(&c3);
|
||||
//~^ error
|
||||
});
|
||||
}
|
41
tests/cfail/lock.rs
Normal file
41
tests/cfail/lock.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
extern crate cortex_m_srp;
|
||||
|
||||
use cortex_m_srp::{C16, C2, P1, P16, P2, P3, Resource};
|
||||
|
||||
static R1: Resource<i32, C2> = Resource::new(0);
|
||||
|
||||
// You CAN'T lock a resource with ceiling C from a task with priority P if P > C
|
||||
fn j1(prio: P3) {
|
||||
R1.lock(&prio, |_, _| {});
|
||||
//~^ error
|
||||
}
|
||||
|
||||
// DON'T lock a resource with ceiling equal to the task priority.
|
||||
// Instead use `claim`
|
||||
fn j2(prio: P2) {
|
||||
R1.lock(&prio, |_, _| {});
|
||||
//~^ error
|
||||
|
||||
// OK
|
||||
let r1 = R1.claim(&prio);
|
||||
}
|
||||
|
||||
// You CAN lock a resource with ceiling C from a task with priority P if C > P
|
||||
fn j3(prio: P1) {
|
||||
// OK
|
||||
R1.lock(&prio, |r1, _| {});
|
||||
}
|
||||
|
||||
static R2: Resource<i32, C16> = Resource::new(0);
|
||||
|
||||
// Tasks with priority less than P16 can't lock a resource with ceiling C16
|
||||
fn j4(prio: P1) {
|
||||
R2.lock(&prio, |_, _| {});
|
||||
//~^ error
|
||||
}
|
||||
|
||||
// Only tasks with priority P16 can claim a resource with ceiling C16
|
||||
fn j5(prio: P16) {
|
||||
// OK
|
||||
let r2 = R2.claim(&prio);
|
||||
}
|
Loading…
Reference in a new issue