Skip to content

Commit 1eb83cd

Browse files
committed
Wrote more documentation.
1 parent 48290b9 commit 1eb83cd

File tree

6 files changed

+232
-89
lines changed

6 files changed

+232
-89
lines changed

Test/VeriFast/tasks/vTaskSwitchContext/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ Therefore, the proof's correctness relies on the correctness of our models.
249249
VeriFast is a program verifier for C and not designed to handle any kind of assembly.
250250
The port-specific assembly is called via macros with a port-specific definition.
251251
We redefined these macros to call dummy function prototypes instead.
252-
We equipped these prototypes with VeriFast contracts that capture the semantics of the original assembly code, cf. `proof/port_contracts.h`.
252+
We equipped these prototypes with VeriFast contracts that capture the semantics of the original assembly code, cf. `proof/port_locking_contracts.h`.
253253
This way, VeriFast refers to the contracts to reason about the macro calls and does not have to deal with the assembly code.
254254

255255

@@ -333,7 +333,7 @@ Therefore, the proof's correctness relies on the correctness of our models.
333333
Otherwise, consuming `I` during the release step will fail and consequently the entire proof will fail.
334334

335335
FreeRTOS uses macros with port-specifc definitions to acquire and release locks and to mask and unmask interrupts.
336-
We abstracted these with VeriFast contracts defined in `proof/port_contracts.h`.
336+
We abstracted these with VeriFast contracts defined in `proof/port_locking_contracts.h`.
337337
The contracts ensure that invoking any synchronization mechanism produces or consumes the corresponding invariant.
338338
The invariants are defined in `proof/lock_predicates.h`
339339

Test/VeriFast/tasks/vTaskSwitchContext/proof/README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ This directory contains the bulk of VeriFast formalizations and proofs.
99
│ This file also contains the lemmas to prove that the task state updates
1010
│ in `prvSelectHighestPriorityTask` preserve the lock invariants.
1111
12-
├── port_contracts.h
12+
├── port_locking_contracts.h
1313
│ Contains VeriFast function contracts for macros with port-specific
14-
│ definitions, e.g., the macros to mask interrupts and to acquire AND
15-
release locks. These port-specific definitions often contain contain
16-
│ inline assembly VeriFast cannot reason about. The contracts allow us
14+
│ definitions used to invoke synchronization mechanisms, e.g., masking
15+
interrupts and acquiring locks. These port-specific definitions often
16+
contain inline assembly VeriFast cannot reason about. The contracts allow us
1717
│ to abstract the semantics of the assembly.
1818
1919
├── ready_list_predicates.h

Test/VeriFast/tasks/vTaskSwitchContext/proof/lock_predicates.h

+66-6
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,37 @@
5555
/* ----------------------------------------------------------------------
5656
* Core local data and access restrictions.
5757
* Some data in FreeRTOS such as the pointer to TCB of the task running
58-
* on core `C` may only be accessed from core `C`. Such core-local data
58+
* on core `C` may only be accessed from core `C`. Such core-local data is
5959
* protected by deactivating interrupts.
6060
*/
6161

6262
/*@
63+
// Represents the state of interrupts (i.e. activated or deactivated) on a
64+
// specific core. The state corresponds to the value of the special register
65+
// used for interrupt masking.
6366
predicate interruptState_p(uint32_t coreID, uint32_t state);
6467
68+
// Given an interrupt state (i.e. the value of the special register used to
69+
// control interrupt masking), this function returns whether the state expresses
70+
// that interrupts are deactivated.
6571
fixpoint bool interruptsDisabled_f(uint32_t);
6672
73+
74+
// This predicate expresses that the core we are currently reasoning about
75+
// (expressed by constant `coreID_f`) is allowed to access the core-local data
76+
// protected by interrupt masking.
6777
predicate coreLocalInterruptInv_p() =
68-
[0.5]pointer(&pxCurrentTCBs[coreID_f], ?currentTCB) &*&
69-
integer_(&xYieldPendings[coreID_f], sizeof(BaseType_t), true, _) &*&
78+
// Read permission to the entry of `pxCurrentTCBs` that stores a pointer
79+
// to the task currenlty running on this core
80+
[0.5]pointer(&pxCurrentTCBs[coreID_f], ?currentTCB)
81+
&*&
82+
// Write permission to the entry of `xYieldPendings` for the current core
83+
integer_(&xYieldPendings[coreID_f], sizeof(BaseType_t), true, _)
84+
&*&
85+
// Write permission to the "critical nesting" field of the task
86+
// currently scheduled on this core. The field allows us to check whether
87+
// the task is currently in a critical section. Necessary to check whether,
88+
// we are allowed to context switch.
7089
TCB_criticalNesting_p(currentTCB, ?gCriticalNesting);
7190
@*/
7291

