|
1 | | -# Intorduction |
| 1 | +# Introduction |
2 | 2 |
|
| 3 | +## Prerequisites |
3 | 4 |
|
| 5 | +If you did not attend the **Tock Workshop**, please follow the [Setup Tutorial](../tock_workshop/index.md). |
| 6 | + |
| 7 | +## Getting Started |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +```shell |
| 12 | +git add . |
| 13 | +git commit -m "tock workshop progress" |
| 14 | +``` |
| 15 | + |
| 16 | +Then, to fetch the branch and work on it, run: |
| 17 | + |
| 18 | +```shell |
| 19 | +git fetch |
| 20 | +git checkout track/embedded |
| 21 | +``` |
| 22 | + |
| 23 | +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` |
| 24 | + |
| 25 | +```shell |
| 26 | +[INFO ] No device name specified. Using default name "tock". |
| 27 | +[INFO ] No serial port with device name "tock" found. |
| 28 | +[INFO ] Found 2 serial ports. |
| 29 | +Multiple serial port options found. Which would you like to use? |
| 30 | +[0] /dev/cu.debug-console - n/a |
| 31 | +[1] /dev/cu.usbmodem1303 - STM32 STLink |
| 32 | + |
| 33 | +Which option? [0] 1 |
| 34 | +[INFO ] Using "/dev/cu.usbmodem1303 - STM32 STLink". |
| 35 | +[INFO ] Listening for serial output. |
| 36 | + |
| 37 | +tock$ |
| 38 | +``` |
| 39 | + |
| 40 | +## Customize your kernel |
| 41 | + |
| 42 | +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. |
| 43 | + |
| 44 | +```shell |
| 45 | +tock$ reset |
| 46 | +Initialization complete. Entering main loop |
| 47 | +tock$ |
| 48 | +``` |
| 49 | + |
| 50 | +Personalize your kernel, by changing the hostname and the welcome message. |
| 51 | + |
| 52 | +## Print Counter Capsule |
| 53 | + |
| 54 | +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. |
| 55 | + |
| 56 | +### The simple way |
| 57 | + |
| 58 | +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`. |
| 59 | + |
| 60 | +### The Tock way |
| 61 | + |
| 62 | +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. |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | +To access this region, you can simply add a new `grant` field in the capsule structure. |
| 67 | + |
| 68 | +```rust title="capsules/extra/src/print_counter.rs" |
| 69 | +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; |
| 70 | + |
| 71 | +// TODO: Define `App` structure. Make sure to satisfy trait constraints. |
| 72 | +struct App; |
| 73 | + |
| 74 | +struct PrintCounter { |
| 75 | + grant: Grant< |
| 76 | + App, |
| 77 | + UpcallCount<0>, // Number of upcalls supported by the capsule |
| 78 | + AllowRoCount<0>, // Number of Read-Only buffers supported |
| 79 | + AllowRwCount<0>, // Number of Read-Write buffers supported |
| 80 | + >, |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +As before, we will need to define a component for this capsule, to initialize it. |
| 85 | + |
| 86 | +```rust title="boards/components/src/print_counter.rs" |
| 87 | +#[macro_export] |
| 88 | +macro_rules! print_counter_component_static { |
| 89 | + ($(,)?) => {{ |
| 90 | + kernel::static_buf!(capsules_extra::print_counter::PrintCounter) |
| 91 | + };}; |
| 92 | +} |
| 93 | + |
| 94 | +pub struct PrintCounterComponent; |
| 95 | + |
| 96 | +impl Component for PrintCounterComponent { |
| 97 | + type StaticInput = &'static mut MaybeUninit<capsules_extra::print_counter::PrintCounter>; |
| 98 | + |
| 99 | + type Output = &'static capsules_extra::print_counter::PrintCounter; |
| 100 | + |
| 101 | + fn finalize(self, static_memory: Self::StaticInput) -> Self::Output { |
| 102 | + todo!() |
| 103 | + } |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +Grants are a sensitive component of the operating system, so the creation and management operations are considered unsafe, and require |
| 108 | +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. |
| 109 | + |
| 110 | +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. |
| 111 | + |
| 112 | +```rust title="boards/components/src/print_counter.rs" |
| 113 | +pub struct PrintCounterComponent { |
| 114 | + driver_num: usize, |
| 115 | + board_kernel: &'static kernel::Kernel, |
| 116 | +} |
| 117 | + |
| 118 | +impl PrintCounterComponent { |
| 119 | + pub fn new(driver_num: usize, board_kernel: &'static kernel::Kernel) -> Self { |
| 120 | + Self { |
| 121 | + driver_num, |
| 122 | + board_kernel, |
| 123 | + } |
| 124 | + } |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +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. |
| 129 | + |
| 130 | +```rust title="boards/components/src/print_counter.rs" |
| 131 | +impl Component for PrintCounterComponent { |
| 132 | + // ... |
| 133 | + |
| 134 | + fn finalize(self, static_memory: Self::StaticInput) -> Self::Output { |
| 135 | + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); |
| 136 | + let grant = self.board_kernel.create_grant(self.driver_num, &grant_cap); |
| 137 | + |
| 138 | + static_memory.write(capsules_extra::print_counter::PrintCounter::new(grant)) |
| 139 | + } |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +:::note `new` constructor |
| 144 | +You will also need to implement the `new` constructor for the `PrintCounter` capsule. |
| 145 | +::: |
| 146 | + |
| 147 | +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. |
| 148 | + |
| 149 | +```rust |
| 150 | +fn allocate_grant(&self, process_id: kernel::ProcessId) -> Result<(), kernel::process::Error> { |
| 151 | + self.grant.enter(process_id, |_, _| {}) |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +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. |
| 156 | + |
| 157 | +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. |
0 commit comments