1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
# Late resources
Some resources are initialized at runtime after the `init` function returns.
It's important that these resources (static variables) are fully initialized
before tasks are allowed to run, that is they must be initialized while
interrupts are disabled.
The example below shows the kind of code that the framework generates to
initialize late resources.
``` rust,noplayground
#[rtic::app(device = ..)]
mod app {
struct Resources {
x: Thing,
}
#[init]
fn init() -> init::LateResources {
// ..
init::LateResources {
x: Thing::new(..),
}
}
#[task(binds = UART0, resources = [x])]
fn foo(c: foo::Context) {
let x: &mut Thing = c.resources.x;
x.frob();
// ..
}
// ..
}
```
The code generated by the framework looks like this:
``` rust,noplayground
fn init(c: init::Context) -> init::LateResources {
// .. user code ..
}
fn foo(c: foo::Context) {
// .. user code ..
}
// Public API
pub mod init {
pub struct LateResources {
pub x: Thing,
}
// ..
}
pub mod foo {
pub struct Resources<'a> {
pub x: &'a mut Thing,
}
pub struct Context<'a> {
pub resources: Resources<'a>,
// ..
}
}
/// Implementation details
mod app {
// uninitialized static
static mut x: MaybeUninit<Thing> = MaybeUninit::uninit();
#[no_mangle]
unsafe fn main() -> ! {
cortex_m::interrupt::disable();
// ..
let late = init(..);
// initialization of late resources
x.as_mut_ptr().write(late.x);
cortex_m::interrupt::enable(); //~ compiler fence
// exceptions, interrupts and tasks can preempt `main` at this point
idle(..)
}
#[no_mangle]
unsafe fn UART0() {
foo(foo::Context {
resources: foo::Resources {
// `x` has been initialized at this point
x: &mut *x.as_mut_ptr(),
},
// ..
})
}
}
```
An important detail here is that `interrupt::enable` behaves like a *compiler
fence*, which prevents the compiler from reordering the write to `X` to *after*
`interrupt::enable`. If the compiler were to do that kind of reordering there
would be a data race between that write and whatever operation `foo` performs on
`X`.
Architectures with more complex instruction pipelines may need a memory barrier
(`atomic::fence`) instead of a compiler fence to fully flush the write operation
before interrupts are re-enabled. The ARM Cortex-M architecture doesn't need a
memory barrier in single-core context.
|