Skip to content

Commit 04a311a

Browse files
Track/embedded (#27)
* added scheduler task * added openocd install disclaimer * initial Embassy track * added eeprom and i2c * fixed broken link --------- Co-authored-by: Irina Nita <[email protected]>
1 parent a8e933b commit 04a311a

File tree

6 files changed

+682
-238
lines changed

6 files changed

+682
-238
lines changed

docs/embedded/0.Tock/index.md

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
---
2+
title: Tock
3+
position: 0
4+
---
5+
6+
# Introduction
7+
8+
## Prerequisites
9+
10+
:::note Ubuntu Virtual Machine
11+
This workshop will not work on Windows systems.
12+
You can use the Ubuntu VM we provide [here](https://drive.google.com/file/d/1WSUo29d9Z8bmcjurvkDUmoAgq1TqaW4H/view?usp=sharing) (only works on VirtualBox).
13+
The username and password are both `ipwembedded`.
14+
The VM has the port 3033 forwarded for SSH connection.
15+
:::
16+
17+
If you did not attend the **Tock Workshop**, please follow the [Setup Tutorial](../../tock_workshop/index.md).
18+
19+
## Getting Started
20+
21+
For this track we will be using the **Nucleo-F429ZI** boards. You will need to **change the branch** you are working on, but first make sure you commit your changes.
22+
23+
```shell
24+
git add .
25+
git commit -m "tock workshop progress"
26+
```
27+
28+
Then, to fetch the branch and work on it, run:
29+
30+
```shell
31+
git fetch
32+
git checkout track/embedded
33+
```
34+
35+
The board's main can be found in the `boards/nucleo_f429zi` subfolder. Try to flash the kernel to the board, using the board's `Makefile`. After you are done flashing, connect to the board using `tockloader listen`
36+
37+
```shell
38+
[INFO ] No device name specified. Using default name "tock".
39+
[INFO ] No serial port with device name "tock" found.
40+
[INFO ] Found 2 serial ports.
41+
Multiple serial port options found. Which would you like to use?
42+
[0] /dev/cu.debug-console - n/a
43+
[1] /dev/cu.usbmodem1303 - STM32 STLink
44+
45+
Which option? [0] 1
46+
[INFO ] Using "/dev/cu.usbmodem1303 - STM32 STLink".
47+
[INFO ] Listening for serial output.
48+
49+
tock$
50+
```
51+
52+
## Customize your kernel
53+
54+
After connecting to Tock's terminal, you can run `help` to see the supported commands. One of them is `reset` and by running it, you can see the default *"welcome"* message.
55+
56+
```shell
57+
tock$ reset
58+
Initialization complete. Entering main loop
59+
tock$
60+
```
61+
62+
Personalize your kernel, by changing the hostname and the welcome message.
63+
64+
## Print Counter Capsule
65+
66+
For this task, you will need to build a capsule that prints a custom message each time it receives a print command from an application, along with a message counter representing the number of commands received. Remember that you will need to implement the `SyscallDriver` trait.
67+
68+
### The simple way
69+
70+
Simplest method to do this is to add a `counter` field in the capsule's structure. One issue you will most likely encounter is that the `command` method required by the `SyscallDriver` trait has a immutable reference to `&self`, so you may need to wrap the counter in a wrapper that allows for inner mutability, such as `Cell`.
71+
72+
### The Tock way
73+
74+
One issue with the previous approach is that the counter would be shared between the applications. This could be an issue for mutually distrustful application. Fortunately, Tock has a mechanism in place for such situations, called `Grant`s, which are per-process memory regions allocated by the kernel in a process memory region for a capsule to store that process’s state.
75+
76+
![Grant](../assets/grant.png)
77+
78+
To access this region, you can simply add a new `grant` field in the capsule structure.
79+
80+
```rust title="capsules/extra/src/print_counter.rs"
81+
use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
82+
83+
// TODO: Define `App` structure. Make sure to satisfy trait constraints.
84+
struct App;
85+
86+
struct PrintCounter {
87+
grant: Grant<
88+
App,
89+
UpcallCount<0>, // Number of upcalls supported by the capsule
90+
AllowRoCount<0>, // Number of Read-Only buffers supported
91+
AllowRwCount<0>, // Number of Read-Write buffers supported
92+
>,
93+
}
94+
```
95+
96+
As before, we will need to define a component for this capsule, to initialize it.
97+
98+
```rust title="boards/components/src/print_counter.rs"
99+
#[macro_export]
100+
macro_rules! print_counter_component_static {
101+
($(,)?) => {{
102+
kernel::static_buf!(capsules_extra::print_counter::PrintCounter)
103+
};};
104+
}
105+
106+
pub struct PrintCounterComponent;
107+
108+
impl Component for PrintCounterComponent {
109+
type StaticInput = &'static mut MaybeUninit<capsules_extra::print_counter::PrintCounter>;
110+
111+
type Output = &'static capsules_extra::print_counter::PrintCounter;
112+
113+
fn finalize(self, static_memory: Self::StaticInput) -> Self::Output {
114+
todo!()
115+
}
116+
}
117+
```
118+
119+
Grants are a sensitive component of the operating system, so the creation and management operations are considered unsafe, and require
120+
privileges to perform. Tock restricts these privileged operations through the use of capabilities, which are tokens implementing `unsafe` traits. Because capsules are forbidden from using unsafe code, these tokens cannot be forged.
121+
122+
Creating a grant is requires a reference to the board's kernel, and a driver number, so we will need to add these parts in the components.
123+
124+
```rust title="boards/components/src/print_counter.rs"
125+
pub struct PrintCounterComponent {
126+
driver_num: usize,
127+
board_kernel: &'static kernel::Kernel,
128+
}
129+
130+
impl PrintCounterComponent {
131+
pub fn new(driver_num: usize, board_kernel: &'static kernel::Kernel) -> Self {
132+
Self {
133+
driver_num,
134+
board_kernel,
135+
}
136+
}
137+
}
138+
```
139+
140+
The capability needed for grant creating is called `MemoryAllocationCapability`, and it can be found in the `kernel::capabilities` module. The `kernel` also exposes the `crate_capability!` macro for ease of use.
141+
142+
```rust title="boards/components/src/print_counter.rs"
143+
impl Component for PrintCounterComponent {
144+
// ...
145+
146+
fn finalize(self, static_memory: Self::StaticInput) -> Self::Output {
147+
let grant_cap = create_capability!(capabilities::MemoryAllocationCapability);
148+
let grant = self.board_kernel.create_grant(self.driver_num, &grant_cap);
149+
150+
static_memory.write(capsules_extra::print_counter::PrintCounter::new(grant))
151+
}
152+
}
153+
```
154+
155+
:::note `new` constructor
156+
You will also need to implement the `new` constructor for the `PrintCounter` capsule.
157+
:::
158+
159+
Next, you must implement the `SyscallDriver` trait, where the command logic will be. For the `allocate_grant` method implementation, it is enough to use the `enter` method of the Grant which takes a closure with two parameters.
160+
161+
```rust
162+
fn allocate_grant(&self, process_id: kernel::ProcessId) -> Result<(), kernel::process::Error> {
163+
self.grant.enter(process_id, |_, _| {})
164+
}
165+
```
166+
167+
For the command logic, you must also use the `enter` API. The first parameter of the closure will be a mutable reference to a `GrantData` wrapper over the previously defined `App`. The wrapper is transparent, meaning it permits accessing fields of the generic type.
168+
169+
The next step is configuring the capsule in the board's main file. Remember you need to add the capsule in the `NucleoF429ZI` structure, the `SyscallDriverLookup` and initialize the printer counter capsule.
170+
171+
## The role of a Scheduler
172+
173+
This task aims to illustrate the importance of OS preemption in the context of untrusted applications. Currently the scheduler used by the board is `kernel::scheduler::RoundRobinSched`, which implements a classical scheduling algorithm, allowing each process to run up to a maximum time slice called **quanta**. In the event that an application tries starving all other processes, the kernel will interrupt the malicious application after its quanta expires and will then schedule another process.
174+
175+
### Trust but verify
176+
177+
:::warning app flashing
178+
<!-- If you are on Windows/VM, using `tockloader install` (which the `Makefile` command uses) may not work, so you will need to bundle the application in a single binary, which you will need to load in the *"old-fashioned"* way, by modifying the `APP` variable in `boards/nucleo_f429zi/Makefile` to point to the bundle TBF.
179+
180+
To create the bundle, you will need to concatenate the two `.tbf`s using `cat`:
181+
182+
```shell
183+
cat app1.tbf app2.tbf > bundle.tbf
184+
```
185+
186+
Then, modify the `Makefile`:
187+
188+
```makefile title="boards/nucleo_f429zi/Makefile"
189+
APP=<path/to/bundle.tbf>
190+
```
191+
192+
Lastly, flash it by running `make program`. -->
193+
194+
You will need to install `openocd` first.
195+
196+
```shell
197+
# Linux
198+
sudo apt install openocd
199+
200+
# Mac
201+
brew install openocd
202+
```
203+
204+
:::
205+
206+
Your task will be to verify the previous claims, by flashing two C applications. One of them will be the `blink` example. After flashing the kernel by running `make flash` in the board's main directory (`boards/nucleo_f429zi`), you can load the application by running `make flash` in the example's root folder (`example/blink`).
207+
208+
As there are no *"malicious"* examples, we will have to add them on our own. In this case, an app that would print a message, then just infinitely spin in a `while` loop is enough. For this, you can adapt the `examples/c_hello` example, the flash it.
209+
210+
If you managed to flash both applications, you should be able to connect to the board using `tockloader listen` and see a similar output when running `list`:
211+
212+
```shell
213+
tockloader listen
214+
[INFO ] No device name specified. Using default name "tock".
215+
[INFO ] No serial port with device name "tock" found.
216+
[INFO ] Found 2 serial ports.
217+
Multiple serial port options found. Which would you like to use?
218+
[0] /dev/cu.debug-console - n/a
219+
[1] /dev/cu.usbmodem1103 - STM32 STLink
220+
221+
Which option? [0] 1
222+
[INFO ] Using "/dev/cu.usbmodem1103 - STM32 STLink".
223+
[INFO ] Listening for serial output.
224+
225+
tock$ list
226+
PID ShortID Name Quanta Syscalls Restarts Grants State
227+
0 Unique blink 0 289 0 1/11 Yielded
228+
1 Unique ws-demo 640 6 0 1/11 Running
229+
tock$
230+
```
231+
232+
You should be able to see the on-board LEDs flashing on the board.
233+
234+
### Cooperation flaw
235+
236+
Now, let's test the same scenario, but with a cooperative scheduling mechanism. You have to first change the kernel's scheduler in the board's `main.rs` file to use the `scheduler::cooperative::CooperativeSched`. Then you must re-flash the kernel by running `make flash`. Fortunately flashing the kernel should preserve the applications, so you will not have to re-flash them as well.
237+
238+
After you are done flashing, check that both applications are present. You can try to reset the board a few times by running `reset` in tock's process console (the terminal you open by running `tockloader listen`).

docs/embedded/1.Embassy/assets/i2c_10bit_address_transmission.svg

Lines changed: 1 addition & 0 deletions
Loading

docs/embedded/1.Embassy/assets/i2c_7bit_address_transmission.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)