@@ -76,6 +95,13 @@ predicate coreLocalInterruptInv_p() =
7695
*/
7796

7897
/*@
98+
// This predicate is used to remember which locks we're currently holding. Each
99+
// Each constists of a pair `(f,id)`. `f` is the fraction of the lock we held
100+
// before acquiring. Remembering the fraction is important to ensure that we
101+
// reproduce the right fraction of the lock predicate when we release the lock.
102+
// Otherwise, we can run into inconsistencies.
103+
// `id` is the ID of the acquired lock, i.e., either `taskLockID_f` or
104+
// `isrLockID_f`.
79105
predicate locked_p(list< pair<real, int> > lockHistory);
80106
@*/
81107

@@ -88,11 +114,13 @@ predicate locked_p(list< pair<real, int> > lockHistory);
88114
/*@
89115
fixpoint int taskLockID_f();
90116
91-
// Represents an acquired task lock.
117+
// Represents an unacquired task lock.
92118
predicate taskLock_p();
93119
94120
// Represents the invariant associated with the the task lock, i.e.,
95-
// access permissions to the resources protected by the lock.
121+
// access permissions to the resources and code regions protected by the lock.
122+
// These are not relevant to the context-switch proof. Therefore, we leave the
123+
// predicate abstract.
96124
predicate taskLockInv_p();
97125
@*/
98126

@@ -107,7 +135,9 @@ fixpoint int isrLockID_f();
107135
predicate isrLock_p();
108136
109137
// Represents the invariant associated with the the ISR lock, i.e.,
110-
// access permissions to the resources protected by the lock.
138+
// access permissions to the resources and code regions protected by the lock.
139+
// These are not relevant to the context-switch proof. Therefore, we leave the
140+
// predicate abstract.
111141
predicate isrLockInv_p();
112142
@*/
113143

