backport: fix ceiling analysis bug

This commit fixes a ceiling bug where the ceiling of a ready queue will be
incorrectly computed. The analysis was not including the priority of the system
timer interrupt (`SysTick`) in the analysis resulting in a priority ceiling
lower than what's required for memory safety which led to data races.

The bug can be observed in the following program:

``` rust
 #[rtfm::app(device = /* .. */)]
const APP: () = {
    #[init]
    fn init() {
        // ..
    }

    #[task(priority = 2)]
    fn foo(x: i32) {
        // ..
    }

    #[task(priority = 1, spawn = [foo], schedule = [foo])]
    fn bar() {
        // ..
    }

    extern "C" {
        fn EXTI0();
        fn EXTI1();
    }
};
```

Here the framework chooses a priority of `2` for the `SysTick` interrupt
(because it matches the priority of the `schedule`-able task `foo`).

Both `SysTick` and `bar::Spawn.foo` need to access the ready queue (which, in
this case, stores the messages sent to task `foo`) but the framework doesn't
account for the priority of `SysTick` (`2`) and chooses a priority ceiling of
`1` for the ready queue (because it matches the priority of task `bar` which can
spawn `foo`).

The result is that `bar::Spawn.foo` modifies the ready queue *without* a
critical section (because `bar`'s priority matches the priority ceiling of the
ready queue) which is wrong because `SysTick` (priority = `3`) can also modify
the ready queue.
This commit is contained in:
Jorge Aparicio 2019-04-21 21:35:36 +02:00
parent 9b9a80d38e
commit 7da8463980

View file

@ -190,7 +190,7 @@ pub fn app(app: &App) -> Analysis {
} }
// Ceiling analysis of free queues (consumer end point) -- first pass // Ceiling analysis of free queues (consumer end point) -- first pass
// Ceiling analysis of ready queues (producer end point) // Ceiling analysis of ready queues (producer end point) -- first pass
// Also compute more Send-ness requirements // Also compute more Send-ness requirements
let mut free_queues: HashMap<_, _> = app.tasks.keys().map(|task| (task.clone(), 0)).collect(); let mut free_queues: HashMap<_, _> = app.tasks.keys().map(|task| (task.clone(), 0)).collect();
let mut ready_queues: HashMap<_, _> = dispatchers.keys().map(|level| (*level, 0)).collect(); let mut ready_queues: HashMap<_, _> = dispatchers.keys().map(|level| (*level, 0)).collect();
@ -215,10 +215,17 @@ pub fn app(app: &App) -> Analysis {
} }
} }
// Ceiling analysis of ready queues (producer end point) -- second pass
// Ceiling analysis of free queues (consumer end point) -- second pass // Ceiling analysis of free queues (consumer end point) -- second pass
// Ceiling analysis of the timer queue // Ceiling analysis of the timer queue
let mut tq_ceiling = tq_priority; let mut tq_ceiling = tq_priority;
for (priority, task) in app.schedule_calls() { for (priority, task) in app.schedule_calls() {
// the system timer handler contends for the spawnee's dispatcher READY_QUEUE
let c = ready_queues
.entry(app.tasks[task].args.priority)
.or_default();
*c = cmp::max(*c, tq_priority);
if let Some(priority) = priority { if let Some(priority) = priority {
// Users of `schedule` contend for the to-be-spawned task FREE_QUEUE (consumer end point) // Users of `schedule` contend for the to-be-spawned task FREE_QUEUE (consumer end point)
let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut"); let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut");