From 595404c5ffc37ce95ffb2b6999ab85e6818bfa50 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Sun, 9 Apr 2017 22:42:17 -0500 Subject: [PATCH] compile time verified ceilings --- .gitignore | 3 +- Cargo.toml | 22 +- build.rs | 103 +++++- src/checked.rs | 78 ----- src/lib.rs | 775 ++++++++++++++++-------------------------- tests/cfail.rs | 16 + tests/cfail/borrow.rs | 22 ++ tests/cfail/lock.rs | 41 +++ 8 files changed, 498 insertions(+), 562 deletions(-) delete mode 100644 src/checked.rs create mode 100644 tests/cfail.rs create mode 100644 tests/cfail/borrow.rs create mode 100644 tests/cfail/lock.rs diff --git a/.gitignore b/.gitignore index a9d37c560c..6dc3db1ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -target +**/*.rs.bk Cargo.lock +target/ diff --git a/Cargo.toml b/Cargo.toml index c51a72ebda..3bc0347d21 100644 --- a/Cargo.toml +++ b/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" \ No newline at end of file +[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"] diff --git a/build.rs b/build.rs index 46bdb71287..19625e56c9 100644 --- a/build.rs +++ b/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"); } diff --git a/src/checked.rs b/src/checked.rs deleted file mode 100644 index ec4114dfe6..0000000000 --- a/src/checked.rs +++ /dev/null @@ -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, 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, old_basepri: u8) { - locked.set(false); - basepri::write(old_basepri); -} - -/// A totally safe `Resource` that panics on misuse -pub struct Resource { - _marker: PhantomData, - data: UnsafeCell, - locked: VolatileCell, -} - -impl Resource -where - C: Ceiling, -{ - /// Creates a new `Resource` with ceiling `C` - pub const fn new(data: T) -> Resource { - 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(&'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(&'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 Sync for Resource {} diff --git a/src/lib.rs b/src/lib.rs index a7b6412b52..6e69684873 100644 --- a/src/lib.rs +++ b/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(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(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 -where - P: 'static, -{ - _marker: PhantomData, - peripheral: Peripheral

, -} - -impl ResourceP -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

) -> 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 ResourceP -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, - { - 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(&'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(&'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

ResourceP { - /// 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 Sync for ResourceP {} - /// A resource -pub struct Resource { - _marker: PhantomData, +pub struct Resource { + _ceiling: PhantomData, data: UnsafeCell, } impl Resource { - /// 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 Resource> +where + C: 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, + ) -> &'cs T + where + SCEILING: GreaterThanOrEqual, + { 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, + ) -> &'task T + where + CEILING: Cmp, + P: 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( + &'static self, + _priority: &P, + f: F, + ) -> R + where + F: FnOnce(&T, C) -> R, + C: Ceiling, + CEILING: Cmp + Cmp + + Level, + P: Priority, + { + unsafe { + let old_basepri = basepri::read(); + basepri_max::write(::hw()); + barrier!(); + let ret = f( + &*self.data.get(), + C { + _0: (), + _marker: PhantomData, + }, + ); + barrier!(); + basepri::write(old_basepri); + ret + } } } -#[cfg(not(thumbv6m))] -impl Resource +unsafe impl Sync for Resource +where + CEILING: Ceiling, +{ +} + +/// A hardware peripheral as a resource +pub struct Peripheral +where + P: 'static, +{ + peripheral: cortex_m::peripheral::Peripheral

, + _ceiling: PhantomData, +} + +impl Peripheral 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, - { - 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(&'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(&'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

,) + -> Self { + Peripheral { + _ceiling: PhantomData, + peripheral: peripheral, + } } } -impl Resource { - /// 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 Sync for Resource {} - -/// 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(interrupt: I) +impl Peripheral> where - I: Nr, + C: 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, + ) -> &'cs Periph + where + SCEILING: GreaterThanOrEqual, + { + unsafe { &*self.peripheral.get() } + } + + /// See [Resource.claim](./struct.Resource.html#method.claim) + pub fn claim<'task, PRIORITY>( + &'static self, + _priority: &'task P, + ) -> &'task Periph + where + CEILING: Cmp, + P: Priority, + { + unsafe { &*self.peripheral.get() } + } + + /// See [Resource.lock](./struct.Resource.html#method.lock) + pub fn lock( + &'static self, + _priority: &P, + f: F, + ) -> R + where + F: FnOnce(&Periph, C) -> R, + C: Ceiling, + CEILING: Cmp + Cmp + + Level, + P: Priority, + { + unsafe { + let old_basepri = basepri::read(); + basepri_max::write(::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 Sync for Peripheral +where + C: Ceiling, +{ +} + +/// Requests the execution of the task `task` +pub fn request(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 { _0: (), + _marker: PhantomData, } -/// 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 { + _0: (), + _marker: PhantomData, } -/// Usable as a ceiling -// XXX this should be a "closed" trait -pub unsafe trait CeilingLike {} +impl P +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 {} +/// 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 {} - 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

() + 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")); diff --git a/tests/cfail.rs b/tests/cfail.rs new file mode 100644 index 0000000000..44c982ce5a --- /dev/null +++ b/tests/cfail.rs @@ -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); +} diff --git a/tests/cfail/borrow.rs b/tests/cfail/borrow.rs new file mode 100644 index 0000000000..fc8638f54f --- /dev/null +++ b/tests/cfail/borrow.rs @@ -0,0 +1,22 @@ +extern crate cortex_m_srp; + +use cortex_m_srp::{C2, C3, C4, P1, Resource}; + +static R1: Resource = Resource::new(0); +static R2: Resource = Resource::new(0); +static R3: Resource = Resource::new(0); +static R4: Resource = 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 + }); +} diff --git a/tests/cfail/lock.rs b/tests/cfail/lock.rs new file mode 100644 index 0000000000..6b1a41124b --- /dev/null +++ b/tests/cfail/lock.rs @@ -0,0 +1,41 @@ +extern crate cortex_m_srp; + +use cortex_m_srp::{C16, C2, P1, P16, P2, P3, Resource}; + +static R1: Resource = 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 = 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); +}