@@ -120,6 +150,36 @@ predicate isrLockInv_p();
120150
/*@
121151
fixpoint int taskISRLockID_f();
122152
153+
// Represents the access rights protected by both the task and the ISR lock.
154+
// Note that FreeRTOS' locking discipline demands the the task lock must be
155+
// acquired before the ISR lock. Once, both locks have been acquired in the
156+
// right order, ths invariant can be produced by invoking the lemma
157+
// `produce_taskISRLockInv` and it can be consumed by invoking
158+
// `consume_taskISRLockInv`. The lemmas ensure that we follow the locking
159+
// discipline.
160+
//
161+
// This invariant expresses fine grained access rights to the following:
162+
// - some global variables:
163+
// + Read permission to the entry of `pxCurrentTCBs` that stores a pointer
164+
// to the task currenly running on the core `coreID_f` our proof currently
165+
// considers. Together with the read permission from
166+
// `coreLocalInterruptInv_p` we get write access to this entry once
167+
// interrupts have been deactivated and both locks have been acquired.
168+
// + Write permission to `uxSchedulerSuspended`.
169+
// + Write permission to `xSchedulerRunning`.
170+
// + Write permission to `uxTopReadyPriority`. This variable stores to top
171+
// priority for which there is a task that is ready to be scheduled.
172+
// - Write access to the ready lists.
173+
// - Fine-grained access permissions for task run states:
174+
// + (RP-All) Read permission for every task.
175+
// + (RP-Current) Read permission for task currently scheduled on this core.
176+
// Together, (RP-All) and (RP-Current) give us a write permission for
177+
// task scheduled on this core.
178+
// + (RP-Unsched) Read permissions for unscheduled tasks.
179+
// Together, (RP-All) and (RP-Unsched) give us write permissions for all
180+
// unscheduled tasks.
181+
// Note that these permissions do not allow us to change the run state of any
182+
// task that is currently scheduled on another core.
123183
predicate taskISRLockInv_p() =
124184
_taskISRLockInv_p(_);
125185

Test/VeriFast/tasks/vTaskSwitchContext/proof/port_contracts.h

-76
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#ifndef PORT_CONTRACTS_H
2+
#define PORT_CONTRACTS_H
3+
4+
/* This file defines function contracts for the macros used to invoke
5+
* synchronization mechanisms, e.g., masking interrupts and acquiring locks.
6+
* The definitions of these macros are port-specific and involve inline
7+
* assembly. VeriFast cannot reason about assembly. Hence, we have to
8+
* abstract the assembly's semantics with these contracts.
9+
*
10+
* Note that we cannot verify that the contracts' correctness. We have to treat
11+
* their correctness as a proof assumption.
12+
*
13+
* Moreover, together with the invariants defined in the proof header
14+
* `lock_predicates.h`, the below contracts define the locking discipline that
15+
* our proof relies on. The file `lock_predicates.h` contains a more detailed
16+
* explanation of the locking discipline.
17+
*
18+
* In short:
19+
* - Data that is only meant to be accessed by the a specific core is protected
20+
* by deactivating interrupts on this core. Access permissions are expressed
21+
* by `coreLocalInterruptInv_p`.
22+
* - The task lock and the ISR lock (i.e. interrupt lock) themselves protect
23+
* data and code regions irrelevant to the switch-context proof. Hence,
24+
* the respective invariants are left abstract, cf. `taskLockInv_p` and
25+
* `isrLockInv_p`.
26+
* - FreeRTOS' locking discipline demands that the task lock is acquired before
27+
* and released after the ISR lock. The contracts defined below ensure that
28+
* we follow this locking discipline.
29+
* - The ready lists and the task run states (i.e. the data most important to
30+
* the context-switch proof) is protected by a combination of the task lock
31+
* and the ISR lock. That is, this data must only be accessed when both
32+
* locks have been acquired in the right order. The invariant
33+
* `taskISRLockInv_p` expresses these access rights. `lock_predicates.h`
34+
* defines lemmas to produce and consume this invariant. The lemmas ensure
35+
* that we only produce the invariant when both locks have been acquired in
36+
* the right order.
37+
*/
38+
39+
// We want our proofs to hold for an arbitrary number of cores.
40+
#undef portGET_CORE_ID
41+
#define portGET_CORE_ID() VF__get_core_num()
42+
43+
/* FreeRTOS core id is always zero based.*/
44+
static uint VF__get_core_num(void);
45+
//@ requires true;
46+
/*@ ensures 0 <= result &*& result < configNUM_CORES &*&
47+
result == coreID_f();
48+
@*/
49+
50+
/*@
51+
// This contant allows proofs to talk about the ID of the core that the
52+
// function we verify is running on. The verified function's contract must
53+
// ensure that this constant holds the value of the current core.
54+
fixpoint uint coreID_f();
55+
56+
lemma void coreID_f_range();
57+
requires true;
58+
ensures 0 <= coreID_f() &*& coreID_f() < configNUM_CORES;
59+
@*/
60+
61+
62+
63+
64+
/* In FreeRTOS interrupts are masked to protect core-local data.
65+
* The invariant `coreLocalInterruptInv_p` expresses what data the masking
66+
* of interrupts protects on a specific core, cf., `lock_predicates.h`.
67+
*
68+
* Deactivating the interrupts on the current core produces the invariant
69+
* `coreLocalInterruptInv_p()` and thereby gives us the permission to access
70+
* the protected data.
71+
*/
72+
#undef portDISABLE_INTERRUPTS
73+
#define portDISABLE_INTERRUPTS VF__portDISABLE_INTERRUPTS
74+
uint32_t VF__portDISABLE_INTERRUPTS();
75+
//@ requires interruptState_p(?coreID, ?state);
76+
/*@ ensures result == state &*&
77+
interruptState_p(coreID, ?newState) &*&
78+
interruptsDisabled_f(newState) == true &*&
79+
interruptsDisabled_f(state) == true
80+
? newState == state
81+
: coreLocalInterruptInv_p();
82+
@*/
83+
84+
85+
/* This macro is used to restore the interrupt state (activated or deactivated)
86+
* to a specific value. When an invokation sets the state from deactivated to
87+
* activated, the invariant `coreLocalInterruptInv_p()` is consumed.
88+
* Thereby, we lose the permission to access the core-local data protected
89+
* by the deactivation of interrupts on this core.
90+
*/
91+
#undef portRESTORE_INTERRUPTS
92+
#define portRESTORE_INTERRUPTS(ulState) VF__portRESTORE_INTERRUPTS(ulState)
93+
void VF__portRESTORE_INTERRUPTS(uint32_t ulState);
94+
/*@ requires interruptState_p(?coreID, ?tmpState) &*&
95+
(interruptsDisabled_f(tmpState) == true && interruptsDisabled_f(ulState) == false)
96+
? coreLocalInterruptInv_p()
97+
: true;
98+
@*/
99+
/*@ ensures interruptState_p(coreID, ulState);
100+
@*/
101+
102+
103+
/* This macro is used to acquire the task lock. The task lock on its own
104+
* protects data and core regions that are not relevant to the context-switch
105+
* proof. Hence, an invocation produces an abstract invariant `taskLockInv_p()`
106+
* and updates the locking history `locked_p(...)` to log that the task log
107+
* has been acquired.
108+
*
109+
* FreeRTOS' locking discipline requires that the task lock must be acquired
110+
* before the ISR lock. The precondition `locked_p(nil)` only allows
111+
* invocations of this macro when no lock has been acquired, yet.
112+
*/
113+
#undef portGET_TASK_LOCK
114+
#define portGET_TASK_LOCK VF__portGET_TASK_LOCK
115+
void VF__portGET_TASK_LOCK();
116+
//@ requires [?f]taskLock_p() &*& locked_p(nil);
117+
//@ ensures taskLockInv_p() &*& locked_p( cons( pair(f, taskLockID_f()), nil) );
118+
119+
120+
/* This macro is used to release the task lock. An invocation consumes the
121+
* task lock invariant `taskLockInv_p` and updates the locking history
122+
* `locked_p(...)` to reflect the release.
123+
*
124+
* FreeRTOS' locking discipline demands that the task lock must be acquired
125+
* before and released after the ISR lock. The precondition
126+
* `locked_p( cons( pair(?f, taskLockID_f()), nil) )` only allows calls to this
127+
* macro when we can prove that we only hold the task lock.
128+
* */
129+
#undef portRELEASE_TASK_LOCK
130+
#define portRELEASE_TASK_LOCK VF__portRELEASE_TASK_LOCK
131+
void VF__portRELEASE_TASK_LOCK();
132+
//@ requires taskLockInv_p() &*& locked_p( cons( pair(?f, taskLockID_f()), nil) );
133+
//@ ensures [f]taskLock_p() &*& locked_p(nil);
134+
135+
136+
/* This macro is used to acquire the ISR lock (i.e. interrupt lock). An
137+
* invocation produces the abstract ISR lock invariant `isrLock_p` and
138+
* updates the locking history `locked_p(...)` to reflect that the lock has
139+
* been acquired.
140+
*/
141+
#undef portGET_ISR_LOCK
142+
#define portGET_ISR_LOCK VF__portGET_ISR_LOCK
143+
void VF__portGET_ISR_LOCK();
144+
//@ requires [?f]isrLock_p() &*& locked_p(?heldLocks);
145+
//@ ensures isrLockInv_p() &*& locked_p( cons( pair(f, isrLockID_f()), heldLocks) );
146+
147+
148+
/* This macro is used to release the ISR lock (i.e. interrupt lock). A call
149+
* consumes the ISR lock invariant and updates the locking history
150+
* `locked_p(...)` to reflect the release.
151+
*/
152+
#undef portRELEASE_ISR_LOCK
153+
#define portRELEASE_ISR_LOCK VF__portRELEASE_ISR_LOCK
154+
void VF__portRELEASE_ISR_LOCK();
155+
//@ requires isrLockInv_p() &*& locked_p( cons( pair(?f, isrLockID_f()), ?heldLocks) );
156+
//@ ensures [f]isrLock_p() &*& locked_p(heldLocks);
157+
158+
159+
#endif /* PORT_CONTRACTS_H */

0 commit comments

Comments
 (0)