compile time verified ceilings

This commit is contained in:
Jorge Aparicio 2017-04-09 22:42:17 -05:00
parent 6ac2625a75
commit 595404c5ff
8 changed files with 498 additions and 562 deletions

3
.gitignore vendored
View file

@ -1,2 +1,3 @@
target
**/*.rs.bk
Cargo.lock
target/

View file

@ -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
View file

@ -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");
}

View file

@ -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> {}

View file

@ -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
where
C: CeilingLike,
{
Resource {
_marker: 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 {
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() }
}
}
#[cfg(not(thumbv6m))]
impl<T, C> Resource<T, C>
/// Creates a new resource with ceiling `C`
pub const fn new(data: T) -> Self
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
Resource {
_ceiling: PhantomData,
data: UnsafeCell::new(data),
}
}
}
impl<T, CEILING> Resource<T, C<CEILING>>
where
SC: HigherThan<C>,
C<CEILING>: Ceiling,
{
unsafe { &*self.data.get() }
}
/// Returns an immutable reference to the inner data without locking
/// Borrows the resource for the duration of another resource's critical
/// section
///
/// # 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()) }
}
}
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>(
/// This operation is zero cost and doesn't impose any additional blocking
pub fn borrow<'cs, SCEILING>(
&'static self,
_ctxt: &'ctxt mut Ctxt,
) -> &'ctxt mut T
_system_ceiling: &'cs C<SCEILING>,
) -> &'cs T
where
Ctxt: Context,
SCEILING: GreaterThanOrEqual<CEILING>,
{
unsafe { &mut *self.data.get() }
}
unsafe { &*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
/// Claims the resource at the task with highest 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)
/// 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
I: Nr,
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 {
// NOTE(safe) atomic write
(*NVIC.get()).set_pending(interrupt)
}
}
/// Fake ceiling, indicates that the resource is shared by cooperative tasks
pub struct C0 {
let old_basepri = basepri::read();
basepri_max::write(<CEILING>::hw());
barrier!();
let ret = f(
&*self.data.get(),
C {
_0: (),
}
/// 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;
}
/// Usable as a ceiling
// XXX this should be a "closed" trait
pub unsafe trait CeilingLike {}
/// This ceiling is lower than `C`
// XXX this should be a "closed" trait
#[cfg(not(thumbv6m))]
pub unsafe trait HigherThan<C> {}
#[cfg(not(thumbv6m))]
macro_rules! ceiling {
($ceiling:ident, $logical:expr) => {
/// Ceiling
pub struct $ceiling {
_0: ()
}
unsafe impl CeilingLike for $ceiling {}
unsafe impl Ceiling for $ceiling {
#[inline(always)]
fn ceiling() -> u8 {
$logical
}
#[inline(always)]
fn hw_ceiling() -> u8 {
((1 << PRIORITY_BITS) - $logical) << (8 - PRIORITY_BITS)
_marker: PhantomData,
},
);
barrier!();
basepri::write(old_basepri);
ret
}
}
}
unsafe impl<T, CEILING> Sync for Resource<T, CEILING>
where
CEILING: Ceiling,
{
}
#[cfg(thumbv6m)]
macro_rules! ceiling {
($($tt:tt)*) => {};
/// A hardware peripheral as a resource
pub struct Peripheral<P, CEILING>
where
P: 'static,
{
peripheral: cortex_m::peripheral::Peripheral<P>,
_ceiling: PhantomData<CEILING>,
}
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);
impl<P, C> Peripheral<P, C>
where
C: Ceiling,
{
/// Assigns a ceiling `C` to the `peripheral`
///
/// # Safety
///
/// 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<Periph, CEILING> Peripheral<Periph, C<CEILING>>
where
C<CEILING>: Ceiling,
{
/// 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
}
}
}
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 type-level priority
pub struct P<T> {
_0: (),
_marker: PhantomData<T>,
}
impl<T> P<T>
where
T: Level,
{
pub fn hw() -> u8 {
T::hw()
}
}
/// A valid ceiling
///
/// DO NOT IMPLEMENT THIS TRAIT YOURSELF
pub unsafe trait Ceiling {}
/// Type-level `>=` operator
///
/// DO NOT IMPLEMENT THIS TRAIT YOURSELF
pub unsafe trait GreaterThanOrEqual<RHS> {}
/// Interrupt hardware level
///
/// DO NOT IMPLEMENT THIS TRAIT YOURSELF
pub unsafe trait Level {
fn hw() -> u8;
}
/// 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() };
#[cfg(not(thumbv6m))]
macro_rules! higher_than {
() => {};
($lowest:ident, $($higher:ident,)*) => {
$(
unsafe impl HigherThan<$lowest> for $higher {}
{
let hw = $crate::$P::hw();
if hw != 0 {
_nvic.set_priority
(::$krate::interrupt::Interrupt::$Interrupt,
hw,
);
}
}
)*
higher_than!($($higher,)*);
// TODO freeze the NVIC.IPR register using the MPU, if available
}
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! 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
View 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
View 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
View 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);
}