├── .github └── workflows │ ├── autoblack.yml │ └── tockloader.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── default.nix ├── docs ├── .gitignore ├── app_installed.md ├── app_padding.md ├── app_tab.md ├── board_interface.md ├── bootloader_serial.md ├── display.md ├── exceptions.md ├── flash_file.md ├── generate_docs.py ├── helpers.md ├── index.md ├── jlinkexe.md ├── main.md ├── nrfprog.md ├── openocd.md ├── tab.md ├── tbfh.md ├── tickv.md └── tockloader.md ├── mkdocs.yml ├── pyproject.toml ├── requirements.txt └── tockloader ├── __init__.py ├── _version.py ├── app_installed.py ├── app_padding.py ├── app_tab.py ├── board_interface.py ├── bootloader_serial.py ├── display.py ├── exceptions.py ├── flash_file.py ├── helpers.py ├── jlinkexe.py ├── kernel_attributes.py ├── main.py ├── nrfjprog.py ├── openocd.py ├── static ├── bscan_spi_xc7a100t.bit └── bscan_spi_xc7a35t.bit ├── stlink.py ├── tab.py ├── tbfh.py ├── tickv.py └── tockloader.py /.github/workflows/autoblack.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action that uses Black to reformat the Python code in an incoming pull request. 2 | # If all Python code in the pull request is compliant with Black then this Action does nothing. 3 | # Othewrwise, Black is run and its changes are committed back to the incoming pull request. 4 | # https://github.com/cclauss/autoblack 5 | 6 | name: autoblack 7 | on: [pull_request] 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Set up Python 3.13 14 | uses: actions/setup-python@v1 15 | with: 16 | python-version: 3.13 17 | - name: Install Black 18 | run: pip install black 19 | - name: Run black --check . 20 | run: black --check . 21 | - name: If needed, commit black changes to the pull request 22 | if: failure() 23 | run: | 24 | black . 25 | git config --global user.name 'autoblack' 26 | git config --global user.email 'tock@users.noreply.github.com' 27 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY 28 | git checkout $GITHUB_HEAD_REF 29 | git commit -am "fixup: Format Python code with Black" 30 | git push 31 | -------------------------------------------------------------------------------- /.github/workflows/tockloader.yml: -------------------------------------------------------------------------------- 1 | name: Tockloader Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Python 3.13 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: 3.13 20 | - name: Install dependencies 21 | run: | 22 | pip install pipx 23 | pipx install build 24 | pyproject-build 25 | pipx install . 26 | - name: Run basic commands 27 | run: | 28 | tockloader --version 29 | tockloader list-known-boards 30 | 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | *.egg-info 4 | *.pyc 5 | __pycache__ 6 | site 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 The Tock Project Developers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include tockloader/static/*.bit 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![TockLoader](http://www.tockos.org/assets/img/tockloader.svg#a "Tockloader Logo") 2 | 3 | Tool for programming Tock onto hardware boards. 4 | 5 | Install 6 | ------- 7 | 8 | ``` 9 | pip3 install pipx 10 | pipx install tockloader 11 | ``` 12 | 13 | If you want tab completions: 14 | 15 | ``` 16 | register-python-argcomplete tockloader >> ~/.bashrc 17 | ``` 18 | 19 | Usage 20 | ----- 21 | 22 | This tool installs a binary called `tockloader`, which supports several commands. 23 | 24 | ### Primary Commands 25 | 26 | These are the main commands for managing apps on a board. 27 | 28 | #### `tockloader install` 29 | 30 | Load Tock applications on to the board. Use `--no-replace` to install 31 | multiple copies of the same app. 32 | 33 | #### `tockloader update` 34 | 35 | Update an application that is already flashed to the board with a new 36 | binary. 37 | 38 | #### `tockloader uninstall [application name(s)]` 39 | 40 | Remove an application from flash by its name. 41 | 42 | 43 | ### Board Inspection Commands 44 | 45 | These query the board for its current state. 46 | 47 | #### `tockloader list` 48 | 49 | Print information about the apps currently loaded onto the board. 50 | 51 | #### `tockloader info` 52 | 53 | Show all properties of the board. 54 | 55 | 56 | ### Utility Commands 57 | 58 | These provide other helpful features. 59 | 60 | #### `tockloader listen` 61 | 62 | Listen to UART `printf()` data from a board. Use the option `--rtt` to use 63 | Segger's RTT listener instead of using a serial port. 64 | 65 | 66 | ### Other Commands 67 | 68 | These provide more internal functionality. 69 | 70 | #### `tockloader flash` 71 | 72 | Load binaries onto hardware platforms that are running a compatible bootloader. 73 | This is used by the [TockOS](https://github.com/helena-project/tock) Make system 74 | when kernel binaries are programmed to the board with `make program`. 75 | 76 | #### `tockloader inspect-tab` 77 | 78 | Show details about a compiled TAB file. 79 | 80 | #### `tockloader enable-app [application name(s)]` 81 | 82 | Enable an app so that the kernel will run it at boot. 83 | 84 | #### `tockloader disable-app [application name(s)]` 85 | 86 | Disable an app so that the kernel will not start it at boot. 87 | 88 | #### `tockloader sticky-app [application name(s)]` 89 | 90 | Mark an app as sticky so that the `--force` flag is required to uninstall it. 91 | 92 | #### `tockloader unsticky-app [application name(s)]` 93 | 94 | Remove the sticky flag from an app. 95 | 96 | #### `tockloader list-attributes` 97 | 98 | Show all of the attributes that are stored on the board. 99 | 100 | #### `tockloader set-attribute [attribute key] [attribute value]` 101 | 102 | Set a particular attribute key to the specified value. This will overwrite 103 | an existing attribute if the key matches. 104 | 105 | #### `tockloader remove-attribute [attribute key]` 106 | 107 | Remove a particular attribute from the board. 108 | 109 | #### `tockloader dump-flash-page [page number]` 110 | 111 | Show the contents of a page of flash. 112 | 113 | #### `tockloader read [address] [# bytes]` 114 | 115 | Read arbitrary flash memory from the board. 116 | 117 | #### `tockloader write [address] [# bytes] [value]` 118 | 119 | Write arbitrary flash memory on the board with a specific value. 120 | 121 | #### `tockloader list-known-boards` 122 | 123 | Print which boards tockloader has default settings for built-in. 124 | 125 | #### `tockloader set-start-address [address]` 126 | 127 | Set the jump address the bootloader uses for the location of the kernel. 128 | 129 | #### `tockloader tbf tlv add|modify|delete [TLVNAME]` 130 | 131 | Interact with TLV structures within a TBF. 132 | 133 | #### `tockloader tbf credential add|delete [credential type]` 134 | 135 | Add and remove credentials in the TBF footer. 136 | 137 | #### `tockloader tbf convert [output format]` 138 | 139 | Convert a TBF to a different format. 140 | 141 | #### `tockloader tickv get|append|invalidate|dump|cleanup|reset [key] [value]` 142 | 143 | Interact with a TicKV key-value database. 144 | 145 | 146 | Specifying the Board 147 | -------------------- 148 | 149 | For tockloader to know how to interface with a particular hardware board, 150 | it tries several options: 151 | 152 | 1. Read the parameters from the bootloader. Tockloader assumes it can open a 153 | serial connection to a 154 | [tock-bootloader](https://github.com/tock/tock-bootloader/) on the board. 155 | 156 | 2. Use `JLinkExe` and `OpenOCD` to discover known boards. 157 | 158 | 3. Use the `--board` command line flag and a list of known boards. 159 | 160 | 4. Use individual command line flags that specify how to interact with the 161 | board. 162 | 163 | If command line flags are passed they take priority over any automatically 164 | discovered options. 165 | 166 | Tockloader has hardcoded parameters for a variety of boards. You can list these 167 | with: 168 | 169 | tockloader list-known-boards 170 | 171 | To use a known board, if it is not automatically discovered, you can: 172 | 173 | tockloader [command] --board [board] 174 | 175 | If your board is not a known board, you can specify the required parameters 176 | via command line options. Note, you also need to provide a name for the board. 177 | 178 | tockloader [command] --board [board] --arch [arch] --page-size [page_size] 179 | 180 | - `board`: The name of the board. This helps prevent incompatible applications 181 | from being flashed on the wrong board. 182 | - `arch`: The architecture of the board. Likely `cortex-m0` or `cortex-m4`. 183 | - `page_size`: The size in bytes of the smallest erasable unit in flash. 184 | 185 | Specifying the Communication Channel 186 | ------------------------------------ 187 | 188 | Tockloader defaults to using a serial connection to an on-chip bootloader to 189 | program and interact with a board. If you need to use a different communication 190 | mechanism, you can specify what Tockloader should use with command line 191 | arguments. Note, Tockloader's board autodiscovery process also selects different 192 | communication channels based on which board it finds. 193 | 194 | To use a JTAG interface using JLinkExe, specify `--jlink`. JLinkExe requires 195 | knowing the device type of the MCU on the board. 196 | 197 | tockloader [command] --board [board] --arch [arch] --page-size [page_size] \ 198 | --jlink --jlink-cmd [jlink_cmd] --jlink-device [device] \ 199 | --jlink-speed [speed] --jlink-if [if] \ 200 | --jlink-serial-number [serial_number] 201 | 202 | - `jlink_cmd`: The JLink executable to invoke. Defaults to `JLinkExe` on 203 | Mac/Linux, and `JLink` on Windows. 204 | - `device`: The JLinkExe device identifier. 205 | - `speed`: The speed value to pass to JLink. Defaults to 1200. 206 | - `if`: The interface to pass to JLink. 207 | - `serial-number`: The serial number of the target board to use with JLink. 208 | 209 | Tockloader can also do JTAG using OpenOCD. OpenOCD needs to know which config 210 | file to use. 211 | 212 | tockloader [command] --board [board] --arch [arch] --page-size [page_size] \ 213 | --openocd --openocd-board [openocd_board] \ 214 | --openocd-cmd [openocd_cmd] \ 215 | --openocd-options [openocd_options] \ 216 | --openocd-commands [openocd_commands] 217 | 218 | - `openocd_board`: The `.cfg` file in the board folder in OpenOCD to use. 219 | - `openocd_cmd`: The OpenOCD executable to invoke. Defaults to `openocd`. 220 | - `openocd_options`: A list of Tock-specific flags used to customize how 221 | Tockloader calls OpenOCD based on experience with various boards and their 222 | quirks. Options include: 223 | - `noreset`: Removes the command `reset init;` from OpenOCD commands. 224 | - `nocmdprefix`: Removes the commands `init; reset init; halt;` from OpenOCD 225 | commands. 226 | - `workareazero`: Adds the command `set WORKAREASIZE 0;` to OpenOCD commands. 227 | - `resume`: Adds the commands `soft_reset_halt; resume;` to OpenOCD commands. 228 | - `openocd_commands`: This sets a custom OpenOCD command string to allow 229 | Tockloader to program arbitrary chips with OpenOCD before support for the 230 | board is officially include in Tockloader. The following main operations can 231 | be customized: 232 | - `program`: Operation used to write a binary to the chip. 233 | - `read`: Operation used to read arbitrary flash memory on the chip. 234 | - `erase`: Operation that erases arbitrary ranges of flash memory on the chip. 235 | 236 | The custom values are specified as key=value pairs, for example, 237 | `--openocd_commands 'program=write_image; halt;' 'erase=flash fillb 238 | {address:#x} 0xff 512;'`. Operation strings can include wildcards which will 239 | get set with the correct value by Tockloader: 240 | - `{{binary}}`: The binary file path. 241 | - `{address:#x}`: The specified address for the binary to be programmed at. 242 | - `{length}`: The number of bytes. Only valid for the `read` operation. 243 | 244 | For STM32 boards, Tockloader supports 245 | [STLINK](https://github.com/stlink-org/stlink). The stlink tool knows how to 246 | interface with the boards, so there are not many flags. 247 | 248 | tockloader [command] --board [board] --arch [arch] --page-size [page_size] \ 249 | --stlink \ 250 | --stinfo-cmd [stinfo_cmd] --stflash-cmd [stflash_cmd] 251 | 252 | - `stinfo_cmd`: The st-info executable to invoke. Defaults to `st-info`. 253 | - `stflash_cmd`: The st-flash executable to invoke. Defaults to `st-flash`. 254 | 255 | Finally, Tockloader can treat a local file as though it were the flash contents 256 | of a board. The file can then be loaded separately onto a board. 257 | 258 | tockloader [command] --flash-file [filepath] 259 | 260 | - `filepath`: The file to use as the flash contents. Will be created if it 261 | doesn't exist. 262 | 263 | 264 | Example Usage 265 | ------------- 266 | 267 | Install an app, make sure it's up to date, and make sure it's the only app on 268 | the board: 269 | 270 | tockloader install --make --erase 271 | 272 | Get all info from the board that can be used to help debugging: 273 | 274 | tockloader info 275 | 276 | Print additionally debugging information. This can be helpful when using JTAG. 277 | 278 | tockloader install --debug 279 | 280 | Get `printf()` data from a board: 281 | 282 | tockloader listen 283 | 284 | Additional Flags 285 | ---------------- 286 | 287 | There are additional flags that might be useful for customizing tockloader's 288 | operation based on the requirements of a particular hardware platform. 289 | 290 | - `--app-address`: Manually specify the address at the beginning of where apps 291 | are stored. This can be in hex or decimal. 292 | - `--bundle-apps`: This forces tockloader to write all apps as a concatenated 293 | bundle using only a single flash command. This will require that anytime any 294 | app changes in any way (e.g. its header changes or the app is updated or a new 295 | app is installed) all apps are re-written. 296 | - `--layout`: Specify exactly how apps and padding apps should be written to the 297 | board. This implies `--erase` and `--force` as all existing (even sticky) apps 298 | will be removed. 299 | 300 | The layout is specified as a string of how apps from TBFs and padding apps 301 | should be written to the board. The syntax for the layout uses the following 302 | identifiers: 303 | 304 | - `T`: indicates to install a TBF app. 305 | - `p`: indicates to install a padding app of `` bytes. 306 | 307 | For example `--layout Tp1024TT` specifies to install the first app at the 308 | `app-address`, then install a 1024 byte padding app, then install the second 309 | app, then install the third app. No board-specific alignment or sizing will 310 | be used; the apps will be installed exactly as described. It can be helpful 311 | to use `tockloader list --map` to view how the apps were actually installed. 312 | 313 | Credentials and Integrity Support 314 | --------------------------------- 315 | 316 | Tockloader supports working with credentials stored in the TBF footer. 317 | Tockloader will attempt to verify that stored credentials are valid for the 318 | given TBF. For credentials that require keys to verify, Tockloader can check the 319 | credential using: 320 | 321 | $ tockloader inspect-tab --verify-credentials [list of key files] 322 | example: 323 | $ tockloader inspect-tab --verify-credentials tockkey.public.der 324 | 325 | Tockloader can also add credentials. To add a hash: 326 | 327 | $ tockloader tbf credential add sha256 328 | 329 | To add an RSA signature: 330 | 331 | $ tockloader tbf credential add rsa2048 --private-key tockkey2048.private.der --public-key tockkey2048.public.der 332 | 333 | To remove credentials: 334 | 335 | $ tockloader tbf credential delete sha256 336 | 337 | 338 | Features 339 | -------- 340 | 341 | - Supported communication protocols 342 | - Serial over USB 343 | - Segger JLinkExe JTAG support 344 | - OpenOCD JTAG support 345 | - JLink RTT listener 346 | - JSON output using `--output-format json` for certain commands. 347 | 348 | 349 | Complete Install Instructions 350 | ----------------------------- 351 | 352 | Tockloader is a Python script that is installed as an executable. 353 | To use Tockloader, you need python3, a couple dependencies, and 354 | the Tockloader package. 355 | 356 | - Ubuntu 357 | ``` 358 | sudo apt install python3-pip 359 | pip3 install -U pip --user # update pip 360 | pip3 install tockloader --user 361 | ``` 362 | 363 | - MacOS 364 | ``` 365 | brew install python3 366 | pip3 install tockloader 367 | ``` 368 | 369 | - Windows 370 | - [Download and Install Python 3](https://www.python.org/downloads/windows/) 371 | - Execute within CMD/PowerShell/...: 372 | ``` 373 | pip3 install tockloader 374 | ``` 375 | 376 | Internal Notes 377 | -------------- 378 | 379 | ### Test Locally 380 | 381 | To test the code locally without installing as a package, from the top-level 382 | directory: 383 | 384 | python3 -m tockloader.main 385 | 386 | 387 | ### Build and Install Locally 388 | 389 | pipx install build 390 | pyproject-build 391 | pipx install . 392 | 393 | ### Upload to PyPI 394 | 395 | pipx install build 396 | pipx install flit 397 | pyproject-build 398 | flit publish 399 | 400 | 401 | ### Build Docs 402 | 403 | pip3 install mkdocs 404 | cd docs 405 | ./generate_docs.py 406 | cd .. 407 | mkdocs serve --dev-addr=0.0.0.0:8001 408 | 409 | ### Create requirements.txt 410 | 411 | pip3 install pipreqs 412 | pipreqs . --force 413 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # Nix expression for building Tockloader 2 | 3 | { pkgs ? import {}, withUnfreePkgs ? true }: 4 | 5 | with builtins; 6 | let 7 | inherit (pkgs) stdenv stdenvNoCC lib; 8 | 9 | nrf-command-line-tools = stdenvNoCC.mkDerivation { 10 | pname = "nrf-command-line-tools"; 11 | version = "10.22.1"; 12 | 13 | src = builtins.fetchurl { 14 | url = "https://nsscprodmedia.blob.core.windows.net/prod/software-and-other-downloads/desktop-software/nrf-command-line-tools/sw/versions-10-x-x/10-22-1/nrf-command-line-tools-10.22.1_linux-amd64.tar.gz"; 15 | sha256 = "sha256:0i3dfhp75rizs7kxyfka166k3zy5hmb28c25377pgnzk6w1yx383"; 16 | }; 17 | 18 | nativeBuildInputs = with pkgs; [ 19 | autoPatchelfHook 20 | ]; 21 | 22 | propagatedBuildInputs = with pkgs; [ 23 | segger-jlink libusb1 24 | ]; 25 | 26 | installPhase = '' 27 | mkdir -p $out/ 28 | cp -r * $out/ 29 | ''; 30 | 31 | meta.license = lib.licenses.unfree; 32 | }; 33 | 34 | python3Packages = lib.fix' (self: with self; pkgs.python3Packages // 35 | { 36 | siphash = buildPythonPackage rec { 37 | pname = "siphash"; 38 | version = "0.0.1"; 39 | 40 | src = fetchPypi { 41 | inherit pname version; 42 | sha256 = "sha256-rul/6V4JoplYGcBYpeSsbZZmGomNf+CtVeO3LJox1GE="; 43 | }; 44 | }; 45 | 46 | pynrfjprog = buildPythonPackage { 47 | pname = "pynrfjprog"; 48 | version = nrf-command-line-tools.version; 49 | 50 | src = nrf-command-line-tools.src; 51 | 52 | preConfigure = '' 53 | cd ./python 54 | ''; 55 | 56 | format = "pyproject"; 57 | 58 | nativeBuildInputs = [ 59 | setuptools 60 | pkgs.autoPatchelfHook 61 | ]; 62 | 63 | buildInputs = [ 64 | nrf-command-line-tools 65 | ]; 66 | 67 | propagatedBuildInputs = [ 68 | tomli-w 69 | future 70 | ]; 71 | 72 | meta.license = lib.licenses.unfree; 73 | }; 74 | }); 75 | in pkgs.python3Packages.buildPythonPackage rec { 76 | pname = "tockloader"; 77 | version = let 78 | pattern = "^__version__ = ['\"]([^'\"]*)['\"]\n"; 79 | in elemAt (match pattern (readFile ./tockloader/_version.py)) 0; 80 | name = "${pname}-${version}"; 81 | 82 | propagatedBuildInputs = with python3Packages; [ 83 | argcomplete 84 | colorama 85 | crcmod 86 | pyserial 87 | toml 88 | tqdm 89 | questionary 90 | pycrypto 91 | siphash 92 | ecdsa 93 | ] ++ (lib.optional withUnfreePkgs pynrfjprog); 94 | 95 | src = ./.; 96 | 97 | # Dependency checks require unfree software 98 | doCheck = withUnfreePkgs; 99 | 100 | # Make other dependencies explicitly available as passthru attributes 101 | passthru = { 102 | inherit nrf-command-line-tools; 103 | pynrfjprog = python3Packages.pynrfjprog; 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | site 3 | -------------------------------------------------------------------------------- /docs/app_installed.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.app_installed Documentation 2 | 3 | ## Class InstalledApp 4 | Representation of a Tock app that is installed on a specific board. This 5 | object is created when Tockloader finds an app already installed on a board. 6 | 7 | At the very least this includes the TBF header and an address of where the 8 | app is on the board. It can also include the actual app binary which is 9 | necessary if the app needs to be moved. 10 | ### \_\_init\_\_ 11 | ```py 12 | 13 | def __init__(self, tbfh, tbff, address, app_binary=None) 14 | 15 | ``` 16 | 17 | 18 | 19 | Initialize self. See help(type(self)) for accurate signature. 20 | 21 | 22 | ### filter\_fixed\_ram\_address 23 | ```py 24 | 25 | def filter_fixed_ram_address(self, ram_address) 26 | 27 | ``` 28 | 29 | 30 | 31 | Specify the start of RAM to filter TBFs in this TAB. For installed apps 32 | this is a no-op because presumably we only installed apps that have a 33 | reasonable RAM parameter. 34 | 35 | 36 | ### fix\_at\_next\_loadable\_address 37 | ```py 38 | 39 | def fix_at_next_loadable_address(self, address) 40 | 41 | ``` 42 | 43 | 44 | 45 | Calculate the next reasonable address where we can put this app where 46 | the address is greater than or equal to `address`. The `address` 47 | argument is the earliest address the app can be at, either the start of 48 | apps or immediately after a previous app. 49 | 50 | If the app doesn't have a fixed address, then we can put it anywhere, 51 | and we just return the address. If the app is compiled with fixed 52 | addresses, then we need to calculate an address. We do a little bit of 53 | "reasonable assuming" here. Fixed addresses are based on where the _app 54 | binary_ must be located. Therefore, the start of the app where the TBF 55 | header goes must be before that. This can be at any address (as long as 56 | the header will fit), but we want to make this simpler, so we just 57 | assume the TBF header should start on a 1024 byte alignment. 58 | 59 | 60 | ### get\_address 61 | ```py 62 | 63 | def get_address(self) 64 | 65 | ``` 66 | 67 | 68 | 69 | Get the address of where on the board the app is or should go. 70 | 71 | 72 | ### get\_app\_binary 73 | ```py 74 | 75 | def get_app_binary(self) 76 | 77 | ``` 78 | 79 | 80 | 81 | Return just the compiled application code binary. Does not include 82 | the TBF header. 83 | 84 | 85 | ### get\_app\_version 86 | ```py 87 | 88 | def get_app_version(self) 89 | 90 | ``` 91 | 92 | 93 | 94 | Return the version number stored in a program header. 95 | 96 | 97 | ### get\_binary 98 | ```py 99 | 100 | def get_binary(self, address) 101 | 102 | ``` 103 | 104 | 105 | 106 | Return the binary array comprising the entire application if it needs to 107 | be written to the board. Otherwise, if it is already installed, return 108 | `None`. 109 | 110 | 111 | ### get\_fixed\_addresses\_flash\_and\_sizes 112 | ```py 113 | 114 | def get_fixed_addresses_flash_and_sizes(self) 115 | 116 | ``` 117 | 118 | 119 | 120 | Return a list of tuples of all addresses in flash this app is compiled 121 | for and the size of the app at that address. 122 | 123 | [(address, size), (address, size), ...] 124 | 125 | 126 | ### get\_header 127 | ```py 128 | 129 | def get_header(self) 130 | 131 | ``` 132 | 133 | 134 | 135 | Return the TBFH object for the header. 136 | 137 | 138 | ### get\_header\_binary 139 | ```py 140 | 141 | def get_header_binary(self) 142 | 143 | ``` 144 | 145 | 146 | 147 | Get the TBF header as a bytes array. 148 | 149 | 150 | ### get\_header\_size 151 | ```py 152 | 153 | def get_header_size(self) 154 | 155 | ``` 156 | 157 | 158 | 159 | Return the size of the TBF header in bytes. 160 | 161 | 162 | ### get\_name 163 | ```py 164 | 165 | def get_name(self) 166 | 167 | ``` 168 | 169 | 170 | 171 | Return the app name. 172 | 173 | 174 | ### get\_size 175 | ```py 176 | 177 | def get_size(self) 178 | 179 | ``` 180 | 181 | 182 | 183 | Return the total size (including TBF header) of this app in bytes. 184 | 185 | 186 | ### has\_app\_binary 187 | ```py 188 | 189 | def has_app_binary(self) 190 | 191 | ``` 192 | 193 | 194 | 195 | Whether we have the actual application binary for this app. 196 | 197 | 198 | ### has\_fixed\_addresses 199 | ```py 200 | 201 | def has_fixed_addresses(self) 202 | 203 | ``` 204 | 205 | 206 | 207 | Return true if the TBF binary is compiled for a fixed address. 208 | 209 | 210 | ### info 211 | ```py 212 | 213 | def info(self, verbose=False) 214 | 215 | ``` 216 | 217 | 218 | 219 | Get a string describing various properties of the app. 220 | 221 | 222 | ### is\_app 223 | ```py 224 | 225 | def is_app(self) 226 | 227 | ``` 228 | 229 | 230 | 231 | Whether this is an app or padding. 232 | 233 | 234 | ### is\_loadable\_at\_address 235 | ```py 236 | 237 | def is_loadable_at_address(self, address) 238 | 239 | ``` 240 | 241 | 242 | 243 | Check if it is possible to load this app at the given address. Returns 244 | True if it is possible, False otherwise. 245 | 246 | 247 | ### is\_modified 248 | ```py 249 | 250 | def is_modified(self) 251 | 252 | ``` 253 | 254 | 255 | 256 | Returns whether this app has been modified by tockloader since it was 257 | initially created by `__init__`. 258 | 259 | 260 | ### is\_sticky 261 | ```py 262 | 263 | def is_sticky(self) 264 | 265 | ``` 266 | 267 | 268 | 269 | Returns true if the app is set as sticky and will not be removed with 270 | a normal app erase command. Sticky apps must be force removed. 271 | 272 | 273 | ### object 274 | ```py 275 | 276 | def object(self) 277 | 278 | ``` 279 | 280 | 281 | 282 | Return a dict object containing the information about this app. 283 | 284 | 285 | ### set\_app\_binary 286 | ```py 287 | 288 | def set_app_binary(self, app_binary) 289 | 290 | ``` 291 | 292 | 293 | 294 | Update the application binary. Likely this binary would come from the 295 | existing contents of flash on a board. 296 | 297 | 298 | ### set\_size 299 | ```py 300 | 301 | def set_size(self, size) 302 | 303 | ``` 304 | 305 | 306 | 307 | Force the entire app to be a certain size. If `size` is smaller than the 308 | actual app an error will be thrown. 309 | 310 | 311 | ### set\_sticky 312 | ```py 313 | 314 | def set_sticky(self) 315 | 316 | ``` 317 | 318 | 319 | 320 | Mark this app as "sticky" in the app's header. This makes it harder to 321 | accidentally remove this app if it is a core service or debug app. 322 | 323 | 324 | ### verify\_credentials 325 | ```py 326 | 327 | def verify_credentials(self, public_keys) 328 | 329 | ``` 330 | 331 | 332 | 333 | Using an optional array of public_key binaries, try to check any 334 | contained credentials to verify they are valid. 335 | 336 | 337 | ### \_\_str\_\_ 338 | ```py 339 | 340 | def __str__(self) 341 | 342 | ``` 343 | 344 | 345 | 346 | Return str(self). 347 | 348 | 349 | 350 | -------------------------------------------------------------------------------- /docs/app_padding.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.app_padding Documentation 2 | 3 | ## Class InstalledPaddingApp 4 | Representation of a placeholder app that is only padding between other apps 5 | that was extracted from a board. 6 | ### \_\_init\_\_ 7 | ```py 8 | 9 | def __init__(self, tbfh, address) 10 | 11 | ``` 12 | 13 | 14 | 15 | Create a `InstalledPaddingApp` from an extracted TBFH. 16 | 17 | 18 | ### get\_address 19 | ```py 20 | 21 | def get_address(self) 22 | 23 | ``` 24 | 25 | 26 | 27 | Get the address of where on the board this padding app is. 28 | 29 | 30 | ### get\_binary 31 | ```py 32 | 33 | def get_binary(self, address=None) 34 | 35 | ``` 36 | 37 | 38 | 39 | Return the binary array comprising the header and the padding between 40 | apps. 41 | 42 | 43 | ### get\_header 44 | ```py 45 | 46 | def get_header(self) 47 | 48 | ``` 49 | 50 | 51 | 52 | Return the header for this padding. 53 | 54 | 55 | ### get\_size 56 | ```py 57 | 58 | def get_size(self) 59 | 60 | ``` 61 | 62 | 63 | 64 | Return the total size of the padding in bytes. 65 | 66 | 67 | ### get\_tbfh 68 | ```py 69 | 70 | def get_tbfh(self) 71 | 72 | ``` 73 | 74 | 75 | 76 | Return the TBF header. 77 | 78 | 79 | ### has\_app\_binary 80 | ```py 81 | 82 | def has_app_binary(self) 83 | 84 | ``` 85 | 86 | 87 | 88 | We can always return the binary for a padding app, so we can always 89 | return true. 90 | 91 | 92 | ### has\_fixed\_addresses 93 | ```py 94 | 95 | def has_fixed_addresses(self) 96 | 97 | ``` 98 | 99 | 100 | 101 | A padding app is not an executable so can be placed anywhere. 102 | 103 | 104 | ### info 105 | ```py 106 | 107 | def info(self, verbose=False) 108 | 109 | ``` 110 | 111 | 112 | 113 | Get a string describing various properties of the padding. 114 | 115 | 116 | ### is\_app 117 | ```py 118 | 119 | def is_app(self) 120 | 121 | ``` 122 | 123 | 124 | 125 | Whether this is an app or padding. 126 | 127 | 128 | ### verify\_credentials 129 | ```py 130 | 131 | def verify_credentials(self, public_keys) 132 | 133 | ``` 134 | 135 | 136 | 137 | Padding apps do not have credentials, so we ignore this. 138 | 139 | 140 | ### \_\_str\_\_ 141 | ```py 142 | 143 | def __str__(self) 144 | 145 | ``` 146 | 147 | 148 | 149 | Return str(self). 150 | 151 | 152 | 153 | 154 | ## Class PaddingApp 155 | Representation of a placeholder app that is only padding between other apps. 156 | ### \_\_init\_\_ 157 | ```py 158 | 159 | def __init__(self, size, address=None) 160 | 161 | ``` 162 | 163 | 164 | 165 | Create a `PaddingApp` based on the amount of size needing in the 166 | padding. 167 | 168 | 169 | ### get\_address 170 | ```py 171 | 172 | def get_address(self) 173 | 174 | ``` 175 | 176 | 177 | 178 | Get the address of where on the board this padding app is. 179 | 180 | 181 | ### get\_binary 182 | ```py 183 | 184 | def get_binary(self, address=None) 185 | 186 | ``` 187 | 188 | 189 | 190 | Return the binary array comprising the header and the padding between 191 | apps. 192 | 193 | 194 | ### get\_header 195 | ```py 196 | 197 | def get_header(self) 198 | 199 | ``` 200 | 201 | 202 | 203 | Return the header for this padding. 204 | 205 | 206 | ### get\_size 207 | ```py 208 | 209 | def get_size(self) 210 | 211 | ``` 212 | 213 | 214 | 215 | Return the total size of the padding in bytes. 216 | 217 | 218 | ### get\_tbfh 219 | ```py 220 | 221 | def get_tbfh(self) 222 | 223 | ``` 224 | 225 | 226 | 227 | Return the TBF header. 228 | 229 | 230 | ### has\_app\_binary 231 | ```py 232 | 233 | def has_app_binary(self) 234 | 235 | ``` 236 | 237 | 238 | 239 | We can always return the binary for a padding app, so we can always 240 | return true. 241 | 242 | 243 | ### has\_fixed\_addresses 244 | ```py 245 | 246 | def has_fixed_addresses(self) 247 | 248 | ``` 249 | 250 | 251 | 252 | A padding app is not an executable so can be placed anywhere. 253 | 254 | 255 | ### info 256 | ```py 257 | 258 | def info(self, verbose=False) 259 | 260 | ``` 261 | 262 | 263 | 264 | Get a string describing various properties of the padding. 265 | 266 | 267 | ### is\_app 268 | ```py 269 | 270 | def is_app(self) 271 | 272 | ``` 273 | 274 | 275 | 276 | Whether this is an app or padding. 277 | 278 | 279 | ### verify\_credentials 280 | ```py 281 | 282 | def verify_credentials(self, public_keys) 283 | 284 | ``` 285 | 286 | 287 | 288 | Padding apps do not have credentials, so we ignore this. 289 | 290 | 291 | ### \_\_str\_\_ 292 | ```py 293 | 294 | def __str__(self) 295 | 296 | ``` 297 | 298 | 299 | 300 | Return str(self). 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /docs/app_tab.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.app_tab Documentation 2 | 3 | ## Class TabApp 4 | Representation of a Tock app for a specific architecture and board from a 5 | TAB file. This is different from a TAB, since a TAB can include compiled 6 | binaries for a range of architectures, or compiled for various scenarios, 7 | which may not be applicable for a particular board. 8 | 9 | A TabApp need not be a single TabTbf, as an app from a TAB can include 10 | multiple TabTbfs if the app was compiled multiple times. This could be for 11 | any reason (e.g. it was signed with different keys, or it uses different 12 | compiler optimizations), but typically this is because it is compiled for 13 | specific addresses in flash and RAM, and there are multiple linked versions 14 | present in the TAB. If so, there will be multiple TabTbfs included in this 15 | App object, and the correct one for the board will be used later. 16 | ### \_\_init\_\_ 17 | ```py 18 | 19 | def __init__(self, tbfs) 20 | 21 | ``` 22 | 23 | 24 | 25 | Create a `TabApp` from a list of TabTbfs. 26 | 27 | 28 | ### add\_credential 29 | ```py 30 | 31 | def add_credential(self, credential_type, public_key, private_key, cleartext_id) 32 | 33 | ``` 34 | 35 | 36 | 37 | Add a credential by type to the TBF footer. 38 | 39 | 40 | ### add\_tbfh\_tlv 41 | ```py 42 | 43 | def add_tbfh_tlv(self, tlvid, parameters) 44 | 45 | ``` 46 | 47 | 48 | 49 | Add a particular TLV to each TBF's header. 50 | 51 | 52 | ### convert 53 | ```py 54 | 55 | def convert(self, format) 56 | 57 | ``` 58 | 59 | 60 | 61 | Convert a TAB-based app to a different format. Valid formats: 62 | - `cbinary`: Create a C struct with a binary representation of the app. 63 | 64 | This is only valid if there is one TBF file. 65 | 66 | 67 | ### corrupt\_tbf 68 | ```py 69 | 70 | def corrupt_tbf(self, field_name, value) 71 | 72 | ``` 73 | 74 | 75 | 76 | Modify the TBF root header just before installing the application. 77 | 78 | 79 | ### delete\_credential 80 | ```py 81 | 82 | def delete_credential(self, credential_type) 83 | 84 | ``` 85 | 86 | 87 | 88 | Remove a credential by ID from the TBF footer. 89 | 90 | 91 | ### delete\_tlv 92 | ```py 93 | 94 | def delete_tlv(self, tlvid) 95 | 96 | ``` 97 | 98 | 99 | 100 | Delete a particular TLV from each TBF header and footer. 101 | 102 | 103 | ### filter\_fixed\_ram\_address 104 | ```py 105 | 106 | def filter_fixed_ram_address(self, ram_address) 107 | 108 | ``` 109 | 110 | 111 | 112 | Specify the start of RAM to filter TBFs in this TAB. TBFs with fixed RAM 113 | addresses that are not reasonably able to fit with the available RAM are 114 | ignored from the TAB. 115 | 116 | 117 | ### fix\_at\_next\_loadable\_address 118 | ```py 119 | 120 | def fix_at_next_loadable_address(self, address) 121 | 122 | ``` 123 | 124 | 125 | 126 | Calculate the next reasonable address where we can put this app where 127 | the address is greater than or equal to `address`. The `address` 128 | argument is the earliest address the app can be at, either the start of 129 | apps or immediately after a previous app. Then return that address. 130 | If we can't satisfy the request, return None. 131 | 132 | The "fix" part means remove all TBFs except for the one that we used 133 | to meet the address requirements. 134 | 135 | If the app doesn't have a fixed address, then we can put it anywhere, 136 | and we just return the address. If the app is compiled with fixed 137 | addresses, then we need to calculate an address. We do a little bit of 138 | "reasonable assuming" here. Fixed addresses are based on where the _app 139 | binary_ must be located. Therefore, the start of the app where the TBF 140 | header goes must be before that. This can be at any address (as long as 141 | the header will fit), but we want to make this simpler, so we just 142 | assume the TBF header should start on a 1024 byte alignment. 143 | 144 | 145 | ### get\_app\_version 146 | ```py 147 | 148 | def get_app_version(self) 149 | 150 | ``` 151 | 152 | 153 | 154 | Return the version number stored in a program header. 155 | 156 | This is only valid if there is only one TBF. 157 | 158 | 159 | ### get\_binary 160 | ```py 161 | 162 | def get_binary(self, address) 163 | 164 | ``` 165 | 166 | 167 | 168 | Return the binary array comprising the entire application. 169 | 170 | This is only valid if there is one TBF file. 171 | 172 | `address` is the address of flash the _start_ of the app will be placed 173 | at. This means where the TBF header will go. 174 | 175 | 176 | ### get\_crt0\_header\_str 177 | ```py 178 | 179 | def get_crt0_header_str(self) 180 | 181 | ``` 182 | 183 | 184 | 185 | Return a string representation of the crt0 header some apps use for 186 | doing PIC fixups. We assume this header is positioned immediately 187 | after the TBF header (AKA at the beginning of the application binary). 188 | 189 | 190 | ### get\_fixed\_addresses\_flash\_and\_sizes 191 | ```py 192 | 193 | def get_fixed_addresses_flash_and_sizes(self) 194 | 195 | ``` 196 | 197 | 198 | 199 | Return a list of tuples of all addresses in flash this app is compiled 200 | for and the size of the app at that address. 201 | 202 | [(address, size), (address, size), ...] 203 | 204 | 205 | ### get\_footers 206 | ```py 207 | 208 | def get_footers(self) 209 | 210 | ``` 211 | 212 | 213 | 214 | Return the footers if there are any. 215 | 216 | 217 | ### get\_header 218 | ```py 219 | 220 | def get_header(self) 221 | 222 | ``` 223 | 224 | 225 | 226 | Return a header if there is only one. 227 | 228 | 229 | ### get\_name 230 | ```py 231 | 232 | def get_name(self) 233 | 234 | ``` 235 | 236 | 237 | 238 | Return the app name. 239 | 240 | 241 | ### get\_names\_and\_binaries 242 | ```py 243 | 244 | def get_names_and_binaries(self) 245 | 246 | ``` 247 | 248 | 249 | 250 | Return (filename, binary) tuples for each contained TBF. This is for 251 | updating a .tab file. 252 | 253 | 254 | ### get\_size 255 | ```py 256 | 257 | def get_size(self) 258 | 259 | ``` 260 | 261 | 262 | 263 | Return the total size (including TBF header) of this app in bytes. 264 | 265 | This is only valid if there is only one TBF. 266 | 267 | 268 | ### has\_app\_binary 269 | ```py 270 | 271 | def has_app_binary(self) 272 | 273 | ``` 274 | 275 | 276 | 277 | Return true if we have an application binary with this app. 278 | 279 | 280 | ### has\_fixed\_addresses 281 | ```py 282 | 283 | def has_fixed_addresses(self) 284 | 285 | ``` 286 | 287 | 288 | 289 | Return true if any TBF binary in this app is compiled for a fixed 290 | address. That likely implies _all_ binaries are compiled for a fixed 291 | address. 292 | 293 | 294 | ### info 295 | ```py 296 | 297 | def info(self, verbose=False) 298 | 299 | ``` 300 | 301 | 302 | 303 | Get a string describing various properties of the app. 304 | 305 | 306 | ### is\_loadable\_at\_address 307 | ```py 308 | 309 | def is_loadable_at_address(self, address) 310 | 311 | ``` 312 | 313 | 314 | 315 | Check if it is possible to load this app at the given address. Returns 316 | True if it is possible, False otherwise. 317 | 318 | 319 | ### is\_modified 320 | ```py 321 | 322 | def is_modified(self) 323 | 324 | ``` 325 | 326 | 327 | 328 | Returns whether this app needs to be flashed on to the board. Since this 329 | is a TabApp, we did not get this app from the board and therefore we 330 | have to flash this to the board. 331 | 332 | 333 | ### modify\_tbfh\_tlv 334 | ```py 335 | 336 | def modify_tbfh_tlv(self, tlvid, field, value) 337 | 338 | ``` 339 | 340 | 341 | 342 | Modify a particular TLV from each TBF header to set field=value. 343 | 344 | 345 | ### set\_minimum\_size 346 | ```py 347 | 348 | def set_minimum_size(self, size) 349 | 350 | ``` 351 | 352 | 353 | 354 | Force each version of the entire app to be a certain size. If `size` is 355 | smaller than the actual app nothing happens. 356 | 357 | 358 | ### set\_size 359 | ```py 360 | 361 | def set_size(self, size) 362 | 363 | ``` 364 | 365 | 366 | 367 | Force the entire app to be a certain size. If `size` is smaller than the 368 | actual app an error will be thrown. 369 | 370 | 371 | ### set\_size\_constraint 372 | ```py 373 | 374 | def set_size_constraint(self, constraint) 375 | 376 | ``` 377 | 378 | 379 | 380 | Change the entire app size for each compilation and architecture based 381 | on certain rules. 382 | 383 | Valid rules: 384 | - None: do nothing 385 | - 'powers_of_two': make sure the entire size is a power of two. 386 | - ('multiple', value): make sure the entire size is a multiple of value. 387 | 388 | 389 | ### set\_sticky 390 | ```py 391 | 392 | def set_sticky(self) 393 | 394 | ``` 395 | 396 | 397 | 398 | Mark this app as "sticky" in the app's header. This makes it harder to 399 | accidentally remove this app if it is a core service or debug app. 400 | 401 | 402 | ### verify\_credentials 403 | ```py 404 | 405 | def verify_credentials(self, public_keys) 406 | 407 | ``` 408 | 409 | 410 | 411 | Using an optional array of public_key binaries, try to check any 412 | contained credentials to verify they are valid. 413 | 414 | 415 | ### \_\_str\_\_ 416 | ```py 417 | 418 | def __str__(self) 419 | 420 | ``` 421 | 422 | 423 | 424 | Return str(self). 425 | 426 | 427 | ### \_concatenate\_and\_truncate\_binary 428 | ```py 429 | 430 | def _concatenate_and_truncate_binary(self, header, program_binary, footer) 431 | 432 | ``` 433 | 434 | 435 | 436 | ### \_get\_tbfs 437 | ```py 438 | 439 | def _get_tbfs(self) 440 | 441 | ``` 442 | 443 | 444 | 445 | Helper function so we can implement TBF filtering. 446 | 447 | For normal TBFs (aka PIC TBFs), this doesn't do anything. For fixed 448 | address TBFs, this filters the list of TBFs within the TAB to only those 449 | that are plausibly within the app memory region for the board. 450 | 451 | 452 | 453 | 454 | ## Class TabTbf 455 | Representation of a compiled app in the Tock Binary Format for use in 456 | Tockloader. 457 | 458 | This correlates to a specific .tbf file storing a .tab file. 459 | ### \_\_init\_\_ 460 | ```py 461 | 462 | def __init__(self, filename, tbfh, binary, tbff) 463 | 464 | ``` 465 | 466 | 467 | 468 | - `filename` is the identifier used in the .tab. 469 | - `tbfh` is the header object 470 | - `binary` is the actual compiled binary code 471 | 472 | 473 | 474 | -------------------------------------------------------------------------------- /docs/board_interface.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.board_interface Documentation 2 | 3 | 4 | Generic interface for communicating with boards. 5 | 6 | While it would be nice if there was only a single method to communicate with 7 | boards, in practice that is not feasible. So, this file includes the interface 8 | that different communication methods must implement to effectively support 9 | tockloader. 10 | 11 | ## Class BoardInterface 12 | Base class for interacting with hardware boards. All of the class functions 13 | should be overridden to support a new method of interacting with a board. 14 | ### \_\_init\_\_ 15 | ```py 16 | 17 | def __init__(self, args) 18 | 19 | ``` 20 | 21 | 22 | 23 | Initialize self. See help(type(self)) for accurate signature. 24 | 25 | 26 | ### attached\_board\_exists 27 | ```py 28 | 29 | def attached_board_exists(self) 30 | 31 | ``` 32 | 33 | 34 | 35 | For this particular board communication channel, check if there appears 36 | to be a valid board attached to the host that tockloader can communicate 37 | with. 38 | 39 | 40 | ### bootloader\_is\_present 41 | ```py 42 | 43 | def bootloader_is_present(self) 44 | 45 | ``` 46 | 47 | 48 | 49 | Check for the Tock bootloader. Returns `True` if it is present, `False` 50 | if not, and `None` if unsure. 51 | 52 | 53 | ### clear\_bytes 54 | ```py 55 | 56 | def clear_bytes(self, address) 57 | 58 | ``` 59 | 60 | 61 | 62 | Clear at least one byte starting at `address`. 63 | 64 | This API is designed to support "ending the linked list of apps", or 65 | clearing flash enough so that the flash after the last valid app will 66 | not parse as a valid TBF header. 67 | 68 | Different chips with different mechanisms for writing/erasing flash make 69 | implementing specific erase behavior difficult. Instead, we provide this 70 | rough API, which is sufficient for the task of ending the linked list, 71 | but doesn't guarantee exactly how many bytes after address will be 72 | cleared, or how they will be cleared. 73 | 74 | 75 | ### determine\_current\_board 76 | ```py 77 | 78 | def determine_current_board(self) 79 | 80 | ``` 81 | 82 | 83 | 84 | Figure out which board we are connected to. Most likely done by reading 85 | the attributes. Doesn't return anything. 86 | 87 | 88 | ### enter\_bootloader\_mode 89 | ```py 90 | 91 | def enter_bootloader_mode(self) 92 | 93 | ``` 94 | 95 | 96 | 97 | Get to a mode where we can read & write flash. 98 | 99 | 100 | ### exit\_bootloader\_mode 101 | ```py 102 | 103 | def exit_bootloader_mode(self) 104 | 105 | ``` 106 | 107 | 108 | 109 | Get out of bootloader mode and go back to running main code. 110 | 111 | 112 | ### flash\_binary 113 | ```py 114 | 115 | def flash_binary(self, address, binary) 116 | 117 | ``` 118 | 119 | 120 | 121 | Write a binary to the address given. 122 | 123 | 124 | ### get\_all\_attributes 125 | ```py 126 | 127 | def get_all_attributes(self) 128 | 129 | ``` 130 | 131 | 132 | 133 | Get all attributes on a board. Returns an array of attribute dicts. 134 | 135 | 136 | ### get\_attribute 137 | ```py 138 | 139 | def get_attribute(self, index) 140 | 141 | ``` 142 | 143 | 144 | 145 | Get a single attribute. Returns a dict with two keys: `key` and `value`. 146 | 147 | 148 | ### get\_board\_arch 149 | ```py 150 | 151 | def get_board_arch(self) 152 | 153 | ``` 154 | 155 | 156 | 157 | Return the architecture of the board we are connected to. 158 | 159 | 160 | ### get\_board\_name 161 | ```py 162 | 163 | def get_board_name(self) 164 | 165 | ``` 166 | 167 | 168 | 169 | Return the name of the board we are connected to. 170 | 171 | 172 | ### get\_bootloader\_version 173 | ```py 174 | 175 | def get_bootloader_version(self) 176 | 177 | ``` 178 | 179 | 180 | 181 | Return the version string of the bootloader. Should return a value 182 | like `0.5.0`, or `None` if it is unknown. 183 | 184 | 185 | ### get\_kernel\_version 186 | ```py 187 | 188 | def get_kernel_version(self) 189 | 190 | ``` 191 | 192 | 193 | 194 | Return the kernel ABI version installed on the board. If the version 195 | cannot be determined, return `None`. 196 | 197 | 198 | ### get\_page\_size 199 | ```py 200 | 201 | def get_page_size(self) 202 | 203 | ``` 204 | 205 | 206 | 207 | Return the size of the page in bytes for the connected board. 208 | 209 | 210 | ### open\_link\_to\_board 211 | ```py 212 | 213 | def open_link_to_board(self) 214 | 215 | ``` 216 | 217 | 218 | 219 | Open a connection to the board. 220 | 221 | 222 | ### print\_known\_boards 223 | ```py 224 | 225 | def print_known_boards(self) 226 | 227 | ``` 228 | 229 | 230 | 231 | Display the boards that have settings configured in tockloader. 232 | 233 | 234 | ### read\_range 235 | ```py 236 | 237 | def read_range(self, address, length) 238 | 239 | ``` 240 | 241 | 242 | 243 | Read a specific range of flash. 244 | 245 | If this fails for some reason this should return an empty binary array. 246 | 247 | 248 | ### run\_terminal 249 | ```py 250 | 251 | def run_terminal(self) 252 | 253 | ``` 254 | 255 | 256 | 257 | ### set\_attribute 258 | ```py 259 | 260 | def set_attribute(self, index, raw) 261 | 262 | ``` 263 | 264 | 265 | 266 | Set a single attribute. 267 | 268 | 269 | ### set\_start\_address 270 | ```py 271 | 272 | def set_start_address(self, address) 273 | 274 | ``` 275 | 276 | 277 | 278 | Set the address the bootloader jumps to to start the actual code. 279 | 280 | 281 | ### translate\_address 282 | ```py 283 | 284 | def translate_address(self, address) 285 | 286 | ``` 287 | 288 | 289 | 290 | Translate an address from MCU address space to the address required for 291 | the board interface. This is used for boards where the address passed to 292 | the board interface is not the address where this region is exposed in 293 | the MCU address space. This method must be called from the board 294 | interface implementation prior to memory accesses. 295 | 296 | 297 | ### \_align\_and\_stretch\_to\_page 298 | ```py 299 | 300 | def _align_and_stretch_to_page(self, address, binary) 301 | 302 | ``` 303 | 304 | 305 | 306 | Return a new (address, binary) that is a multiple of the page size 307 | and is aligned to page boundaries. 308 | 309 | 310 | ### \_configure\_from\_known\_boards 311 | ```py 312 | 313 | def _configure_from_known_boards(self) 314 | 315 | ``` 316 | 317 | 318 | 319 | If we know the name of the board we are interfacing with, this function 320 | tries to use the `KNOWN_BOARDS` array to populate other needed settings 321 | if they have not already been set from other methods. 322 | 323 | This can be used in multiple locations. First, it is used when 324 | tockloader first starts because if a user passes in the `--board` 325 | argument then we know the board and can try to pull in settings from 326 | KNOWN_BOARDS. Ideally, however, the user doesn't have to pass in any 327 | arguments, but then we won't know what board until after we have had a 328 | chance to read its attributes. The board at least needs the "board" 329 | attribute to be set, and then we can use KNOWN_BOARDS to fill in the 330 | rest. 331 | 332 | 333 | ### \_decode\_attribute 334 | ```py 335 | 336 | def _decode_attribute(self, raw) 337 | 338 | ``` 339 | 340 | 341 | 342 | 343 | -------------------------------------------------------------------------------- /docs/bootloader_serial.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.bootloader_serial Documentation 2 | 3 | 4 | Interface with a board over serial that is using the 5 | [Tock Bootloader](https://github.com/tock/tock-bootloader). 6 | 7 | ## Class BootloaderSerial 8 | Implementation of `BoardInterface` for the Tock Bootloader over serial. 9 | ### \_\_init\_\_ 10 | ```py 11 | 12 | def __init__(self, args) 13 | 14 | ``` 15 | 16 | 17 | 18 | Initialize self. See help(type(self)) for accurate signature. 19 | 20 | 21 | ### attached\_board\_exists 22 | ```py 23 | 24 | def attached_board_exists(self) 25 | 26 | ``` 27 | 28 | 29 | 30 | For this particular board communication channel, check if there appears 31 | to be a valid board attached to the host that tockloader can communicate 32 | with. 33 | 34 | 35 | ### bootloader\_is\_present 36 | ```py 37 | 38 | def bootloader_is_present(self) 39 | 40 | ``` 41 | 42 | 43 | 44 | For this communication protocol we can safely say the bootloader is 45 | present. 46 | 47 | 48 | ### clear\_bytes 49 | ```py 50 | 51 | def clear_bytes(self, address) 52 | 53 | ``` 54 | 55 | 56 | 57 | Clear at least one byte starting at `address`. 58 | 59 | This API is designed to support "ending the linked list of apps", or 60 | clearing flash enough so that the flash after the last valid app will 61 | not parse as a valid TBF header. 62 | 63 | Different chips with different mechanisms for writing/erasing flash make 64 | implementing specific erase behavior difficult. Instead, we provide this 65 | rough API, which is sufficient for the task of ending the linked list, 66 | but doesn't guarantee exactly how many bytes after address will be 67 | cleared, or how they will be cleared. 68 | 69 | 70 | ### determine\_current\_board 71 | ```py 72 | 73 | def determine_current_board(self) 74 | 75 | ``` 76 | 77 | 78 | 79 | Figure out which board we are connected to. Most likely done by reading 80 | the attributes. Doesn't return anything. 81 | 82 | 83 | ### enter\_bootloader\_mode 84 | ```py 85 | 86 | def enter_bootloader_mode(self) 87 | 88 | ``` 89 | 90 | 91 | 92 | Reset the chip and assert the bootloader select pin to enter bootloader 93 | mode. Handle retries if necessary. 94 | 95 | 96 | ### erase\_page 97 | ```py 98 | 99 | def erase_page(self, address) 100 | 101 | ``` 102 | 103 | 104 | 105 | ### exit\_bootloader\_mode 106 | ```py 107 | 108 | def exit_bootloader_mode(self) 109 | 110 | ``` 111 | 112 | 113 | 114 | Reset the chip to exit bootloader mode. 115 | 116 | 117 | ### flash\_binary 118 | ```py 119 | 120 | def flash_binary(self, address, binary, pad=True) 121 | 122 | ``` 123 | 124 | 125 | 126 | Write pages until a binary has been flashed. binary must have a length 127 | that is a multiple of page size. 128 | 129 | 130 | ### get\_all\_attributes 131 | ```py 132 | 133 | def get_all_attributes(self) 134 | 135 | ``` 136 | 137 | 138 | 139 | Get all attributes on a board. Returns an array of attribute dicts. 140 | 141 | 142 | ### get\_attribute 143 | ```py 144 | 145 | def get_attribute(self, index) 146 | 147 | ``` 148 | 149 | 150 | 151 | Get a single attribute. Returns a dict with two keys: `key` and `value`. 152 | 153 | 154 | ### get\_board\_arch 155 | ```py 156 | 157 | def get_board_arch(self) 158 | 159 | ``` 160 | 161 | 162 | 163 | Return the architecture of the board we are connected to. 164 | 165 | 166 | ### get\_board\_name 167 | ```py 168 | 169 | def get_board_name(self) 170 | 171 | ``` 172 | 173 | 174 | 175 | Return the name of the board we are connected to. 176 | 177 | 178 | ### get\_bootloader\_version 179 | ```py 180 | 181 | def get_bootloader_version(self) 182 | 183 | ``` 184 | 185 | 186 | 187 | Return the version string of the bootloader. Should return a value 188 | like `0.5.0`, or `None` if it is unknown. 189 | 190 | 191 | ### get\_kernel\_version 192 | ```py 193 | 194 | def get_kernel_version(self) 195 | 196 | ``` 197 | 198 | 199 | 200 | Return the kernel ABI version installed on the board. If the version 201 | cannot be determined, return `None`. 202 | 203 | 204 | ### get\_page\_size 205 | ```py 206 | 207 | def get_page_size(self) 208 | 209 | ``` 210 | 211 | 212 | 213 | Return the size of the page in bytes for the connected board. 214 | 215 | 216 | ### open\_link\_to\_board 217 | ```py 218 | 219 | def open_link_to_board(self, listen=False) 220 | 221 | ``` 222 | 223 | 224 | 225 | Open the serial port to the chip/bootloader. 226 | 227 | Also sets up a local port for determining when two Tockloader instances 228 | are running simultaneously. 229 | 230 | Set the argument `listen` to true if the serial port is being setup 231 | because we are planning to run `run_terminal`. 232 | 233 | 234 | ### print\_known\_boards 235 | ```py 236 | 237 | def print_known_boards(self) 238 | 239 | ``` 240 | 241 | 242 | 243 | Display the boards that have settings configured in tockloader. 244 | 245 | 246 | ### read\_range 247 | ```py 248 | 249 | def read_range(self, address, length) 250 | 251 | ``` 252 | 253 | 254 | 255 | Read a specific range of flash. 256 | 257 | If this fails for some reason this should return an empty binary array. 258 | 259 | 260 | ### run\_terminal 261 | ```py 262 | 263 | def run_terminal(self) 264 | 265 | ``` 266 | 267 | 268 | 269 | Run miniterm for receiving data from the board. 270 | 271 | 272 | ### set\_attribute 273 | ```py 274 | 275 | def set_attribute(self, index, raw) 276 | 277 | ``` 278 | 279 | 280 | 281 | Set a single attribute. 282 | 283 | 284 | ### set\_start\_address 285 | ```py 286 | 287 | def set_start_address(self, address) 288 | 289 | ``` 290 | 291 | 292 | 293 | Set the address the bootloader jumps to to start the actual code. 294 | 295 | 296 | ### translate\_address 297 | ```py 298 | 299 | def translate_address(self, address) 300 | 301 | ``` 302 | 303 | 304 | 305 | Translate an address from MCU address space to the address required for 306 | the board interface. This is used for boards where the address passed to 307 | the board interface is not the address where this region is exposed in 308 | the MCU address space. This method must be called from the board 309 | interface implementation prior to memory accesses. 310 | 311 | 312 | ### \_align\_and\_stretch\_to\_page 313 | ```py 314 | 315 | def _align_and_stretch_to_page(self, address, binary) 316 | 317 | ``` 318 | 319 | 320 | 321 | Return a new (address, binary) that is a multiple of the page size 322 | and is aligned to page boundaries. 323 | 324 | 325 | ### \_change\_baud\_rate 326 | ```py 327 | 328 | def _change_baud_rate(self, baud_rate) 329 | 330 | ``` 331 | 332 | 333 | 334 | If the bootloader on the board supports it and if it succeeds, try to 335 | increase the baud rate to make everything faster. 336 | 337 | 338 | ### \_check\_crc 339 | ```py 340 | 341 | def _check_crc(self, address, binary, valid_pages) 342 | 343 | ``` 344 | 345 | 346 | 347 | Compares the CRC of the local binary to the one calculated by the 348 | bootloader. 349 | 350 | 351 | ### \_configure\_from\_known\_boards 352 | ```py 353 | 354 | def _configure_from_known_boards(self) 355 | 356 | ``` 357 | 358 | 359 | 360 | If we know the name of the board we are interfacing with, this function 361 | tries to use the `KNOWN_BOARDS` array to populate other needed settings 362 | if they have not already been set from other methods. 363 | 364 | This can be used in multiple locations. First, it is used when 365 | tockloader first starts because if a user passes in the `--board` 366 | argument then we know the board and can try to pull in settings from 367 | KNOWN_BOARDS. Ideally, however, the user doesn't have to pass in any 368 | arguments, but then we won't know what board until after we have had a 369 | chance to read its attributes. The board at least needs the "board" 370 | attribute to be set, and then we can use KNOWN_BOARDS to fill in the 371 | rest. 372 | 373 | 374 | ### \_configure\_serial\_port 375 | ```py 376 | 377 | def _configure_serial_port(self, port) 378 | 379 | ``` 380 | 381 | 382 | 383 | Helper function to configure the serial port so we can read/write with 384 | it. 385 | 386 | 387 | ### \_decode\_attribute 388 | ```py 389 | 390 | def _decode_attribute(self, raw) 391 | 392 | ``` 393 | 394 | 395 | 396 | ### \_determine\_port 397 | ```py 398 | 399 | def _determine_port(self, any=False) 400 | 401 | ``` 402 | 403 | 404 | 405 | Helper function to determine which serial port on the host to use to 406 | connect to the board. 407 | 408 | Set `any` to true to return a device without prompting the user (i.e. 409 | just return any port if there are multiple). 410 | 411 | 412 | ### \_exit\_bootloader 413 | ```py 414 | 415 | def _exit_bootloader(self) 416 | 417 | ``` 418 | 419 | 420 | 421 | Tell the bootloader on the board to exit so the main software can run. 422 | 423 | This uses a command sent over the serial port to the bootloader. 424 | 425 | 426 | ### \_get\_crc\_internal\_flash 427 | ```py 428 | 429 | def _get_crc_internal_flash(self, address, length) 430 | 431 | ``` 432 | 433 | 434 | 435 | Get the bootloader to compute a CRC. 436 | 437 | 438 | ### \_get\_serial\_port\_hash 439 | ```py 440 | 441 | def _get_serial_port_hash(self) 442 | 443 | ``` 444 | 445 | 446 | 447 | Get an identifier that will be consistent for this serial port on this 448 | machine that is also guaranteed to not have any special characters (like 449 | slashes) that would interfere with using as a file name. 450 | 451 | 452 | ### \_get\_serial\_port\_hashed\_to\_ip\_port 453 | ```py 454 | 455 | def _get_serial_port_hashed_to_ip_port(self) 456 | 457 | ``` 458 | 459 | 460 | 461 | This is a bit of a hack, but it's means to find a reasonably unlikely 462 | to collide port number based on the serial port used to talk to the 463 | board. 464 | 465 | 466 | ### \_issue\_command 467 | ```py 468 | 469 | def _issue_command(self, command, message, sync, response_len, response_code, show_errors=True) 470 | 471 | ``` 472 | 473 | 474 | 475 | Setup a command to send to the bootloader and handle the response. 476 | 477 | 478 | ### \_open\_serial\_port 479 | ```py 480 | 481 | def _open_serial_port(self) 482 | 483 | ``` 484 | 485 | 486 | 487 | Helper function for calling `self.sp.open()`. 488 | 489 | Serial ports on different OSes and systems can be finicky, and this 490 | enables retries to try to hide failures. 491 | 492 | 493 | ### \_ping\_bootloader\_and\_wait\_for\_response 494 | ```py 495 | 496 | def _ping_bootloader_and_wait_for_response(self) 497 | 498 | ``` 499 | 500 | 501 | 502 | Throws an exception if the device does not respond with a PONG. 503 | 504 | 505 | ### \_server\_thread 506 | ```py 507 | 508 | def _server_thread(self) 509 | 510 | ``` 511 | 512 | 513 | 514 | ### \_toggle\_bootloader\_entry\_DTR\_RTS 515 | ```py 516 | 517 | def _toggle_bootloader_entry_DTR_RTS(self) 518 | 519 | ``` 520 | 521 | 522 | 523 | Use the DTR and RTS lines on UART to reset the chip and assert the 524 | bootloader select pin to enter bootloader mode so that the chip will 525 | start in bootloader mode. 526 | 527 | 528 | ### \_toggle\_bootloader\_entry\_baud\_rate 529 | ```py 530 | 531 | def _toggle_bootloader_entry_baud_rate(self) 532 | 533 | ``` 534 | 535 | 536 | 537 | Set the baud rate to 1200 so that the chip will restart into the 538 | bootloader (if that feature exists). 539 | 540 | Returns `True` if it successfully started the bootloader, `False` 541 | otherwise. 542 | 543 | 544 | ### \_wait\_for\_serial\_port 545 | ```py 546 | 547 | def _wait_for_serial_port(self) 548 | 549 | ``` 550 | 551 | 552 | 553 | Wait for the serial port to re-appear, aka the bootloader has started. 554 | 555 | 556 | 557 | -------------------------------------------------------------------------------- /docs/display.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.display Documentation 2 | 3 | 4 | Utilities for creating output in various formats. 5 | 6 | ## Class Display 7 | None 8 | ### \_\_init\_\_ 9 | ```py 10 | 11 | def __init__(self, show_headers) 12 | 13 | ``` 14 | 15 | 16 | 17 | Arguments: 18 | - show_headers: bool, if True, label each section in the display output. 19 | 20 | 21 | ### bootloader\_version 22 | ```py 23 | 24 | def bootloader_version(self, version) 25 | 26 | ``` 27 | 28 | 29 | 30 | Show the bootloader version stored in the bootloader itself. 31 | 32 | 33 | ### get 34 | ```py 35 | 36 | def get(self) 37 | 38 | ``` 39 | 40 | 41 | 42 | ### kernel\_attributes 43 | ```py 44 | 45 | def kernel_attributes(self, kern_attrs) 46 | 47 | ``` 48 | 49 | 50 | 51 | Show the kernel attributes stored in the kernel binary. 52 | 53 | 54 | ### list\_apps 55 | ```py 56 | 57 | def list_apps(self, apps, verbose, quiet) 58 | 59 | ``` 60 | 61 | 62 | 63 | Show information about a list of apps. 64 | 65 | 66 | ### list\_attributes 67 | ```py 68 | 69 | def list_attributes(self, attributes) 70 | 71 | ``` 72 | 73 | 74 | 75 | Show the key value pairs for a list of attributes. 76 | 77 | 78 | 79 | 80 | ## Class HumanReadableDisplay 81 | Format output as a string meant to be human readable. 82 | ### \_\_init\_\_ 83 | ```py 84 | 85 | def __init__(self, show_headers=False) 86 | 87 | ``` 88 | 89 | 90 | 91 | Arguments: 92 | - show_headers: bool, if True, label each section in the display output. 93 | 94 | 95 | ### bootloader\_version 96 | ```py 97 | 98 | def bootloader_version(self, version) 99 | 100 | ``` 101 | 102 | 103 | 104 | Show the bootloader version stored in the bootloader itself. 105 | 106 | 107 | ### get 108 | ```py 109 | 110 | def get(self) 111 | 112 | ``` 113 | 114 | 115 | 116 | ### kernel\_attributes 117 | ```py 118 | 119 | def kernel_attributes(self, kern_attrs) 120 | 121 | ``` 122 | 123 | 124 | 125 | Show the kernel attributes stored in the kernel binary. 126 | 127 | 128 | ### list\_apps 129 | ```py 130 | 131 | def list_apps(self, apps, verbose, quiet) 132 | 133 | ``` 134 | 135 | 136 | 137 | Show information about a list of apps. 138 | 139 | 140 | ### list\_attributes 141 | ```py 142 | 143 | def list_attributes(self, attributes) 144 | 145 | ``` 146 | 147 | 148 | 149 | Show the key value pairs for a list of attributes. 150 | 151 | 152 | ### show\_app\_map\_actual\_address 153 | ```py 154 | 155 | def show_app_map_actual_address(self, apps) 156 | 157 | ``` 158 | 159 | 160 | 161 | Show a map of installed applications with known addresses. Example: 162 | 163 | ``` 164 | 0x30000┬──────────────────────────────────────────────────┐ 165 | │App: blink [Installed]│ 166 | │ Length: 16384 (0x4000) │ 167 | 0x34000┴──────────────────────────────────────────────────┘ 168 | 0x38000┬──────────────────────────────────────────────────┐ 169 | │App: blink [Installed]│ 170 | │ Length: 16384 (0x4000) │ 171 | 0x3c000┴──────────────────────────────────────────────────┘ 172 | ``` 173 | 174 | 175 | ### show\_app\_map\_from\_address 176 | ```py 177 | 178 | def show_app_map_from_address(self, apps, start_address) 179 | 180 | ``` 181 | 182 | 183 | 184 | Print a layout map of apps assuming they are located back-to-back 185 | starting from `start_address`. Example: 186 | 187 | ``` 188 | 0x30000┬──────────────────────────────────────────────────┐ 189 | │App: blink [Installed]│ 190 | │ Length: 16384 (0x4000) │ 191 | 0x34000┼──────────────────────────────────────────────────┤ 192 | │App: blink [Installed]│ 193 | │ Length: 16384 (0x4000) │ 194 | 0x3c000┴──────────────────────────────────────────────────┘ 195 | ``` 196 | 197 | 198 | ### show\_board\_visual 199 | ```py 200 | 201 | def show_board_visual(self, apps) 202 | 203 | ``` 204 | 205 | 206 | 207 | 208 | 209 | ## Class JSONDisplay 210 | Format output as JSON. 211 | ### \_\_init\_\_ 212 | ```py 213 | 214 | def __init__(self) 215 | 216 | ``` 217 | 218 | 219 | 220 | Arguments: 221 | - show_headers: bool, if True, label each section in the display output. 222 | 223 | 224 | ### bootloader\_version 225 | ```py 226 | 227 | def bootloader_version(self, version) 228 | 229 | ``` 230 | 231 | 232 | 233 | Show the bootloader version stored in the bootloader itself. 234 | 235 | 236 | ### get 237 | ```py 238 | 239 | def get(self) 240 | 241 | ``` 242 | 243 | 244 | 245 | ### kernel\_attributes 246 | ```py 247 | 248 | def kernel_attributes(self, kern_attrs) 249 | 250 | ``` 251 | 252 | 253 | 254 | Show the kernel attributes stored in the kernel binary. 255 | 256 | 257 | ### list\_apps 258 | ```py 259 | 260 | def list_apps(self, apps, verbose, quiet) 261 | 262 | ``` 263 | 264 | 265 | 266 | Show information about a list of apps. 267 | 268 | 269 | ### list\_attributes 270 | ```py 271 | 272 | def list_attributes(self, attributes) 273 | 274 | ``` 275 | 276 | 277 | 278 | Show the key value pairs for a list of attributes. 279 | 280 | 281 | 282 | 283 | ## Class VisualDisplay 284 | Format output as an ASCII art string. 285 | 286 | ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────┐ 287 | │ | │ | │ | │ | 288 | │ | │ | │ | │ | 289 | │ | │ | │ | │ | 290 | │ version | │ version | │ version | │ blink | 291 | │ | │ | │ | │ | 292 | │ | │ | │ | │ | 293 | │ | │ | │ | │ | 294 | │ | │ | │ | │ | 295 | └─────────┘ └─────────┘ └─────────┘ └───────┘ 296 | ┌───────────────────────────────────────────┐ 297 | │ Kernel | 298 | └───────────────────────────────────────────┘ 299 | ### \_\_init\_\_ 300 | ```py 301 | 302 | def __init__(self) 303 | 304 | ``` 305 | 306 | 307 | 308 | Arguments: 309 | - show_headers: bool, if True, label each section in the display output. 310 | 311 | 312 | ### bootloader\_version 313 | ```py 314 | 315 | def bootloader_version(self, version) 316 | 317 | ``` 318 | 319 | 320 | 321 | Show the bootloader version stored in the bootloader itself. 322 | 323 | 324 | ### get 325 | ```py 326 | 327 | def get(self) 328 | 329 | ``` 330 | 331 | 332 | 333 | ### kernel\_attributes 334 | ```py 335 | 336 | def kernel_attributes(self, kern_attrs) 337 | 338 | ``` 339 | 340 | 341 | 342 | Show the kernel attributes stored in the kernel binary. 343 | 344 | 345 | ### list\_apps 346 | ```py 347 | 348 | def list_apps(self, apps, verbose, quiet) 349 | 350 | ``` 351 | 352 | 353 | 354 | Show information about a list of apps. 355 | 356 | 357 | ### list\_attributes 358 | ```py 359 | 360 | def list_attributes(self, attributes) 361 | 362 | ``` 363 | 364 | 365 | 366 | Show the key value pairs for a list of attributes. 367 | 368 | 369 | ### \_width 370 | ```py 371 | 372 | def _width(self) 373 | 374 | ``` 375 | 376 | 377 | 378 | 379 | 380 | ### app\_bracket 381 | ```py 382 | 383 | def app_bracket(width, left, right) 384 | 385 | ``` 386 | 387 | 388 | 389 | ### choose 390 | ```py 391 | 392 | def choose(b, t, f) 393 | 394 | ``` 395 | 396 | 397 | 398 | ### end\_of\_app 399 | ```py 400 | 401 | def end_of_app(width, address, continuing) 402 | 403 | ``` 404 | 405 | 406 | 407 | ### start\_of\_app 408 | ```py 409 | 410 | def start_of_app(width, address) 411 | 412 | ``` 413 | 414 | 415 | -------------------------------------------------------------------------------- /docs/exceptions.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.exceptions Documentation 2 | 3 | ## Class ChannelAddressErrorException 4 | Raised when a particular channel to a board cannot support the request 5 | operation, likely due to the specific address. 6 | 7 | 8 | ## Class TockLoaderException 9 | Raised when Tockloader detects an issue. 10 | 11 | -------------------------------------------------------------------------------- /docs/flash_file.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.flash_file Documentation 2 | 3 | 4 | Interface to a board's flash file. This module does not directly interface to a 5 | proper board, but can be used to manipulate a board's flash dump. 6 | 7 | ## Class FlashFile 8 | Implementation of `BoardInterface` for flash files. 9 | ### \_\_init\_\_ 10 | ```py 11 | 12 | def __init__(self, args) 13 | 14 | ``` 15 | 16 | 17 | 18 | Initialize self. See help(type(self)) for accurate signature. 19 | 20 | 21 | ### attached\_board\_exists 22 | ```py 23 | 24 | def attached_board_exists(self) 25 | 26 | ``` 27 | 28 | 29 | 30 | For this particular board communication channel, check if there appears 31 | to be a valid board attached to the host that tockloader can communicate 32 | with. 33 | 34 | 35 | ### bootloader\_is\_present 36 | ```py 37 | 38 | def bootloader_is_present(self) 39 | 40 | ``` 41 | 42 | 43 | 44 | Check for the Tock bootloader. Returns `True` if it is present, `False` 45 | if not, and `None` if unsure. 46 | 47 | 48 | ### clear\_bytes 49 | ```py 50 | 51 | def clear_bytes(self, address) 52 | 53 | ``` 54 | 55 | 56 | 57 | Clear at least one byte starting at `address`. 58 | 59 | This API is designed to support "ending the linked list of apps", or 60 | clearing flash enough so that the flash after the last valid app will 61 | not parse as a valid TBF header. 62 | 63 | Different chips with different mechanisms for writing/erasing flash make 64 | implementing specific erase behavior difficult. Instead, we provide this 65 | rough API, which is sufficient for the task of ending the linked list, 66 | but doesn't guarantee exactly how many bytes after address will be 67 | cleared, or how they will be cleared. 68 | 69 | 70 | ### determine\_current\_board 71 | ```py 72 | 73 | def determine_current_board(self) 74 | 75 | ``` 76 | 77 | 78 | 79 | Figure out which board we are connected to. Most likely done by reading 80 | the attributes. Doesn't return anything. 81 | 82 | 83 | ### enter\_bootloader\_mode 84 | ```py 85 | 86 | def enter_bootloader_mode(self) 87 | 88 | ``` 89 | 90 | 91 | 92 | Get to a mode where we can read & write flash. 93 | 94 | 95 | ### exit\_bootloader\_mode 96 | ```py 97 | 98 | def exit_bootloader_mode(self) 99 | 100 | ``` 101 | 102 | 103 | 104 | Get out of bootloader mode and go back to running main code. 105 | 106 | 107 | ### flash\_binary 108 | ```py 109 | 110 | def flash_binary(self, address, binary) 111 | 112 | ``` 113 | 114 | 115 | 116 | Write a binary to the address given. 117 | 118 | 119 | ### get\_all\_attributes 120 | ```py 121 | 122 | def get_all_attributes(self) 123 | 124 | ``` 125 | 126 | 127 | 128 | Get all attributes on a board. Returns an array of attribute dicts. 129 | 130 | 131 | ### get\_attribute 132 | ```py 133 | 134 | def get_attribute(self, index) 135 | 136 | ``` 137 | 138 | 139 | 140 | Get a single attribute. Returns a dict with two keys: `key` and `value`. 141 | 142 | 143 | ### get\_board\_arch 144 | ```py 145 | 146 | def get_board_arch(self) 147 | 148 | ``` 149 | 150 | 151 | 152 | Return the architecture of the board we are connected to. 153 | 154 | 155 | ### get\_board\_name 156 | ```py 157 | 158 | def get_board_name(self) 159 | 160 | ``` 161 | 162 | 163 | 164 | Return the name of the board we are connected to. 165 | 166 | 167 | ### get\_bootloader\_version 168 | ```py 169 | 170 | def get_bootloader_version(self) 171 | 172 | ``` 173 | 174 | 175 | 176 | Return the version string of the bootloader. Should return a value 177 | like `0.5.0`, or `None` if it is unknown. 178 | 179 | 180 | ### get\_kernel\_version 181 | ```py 182 | 183 | def get_kernel_version(self) 184 | 185 | ``` 186 | 187 | 188 | 189 | Return the kernel ABI version installed on the board. If the version 190 | cannot be determined, return `None`. 191 | 192 | 193 | ### get\_page\_size 194 | ```py 195 | 196 | def get_page_size(self) 197 | 198 | ``` 199 | 200 | 201 | 202 | Return the size of the page in bytes for the connected board. 203 | 204 | 205 | ### open\_link\_to\_board 206 | ```py 207 | 208 | def open_link_to_board(self) 209 | 210 | ``` 211 | 212 | 213 | 214 | Open a link to the board by opening the flash file for reading and 215 | writing. 216 | 217 | 218 | ### print\_known\_boards 219 | ```py 220 | 221 | def print_known_boards(self) 222 | 223 | ``` 224 | 225 | 226 | 227 | Display the boards that have settings configured in tockloader. 228 | 229 | 230 | ### read\_range 231 | ```py 232 | 233 | def read_range(self, address, length) 234 | 235 | ``` 236 | 237 | 238 | 239 | Read a specific range of flash. 240 | 241 | If this fails for some reason this should return an empty binary array. 242 | 243 | 244 | ### run\_terminal 245 | ```py 246 | 247 | def run_terminal(self) 248 | 249 | ``` 250 | 251 | 252 | 253 | ### set\_attribute 254 | ```py 255 | 256 | def set_attribute(self, index, raw) 257 | 258 | ``` 259 | 260 | 261 | 262 | Set a single attribute. 263 | 264 | 265 | ### set\_start\_address 266 | ```py 267 | 268 | def set_start_address(self, address) 269 | 270 | ``` 271 | 272 | 273 | 274 | Set the address the bootloader jumps to to start the actual code. 275 | 276 | 277 | ### translate\_address 278 | ```py 279 | 280 | def translate_address(self, address) 281 | 282 | ``` 283 | 284 | 285 | 286 | Translate an address from MCU address space to the address required for 287 | the board interface. This is used for boards where the address passed to 288 | the board interface is not the address where this region is exposed in 289 | the MCU address space. This method must be called from the board 290 | interface implementation prior to memory accesses. 291 | 292 | 293 | ### \_align\_and\_stretch\_to\_page 294 | ```py 295 | 296 | def _align_and_stretch_to_page(self, address, binary) 297 | 298 | ``` 299 | 300 | 301 | 302 | Return a new (address, binary) that is a multiple of the page size 303 | and is aligned to page boundaries. 304 | 305 | 306 | ### \_configure\_from\_known\_boards 307 | ```py 308 | 309 | def _configure_from_known_boards(self) 310 | 311 | ``` 312 | 313 | 314 | 315 | If we know the name of the board we are interfacing with, this function 316 | tries to use the `KNOWN_BOARDS` array to populate other needed settings 317 | if they have not already been set from other methods. 318 | 319 | This can be used in multiple locations. First, it is used when 320 | tockloader first starts because if a user passes in the `--board` 321 | argument then we know the board and can try to pull in settings from 322 | KNOWN_BOARDS. Ideally, however, the user doesn't have to pass in any 323 | arguments, but then we won't know what board until after we have had a 324 | chance to read its attributes. The board at least needs the "board" 325 | attribute to be set, and then we can use KNOWN_BOARDS to fill in the 326 | rest. 327 | 328 | 329 | ### \_decode\_attribute 330 | ```py 331 | 332 | def _decode_attribute(self, raw) 333 | 334 | ``` 335 | 336 | 337 | 338 | 339 | -------------------------------------------------------------------------------- /docs/generate_docs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import inspect 4 | import pydoc 5 | import os, sys 6 | 7 | # This script generates mkdocs friendly Markdown documentation from a python package. 8 | # It is based on the the following blog post by Christian Medina 9 | # https://medium.com/python-pandemonium/python-introspection-with-the-inspect-module-2c85d5aa5a48#.twcmlyack 10 | # https://gist.github.com/dvirsky/30ffbd3c7d8f37d4831b30671b681c24 11 | 12 | module_header = "# Package {} Documentation\n" 13 | class_header = "## Class {}" 14 | function_header = "### {}" 15 | 16 | 17 | def getmarkdown(module): 18 | output = [module_header.format(module.__name__)] 19 | 20 | if module.__doc__: 21 | output.append(module.__doc__) 22 | 23 | output.extend(getclasses(module)) 24 | output.extend(getfunctions(module)) 25 | return "\n".join((str(x) for x in output)) 26 | 27 | 28 | def getclasses(item, depth=0): 29 | output = [] 30 | for cl in pydoc.inspect.getmembers(item, pydoc.inspect.isclass): 31 | # Make sure we are only getting classes in this file 32 | if depth == 0: 33 | if item.__name__ != cl[1].__module__: 34 | continue 35 | 36 | # Ignore bogus stuff 37 | if cl[0] == "__class__" or cl[0].startswith("_"): 38 | continue 39 | 40 | # Consider anything that starts with _ private 41 | # and don't document it 42 | output.append(class_header.format(cl[0])) 43 | # Get the docstring 44 | output.append(pydoc.inspect.getdoc(cl[1])) 45 | # Get the functions 46 | output.extend(getfunctions(cl[1])) 47 | # Recurse into any subclasses 48 | output.extend(getclasses(cl[1], depth + 1)) 49 | output.append("\n") 50 | return output 51 | 52 | 53 | def getfunctions(item): 54 | output = [] 55 | at_end = [] 56 | for func in pydoc.inspect.getmembers(item, pydoc.inspect.isfunction): 57 | out = output 58 | if func[0].startswith("_") and func[0] != "__init__": 59 | out = at_end 60 | 61 | out.append(function_header.format(func[0].replace("_", "\\_"))) 62 | 63 | # Get the signature 64 | out.append("```py\n") 65 | out.append( 66 | "def {}{}\n".format( 67 | func[0], 68 | str(inspect.signature(func[1])), 69 | ) 70 | ) 71 | out.append("```\n") 72 | 73 | # get the docstring 74 | if pydoc.inspect.getdoc(func[1]): 75 | out.append("\n") 76 | out.append(pydoc.inspect.getdoc(func[1])) 77 | 78 | out.append("\n") 79 | return output + at_end 80 | 81 | 82 | def generatedocs(module, filename): 83 | try: 84 | sys.path.insert(0, os.getcwd() + "/..") 85 | # Attempt import 86 | mod = pydoc.safeimport(module) 87 | if mod is None: 88 | print("Module not found") 89 | 90 | # Module imported correctly, let's create the docs 91 | with open(filename, "w") as f: 92 | f.write(getmarkdown(mod)) 93 | except pydoc.ErrorDuringImport as e: 94 | print("Error while trying to import " + module) 95 | 96 | 97 | # if __name__ == '__main__': 98 | generatedocs("tockloader.main", "main.md") 99 | generatedocs("tockloader.tockloader", "tockloader.md") 100 | generatedocs("tockloader.board_interface", "board_interface.md") 101 | generatedocs("tockloader.bootloader_serial", "bootloader_serial.md") 102 | generatedocs("tockloader.jlinkexe", "jlinkexe.md") 103 | generatedocs("tockloader.openocd", "openocd.md") 104 | generatedocs("tockloader.nrfjprog", "nrfprog.md") 105 | generatedocs("tockloader.flash_file", "flash_file.md") 106 | generatedocs("tockloader.tab", "tab.md") 107 | generatedocs("tockloader.app_installed", "app_installed.md") 108 | generatedocs("tockloader.app_tab", "app_tab.md") 109 | generatedocs("tockloader.app_padding", "app_padding.md") 110 | generatedocs("tockloader.tbfh", "tbfh.md") 111 | generatedocs("tockloader.tickv", "tickv.md") 112 | generatedocs("tockloader.exceptions", "exceptions.md") 113 | generatedocs("tockloader.helpers", "helpers.md") 114 | generatedocs("tockloader.display", "display.md") 115 | 116 | # Make index from readme 117 | with open("../README.md") as infile: 118 | with open("index.md", "w") as outfile: 119 | outfile.write(infile.read()) 120 | -------------------------------------------------------------------------------- /docs/helpers.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.helpers Documentation 2 | 3 | 4 | Various helper functions that tockloader uses. Mostly for interacting with 5 | users in a nice way. 6 | 7 | ## Class ListToDictAction 8 | `argparse` action to convert `[['key', 'val'], ['key2', 'val2']]` to 9 | `{'key': 'val', 'key2': 'val2'}`. 10 | 11 | This will also do the following conversions: 12 | - `[[]]` -> `{}` 13 | - `[['k': 'v'], []]` -> `{'k': 'v'}` 14 | - `[['k': 'v'], ['']]` -> `{'k': 'v'}` 15 | - `[['k': 'v'], ['a']]` -> `{'k': 'v', 'a': ''}` 16 | ### \_\_init\_\_ 17 | ```py 18 | 19 | def __init__(self, option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None, deprecated=False) 20 | 21 | ``` 22 | 23 | 24 | 25 | Initialize self. See help(type(self)) for accurate signature. 26 | 27 | 28 | ### format\_usage 29 | ```py 30 | 31 | def format_usage(self) 32 | 33 | ``` 34 | 35 | 36 | 37 | ### \_\_call\_\_ 38 | ```py 39 | 40 | def __call__(self, parser, namespace, values, option_string=None) 41 | 42 | ``` 43 | 44 | 45 | 46 | Call self as a function. 47 | 48 | 49 | ### \_\_repr\_\_ 50 | ```py 51 | 52 | def __repr__(self) 53 | 54 | ``` 55 | 56 | 57 | 58 | Return repr(self). 59 | 60 | 61 | ### \_get\_args 62 | ```py 63 | 64 | def _get_args(self) 65 | 66 | ``` 67 | 68 | 69 | 70 | ### \_get\_kwargs 71 | ```py 72 | 73 | def _get_kwargs(self) 74 | 75 | ``` 76 | 77 | 78 | 79 | 80 | 81 | ### menu 82 | ```py 83 | 84 | def menu(options, *, return_type, default_index=0, prompt='Which option? ', title='') 85 | 86 | ``` 87 | 88 | 89 | 90 | Present a menu of choices to a user 91 | 92 | `options` should be a like-list object whose iterated objects can be coerced 93 | into strings. 94 | 95 | `return_type` must be set to one of 96 | - "index" - for the index into the options array 97 | - "value" - for the option value chosen 98 | 99 | `default_index` is the index to present as the default value (what happens 100 | if the user simply presses enter). Passing `None` disables default 101 | selection. 102 | 103 | 104 | ### menu\_multiple 105 | ```py 106 | 107 | def menu_multiple(options, prompt='Make your selections:') 108 | 109 | ``` 110 | 111 | 112 | 113 | ### menu\_multiple\_indices 114 | ```py 115 | 116 | def menu_multiple_indices(options, prompt='Make your selections:') 117 | 118 | ``` 119 | 120 | 121 | 122 | ### menu\_new 123 | ```py 124 | 125 | def menu_new(options, *, return_type, default_index=None, prompt='', title='') 126 | 127 | ``` 128 | 129 | 130 | 131 | Present an interactive menu of choices to a user. 132 | 133 | `options` should be a like-list object whose iterated objects can be coerced 134 | into strings. 135 | 136 | `return_type` must be set to one of: 137 | - "index" - for the index into the options array 138 | - "value" - for the option value chosen 139 | 140 | `default_index` is the index to present as the default value (what happens 141 | if the user simply presses enter). Passing `None` disables default 142 | selection. 143 | 144 | 145 | ### menu\_new\_yes\_no 146 | ```py 147 | 148 | def menu_new_yes_no(prompt='') 149 | 150 | ``` 151 | 152 | 153 | 154 | Present an interactive yes/no prompt to the user. 155 | 156 | 157 | ### number\_or 158 | ```py 159 | 160 | def number_or(value) 161 | 162 | ``` 163 | 164 | 165 | 166 | Try to format value as a number. If that fails, just leave it alone. 167 | 168 | 169 | ### plural 170 | ```py 171 | 172 | def plural(value) 173 | 174 | ``` 175 | 176 | 177 | 178 | Return '' or 's' based on whether the `value` means a string should have 179 | a plural word. 180 | 181 | `value` can be a list or a number. If the number or the length of the list 182 | is 1, then '' will be returned. Otherwise 's'. 183 | 184 | 185 | ### print\_flash 186 | ```py 187 | 188 | def print_flash(address, flash) 189 | 190 | ``` 191 | 192 | 193 | 194 | Return binary data in a nice hexdump format. 195 | 196 | 197 | ### set\_terminal\_title 198 | ```py 199 | 200 | def set_terminal_title(title) 201 | 202 | ``` 203 | 204 | 205 | 206 | ### set\_terminal\_title\_from\_port 207 | ```py 208 | 209 | def set_terminal_title_from_port(port) 210 | 211 | ``` 212 | 213 | 214 | 215 | Set the title of the user's terminal for Tockloader. 216 | 217 | 218 | ### set\_terminal\_title\_from\_port\_info 219 | ```py 220 | 221 | def set_terminal_title_from_port_info(info) 222 | 223 | ``` 224 | 225 | 226 | 227 | Set a terminal title from a `pyserial` object. 228 | 229 | 230 | ### text\_in\_box 231 | ```py 232 | 233 | def text_in_box(string, box_width, box_height=3) 234 | 235 | ``` 236 | 237 | 238 | 239 | Return a string like: 240 | ``` 241 | ┌───────────────┐ 242 | │ str │ 243 | └───────────────┘ 244 | ``` 245 | 246 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # ![TockLoader](http://www.tockos.org/assets/img/tockloader.svg#a "Tockloader Logo") 2 | 3 | Tool for programming Tock onto hardware boards. 4 | 5 | Install 6 | ------- 7 | 8 | ``` 9 | pip3 install pipx 10 | pipx install tockloader 11 | ``` 12 | 13 | If you want tab completions: 14 | 15 | ``` 16 | register-python-argcomplete tockloader >> ~/.bashrc 17 | ``` 18 | 19 | Usage 20 | ----- 21 | 22 | This tool installs a binary called `tockloader`, which supports several commands. 23 | 24 | ### Primary Commands 25 | 26 | These are the main commands for managing apps on a board. 27 | 28 | #### `tockloader install` 29 | 30 | Load Tock applications on to the board. Use `--no-replace` to install 31 | multiple copies of the same app. 32 | 33 | #### `tockloader update` 34 | 35 | Update an application that is already flashed to the board with a new 36 | binary. 37 | 38 | #### `tockloader uninstall [application name(s)]` 39 | 40 | Remove an application from flash by its name. 41 | 42 | 43 | ### Board Inspection Commands 44 | 45 | These query the board for its current state. 46 | 47 | #### `tockloader list` 48 | 49 | Print information about the apps currently loaded onto the board. 50 | 51 | #### `tockloader info` 52 | 53 | Show all properties of the board. 54 | 55 | 56 | ### Utility Commands 57 | 58 | These provide other helpful features. 59 | 60 | #### `tockloader listen` 61 | 62 | Listen to UART `printf()` data from a board. Use the option `--rtt` to use 63 | Segger's RTT listener instead of using a serial port. 64 | 65 | 66 | ### Other Commands 67 | 68 | These provide more internal functionality. 69 | 70 | #### `tockloader flash` 71 | 72 | Load binaries onto hardware platforms that are running a compatible bootloader. 73 | This is used by the [TockOS](https://github.com/helena-project/tock) Make system 74 | when kernel binaries are programmed to the board with `make program`. 75 | 76 | #### `tockloader inspect-tab` 77 | 78 | Show details about a compiled TAB file. 79 | 80 | #### `tockloader enable-app [application name(s)]` 81 | 82 | Enable an app so that the kernel will run it at boot. 83 | 84 | #### `tockloader disable-app [application name(s)]` 85 | 86 | Disable an app so that the kernel will not start it at boot. 87 | 88 | #### `tockloader sticky-app [application name(s)]` 89 | 90 | Mark an app as sticky so that the `--force` flag is required to uninstall it. 91 | 92 | #### `tockloader unsticky-app [application name(s)]` 93 | 94 | Remove the sticky flag from an app. 95 | 96 | #### `tockloader list-attributes` 97 | 98 | Show all of the attributes that are stored on the board. 99 | 100 | #### `tockloader set-attribute [attribute key] [attribute value]` 101 | 102 | Set a particular attribute key to the specified value. This will overwrite 103 | an existing attribute if the key matches. 104 | 105 | #### `tockloader remove-attribute [attribute key]` 106 | 107 | Remove a particular attribute from the board. 108 | 109 | #### `tockloader dump-flash-page [page number]` 110 | 111 | Show the contents of a page of flash. 112 | 113 | #### `tockloader read [address] [# bytes]` 114 | 115 | Read arbitrary flash memory from the board. 116 | 117 | #### `tockloader write [address] [# bytes] [value]` 118 | 119 | Write arbitrary flash memory on the board with a specific value. 120 | 121 | #### `tockloader list-known-boards` 122 | 123 | Print which boards tockloader has default settings for built-in. 124 | 125 | #### `tockloader set-start-address [address]` 126 | 127 | Set the jump address the bootloader uses for the location of the kernel. 128 | 129 | #### `tockloader tbf tlv add|modify|delete [TLVNAME]` 130 | 131 | Interact with TLV structures within a TBF. 132 | 133 | #### `tockloader tbf credential add|delete [credential type]` 134 | 135 | Add and remove credentials in the TBF footer. 136 | 137 | #### `tockloader tbf convert [output format]` 138 | 139 | Convert a TBF to a different format. 140 | 141 | #### `tockloader tickv get|append|invalidate|dump|cleanup|reset [key] [value]` 142 | 143 | Interact with a TicKV key-value database. 144 | 145 | 146 | Specifying the Board 147 | -------------------- 148 | 149 | For tockloader to know how to interface with a particular hardware board, 150 | it tries several options: 151 | 152 | 1. Read the parameters from the bootloader. Tockloader assumes it can open a 153 | serial connection to a 154 | [tock-bootloader](https://github.com/tock/tock-bootloader/) on the board. 155 | 156 | 2. Use `JLinkExe` and `OpenOCD` to discover known boards. 157 | 158 | 3. Use the `--board` command line flag and a list of known boards. 159 | 160 | 4. Use individual command line flags that specify how to interact with the 161 | board. 162 | 163 | If command line flags are passed they take priority over any automatically 164 | discovered options. 165 | 166 | Tockloader has hardcoded parameters for a variety of boards. You can list these 167 | with: 168 | 169 | tockloader list-known-boards 170 | 171 | To use a known board, if it is not automatically discovered, you can: 172 | 173 | tockloader [command] --board [board] 174 | 175 | If your board is not a known board, you can specify the required parameters 176 | via command line options. Note, you also need to provide a name for the board. 177 | 178 | tockloader [command] --board [board] --arch [arch] --page-size [page_size] 179 | 180 | - `board`: The name of the board. This helps prevent incompatible applications 181 | from being flashed on the wrong board. 182 | - `arch`: The architecture of the board. Likely `cortex-m0` or `cortex-m4`. 183 | - `page_size`: The size in bytes of the smallest erasable unit in flash. 184 | 185 | Specifying the Communication Channel 186 | ------------------------------------ 187 | 188 | Tockloader defaults to using a serial connection to an on-chip bootloader to 189 | program and interact with a board. If you need to use a different communication 190 | mechanism, you can specify what Tockloader should use with command line 191 | arguments. Note, Tockloader's board autodiscovery process also selects different 192 | communication channels based on which board it finds. 193 | 194 | To use a JTAG interface using JLinkExe, specify `--jlink`. JLinkExe requires 195 | knowing the device type of the MCU on the board. 196 | 197 | tockloader [command] --board [board] --arch [arch] --page-size [page_size] \ 198 | --jlink --jlink-cmd [jlink_cmd] --jlink-device [device] \ 199 | --jlink-speed [speed] --jlink-if [if] \ 200 | --jlink-serial-number [serial_number] 201 | 202 | - `jlink_cmd`: The JLink executable to invoke. Defaults to `JLinkExe` on 203 | Mac/Linux, and `JLink` on Windows. 204 | - `device`: The JLinkExe device identifier. 205 | - `speed`: The speed value to pass to JLink. Defaults to 1200. 206 | - `if`: The interface to pass to JLink. 207 | - `serial-number`: The serial number of the target board to use with JLink. 208 | 209 | Tockloader can also do JTAG using OpenOCD. OpenOCD needs to know which config 210 | file to use. 211 | 212 | tockloader [command] --board [board] --arch [arch] --page-size [page_size] \ 213 | --openocd --openocd-board [openocd_board] \ 214 | --openocd-cmd [openocd_cmd] \ 215 | --openocd-options [openocd_options] \ 216 | --openocd-commands [openocd_commands] 217 | 218 | - `openocd_board`: The `.cfg` file in the board folder in OpenOCD to use. 219 | - `openocd_cmd`: The OpenOCD executable to invoke. Defaults to `openocd`. 220 | - `openocd_options`: A list of Tock-specific flags used to customize how 221 | Tockloader calls OpenOCD based on experience with various boards and their 222 | quirks. Options include: 223 | - `noreset`: Removes the command `reset init;` from OpenOCD commands. 224 | - `nocmdprefix`: Removes the commands `init; reset init; halt;` from OpenOCD 225 | commands. 226 | - `workareazero`: Adds the command `set WORKAREASIZE 0;` to OpenOCD commands. 227 | - `resume`: Adds the commands `soft_reset_halt; resume;` to OpenOCD commands. 228 | - `openocd_commands`: This sets a custom OpenOCD command string to allow 229 | Tockloader to program arbitrary chips with OpenOCD before support for the 230 | board is officially include in Tockloader. The following main operations can 231 | be customized: 232 | - `program`: Operation used to write a binary to the chip. 233 | - `read`: Operation used to read arbitrary flash memory on the chip. 234 | - `erase`: Operation that erases arbitrary ranges of flash memory on the chip. 235 | 236 | The custom values are specified as key=value pairs, for example, 237 | `--openocd_commands 'program=write_image; halt;' 'erase=flash fillb 238 | {address:#x} 0xff 512;'`. Operation strings can include wildcards which will 239 | get set with the correct value by Tockloader: 240 | - `{{binary}}`: The binary file path. 241 | - `{address:#x}`: The specified address for the binary to be programmed at. 242 | - `{length}`: The number of bytes. Only valid for the `read` operation. 243 | 244 | For STM32 boards, Tockloader supports 245 | [STLINK](https://github.com/stlink-org/stlink). The stlink tool knows how to 246 | interface with the boards, so there are not many flags. 247 | 248 | tockloader [command] --board [board] --arch [arch] --page-size [page_size] \ 249 | --stlink \ 250 | --stinfo-cmd [stinfo_cmd] --stflash-cmd [stflash_cmd] 251 | 252 | - `stinfo_cmd`: The st-info executable to invoke. Defaults to `st-info`. 253 | - `stflash_cmd`: The st-flash executable to invoke. Defaults to `st-flash`. 254 | 255 | Finally, Tockloader can treat a local file as though it were the flash contents 256 | of a board. The file can then be loaded separately onto a board. 257 | 258 | tockloader [command] --flash-file [filepath] 259 | 260 | - `filepath`: The file to use as the flash contents. Will be created if it 261 | doesn't exist. 262 | 263 | 264 | Example Usage 265 | ------------- 266 | 267 | Install an app, make sure it's up to date, and make sure it's the only app on 268 | the board: 269 | 270 | tockloader install --make --erase 271 | 272 | Get all info from the board that can be used to help debugging: 273 | 274 | tockloader info 275 | 276 | Print additionally debugging information. This can be helpful when using JTAG. 277 | 278 | tockloader install --debug 279 | 280 | Get `printf()` data from a board: 281 | 282 | tockloader listen 283 | 284 | Additional Flags 285 | ---------------- 286 | 287 | There are additional flags that might be useful for customizing tockloader's 288 | operation based on the requirements of a particular hardware platform. 289 | 290 | - `--app-address`: Manually specify the address at the beginning of where apps 291 | are stored. This can be in hex or decimal. 292 | - `--bundle-apps`: This forces tockloader to write all apps as a concatenated 293 | bundle using only a single flash command. This will require that anytime any 294 | app changes in any way (e.g. its header changes or the app is updated or a new 295 | app is installed) all apps are re-written. 296 | - `--layout`: Specify exactly how apps and padding apps should be written to the 297 | board. This implies `--erase` and `--force` as all existing (even sticky) apps 298 | will be removed. 299 | 300 | The layout is specified as a string of how apps from TBFs and padding apps 301 | should be written to the board. The syntax for the layout uses the following 302 | identifiers: 303 | 304 | - `T`: indicates to install a TBF app. 305 | - `p`: indicates to install a padding app of `` bytes. 306 | 307 | For example `--layout Tp1024TT` specifies to install the first app at the 308 | `app-address`, then install a 1024 byte padding app, then install the second 309 | app, then install the third app. No board-specific alignment or sizing will 310 | be used; the apps will be installed exactly as described. It can be helpful 311 | to use `tockloader list --map` to view how the apps were actually installed. 312 | 313 | Credentials and Integrity Support 314 | --------------------------------- 315 | 316 | Tockloader supports working with credentials stored in the TBF footer. 317 | Tockloader will attempt to verify that stored credentials are valid for the 318 | given TBF. For credentials that require keys to verify, Tockloader can check the 319 | credential using: 320 | 321 | $ tockloader inspect-tab --verify-credentials [list of key files] 322 | example: 323 | $ tockloader inspect-tab --verify-credentials tockkey.public.der 324 | 325 | Tockloader can also add credentials. To add a hash: 326 | 327 | $ tockloader tbf credential add sha256 328 | 329 | To add an RSA signature: 330 | 331 | $ tockloader tbf credential add rsa2048 --private-key tockkey2048.private.der --public-key tockkey2048.public.der 332 | 333 | To remove credentials: 334 | 335 | $ tockloader tbf credential delete sha256 336 | 337 | 338 | Features 339 | -------- 340 | 341 | - Supported communication protocols 342 | - Serial over USB 343 | - Segger JLinkExe JTAG support 344 | - OpenOCD JTAG support 345 | - JLink RTT listener 346 | - JSON output using `--output-format json` for certain commands. 347 | 348 | 349 | Complete Install Instructions 350 | ----------------------------- 351 | 352 | Tockloader is a Python script that is installed as an executable. 353 | To use Tockloader, you need python3, a couple dependencies, and 354 | the Tockloader package. 355 | 356 | - Ubuntu 357 | ``` 358 | sudo apt install python3-pip 359 | pip3 install -U pip --user # update pip 360 | pip3 install tockloader --user 361 | ``` 362 | 363 | - MacOS 364 | ``` 365 | brew install python3 366 | pip3 install tockloader 367 | ``` 368 | 369 | - Windows 370 | - [Download and Install Python 3](https://www.python.org/downloads/windows/) 371 | - Execute within CMD/PowerShell/...: 372 | ``` 373 | pip3 install tockloader 374 | ``` 375 | 376 | Internal Notes 377 | -------------- 378 | 379 | ### Test Locally 380 | 381 | To test the code locally without installing as a package, from the top-level 382 | directory: 383 | 384 | python3 -m tockloader.main 385 | 386 | 387 | ### Build and Install Locally 388 | 389 | pipx install build 390 | pyproject-build 391 | pipx install . 392 | 393 | ### Upload to PyPI 394 | 395 | pipx install build 396 | pipx install flit 397 | pyproject-build 398 | flit publish 399 | 400 | 401 | ### Build Docs 402 | 403 | pip3 install mkdocs 404 | cd docs 405 | ./generate_docs.py 406 | cd .. 407 | mkdocs serve --dev-addr=0.0.0.0:8001 408 | 409 | ### Create requirements.txt 410 | 411 | pip3 install pipreqs 412 | pipreqs . --force 413 | -------------------------------------------------------------------------------- /docs/jlinkexe.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.jlinkexe Documentation 2 | 3 | 4 | Interface for boards using Segger's JLinkExe program. 5 | 6 | All communication with the board is done using JLinkExe commands and scripts. 7 | 8 | Different MCUs require different command line arguments so that the JLinkExe 9 | tool knows which JTAG interface it is talking to. Since we don't want to burden 10 | the user with specifying the board each time, we default to using a generic 11 | cortex-m0 target, and use that to read the bootloader attributes to get the 12 | correct version. Once we know more about the board we are talking to we use the 13 | correct command line argument for future communication. 14 | 15 | ## Class JLinkExe 16 | Base class for interacting with hardware boards. All of the class functions 17 | should be overridden to support a new method of interacting with a board. 18 | ### \_\_init\_\_ 19 | ```py 20 | 21 | def __init__(self, args) 22 | 23 | ``` 24 | 25 | 26 | 27 | Initialize self. See help(type(self)) for accurate signature. 28 | 29 | 30 | ### attached\_board\_exists 31 | ```py 32 | 33 | def attached_board_exists(self) 34 | 35 | ``` 36 | 37 | 38 | 39 | For this particular board communication channel, check if there appears 40 | to be a valid board attached to the host that tockloader can communicate 41 | with. 42 | 43 | 44 | ### bootloader\_is\_present 45 | ```py 46 | 47 | def bootloader_is_present(self) 48 | 49 | ``` 50 | 51 | 52 | 53 | Check for the Tock bootloader. Returns `True` if it is present, `False` 54 | if not, and `None` if unsure. 55 | 56 | 57 | ### clear\_bytes 58 | ```py 59 | 60 | def clear_bytes(self, address) 61 | 62 | ``` 63 | 64 | 65 | 66 | Clear at least one byte starting at `address`. 67 | 68 | This API is designed to support "ending the linked list of apps", or 69 | clearing flash enough so that the flash after the last valid app will 70 | not parse as a valid TBF header. 71 | 72 | Different chips with different mechanisms for writing/erasing flash make 73 | implementing specific erase behavior difficult. Instead, we provide this 74 | rough API, which is sufficient for the task of ending the linked list, 75 | but doesn't guarantee exactly how many bytes after address will be 76 | cleared, or how they will be cleared. 77 | 78 | 79 | ### determine\_current\_board 80 | ```py 81 | 82 | def determine_current_board(self) 83 | 84 | ``` 85 | 86 | 87 | 88 | Figure out which board we are connected to. Most likely done by reading 89 | the attributes. Doesn't return anything. 90 | 91 | 92 | ### enter\_bootloader\_mode 93 | ```py 94 | 95 | def enter_bootloader_mode(self) 96 | 97 | ``` 98 | 99 | 100 | 101 | Get to a mode where we can read & write flash. 102 | 103 | 104 | ### exit\_bootloader\_mode 105 | ```py 106 | 107 | def exit_bootloader_mode(self) 108 | 109 | ``` 110 | 111 | 112 | 113 | Get out of bootloader mode and go back to running main code. 114 | 115 | 116 | ### flash\_binary 117 | ```py 118 | 119 | def flash_binary(self, address, binary, pad=False) 120 | 121 | ``` 122 | 123 | 124 | 125 | Write using JTAG 126 | 127 | 128 | ### get\_all\_attributes 129 | ```py 130 | 131 | def get_all_attributes(self) 132 | 133 | ``` 134 | 135 | 136 | 137 | Get all attributes on a board. Returns an array of attribute dicts. 138 | 139 | 140 | ### get\_attribute 141 | ```py 142 | 143 | def get_attribute(self, index) 144 | 145 | ``` 146 | 147 | 148 | 149 | Get a single attribute. Returns a dict with two keys: `key` and `value`. 150 | 151 | 152 | ### get\_board\_arch 153 | ```py 154 | 155 | def get_board_arch(self) 156 | 157 | ``` 158 | 159 | 160 | 161 | Return the architecture of the board we are connected to. 162 | 163 | 164 | ### get\_board\_name 165 | ```py 166 | 167 | def get_board_name(self) 168 | 169 | ``` 170 | 171 | 172 | 173 | Return the name of the board we are connected to. 174 | 175 | 176 | ### get\_bootloader\_version 177 | ```py 178 | 179 | def get_bootloader_version(self) 180 | 181 | ``` 182 | 183 | 184 | 185 | Return the version string of the bootloader. Should return a value 186 | like `0.5.0`, or `None` if it is unknown. 187 | 188 | 189 | ### get\_kernel\_version 190 | ```py 191 | 192 | def get_kernel_version(self) 193 | 194 | ``` 195 | 196 | 197 | 198 | Return the kernel ABI version installed on the board. If the version 199 | cannot be determined, return `None`. 200 | 201 | 202 | ### get\_page\_size 203 | ```py 204 | 205 | def get_page_size(self) 206 | 207 | ``` 208 | 209 | 210 | 211 | Return the size of the page in bytes for the connected board. 212 | 213 | 214 | ### open\_link\_to\_board 215 | ```py 216 | 217 | def open_link_to_board(self) 218 | 219 | ``` 220 | 221 | 222 | 223 | Open a connection to the board. 224 | 225 | 226 | ### print\_known\_boards 227 | ```py 228 | 229 | def print_known_boards(self) 230 | 231 | ``` 232 | 233 | 234 | 235 | Display the boards that have settings configured in tockloader. 236 | 237 | 238 | ### read\_range 239 | ```py 240 | 241 | def read_range(self, address, length) 242 | 243 | ``` 244 | 245 | 246 | 247 | Read a specific range of flash. 248 | 249 | If this fails for some reason this should return an empty binary array. 250 | 251 | 252 | ### run\_terminal 253 | ```py 254 | 255 | def run_terminal(self) 256 | 257 | ``` 258 | 259 | 260 | 261 | Use JLinkRTTClient to listen for RTT messages. 262 | 263 | 264 | ### set\_attribute 265 | ```py 266 | 267 | def set_attribute(self, index, raw) 268 | 269 | ``` 270 | 271 | 272 | 273 | Set a single attribute. 274 | 275 | 276 | ### set\_start\_address 277 | ```py 278 | 279 | def set_start_address(self, address) 280 | 281 | ``` 282 | 283 | 284 | 285 | Set the address the bootloader jumps to to start the actual code. 286 | 287 | 288 | ### translate\_address 289 | ```py 290 | 291 | def translate_address(self, address) 292 | 293 | ``` 294 | 295 | 296 | 297 | Translate an address from MCU address space to the address required for 298 | the board interface. This is used for boards where the address passed to 299 | the board interface is not the address where this region is exposed in 300 | the MCU address space. This method must be called from the board 301 | interface implementation prior to memory accesses. 302 | 303 | 304 | ### \_align\_and\_stretch\_to\_page 305 | ```py 306 | 307 | def _align_and_stretch_to_page(self, address, binary) 308 | 309 | ``` 310 | 311 | 312 | 313 | Return a new (address, binary) that is a multiple of the page size 314 | and is aligned to page boundaries. 315 | 316 | 317 | ### \_configure\_from\_known\_boards 318 | ```py 319 | 320 | def _configure_from_known_boards(self) 321 | 322 | ``` 323 | 324 | 325 | 326 | If we know the name of the board we are interfacing with, this function 327 | tries to use the `KNOWN_BOARDS` array to populate other needed settings 328 | if they have not already been set from other methods. 329 | 330 | This can be used in multiple locations. First, it is used when 331 | tockloader first starts because if a user passes in the `--board` 332 | argument then we know the board and can try to pull in settings from 333 | KNOWN_BOARDS. Ideally, however, the user doesn't have to pass in any 334 | arguments, but then we won't know what board until after we have had a 335 | chance to read its attributes. The board at least needs the "board" 336 | attribute to be set, and then we can use KNOWN_BOARDS to fill in the 337 | rest. 338 | 339 | 340 | ### \_decode\_attribute 341 | ```py 342 | 343 | def _decode_attribute(self, raw) 344 | 345 | ``` 346 | 347 | 348 | 349 | ### \_get\_tockloader\_board\_from\_emulators 350 | ```py 351 | 352 | def _get_tockloader_board_from_emulators(self, emulators) 353 | 354 | ``` 355 | 356 | 357 | 358 | Returns None or a board name if we can parse the emulators list 359 | and find a valid board. 360 | 361 | To add to this list, connect your board, then: 362 | 363 | $ JLinkExe 364 | > ShowEmuList 365 | 366 | and hope there is something unique we can match on. 367 | 368 | 369 | ### \_list\_emulators 370 | ```py 371 | 372 | def _list_emulators(self) 373 | 374 | ``` 375 | 376 | 377 | 378 | Retrieve a list of JLink compatible devices. 379 | 380 | 381 | ### \_run\_jtag\_commands 382 | ```py 383 | 384 | def _run_jtag_commands(self, commands, binary, write=True) 385 | 386 | ``` 387 | 388 | 389 | 390 | - `commands`: List of JLinkExe commands. Use {binary} for where the name 391 | of the binary file should be substituted. 392 | - `binary`: A bytes() object that will be used to write to the board. 393 | - `write`: Set to true if the command writes binaries to the board. Set 394 | to false if the command will read bits from the board. 395 | 396 | 397 | 398 | -------------------------------------------------------------------------------- /docs/main.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.main Documentation 2 | 3 | 4 | ### Main command line interface for Tockloader. 5 | 6 | Each `tockloader` command is mapped to a function which calls the correct 7 | tockloader class function. This file also handles discovering and reading in TAB 8 | files. 9 | 10 | ### check\_and\_run\_make 11 | ```py 12 | 13 | def check_and_run_make(args) 14 | 15 | ``` 16 | 17 | 18 | 19 | Checks for a Makefile, and it it exists runs `make`. 20 | 21 | 22 | ### collect\_tabs 23 | ```py 24 | 25 | def collect_tabs(args) 26 | 27 | ``` 28 | 29 | 30 | 31 | Load in Tock Application Bundle (TAB) files. If none are specified, this 32 | searches for them in subfolders. 33 | 34 | Also allow downloading apps by name from a server. 35 | 36 | 37 | ### command\_disable\_app 38 | ```py 39 | 40 | def command_disable_app(args) 41 | 42 | ``` 43 | 44 | 45 | 46 | ### command\_dump\_flash\_page 47 | ```py 48 | 49 | def command_dump_flash_page(args) 50 | 51 | ``` 52 | 53 | 54 | 55 | ### command\_enable\_app 56 | ```py 57 | 58 | def command_enable_app(args) 59 | 60 | ``` 61 | 62 | 63 | 64 | ### command\_erase\_apps 65 | ```py 66 | 67 | def command_erase_apps(args) 68 | 69 | ``` 70 | 71 | 72 | 73 | ### command\_flash 74 | ```py 75 | 76 | def command_flash(args) 77 | 78 | ``` 79 | 80 | 81 | 82 | ### command\_info 83 | ```py 84 | 85 | def command_info(args) 86 | 87 | ``` 88 | 89 | 90 | 91 | ### command\_inspect\_tab 92 | ```py 93 | 94 | def command_inspect_tab(args) 95 | 96 | ``` 97 | 98 | 99 | 100 | ### command\_install 101 | ```py 102 | 103 | def command_install(args) 104 | 105 | ``` 106 | 107 | 108 | 109 | ### command\_list 110 | ```py 111 | 112 | def command_list(args) 113 | 114 | ``` 115 | 116 | 117 | 118 | ### command\_list\_attributes 119 | ```py 120 | 121 | def command_list_attributes(args) 122 | 123 | ``` 124 | 125 | 126 | 127 | ### command\_list\_known\_boards 128 | ```py 129 | 130 | def command_list_known_boards(args) 131 | 132 | ``` 133 | 134 | 135 | 136 | ### command\_listen 137 | ```py 138 | 139 | def command_listen(args) 140 | 141 | ``` 142 | 143 | 144 | 145 | ### command\_read 146 | ```py 147 | 148 | def command_read(args) 149 | 150 | ``` 151 | 152 | 153 | 154 | Read the correct flash range from the chip. 155 | 156 | 157 | ### command\_remove\_attribute 158 | ```py 159 | 160 | def command_remove_attribute(args) 161 | 162 | ``` 163 | 164 | 165 | 166 | ### command\_set\_attribute 167 | ```py 168 | 169 | def command_set_attribute(args) 170 | 171 | ``` 172 | 173 | 174 | 175 | ### command\_set\_start\_address 176 | ```py 177 | 178 | def command_set_start_address(args) 179 | 180 | ``` 181 | 182 | 183 | 184 | ### command\_sticky\_app 185 | ```py 186 | 187 | def command_sticky_app(args) 188 | 189 | ``` 190 | 191 | 192 | 193 | ### command\_tbf\_convert 194 | ```py 195 | 196 | def command_tbf_convert(args) 197 | 198 | ``` 199 | 200 | 201 | 202 | ### command\_tbf\_credential\_add 203 | ```py 204 | 205 | def command_tbf_credential_add(args) 206 | 207 | ``` 208 | 209 | 210 | 211 | ### command\_tbf\_credential\_delete 212 | ```py 213 | 214 | def command_tbf_credential_delete(args) 215 | 216 | ``` 217 | 218 | 219 | 220 | ### command\_tbf\_tlv\_add 221 | ```py 222 | 223 | def command_tbf_tlv_add(args) 224 | 225 | ``` 226 | 227 | 228 | 229 | ### command\_tbf\_tlv\_delete 230 | ```py 231 | 232 | def command_tbf_tlv_delete(args) 233 | 234 | ``` 235 | 236 | 237 | 238 | ### command\_tbf\_tlv\_modify 239 | ```py 240 | 241 | def command_tbf_tlv_modify(args) 242 | 243 | ``` 244 | 245 | 246 | 247 | ### command\_tickv\_append 248 | ```py 249 | 250 | def command_tickv_append(args) 251 | 252 | ``` 253 | 254 | 255 | 256 | ### command\_tickv\_append\_rsa\_key 257 | ```py 258 | 259 | def command_tickv_append_rsa_key(args) 260 | 261 | ``` 262 | 263 | 264 | 265 | Helper operation to store an RSA public key in a TicKV database. This adds 266 | two key-value pairs: 267 | 268 | 1. `rsa-key-n` 269 | 2. `rsa-key-e` 270 | 271 | where `` is the size of the key. So, for 2048 bit RSA keys the two 272 | TicKV keys will be `rsa2048-key-n` and `rsa2048-key-e`. 273 | 274 | The actual values for n and e are stored as byte arrays. 275 | 276 | 277 | ### command\_tickv\_cleanup 278 | ```py 279 | 280 | def command_tickv_cleanup(args) 281 | 282 | ``` 283 | 284 | 285 | 286 | ### command\_tickv\_dump 287 | ```py 288 | 289 | def command_tickv_dump(args) 290 | 291 | ``` 292 | 293 | 294 | 295 | ### command\_tickv\_get 296 | ```py 297 | 298 | def command_tickv_get(args) 299 | 300 | ``` 301 | 302 | 303 | 304 | ### command\_tickv\_hash 305 | ```py 306 | 307 | def command_tickv_hash(args) 308 | 309 | ``` 310 | 311 | 312 | 313 | ### command\_tickv\_invalidate 314 | ```py 315 | 316 | def command_tickv_invalidate(args) 317 | 318 | ``` 319 | 320 | 321 | 322 | ### command\_tickv\_reset 323 | ```py 324 | 325 | def command_tickv_reset(args) 326 | 327 | ``` 328 | 329 | 330 | 331 | ### command\_uninstall 332 | ```py 333 | 334 | def command_uninstall(args) 335 | 336 | ``` 337 | 338 | 339 | 340 | ### command\_unsticky\_app 341 | ```py 342 | 343 | def command_unsticky_app(args) 344 | 345 | ``` 346 | 347 | 348 | 349 | ### command\_update 350 | ```py 351 | 352 | def command_update(args) 353 | 354 | ``` 355 | 356 | 357 | 358 | ### command\_write 359 | ```py 360 | 361 | def command_write(args) 362 | 363 | ``` 364 | 365 | 366 | 367 | Write flash range on the chip with a specific value. 368 | 369 | 370 | ### get\_addable\_tlvs 371 | ```py 372 | 373 | def get_addable_tlvs() 374 | 375 | ``` 376 | 377 | 378 | 379 | Return a list of (tlv_name, #parameters) tuples for all TLV types that 380 | tockloader can add. 381 | 382 | 383 | ### main 384 | ```py 385 | 386 | def main() 387 | 388 | ``` 389 | 390 | 391 | 392 | Read in command line arguments and call the correct command function. 393 | 394 | -------------------------------------------------------------------------------- /docs/nrfprog.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.nrfjprog Documentation 2 | 3 | 4 | Interface for boards using nrfjprog. 5 | 6 | ## Class nrfjprog 7 | Base class for interacting with hardware boards. All of the class functions 8 | should be overridden to support a new method of interacting with a board. 9 | ### \_\_init\_\_ 10 | ```py 11 | 12 | def __init__(self, args) 13 | 14 | ``` 15 | 16 | 17 | 18 | Initialize self. See help(type(self)) for accurate signature. 19 | 20 | 21 | ### attached\_board\_exists 22 | ```py 23 | 24 | def attached_board_exists(self) 25 | 26 | ``` 27 | 28 | 29 | 30 | For this particular board communication channel, check if there appears 31 | to be a valid board attached to the host that tockloader can communicate 32 | with. 33 | 34 | 35 | ### bootloader\_is\_present 36 | ```py 37 | 38 | def bootloader_is_present(self) 39 | 40 | ``` 41 | 42 | 43 | 44 | Check for the Tock bootloader. Returns `True` if it is present, `False` 45 | if not, and `None` if unsure. 46 | 47 | 48 | ### clear\_bytes 49 | ```py 50 | 51 | def clear_bytes(self, address) 52 | 53 | ``` 54 | 55 | 56 | 57 | Clear at least one byte starting at `address`. 58 | 59 | This API is designed to support "ending the linked list of apps", or 60 | clearing flash enough so that the flash after the last valid app will 61 | not parse as a valid TBF header. 62 | 63 | Different chips with different mechanisms for writing/erasing flash make 64 | implementing specific erase behavior difficult. Instead, we provide this 65 | rough API, which is sufficient for the task of ending the linked list, 66 | but doesn't guarantee exactly how many bytes after address will be 67 | cleared, or how they will be cleared. 68 | 69 | 70 | ### determine\_current\_board 71 | ```py 72 | 73 | def determine_current_board(self) 74 | 75 | ``` 76 | 77 | 78 | 79 | Figure out which board we are connected to. Most likely done by reading 80 | the attributes. Doesn't return anything. 81 | 82 | 83 | ### enter\_bootloader\_mode 84 | ```py 85 | 86 | def enter_bootloader_mode(self) 87 | 88 | ``` 89 | 90 | 91 | 92 | Get to a mode where we can read & write flash. 93 | 94 | 95 | ### exit\_bootloader\_mode 96 | ```py 97 | 98 | def exit_bootloader_mode(self) 99 | 100 | ``` 101 | 102 | 103 | 104 | Get out of bootloader mode and go back to running main code. 105 | 106 | 107 | ### flash\_binary 108 | ```py 109 | 110 | def flash_binary(self, address, binary, pad=False) 111 | 112 | ``` 113 | 114 | 115 | 116 | Write using nrfjprog. 117 | 118 | 119 | ### get\_all\_attributes 120 | ```py 121 | 122 | def get_all_attributes(self) 123 | 124 | ``` 125 | 126 | 127 | 128 | Get all attributes on a board. Returns an array of attribute dicts. 129 | 130 | 131 | ### get\_attribute 132 | ```py 133 | 134 | def get_attribute(self, index) 135 | 136 | ``` 137 | 138 | 139 | 140 | Get a single attribute. Returns a dict with two keys: `key` and `value`. 141 | 142 | 143 | ### get\_board\_arch 144 | ```py 145 | 146 | def get_board_arch(self) 147 | 148 | ``` 149 | 150 | 151 | 152 | Return the architecture of the board we are connected to. 153 | 154 | 155 | ### get\_board\_name 156 | ```py 157 | 158 | def get_board_name(self) 159 | 160 | ``` 161 | 162 | 163 | 164 | Return the name of the board we are connected to. 165 | 166 | 167 | ### get\_bootloader\_version 168 | ```py 169 | 170 | def get_bootloader_version(self) 171 | 172 | ``` 173 | 174 | 175 | 176 | Return the version string of the bootloader. Should return a value 177 | like `0.5.0`, or `None` if it is unknown. 178 | 179 | 180 | ### get\_kernel\_version 181 | ```py 182 | 183 | def get_kernel_version(self) 184 | 185 | ``` 186 | 187 | 188 | 189 | Return the kernel ABI version installed on the board. If the version 190 | cannot be determined, return `None`. 191 | 192 | 193 | ### get\_page\_size 194 | ```py 195 | 196 | def get_page_size(self) 197 | 198 | ``` 199 | 200 | 201 | 202 | Return the size of the page in bytes for the connected board. 203 | 204 | 205 | ### open\_link\_to\_board 206 | ```py 207 | 208 | def open_link_to_board(self) 209 | 210 | ``` 211 | 212 | 213 | 214 | Open a connection to the board. 215 | 216 | 217 | ### print\_known\_boards 218 | ```py 219 | 220 | def print_known_boards(self) 221 | 222 | ``` 223 | 224 | 225 | 226 | Display the boards that have settings configured in tockloader. 227 | 228 | 229 | ### read\_range 230 | ```py 231 | 232 | def read_range(self, address, length) 233 | 234 | ``` 235 | 236 | 237 | 238 | Read using nrfjprog. 239 | 240 | 241 | ### run\_terminal 242 | ```py 243 | 244 | def run_terminal(self) 245 | 246 | ``` 247 | 248 | 249 | 250 | ### set\_attribute 251 | ```py 252 | 253 | def set_attribute(self, index, raw) 254 | 255 | ``` 256 | 257 | 258 | 259 | Set a single attribute. 260 | 261 | 262 | ### set\_start\_address 263 | ```py 264 | 265 | def set_start_address(self, address) 266 | 267 | ``` 268 | 269 | 270 | 271 | Set the address the bootloader jumps to to start the actual code. 272 | 273 | 274 | ### translate\_address 275 | ```py 276 | 277 | def translate_address(self, address) 278 | 279 | ``` 280 | 281 | 282 | 283 | Translate an address from MCU address space to the address required for 284 | the board interface. This is used for boards where the address passed to 285 | the board interface is not the address where this region is exposed in 286 | the MCU address space. This method must be called from the board 287 | interface implementation prior to memory accesses. 288 | 289 | 290 | ### \_align\_and\_stretch\_to\_page 291 | ```py 292 | 293 | def _align_and_stretch_to_page(self, address, binary) 294 | 295 | ``` 296 | 297 | 298 | 299 | Return a new (address, binary) that is a multiple of the page size 300 | and is aligned to page boundaries. 301 | 302 | 303 | ### \_configure\_from\_known\_boards 304 | ```py 305 | 306 | def _configure_from_known_boards(self) 307 | 308 | ``` 309 | 310 | 311 | 312 | If we know the name of the board we are interfacing with, this function 313 | tries to use the `KNOWN_BOARDS` array to populate other needed settings 314 | if they have not already been set from other methods. 315 | 316 | This can be used in multiple locations. First, it is used when 317 | tockloader first starts because if a user passes in the `--board` 318 | argument then we know the board and can try to pull in settings from 319 | KNOWN_BOARDS. Ideally, however, the user doesn't have to pass in any 320 | arguments, but then we won't know what board until after we have had a 321 | chance to read its attributes. The board at least needs the "board" 322 | attribute to be set, and then we can use KNOWN_BOARDS to fill in the 323 | rest. 324 | 325 | 326 | ### \_decode\_attribute 327 | ```py 328 | 329 | def _decode_attribute(self, raw) 330 | 331 | ``` 332 | 333 | 334 | 335 | 336 | -------------------------------------------------------------------------------- /docs/openocd.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.openocd Documentation 2 | 3 | 4 | Interface for boards using OpenOCD. 5 | 6 | This interface has a special option called `openocd_options` which is just a 7 | list of strings that are interpreted as flags to the OpenOCD class in this file. 8 | These allow individual boards to have custom operations in a semi-reasonable 9 | way. Note, I just made up the string (flag) names; they are not passed to 10 | OpenOCD directly. 11 | 12 | ## Class OpenOCD 13 | Base class for interacting with hardware boards. All of the class functions 14 | should be overridden to support a new method of interacting with a board. 15 | ### \_\_init\_\_ 16 | ```py 17 | 18 | def __init__(self, args) 19 | 20 | ``` 21 | 22 | 23 | 24 | Initialize self. See help(type(self)) for accurate signature. 25 | 26 | 27 | ### attached\_board\_exists 28 | ```py 29 | 30 | def attached_board_exists(self) 31 | 32 | ``` 33 | 34 | 35 | 36 | For this particular board communication channel, check if there appears 37 | to be a valid board attached to the host that tockloader can communicate 38 | with. 39 | 40 | 41 | ### bootloader\_is\_present 42 | ```py 43 | 44 | def bootloader_is_present(self) 45 | 46 | ``` 47 | 48 | 49 | 50 | Check for the Tock bootloader. Returns `True` if it is present, `False` 51 | if not, and `None` if unsure. 52 | 53 | 54 | ### clear\_bytes 55 | ```py 56 | 57 | def clear_bytes(self, address) 58 | 59 | ``` 60 | 61 | 62 | 63 | Clear at least one byte starting at `address`. 64 | 65 | This API is designed to support "ending the linked list of apps", or 66 | clearing flash enough so that the flash after the last valid app will 67 | not parse as a valid TBF header. 68 | 69 | Different chips with different mechanisms for writing/erasing flash make 70 | implementing specific erase behavior difficult. Instead, we provide this 71 | rough API, which is sufficient for the task of ending the linked list, 72 | but doesn't guarantee exactly how many bytes after address will be 73 | cleared, or how they will be cleared. 74 | 75 | 76 | ### determine\_current\_board 77 | ```py 78 | 79 | def determine_current_board(self) 80 | 81 | ``` 82 | 83 | 84 | 85 | Figure out which board we are connected to. Most likely done by reading 86 | the attributes. Doesn't return anything. 87 | 88 | 89 | ### enter\_bootloader\_mode 90 | ```py 91 | 92 | def enter_bootloader_mode(self) 93 | 94 | ``` 95 | 96 | 97 | 98 | Get to a mode where we can read & write flash. 99 | 100 | 101 | ### exit\_bootloader\_mode 102 | ```py 103 | 104 | def exit_bootloader_mode(self) 105 | 106 | ``` 107 | 108 | 109 | 110 | Get out of bootloader mode and go back to running main code. 111 | 112 | 113 | ### flash\_binary 114 | ```py 115 | 116 | def flash_binary(self, address, binary, pad=False) 117 | 118 | ``` 119 | 120 | 121 | 122 | Write using openocd `program` command. 123 | 124 | 125 | ### get\_all\_attributes 126 | ```py 127 | 128 | def get_all_attributes(self) 129 | 130 | ``` 131 | 132 | 133 | 134 | Get all attributes on a board. Returns an array of attribute dicts. 135 | 136 | 137 | ### get\_attribute 138 | ```py 139 | 140 | def get_attribute(self, index) 141 | 142 | ``` 143 | 144 | 145 | 146 | Get a single attribute. Returns a dict with two keys: `key` and `value`. 147 | 148 | 149 | ### get\_board\_arch 150 | ```py 151 | 152 | def get_board_arch(self) 153 | 154 | ``` 155 | 156 | 157 | 158 | Return the architecture of the board we are connected to. 159 | 160 | 161 | ### get\_board\_name 162 | ```py 163 | 164 | def get_board_name(self) 165 | 166 | ``` 167 | 168 | 169 | 170 | Return the name of the board we are connected to. 171 | 172 | 173 | ### get\_bootloader\_version 174 | ```py 175 | 176 | def get_bootloader_version(self) 177 | 178 | ``` 179 | 180 | 181 | 182 | Return the version string of the bootloader. Should return a value 183 | like `0.5.0`, or `None` if it is unknown. 184 | 185 | 186 | ### get\_kernel\_version 187 | ```py 188 | 189 | def get_kernel_version(self) 190 | 191 | ``` 192 | 193 | 194 | 195 | Return the kernel ABI version installed on the board. If the version 196 | cannot be determined, return `None`. 197 | 198 | 199 | ### get\_page\_size 200 | ```py 201 | 202 | def get_page_size(self) 203 | 204 | ``` 205 | 206 | 207 | 208 | Return the size of the page in bytes for the connected board. 209 | 210 | 211 | ### open\_link\_to\_board 212 | ```py 213 | 214 | def open_link_to_board(self) 215 | 216 | ``` 217 | 218 | 219 | 220 | Open a connection to the board. 221 | 222 | 223 | ### print\_known\_boards 224 | ```py 225 | 226 | def print_known_boards(self) 227 | 228 | ``` 229 | 230 | 231 | 232 | Display the boards that have settings configured in tockloader. 233 | 234 | 235 | ### read\_range 236 | ```py 237 | 238 | def read_range(self, address, length) 239 | 240 | ``` 241 | 242 | 243 | 244 | Read a specific range of flash. 245 | 246 | If this fails for some reason this should return an empty binary array. 247 | 248 | 249 | ### run\_terminal 250 | ```py 251 | 252 | def run_terminal(self) 253 | 254 | ``` 255 | 256 | 257 | 258 | ### set\_attribute 259 | ```py 260 | 261 | def set_attribute(self, index, raw) 262 | 263 | ``` 264 | 265 | 266 | 267 | Set a single attribute. 268 | 269 | 270 | ### set\_start\_address 271 | ```py 272 | 273 | def set_start_address(self, address) 274 | 275 | ``` 276 | 277 | 278 | 279 | Set the address the bootloader jumps to to start the actual code. 280 | 281 | 282 | ### translate\_address 283 | ```py 284 | 285 | def translate_address(self, address) 286 | 287 | ``` 288 | 289 | 290 | 291 | Translate an address from MCU address space to the address required for 292 | the board interface. This is used for boards where the address passed to 293 | the board interface is not the address where this region is exposed in 294 | the MCU address space. This method must be called from the board 295 | interface implementation prior to memory accesses. 296 | 297 | 298 | ### \_align\_and\_stretch\_to\_page 299 | ```py 300 | 301 | def _align_and_stretch_to_page(self, address, binary) 302 | 303 | ``` 304 | 305 | 306 | 307 | Return a new (address, binary) that is a multiple of the page size 308 | and is aligned to page boundaries. 309 | 310 | 311 | ### \_configure\_from\_known\_boards 312 | ```py 313 | 314 | def _configure_from_known_boards(self) 315 | 316 | ``` 317 | 318 | 319 | 320 | If we know the name of the board we are interfacing with, this function 321 | tries to use the `KNOWN_BOARDS` array to populate other needed settings 322 | if they have not already been set from other methods. 323 | 324 | This can be used in multiple locations. First, it is used when 325 | tockloader first starts because if a user passes in the `--board` 326 | argument then we know the board and can try to pull in settings from 327 | KNOWN_BOARDS. Ideally, however, the user doesn't have to pass in any 328 | arguments, but then we won't know what board until after we have had a 329 | chance to read its attributes. The board at least needs the "board" 330 | attribute to be set, and then we can use KNOWN_BOARDS to fill in the 331 | rest. 332 | 333 | 334 | ### \_decode\_attribute 335 | ```py 336 | 337 | def _decode_attribute(self, raw) 338 | 339 | ``` 340 | 341 | 342 | 343 | ### \_gather\_openocd\_cmdline 344 | ```py 345 | 346 | def _gather_openocd_cmdline(self, commands, binary, write=True, exit=True) 347 | 348 | ``` 349 | 350 | 351 | 352 | - `commands`: List of openocd commands. Use {binary} for where the name 353 | of the binary file should be substituted. 354 | - `binary`: A bytes() object that will be used to write to the board. 355 | - `write`: Set to true if the command writes binaries to the board. Set 356 | to false if the command will read bits from the board. 357 | - `exit`: When `True`, openocd will exit after executing commands. 358 | 359 | 360 | ### \_list\_emulators 361 | ```py 362 | 363 | def _list_emulators(self) 364 | 365 | ``` 366 | 367 | 368 | 369 | Return a list of board names that are attached to the host. 370 | 371 | 372 | ### \_run\_openocd\_commands 373 | ```py 374 | 375 | def _run_openocd_commands(self, commands, binary, write=True) 376 | 377 | ``` 378 | 379 | 380 | 381 | 382 | -------------------------------------------------------------------------------- /docs/tab.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.tab Documentation 2 | 3 | ## Class TAB 4 | Tock Application Bundle object. This class handles the TAB format. 5 | ### \_\_init\_\_ 6 | ```py 7 | 8 | def __init__(self, tab_path, args=Namespace()) 9 | 10 | ``` 11 | 12 | 13 | 14 | Initialize self. See help(type(self)) for accurate signature. 15 | 16 | 17 | ### extract\_app 18 | ```py 19 | 20 | def extract_app(self, arch) 21 | 22 | ``` 23 | 24 | 25 | 26 | Return a `TabApp` object from this TAB, or `None` if the requested 27 | architecture is not present in the TAB. You must specify the desired MCU 28 | architecture so the correct App object can be retrieved. Note that an 29 | architecture may have multiple TBF files if the app is compiled for a 30 | fixed address, and multiple fixed address versions are included in the 31 | TAB. 32 | 33 | 34 | ### extract\_tbf 35 | ```py 36 | 37 | def extract_tbf(self, tbf_name) 38 | 39 | ``` 40 | 41 | 42 | 43 | Return a `TabApp` object from this TAB. You must specify the 44 | desired TBF name, and only that TBF will be returned. 45 | 46 | 47 | ### get\_app\_name 48 | ```py 49 | 50 | def get_app_name(self) 51 | 52 | ``` 53 | 54 | 55 | 56 | Return the app name from the metadata file. 57 | 58 | 59 | ### get\_compatible\_boards 60 | ```py 61 | 62 | def get_compatible_boards(self) 63 | 64 | ``` 65 | 66 | 67 | 68 | Return a list of compatible boards from the metadata file. 69 | 70 | 71 | ### get\_supported\_architectures 72 | ```py 73 | 74 | def get_supported_architectures(self) 75 | 76 | ``` 77 | 78 | 79 | 80 | Return a list of architectures that this TAB has compiled binaries for. 81 | Note that this will return all architectures that have any TBF binary, 82 | but some of those TBF binaries may be compiled for very specific 83 | addresses. That is, there isn't a guarantee that the TBF file will work 84 | on any chip with one of the supported architectures. 85 | 86 | 87 | ### get\_tbf\_names 88 | ```py 89 | 90 | def get_tbf_names(self) 91 | 92 | ``` 93 | 94 | 95 | 96 | Returns a list of the names of all of the .tbf files contained in the 97 | TAB, without the extension. 98 | 99 | 100 | ### is\_compatible\_with\_board 101 | ```py 102 | 103 | def is_compatible_with_board(self, board) 104 | 105 | ``` 106 | 107 | 108 | 109 | Check if the Tock app is compatible with a particular Tock board. 110 | 111 | 112 | ### is\_compatible\_with\_kernel\_version 113 | ```py 114 | 115 | def is_compatible_with_kernel_version(self, kernel_version) 116 | 117 | ``` 118 | 119 | 120 | 121 | Check if the Tock app is compatible with the version of the kernel. 122 | Default to yes unless we can be confident the answer is no. 123 | 124 | `kernel_version` should be a string, or None if the kernel API version 125 | is unknown. 126 | 127 | 128 | ### update\_tbf 129 | ```py 130 | 131 | def update_tbf(self, app) 132 | 133 | ``` 134 | 135 | 136 | 137 | Inserts a new or modified `TabApp` into the .tab file. 138 | 139 | Only works with .tab files stored locally. 140 | 141 | 142 | ### \_\_str\_\_ 143 | ```py 144 | 145 | def __str__(self) 146 | 147 | ``` 148 | 149 | 150 | 151 | Return str(self). 152 | 153 | 154 | ### \_extract\_tbf\_from\_filebuffer 155 | ```py 156 | 157 | def _extract_tbf_from_filebuffer(self, tbf_filename, binary) 158 | 159 | ``` 160 | 161 | 162 | 163 | ### \_get\_metadata\_key 164 | ```py 165 | 166 | def _get_metadata_key(self, key) 167 | 168 | ``` 169 | 170 | 171 | 172 | Return the value for a specific key from the metadata file. 173 | 174 | 175 | ### \_parse\_metadata 176 | ```py 177 | 178 | def _parse_metadata(self) 179 | 180 | ``` 181 | 182 | 183 | 184 | Open and parse the included metadata file in the TAB, returning the 185 | key-value pairs as a dict. 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /docs/tockloader.md: -------------------------------------------------------------------------------- 1 | # Package tockloader.tockloader Documentation 2 | 3 | 4 | Main Tockloader interface. 5 | 6 | All high-level logic is contained here. All board-specific or communication 7 | channel specific code is in other files. 8 | 9 | ## Class TockLoader 10 | Implement all Tockloader commands. All logic for how apps are arranged 11 | is contained here. 12 | ### \_\_init\_\_ 13 | ```py 14 | 15 | def __init__(self, args) 16 | 17 | ``` 18 | 19 | 20 | 21 | Initialize self. See help(type(self)) for accurate signature. 22 | 23 | 24 | ### dump\_flash\_page 25 | ```py 26 | 27 | def dump_flash_page(self, page_num) 28 | 29 | ``` 30 | 31 | 32 | 33 | Print one page of flash contents. 34 | 35 | 36 | ### erase\_apps 37 | ```py 38 | 39 | def erase_apps(self) 40 | 41 | ``` 42 | 43 | 44 | 45 | Erase flash where apps go. All apps are not actually cleared, we just 46 | overwrite the header of the first app. 47 | 48 | 49 | ### flash\_binary 50 | ```py 51 | 52 | def flash_binary(self, binary, address, pad=None) 53 | 54 | ``` 55 | 56 | 57 | 58 | Tell the bootloader to save the binary blob to an address in internal 59 | flash. 60 | 61 | This will pad the binary as needed, so don't worry about the binary 62 | being a certain length. 63 | 64 | This accepts an optional `pad` parameter. If used, the `pad` parameter 65 | is a tuple of `(length, value)` signifying the number of bytes to pad, 66 | and the particular byte to use for the padding. 67 | 68 | 69 | ### info 70 | ```py 71 | 72 | def info(self) 73 | 74 | ``` 75 | 76 | 77 | 78 | Print all info about this board. 79 | 80 | 81 | ### install 82 | ```py 83 | 84 | def install(self, tabs, replace='yes', erase=False, sticky=False, layout=None) 85 | 86 | ``` 87 | 88 | 89 | 90 | Add or update TABs on the board. 91 | 92 | - `replace` can be "yes", "no", or "only" 93 | - `erase` if true means erase all other apps before installing 94 | - `layout` is a layout string for specifying how apps should be installed 95 | 96 | 97 | ### list\_apps 98 | ```py 99 | 100 | def list_apps(self, verbose, quiet, map, verify_credentials_public_keys) 101 | 102 | ``` 103 | 104 | 105 | 106 | Query the chip's flash to determine which apps are installed. 107 | 108 | - `verbose` - bool: Show details about TBF. 109 | - `quiet` - bool: Just show the app name. 110 | - `map` - bool: Show a diagram listing apps with addresses. 111 | - `verify_credentials_public_keys`: Either `None`, meaning do not verify 112 | any credentials, or a list of public keys binaries to use to help 113 | verify credentials. The list can be empty and all credentials that can 114 | be checked without keys will be verified. 115 | 116 | 117 | ### list\_attributes 118 | ```py 119 | 120 | def list_attributes(self) 121 | 122 | ``` 123 | 124 | 125 | 126 | Download all attributes stored on the board. 127 | 128 | 129 | ### open 130 | ```py 131 | 132 | def open(self) 133 | 134 | ``` 135 | 136 | 137 | 138 | Select and then open the correct channel to talk to the board. 139 | 140 | For the bootloader, this means opening a serial port. For JTAG, not much 141 | needs to be done. 142 | 143 | 144 | ### print\_known\_boards 145 | ```py 146 | 147 | def print_known_boards(self) 148 | 149 | ``` 150 | 151 | 152 | 153 | Simple function to print to console the boards that are hardcoded 154 | into Tockloader to make them easier to use. 155 | 156 | 157 | ### read\_flash 158 | ```py 159 | 160 | def read_flash(self, address, length) 161 | 162 | ``` 163 | 164 | 165 | 166 | Print some flash contents. 167 | 168 | 169 | ### remove\_attribute 170 | ```py 171 | 172 | def remove_attribute(self, key) 173 | 174 | ``` 175 | 176 | 177 | 178 | Remove an existing attribute already stored on the board. 179 | 180 | 181 | ### run\_terminal 182 | ```py 183 | 184 | def run_terminal(self) 185 | 186 | ``` 187 | 188 | 189 | 190 | Create an interactive terminal session with the board. 191 | 192 | This is a special-case use of Tockloader where this is really a helper 193 | function for running some sort of underlying terminal-like operation. 194 | Therefore, how we set this up is a little different from other 195 | tockloader commands. In particular, we do _not_ want `tockloader.open()` 196 | to have been called at this point. 197 | 198 | 199 | ### set\_attribute 200 | ```py 201 | 202 | def set_attribute(self, key, value) 203 | 204 | ``` 205 | 206 | 207 | 208 | Change an attribute stored on the board. 209 | 210 | 211 | ### set\_flag 212 | ```py 213 | 214 | def set_flag(self, app_names, flag_name, flag_value) 215 | 216 | ``` 217 | 218 | 219 | 220 | Set a flag in the TBF header. 221 | 222 | 223 | ### set\_start\_address 224 | ```py 225 | 226 | def set_start_address(self, address) 227 | 228 | ``` 229 | 230 | 231 | 232 | Set the address that the bootloader jumps to to run kernel code. 233 | 234 | 235 | ### tickv\_append 236 | ```py 237 | 238 | def tickv_append(self, key, value=None, write_id=0) 239 | 240 | ``` 241 | 242 | 243 | 244 | Add a key,value pair to the database. The first argument can a list of 245 | key, value pairs. 246 | 247 | 248 | ### tickv\_cleanup 249 | ```py 250 | 251 | def tickv_cleanup(self) 252 | 253 | ``` 254 | 255 | 256 | 257 | Clean the database by remove invalid objects and re-storing valid 258 | objects. 259 | 260 | 261 | ### tickv\_dump 262 | ```py 263 | 264 | def tickv_dump(self) 265 | 266 | ``` 267 | 268 | 269 | 270 | Display all of the contents of a TicKV database. 271 | 272 | 273 | ### tickv\_get 274 | ```py 275 | 276 | def tickv_get(self, key) 277 | 278 | ``` 279 | 280 | 281 | 282 | Read a key, value pair from a TicKV database on a board. 283 | 284 | 285 | ### tickv\_hash 286 | ```py 287 | 288 | def tickv_hash(self, key) 289 | 290 | ``` 291 | 292 | 293 | 294 | Return the hash of the specified key. 295 | 296 | 297 | ### tickv\_invalidate 298 | ```py 299 | 300 | def tickv_invalidate(self, key) 301 | 302 | ``` 303 | 304 | 305 | 306 | Invalidate a particular key in the database. 307 | 308 | 309 | ### tickv\_reset 310 | ```py 311 | 312 | def tickv_reset(self) 313 | 314 | ``` 315 | 316 | 317 | 318 | Reset the database by erasing it and re-initializing. 319 | 320 | 321 | ### uninstall\_app 322 | ```py 323 | 324 | def uninstall_app(self, app_names) 325 | 326 | ``` 327 | 328 | 329 | 330 | If an app by this name exists, remove it from the chip. If no name is 331 | given, present the user with a list of apps to remove. 332 | 333 | 334 | ### write\_flash 335 | ```py 336 | 337 | def write_flash(self, address, length, value) 338 | 339 | ``` 340 | 341 | 342 | 343 | Write a byte to some flash contents. 344 | 345 | 346 | ### \_app\_is\_aligned\_correctly 347 | ```py 348 | 349 | def _app_is_aligned_correctly(self, address, size) 350 | 351 | ``` 352 | 353 | 354 | 355 | Check if putting an app at this address will be OK with the MPU. 356 | 357 | 358 | ### \_bootloader\_is\_present 359 | ```py 360 | 361 | def _bootloader_is_present(self) 362 | 363 | ``` 364 | 365 | 366 | 367 | Check if a bootloader exists on this board. It is specified by the 368 | string "TOCKBOOTLOADER" being at address 0x400. 369 | 370 | 371 | ### \_extract\_all\_app\_headers 372 | ```py 373 | 374 | def _extract_all_app_headers(self, verbose=False, extract_app_binary=False) 375 | 376 | ``` 377 | 378 | 379 | 380 | Iterate through the flash on the board for the header information about 381 | each app. 382 | 383 | Options: 384 | - `verbose`: Show ALL apps, including padding apps. 385 | - `extract_app_binary`: Get the actual app binary in addition to the 386 | headers. 387 | 388 | 389 | ### \_extract\_apps\_from\_tabs 390 | ```py 391 | 392 | def _extract_apps_from_tabs(self, tabs, arch) 393 | 394 | ``` 395 | 396 | 397 | 398 | Iterate through the list of TABs and create the app object for each. 399 | 400 | 401 | ### \_get\_apps\_start\_address 402 | ```py 403 | 404 | def _get_apps_start_address(self) 405 | 406 | ``` 407 | 408 | 409 | 410 | Return the address in flash where applications start on this platform. 411 | This might be set on the board itself, in the command line arguments 412 | to Tockloader, or just be the default. 413 | 414 | 415 | ### \_get\_memory\_start\_address 416 | ```py 417 | 418 | def _get_memory_start_address(self) 419 | 420 | ``` 421 | 422 | 423 | 424 | Return the address in memory where application RAM starts on this 425 | platform. We mostly don't know this, so it may be None. 426 | 427 | 428 | ### \_print\_apps 429 | ```py 430 | 431 | def _print_apps(self, apps, verbose, quiet) 432 | 433 | ``` 434 | 435 | 436 | 437 | Print information about a list of apps 438 | 439 | 440 | ### \_replace\_with\_padding 441 | ```py 442 | 443 | def _replace_with_padding(self, app) 444 | 445 | ``` 446 | 447 | 448 | 449 | Update the TBF header of installed app `app` with a padding header 450 | to effectively uninstall it. 451 | 452 | 453 | ### \_reshuffle\_apps 454 | ```py 455 | 456 | def _reshuffle_apps(self, apps, preserve_order=False) 457 | 458 | ``` 459 | 460 | 461 | 462 | Given an array of apps, some of which are new and some of which exist, 463 | sort them so we can write them to flash. 464 | 465 | This function is really the driver of tockloader, and is responsible for 466 | setting up applications in a way that can be successfully used by the 467 | board. 468 | 469 | If `preserve_order` is set to `True` this won't actually do any 470 | shuffling, and will instead load apps with padding in the order they are 471 | in the array. 472 | 473 | 474 | ### \_set\_attribute 475 | ```py 476 | 477 | def _set_attribute(self, key, value, log_status=True) 478 | 479 | ``` 480 | 481 | 482 | 483 | Internal function for setting an attribute stored on the board. 484 | 485 | Bootloader mode must be active. 486 | 487 | Returns None if successful and an error string if not. 488 | 489 | 490 | ### \_start\_communication\_with\_board 491 | ```py 492 | 493 | def _start_communication_with_board(self) 494 | 495 | ``` 496 | 497 | 498 | 499 | Based on the transport method used, there may be some setup required 500 | to connect to the board. This function runs the setup needed to connect 501 | to the board. It also times the operation. 502 | 503 | For the bootloader, the board needs to be reset and told to enter the 504 | bootloader mode. For JTAG, this is unnecessary. 505 | 506 | 507 | ### \_tickv\_get\_database 508 | ```py 509 | 510 | def _tickv_get_database(self) 511 | 512 | ``` 513 | 514 | 515 | 516 | Read the flash for a TicKV database. Since this might be stored on 517 | external flash, we might need to use a backup mechanism to read the 518 | flash. 519 | 520 | 521 | ### \_tickv\_write\_database 522 | ```py 523 | 524 | def _tickv_write_database(self, tickv_db) 525 | 526 | ``` 527 | 528 | 529 | 530 | Write a TicKV database back to flash, overwriting the existing database. 531 | 532 | 533 | ### \_update\_board\_specific\_options 534 | ```py 535 | 536 | def _update_board_specific_options(self) 537 | 538 | ``` 539 | 540 | 541 | 542 | This uses the name of the board to update any hard-coded options about 543 | how Tockloader should function. This is a convenience mechanism, as it 544 | prevents users from having to pass them in through command line arguments. 545 | 546 | 547 | 548 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Tockloader 2 | theme: readthedocs 3 | pages: 4 | - Home: 'index.md' 5 | - API Documentation: 6 | - Main: main.md 7 | - Tockloader: tockloader.md 8 | - Interface Channels: 9 | - Board Interface: board_interface.md 10 | - Bootloader Serial: bootloader_serial.md 11 | - JLinkExe: jlinkexe.md 12 | - OpenOCD: openocd.md 13 | - nrfjprog: nrfjprog.md 14 | - Flash File: flash_file.md 15 | - App: 16 | - Installed Apps: app_installed.md 17 | - Apps from TABs: app_tab.md 18 | - Padding Apps: app_padding.md 19 | - TAB: tab.md 20 | - TBF Header: tbfh.md 21 | - TicKV: tickv.md 22 | - Display: display.md 23 | - Exceptions: exceptions.md 24 | - Helpers: helpers.md 25 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.11,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "tockloader" 7 | authors = [{name = "Tock Project Developers", email = "devel@lists.tockos.org"}] 8 | readme = "README.md" 9 | license = "MIT" 10 | license-files = ["LICENSE"] 11 | dynamic = ["version", "description"] 12 | requires-python = ">= 3.9" 13 | dependencies = [ 14 | "argcomplete >= 1.8.2", 15 | "colorama >= 0.3.7", 16 | "crcmod >= 1.7", 17 | "ecdsa >= 0.19.1", 18 | "pycryptodome >= 3.15.0", 19 | "pynrfjprog == 10.19.0", 20 | "pyserial >= 3.0.1", 21 | "siphash >= 0.0.1", 22 | "six >= 1.9.0", 23 | "toml >= 0.10.2", 24 | "tqdm >= 4.45.0 ", 25 | "questionary >= 1.10.0", 26 | ] 27 | 28 | [project.urls] 29 | Home = "https://github.com/tock/tockloader" 30 | 31 | [project.scripts] 32 | tockloader = "tockloader.main:main" 33 | 34 | [tool.flit.sdist] 35 | include = [".gitignore", "docs/"] 36 | exclude = ["**/__pycache__", "docs/_build", "**/*.egg-info", "tests/packages/*/build"] 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argcomplete==3.6.1 2 | colorama==0.4.6 3 | crcmod==1.7 4 | ecdsa==0.19.1 5 | pycryptodome==3.22.0 6 | pynrfjprog==10.19.0 7 | pyserial==3.5 8 | questionary==2.1.0 9 | setuptools==78.1.1 10 | siphash==0.0.1 11 | toml==0.10.2 12 | tqdm==4.67.1 13 | -------------------------------------------------------------------------------- /tockloader/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tockloader is a tool for installing Tock applications. 3 | """ 4 | 5 | from ._version import __version__ 6 | -------------------------------------------------------------------------------- /tockloader/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.15.0.dev1" 2 | -------------------------------------------------------------------------------- /tockloader/app_installed.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import textwrap 3 | 4 | 5 | class InstalledApp: 6 | """ 7 | Representation of a Tock app that is installed on a specific board. This 8 | object is created when Tockloader finds an app already installed on a board. 9 | 10 | At the very least this includes the TBF header and an address of where the 11 | app is on the board. It can also include the actual app binary which is 12 | necessary if the app needs to be moved. 13 | """ 14 | 15 | def __init__(self, tbfh, tbff, address, app_binary=None): 16 | self.tbfh = tbfh # A `TBFHeader` object representing this app's header. 17 | self.tbff = tbff # A `TBFFooter` object representing this app's footer. 18 | self.app_binary = app_binary # A binary array of the app _after_ the header. 19 | self.address = address # Where on the board this app currently is. 20 | 21 | # Store the length of the TBF header and any padding before the actual 22 | # app binary. This allows us to keep track of if the actual application 23 | # has to move or not. 24 | self.original_size_before_app = tbfh.get_size_before_app() 25 | 26 | # A flag indicating if this app has been modified by tockloader. 27 | self.app_binary_modified = False 28 | 29 | def get_name(self): 30 | """ 31 | Return the app name. 32 | """ 33 | return self.tbfh.get_app_name() 34 | 35 | def get_app_version(self): 36 | """ 37 | Return the version number stored in a program header. 38 | """ 39 | return self.tbfh.get_app_version() 40 | 41 | def is_app(self): 42 | """ 43 | Whether this is an app or padding. 44 | """ 45 | return True 46 | 47 | def is_modified(self): 48 | """ 49 | Returns whether this app has been modified by tockloader since it was 50 | initially created by `__init__`. 51 | """ 52 | return self.app_binary_modified or self.tbfh.is_modified() 53 | 54 | def is_sticky(self): 55 | """ 56 | Returns true if the app is set as sticky and will not be removed with 57 | a normal app erase command. Sticky apps must be force removed. 58 | """ 59 | return self.tbfh.is_sticky() 60 | 61 | def set_sticky(self): 62 | """ 63 | Mark this app as "sticky" in the app's header. This makes it harder to 64 | accidentally remove this app if it is a core service or debug app. 65 | """ 66 | self.tbfh.set_flag("sticky", True) 67 | 68 | def get_size(self): 69 | """ 70 | Return the total size (including TBF header) of this app in bytes. 71 | """ 72 | return self.tbfh.get_app_size() 73 | 74 | def set_size(self, size): 75 | """ 76 | Force the entire app to be a certain size. If `size` is smaller than the 77 | actual app an error will be thrown. 78 | """ 79 | header_size = self.tbfh.get_header_size() 80 | binary_size = len(self.app_binary) 81 | current_size = header_size + binary_size 82 | if size < current_size: 83 | raise TockLoaderException( 84 | "Cannot make app smaller. Current size: {} bytes".format(current_size) 85 | ) 86 | self.tbfh.set_app_size(size) 87 | 88 | def has_fixed_addresses(self): 89 | """ 90 | Return true if the TBF binary is compiled for a fixed address. 91 | """ 92 | return self.tbfh.has_fixed_addresses() 93 | 94 | def filter_fixed_ram_address(self, ram_address): 95 | """ 96 | Specify the start of RAM to filter TBFs in this TAB. For installed apps 97 | this is a no-op because presumably we only installed apps that have a 98 | reasonable RAM parameter. 99 | """ 100 | pass 101 | 102 | def get_fixed_addresses_flash_and_sizes(self): 103 | """ 104 | Return a list of tuples of all addresses in flash this app is compiled 105 | for and the size of the app at that address. 106 | 107 | [(address, size), (address, size), ...] 108 | """ 109 | return [(self.tbfh.get_fixed_addresses()[1], self.tbfh.get_app_size())] 110 | 111 | def is_loadable_at_address(self, address): 112 | """ 113 | Check if it is possible to load this app at the given address. Returns 114 | True if it is possible, False otherwise. 115 | """ 116 | if not self.has_fixed_addresses(): 117 | # If there are not fixed addresses, then we can flash this anywhere. 118 | return True 119 | 120 | fixed_flash_address = self.tbfh.get_fixed_addresses()[1] 121 | tbf_header_length = self.tbfh.get_header_size() 122 | 123 | # Ok, we have to be a little tricky here. What we actually care 124 | # about is ensuring that the application binary itself ends up at 125 | # the requested fixed address. However, what this function has to do 126 | # is see if the start of the TBF header can go at the requested 127 | # address. We have some flexibility, since we can make the header 128 | # larger so that it pushes the application binary to the correct 129 | # address. So, we want to see if we can reasonably do that. If we 130 | # are within 128 bytes, we say that we can. 131 | if ( 132 | fixed_flash_address >= (address + tbf_header_length) 133 | and (address + tbf_header_length + 128) > fixed_flash_address 134 | ): 135 | return True 136 | 137 | return False 138 | 139 | def fix_at_next_loadable_address(self, address): 140 | """ 141 | Calculate the next reasonable address where we can put this app where 142 | the address is greater than or equal to `address`. The `address` 143 | argument is the earliest address the app can be at, either the start of 144 | apps or immediately after a previous app. 145 | 146 | If the app doesn't have a fixed address, then we can put it anywhere, 147 | and we just return the address. If the app is compiled with fixed 148 | addresses, then we need to calculate an address. We do a little bit of 149 | "reasonable assuming" here. Fixed addresses are based on where the _app 150 | binary_ must be located. Therefore, the start of the app where the TBF 151 | header goes must be before that. This can be at any address (as long as 152 | the header will fit), but we want to make this simpler, so we just 153 | assume the TBF header should start on a 1024 byte alignment. 154 | """ 155 | if not self.has_fixed_addresses(): 156 | # No fixed addresses means we can put the app anywhere. 157 | return address 158 | 159 | def align_down_to(v, a): 160 | """ 161 | Calculate the address correctly aligned to `a` that is lower than or 162 | equal to `v`. 163 | """ 164 | return v - (v % a) 165 | 166 | # Get the fixed flash address. This address is fixed, and our starting 167 | # address must be before it. 168 | fixed_flash_address = self.tbfh.get_fixed_addresses()[1] 169 | 170 | # Align to get a reasonable address for this app. 171 | wanted_address = align_down_to(fixed_flash_address, 1024) 172 | 173 | if wanted_address >= address: 174 | return wanted_address 175 | else: 176 | return None 177 | 178 | def get_header(self): 179 | """ 180 | Return the TBFH object for the header. 181 | """ 182 | return self.tbfh 183 | 184 | def get_header_size(self): 185 | """ 186 | Return the size of the TBF header in bytes. 187 | """ 188 | return self.tbfh.get_header_size() 189 | 190 | def get_header_binary(self): 191 | """ 192 | Get the TBF header as a bytes array. 193 | """ 194 | return self.tbfh.get_binary() 195 | 196 | def set_app_binary(self, app_binary): 197 | """ 198 | Update the application binary. Likely this binary would come from the 199 | existing contents of flash on a board. 200 | """ 201 | self.app_binary = app_binary 202 | self.app_binary_modified = True 203 | 204 | def get_address(self): 205 | """ 206 | Get the address of where on the board the app is or should go. 207 | """ 208 | return self.address 209 | 210 | def verify_credentials(self, public_keys): 211 | """ 212 | Using an optional array of public_key binaries, try to check any 213 | contained credentials to verify they are valid. 214 | """ 215 | integrity_blob = self.tbfh.get_binary() + self.app_binary 216 | self.tbff.verify_credentials(public_keys, integrity_blob) 217 | 218 | def has_app_binary(self): 219 | """ 220 | Whether we have the actual application binary for this app. 221 | """ 222 | return self.app_binary != None 223 | 224 | def get_app_binary(self): 225 | """ 226 | Return just the compiled application code binary. Does not include 227 | the TBF header. 228 | """ 229 | return self.app_binary 230 | 231 | def get_binary(self, address): 232 | """ 233 | Return the binary array comprising the entire application if it needs to 234 | be written to the board. Otherwise, if it is already installed, return 235 | `None`. 236 | """ 237 | # Since this is an already installed app, we first check to see if 238 | # anything actually changed. If not, then we don't need to re-flash this 239 | # app. 240 | if not self.is_modified() and address == self.address: 241 | return None 242 | 243 | # Set the starting address for this app. This is only relevant with 244 | # fixed addresses, and is a no-op for apps which are not compiled for 245 | # fixed addresses. 246 | self.tbfh.adjust_starting_address(address) 247 | 248 | # Now, we can check if we need to flash the header and the app binary, 249 | # or just the header. We need to flash both the header _and_ the app 250 | # binary 251 | # 252 | # - if the length of the header changed, or 253 | # - if the app binary itself has changed, or 254 | # - if the location of the app has changed 255 | if ( 256 | self.tbfh.get_size_before_app() != self.original_size_before_app 257 | or self.app_binary_modified 258 | or address != self.address 259 | ): 260 | # Get the actual full app binary. 261 | binary = self.tbfh.get_binary() + self.app_binary 262 | 263 | # Check that the binary is not longer than it is supposed to be. 264 | # This might happen if the size was changed, but any code using this 265 | # binary has no way to check. If the binary is too long, we truncate 266 | # the actual binary blob (which should just be padding) to the 267 | # correct length. If it is too short it is ok, since the board 268 | # shouldn't care what is in the flash memory the app is not using. 269 | size = self.get_size() 270 | if len(binary) > size: 271 | logging.info( 272 | "Binary is larger than what it says in the header. Actual:{}, expected:{}".format( 273 | len(binary), size 274 | ) 275 | ) 276 | logging.info("Truncating binary to match.") 277 | 278 | # Check on what we would be removing. If it is all zeros, we 279 | # determine that it is OK to truncate. 280 | to_remove = binary[size:] 281 | if len(to_remove) != to_remove.count(0): 282 | raise TockLoaderException("Error truncating binary. Not zero.") 283 | 284 | return binary 285 | 286 | else: 287 | # Only the header needs to be flashed 288 | return self.tbfh.get_binary() 289 | 290 | def info(self, verbose=False): 291 | """ 292 | Get a string describing various properties of the app. 293 | """ 294 | offset = self.address 295 | 296 | out = "" 297 | out += "Name: {}\n".format(self.get_name()) 298 | out += "Version: {}\n".format(self.get_app_version()) 299 | out += "Enabled: {}\n".format(self.tbfh.is_enabled()) 300 | out += "Sticky: {}\n".format(self.tbfh.is_sticky()) 301 | out += "Total Size in Flash: {} bytes\n".format(self.get_size()) 302 | 303 | if verbose: 304 | out += "Address in Flash: {:#x}\n".format(offset) 305 | out += textwrap.indent(self.tbfh.to_str_at_address(offset), " ") 306 | if self.tbff: 307 | out += "\n" 308 | out += textwrap.indent( 309 | self.tbff.to_str_at_address( 310 | offset + self.tbfh.get_binary_end_offset() 311 | ), 312 | " ", 313 | ) 314 | return out 315 | 316 | def object(self): 317 | """ 318 | Return a dict object containing the information about this app. 319 | """ 320 | return { 321 | "name": self.get_name(), 322 | "enabled": self.tbfh.is_enabled(), 323 | "sticky": self.tbfh.is_sticky(), 324 | "size": self.get_size(), 325 | "address": self.address, 326 | "header": self.tbfh.object(), 327 | "footer": self.tbff.object(), 328 | } 329 | 330 | def __str__(self): 331 | return self.get_name() 332 | -------------------------------------------------------------------------------- /tockloader/app_padding.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | 3 | from .tbfh import TBFHeaderPadding 4 | 5 | 6 | class PaddingApp: 7 | """ 8 | Representation of a placeholder app that is only padding between other apps. 9 | """ 10 | 11 | def __init__(self, size, address=None): 12 | """ 13 | Create a `PaddingApp` based on the amount of size needing in the 14 | padding. 15 | """ 16 | self.tbfh = TBFHeaderPadding(size) 17 | self.address = address 18 | 19 | def is_app(self): 20 | """ 21 | Whether this is an app or padding. 22 | """ 23 | return False 24 | 25 | def get_header(self): 26 | """ 27 | Return the header for this padding. 28 | """ 29 | return self.tbfh 30 | 31 | def get_size(self): 32 | """ 33 | Return the total size of the padding in bytes. 34 | """ 35 | return self.tbfh.get_app_size() 36 | 37 | def get_tbfh(self): 38 | """ 39 | Return the TBF header. 40 | """ 41 | return self.tbfh 42 | 43 | def verify_credentials(self, public_keys): 44 | """ 45 | Padding apps do not have credentials, so we ignore this. 46 | """ 47 | pass 48 | 49 | def has_fixed_addresses(self): 50 | """ 51 | A padding app is not an executable so can be placed anywhere. 52 | """ 53 | return False 54 | 55 | def get_address(self): 56 | """ 57 | Get the address of where on the board this padding app is. 58 | """ 59 | return self.address 60 | 61 | def has_app_binary(self): 62 | """ 63 | We can always return the binary for a padding app, so we can always 64 | return true. 65 | """ 66 | return True 67 | 68 | def get_binary(self, address=None): 69 | """ 70 | Return the binary array comprising the header and the padding between 71 | apps. 72 | """ 73 | tbfh_binary = self.tbfh.get_binary() 74 | # Calculate the padding length. 75 | padding_binary_size = self.get_size() - len(tbfh_binary) 76 | return tbfh_binary + b"\0" * padding_binary_size 77 | 78 | def info(self, verbose=False): 79 | """ 80 | Get a string describing various properties of the padding. 81 | """ 82 | out = "" 83 | out += "Total Size in Flash: {} bytes\n".format(self.get_size()) 84 | 85 | if verbose: 86 | out += textwrap.indent(str(self.tbfh), " ") 87 | return out 88 | 89 | def __str__(self): 90 | return "PaddingApp({})".format(self.get_size()) 91 | 92 | 93 | class InstalledPaddingApp(PaddingApp): 94 | """ 95 | Representation of a placeholder app that is only padding between other apps 96 | that was extracted from a board. 97 | """ 98 | 99 | def __init__(self, tbfh, address): 100 | """ 101 | Create a `InstalledPaddingApp` from an extracted TBFH. 102 | """ 103 | self.tbfh = tbfh 104 | self.address = address 105 | 106 | def info(self, verbose=False): 107 | """ 108 | Get a string describing various properties of the padding. 109 | """ 110 | out = "" 111 | out += "Total Size in Flash: {} bytes\n".format(self.get_size()) 112 | 113 | if verbose: 114 | out += "Address in Flash: {:#x}\n".format(self.address) 115 | out += textwrap.indent(self.tbfh.to_str_at_address(self.address), " ") 116 | return out 117 | 118 | def __str__(self): 119 | return "PaddingApp({})".format(self.get_size()) 120 | -------------------------------------------------------------------------------- /tockloader/display.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for creating output in various formats. 3 | """ 4 | 5 | import json 6 | import logging 7 | import textwrap 8 | 9 | from . import helpers 10 | from .app_installed import InstalledApp 11 | from .app_padding import PaddingApp 12 | from .app_tab import TabApp 13 | 14 | 15 | class Display: 16 | def __init__(self, show_headers): 17 | """ 18 | Arguments: 19 | - show_headers: bool, if True, label each section in the display output. 20 | """ 21 | pass 22 | 23 | def list_apps(self, apps, verbose, quiet): 24 | """ 25 | Show information about a list of apps. 26 | """ 27 | pass 28 | 29 | def list_attributes(self, attributes): 30 | """ 31 | Show the key value pairs for a list of attributes. 32 | """ 33 | pass 34 | 35 | def bootloader_version(self, version): 36 | """ 37 | Show the bootloader version stored in the bootloader itself. 38 | """ 39 | pass 40 | 41 | def kernel_attributes(self, kern_attrs): 42 | """ 43 | Show the kernel attributes stored in the kernel binary. 44 | """ 45 | pass 46 | 47 | def get(self): 48 | return self.out 49 | 50 | 51 | def choose(b, t, f): 52 | if b: 53 | return t 54 | return f 55 | 56 | 57 | def start_of_app(width, address): 58 | return "{:>#10x}┬{}┐\n".format(address, "─" * width) 59 | 60 | 61 | def end_of_app(width, address, continuing): 62 | left_corner = choose(continuing, "┼", "┴") 63 | right_corner = choose(continuing, "┤", "┘") 64 | return "{:>#10x}{}{}{}\n".format(address, left_corner, "─" * width, right_corner) 65 | 66 | 67 | def app_bracket(width, left, right): 68 | if len(left) + len(right) >= (width - 1): 69 | room_for_left = width - 1 - 1 - len(right) 70 | left = "{}…".format(left[0:room_for_left]) 71 | left_size = width - len(right) 72 | content = "{:<{left_size}}{}".format(left, right, left_size=left_size) 73 | return "{}│{}│\n".format(" " * 10, content) 74 | 75 | 76 | class HumanReadableDisplay(Display): 77 | """ 78 | Format output as a string meant to be human readable. 79 | """ 80 | 81 | def __init__(self, show_headers=False): 82 | self.out = "" 83 | self.show_headers = show_headers 84 | 85 | def list_apps(self, apps, verbose, quiet): 86 | if self.show_headers: 87 | self.out += "Apps:\n" 88 | 89 | if not quiet: 90 | # Print info about each app 91 | for i, app in enumerate(apps): 92 | if app.is_app(): 93 | self.out += helpers.text_in_box("App {}".format(i), 52) + "\n" 94 | 95 | # # Check if this app is OK with the MPU region requirements. 96 | # # TODO: put this back! 97 | # if not self._app_is_aligned_correctly( 98 | # app.get_address(), app.get_size() 99 | # ): 100 | # self.out += " [WARNING] App is misaligned for the MPU\n" 101 | 102 | self.out += textwrap.indent(app.info(verbose), " ") + "\n\n" 103 | else: 104 | # Display padding 105 | self.out += helpers.text_in_box("Padding", 52) + "\n" 106 | self.out += textwrap.indent(app.info(verbose), " ") + "\n\n" 107 | 108 | if len(apps) == 0: 109 | logging.info("No found apps.") 110 | 111 | else: 112 | # In quiet mode just show the names. 113 | self.out += " ".join([app.get_name() for app in apps]) 114 | 115 | def show_app_map_from_address(self, apps, start_address): 116 | """ 117 | Print a layout map of apps assuming they are located back-to-back 118 | starting from `start_address`. Example: 119 | 120 | ``` 121 | 0x30000┬──────────────────────────────────────────────────┐ 122 | │App: blink [Installed]│ 123 | │ Length: 16384 (0x4000) │ 124 | 0x34000┼──────────────────────────────────────────────────┤ 125 | │App: blink [Installed]│ 126 | │ Length: 16384 (0x4000) │ 127 | 0x3c000┴──────────────────────────────────────────────────┘ 128 | ``` 129 | """ 130 | out = "" 131 | address = start_address 132 | for i, app in enumerate(apps): 133 | continuing = i < len(apps) - 1 134 | size = app.get_size() 135 | 136 | if i == 0: 137 | out += start_of_app(50, address) 138 | 139 | if isinstance(app, TabApp): 140 | title = "App: {}".format(app.get_name()) 141 | out += app_bracket(50, title, "[From TAB]") 142 | elif isinstance(app, InstalledApp): 143 | title = "App: {}".format(app.get_name()) 144 | out += app_bracket(50, title, "[Installed]") 145 | elif isinstance(app, PaddingApp): 146 | out += app_bracket(50, "Padding", "") 147 | 148 | address += size 149 | 150 | out += app_bracket(50, " Length: {} ({:#x})".format(size, size), "") 151 | out += end_of_app(50, address, continuing) 152 | 153 | self.out += out 154 | 155 | def show_app_map_actual_address(self, apps): 156 | """ 157 | Show a map of installed applications with known addresses. Example: 158 | 159 | ``` 160 | 0x30000┬──────────────────────────────────────────────────┐ 161 | │App: blink [Installed]│ 162 | │ Length: 16384 (0x4000) │ 163 | 0x34000┴──────────────────────────────────────────────────┘ 164 | 0x38000┬──────────────────────────────────────────────────┐ 165 | │App: blink [Installed]│ 166 | │ Length: 16384 (0x4000) │ 167 | 0x3c000┴──────────────────────────────────────────────────┘ 168 | ``` 169 | """ 170 | 171 | out = "" 172 | prev_address = -1 173 | for i, app in enumerate(apps): 174 | size = app.get_size() 175 | active_address = app.get_address() 176 | 177 | if active_address != prev_address: 178 | out += start_of_app(50, active_address) 179 | 180 | if isinstance(app, TabApp): 181 | title = "App: {}".format(app.get_name()) 182 | out += app_bracket(50, title, "[From TAB]") 183 | elif isinstance(app, InstalledApp): 184 | title = "App: {}".format(app.get_name()) 185 | out += app_bracket(50, title, "[Installed]") 186 | elif isinstance(app, PaddingApp): 187 | out += app_bracket(50, "Padding", "") 188 | out += app_bracket(50, " Length: {} ({:#x})".format(size, size), "") 189 | 190 | prev_address = active_address + size 191 | 192 | # Check if the next app starts at the address the current app ends 193 | # at. 194 | immediately_after = False 195 | if i < len(apps) - 1: 196 | next_address = apps[i + 1].get_address() 197 | immediately_after = prev_address == next_address 198 | 199 | out += end_of_app(50, prev_address, immediately_after) 200 | 201 | self.out += out 202 | 203 | def show_board_visual(self, apps): 204 | def horizontal_add(existing, new): 205 | existing_lines = existing.split("\n") 206 | new_lines = existing.split("\n") 207 | out = "" 208 | for i, l in enumerate(existing_lines): 209 | out += "{} {}\n".format(l, new_lines[i]) 210 | return out 211 | 212 | out = "" 213 | for i, app in enumerate(apps): 214 | name = app.get_name() 215 | box_width = len(name) + 4 216 | box = helpers.text_in_box(name, box_width, 10) 217 | if i == 0: 218 | out = box 219 | else: 220 | out = horizontal_add(out, box) 221 | 222 | self.out = out 223 | 224 | def list_attributes(self, attributes): 225 | if self.show_headers: 226 | self.out += "Attributes:\n" 227 | 228 | for index, attribute in enumerate(attributes): 229 | if attribute: 230 | self.out += "{:02d}: {:>8} = {}\n".format( 231 | index, attribute["key"], attribute["value"] 232 | ) 233 | 234 | else: 235 | self.out += "{:02d}:\n".format(index) 236 | 237 | def bootloader_version(self, version): 238 | self.out += "Bootloader version: {}\n".format(version) 239 | 240 | def kernel_attributes(self, kern_attrs): 241 | self.out += kern_attrs.info() 242 | 243 | 244 | class VisualDisplay(Display): 245 | """ 246 | Format output as an ASCII art string. 247 | 248 | ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────┐ 249 | │ | │ | │ | │ | 250 | │ | │ | │ | │ | 251 | │ | │ | │ | │ | 252 | │ version | │ version | │ version | │ blink | 253 | │ | │ | │ | │ | 254 | │ | │ | │ | │ | 255 | │ | │ | │ | │ | 256 | │ | │ | │ | │ | 257 | └─────────┘ └─────────┘ └─────────┘ └───────┘ 258 | ┌───────────────────────────────────────────┐ 259 | │ Kernel | 260 | └───────────────────────────────────────────┘ 261 | """ 262 | 263 | def __init__(self): 264 | self.out = "" 265 | 266 | def list_apps(self, apps, verbose, quiet): 267 | def horizontal_add(existing, new): 268 | existing_lines = existing.split("\n") 269 | new_lines = new.split("\n") 270 | out = "" 271 | for i, l in enumerate(existing_lines): 272 | if len(l) > 0: 273 | out += "{} {}\n".format(l, new_lines[i]) 274 | return out[:-1] 275 | 276 | out = "" 277 | for i, app in enumerate(apps): 278 | name = app.get_name() 279 | box_width = len(name) + 4 280 | box = helpers.text_in_box(name, box_width, 10) + "\n" 281 | if i == 0: 282 | out = box 283 | else: 284 | out = horizontal_add(out, box) 285 | 286 | self.out += out 287 | 288 | def list_attributes(self, attributes): 289 | pass 290 | 291 | def bootloader_version(self, version): 292 | pass 293 | 294 | def kernel_attributes(self, kern_attrs): 295 | width = self._width() 296 | out = "\n" 297 | out += helpers.text_in_box("Tock Kernel", width) 298 | self.out += out 299 | 300 | def _width(self): 301 | width = 0 302 | for l in self.out.split("\n"): 303 | if len(l) > width: 304 | width = len(l) 305 | return width 306 | 307 | 308 | class JSONDisplay(Display): 309 | """ 310 | Format output as JSON. 311 | """ 312 | 313 | def __init__(self): 314 | self.object = {} 315 | 316 | def list_apps(self, apps, verbose, quiet): 317 | self.object["apps"] = [] 318 | 319 | for app in apps: 320 | self.object["apps"].append(app.object()) 321 | 322 | def list_attributes(self, attributes): 323 | self.object["attributes"] = [] 324 | for index, attribute in enumerate(attributes): 325 | if attribute: 326 | self.object["attributes"].append((attribute["key"], attribute["value"])) 327 | else: 328 | self.object["attributes"].append(None) 329 | 330 | def bootloader_version(self, version): 331 | self.object["bootloader_version"] = version 332 | 333 | def get(self): 334 | return json.dumps(self.object, indent=2) 335 | -------------------------------------------------------------------------------- /tockloader/exceptions.py: -------------------------------------------------------------------------------- 1 | class TockLoaderException(Exception): 2 | """ 3 | Raised when Tockloader detects an issue. 4 | """ 5 | 6 | pass 7 | 8 | 9 | class ChannelAddressErrorException(Exception): 10 | """ 11 | Raised when a particular channel to a board cannot support the request 12 | operation, likely due to the specific address. 13 | """ 14 | 15 | pass 16 | -------------------------------------------------------------------------------- /tockloader/flash_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface to a board's flash file. This module does not directly interface to a 3 | proper board, but can be used to manipulate a board's flash dump. 4 | """ 5 | 6 | import atexit 7 | import logging 8 | import os 9 | 10 | from .board_interface import BoardInterface 11 | from .exceptions import TockLoaderException 12 | 13 | 14 | class FlashFile(BoardInterface): 15 | """ 16 | Implementation of `BoardInterface` for flash files. 17 | """ 18 | 19 | def __init__(self, args): 20 | super().__init__(args) 21 | 22 | # Store the passed filepath 23 | self.filepath = args.flash_file 24 | 25 | # Boards should limit the file size to match their flash. However, we 26 | # don't want users to accidentally create gigantic files using the 27 | # `--flash-file` flag, so we set a cap at 128 MB. If a future Tock board 28 | # needs more than that...well we can revisit this then. 29 | self.max_size = 0x8000000 30 | 31 | # Load custom settings for the flash-file from the board definition. 32 | if self.board and self.board in self.KNOWN_BOARDS: 33 | logging.info('Using settings from KNOWN_BOARDS["{}"]'.format(self.board)) 34 | board = self.KNOWN_BOARDS[self.board] 35 | flash_file_opts = board["flash_file"] if "flash_file" in board else {} 36 | 37 | if "max_size" in flash_file_opts: 38 | self.max_size = flash_file_opts["max_size"] 39 | 40 | # Log the most important, finalized settings to the user 41 | logging.info('Operating on flash file "{}".'.format(self.filepath)) 42 | if self.max_size != None: 43 | logging.info("Limiting flash size to {:#x} bytes.".format(self.max_size)) 44 | 45 | def open_link_to_board(self): 46 | """ 47 | Open a link to the board by opening the flash file for reading and 48 | writing. 49 | """ 50 | try: 51 | # We want to preserve the flash contents in the file. 52 | self.file_handle = open(self.filepath, "r+b") 53 | except: 54 | # But if the file doesn't exist, create it. 55 | self.file_handle = open(self.filepath, "w+b") 56 | 57 | def file_handle_cleanup(): 58 | if self.file_handle is not None: 59 | self.file_handle.close() 60 | 61 | atexit.register(file_handle_cleanup) 62 | 63 | def flash_binary(self, address, binary): 64 | # Write the passed binary data to the given address. This will 65 | # automatically extend the file size if necessary. Thus we must be 66 | # careful to not write past the end of our virtual flash, if one is 67 | # defined. 68 | address = self.translate_address(address) 69 | 70 | # Cap the write size to respect the `max_size` setting. 71 | write_len = max(min(self.max_size - address, len(binary)), 0) 72 | if not write_len == len(binary): 73 | logging.warning("Truncating write due to maximum flash-file size limits") 74 | 75 | self.file_handle.seek(address) 76 | self.file_handle.write(binary[:write_len]) 77 | 78 | def read_range(self, address, length): 79 | # Read from the given address in the file. Specifying an invalid address 80 | # will not cause the file to be extended automatically. Nonetheless we 81 | # should respect the end of our virtual flash, if one is defined. 82 | address = self.translate_address(address) 83 | 84 | # Cap the read size to respect the `max_size` setting. 85 | read_len = max(min(self.max_size - address, length), 0) 86 | if not read_len == length: 87 | logging.warning("Truncating read due to maximum flash-file size limits") 88 | 89 | self.file_handle.seek(address) 90 | return self.file_handle.read(read_len) 91 | 92 | def clear_bytes(self, address): 93 | # For this simple implementation, given we are not operating on a real 94 | # flash, it's fine to just set the single byte at the specified address 95 | # to zero. 96 | self.flash_binary(address, bytes([0])) 97 | -------------------------------------------------------------------------------- /tockloader/helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Various helper functions that tockloader uses. Mostly for interacting with 3 | users in a nice way. 4 | """ 5 | 6 | import argparse 7 | import binascii 8 | import string 9 | import sys 10 | 11 | import colorama 12 | import questionary 13 | 14 | 15 | def set_terminal_title(title): 16 | if sys.stdout.isatty(): 17 | sys.stdout.write(colorama.ansi.set_title(title)) 18 | sys.stdout.flush() 19 | 20 | 21 | def set_terminal_title_from_port_info(info): 22 | """ 23 | Set a terminal title from a `pyserial` object. 24 | """ 25 | extras = ["Tockloader"] 26 | if info.manufacturer and info.manufacturer != "n/a": 27 | extras.append(info.manufacturer) 28 | if info.name and info.name != "n/a": 29 | extras.append(info.name) 30 | if info.description and info.description != "n/a": 31 | extras.append(info.description) 32 | # if info.hwid and info.hwid != 'n/a': 33 | # extras.append(info.hwid) 34 | if info.product and info.product != "n/a": 35 | if info.product != info.description: 36 | extras.append(info.product) 37 | title = " : ".join(extras) 38 | 39 | set_terminal_title(title) 40 | 41 | 42 | def set_terminal_title_from_port(port): 43 | """ 44 | Set the title of the user's terminal for Tockloader. 45 | """ 46 | set_terminal_title("Tockloader : {}".format(port)) 47 | 48 | 49 | def menu_new(options, *, return_type, default_index=None, prompt="", title=""): 50 | """ 51 | Present an interactive menu of choices to a user. 52 | 53 | `options` should be a like-list object whose iterated objects can be coerced 54 | into strings. 55 | 56 | `return_type` must be set to one of: 57 | - "index" - for the index into the options array 58 | - "value" - for the option value chosen 59 | 60 | `default_index` is the index to present as the default value (what happens 61 | if the user simply presses enter). Passing `None` disables default 62 | selection. 63 | """ 64 | 65 | prompt_to_show = prompt 66 | if len(title) > len(prompt_to_show): 67 | prompt_to_show = title 68 | 69 | default = None 70 | if default_index: 71 | default = options[default_index] 72 | 73 | response = questionary.select( 74 | prompt_to_show, choices=options, default=default, qmark="" 75 | ).ask() 76 | 77 | if return_type == "index": 78 | return options.index(response) 79 | elif return_type == "value": 80 | return response 81 | else: 82 | raise NotImplementedError("Menu caller asked for bad return_type") 83 | 84 | 85 | def menu_new_yes_no(prompt=""): 86 | """ 87 | Present an interactive yes/no prompt to the user. 88 | """ 89 | response = questionary.select( 90 | prompt, choices=["Yes", "No"], default=None, qmark="" 91 | ).ask() 92 | return response == "Yes" 93 | 94 | 95 | def menu(options, *, return_type, default_index=0, prompt="Which option? ", title=""): 96 | """ 97 | Present a menu of choices to a user 98 | 99 | `options` should be a like-list object whose iterated objects can be coerced 100 | into strings. 101 | 102 | `return_type` must be set to one of 103 | - "index" - for the index into the options array 104 | - "value" - for the option value chosen 105 | 106 | `default_index` is the index to present as the default value (what happens 107 | if the user simply presses enter). Passing `None` disables default 108 | selection. 109 | """ 110 | prompt_to_show = prompt 111 | print(title) 112 | for i, opt in enumerate(options): 113 | print("[{}]\t{}".format(i, opt)) 114 | if default_index is not None: 115 | prompt_to_show += "[{}] ".format(default_index) 116 | print() 117 | 118 | resp = input(prompt_to_show) 119 | if resp == "": 120 | resp = default_index 121 | else: 122 | try: 123 | resp = int(resp) 124 | if resp < 0 or resp >= len(options): 125 | raise ValueError 126 | except: 127 | return menu( 128 | options, 129 | return_type=return_type, 130 | default_index=default_index, 131 | prompt=prompt, 132 | title=title, 133 | ) 134 | 135 | if return_type == "index": 136 | return resp 137 | elif return_type == "value": 138 | return options[resp] 139 | else: 140 | raise NotImplementedError("Menu caller asked for bad return_type") 141 | 142 | 143 | def menu_multiple(options, prompt="Make your selections:"): 144 | choices = questionary.checkbox(prompt, choices=options, qmark="").ask() 145 | if choices == None: 146 | return [] 147 | return choices 148 | 149 | 150 | def menu_multiple_indices(options, prompt="Make your selections:"): 151 | qchoices = [] 152 | for i, choice in enumerate(options): 153 | qchoices.append(questionary.Choice(choice, value=i)) 154 | 155 | choices = questionary.checkbox(prompt, choices=qchoices, qmark="").ask() 156 | if choices == None: 157 | return [] 158 | return choices 159 | 160 | 161 | def plural(value): 162 | """ 163 | Return '' or 's' based on whether the `value` means a string should have 164 | a plural word. 165 | 166 | `value` can be a list or a number. If the number or the length of the list 167 | is 1, then '' will be returned. Otherwise 's'. 168 | """ 169 | try: 170 | value = len(value) 171 | except: 172 | pass 173 | if value == 1: 174 | return "" 175 | else: 176 | return "s" 177 | 178 | 179 | def number_or(value): 180 | """ 181 | Try to format value as a number. If that fails, just leave it alone. 182 | """ 183 | try: 184 | return int(value, 0) 185 | except: 186 | return value 187 | 188 | 189 | def text_in_box(string, box_width, box_height=3): 190 | """ 191 | Return a string like: 192 | ``` 193 | ┌───────────────┐ 194 | │ str │ 195 | └───────────────┘ 196 | ``` 197 | """ 198 | string_len = box_width - 4 199 | truncated_str = ( 200 | (string[: string_len - 3] + "...") if len(string) > string_len else string 201 | ) 202 | 203 | vertical_padding = max(box_height - 3, 0) 204 | vertical_padding_top = vertical_padding // 2 205 | vertical_padding_bottom = vertical_padding - vertical_padding_top 206 | 207 | out = "┌{}┐\n".format("─" * (box_width - 2)) 208 | for i in range(0, vertical_padding_top): 209 | out += "│ {} |\n".format(" " * string_len) 210 | out += "│ {} |\n".format(truncated_str.ljust(string_len)) 211 | for i in range(0, vertical_padding_bottom): 212 | out += "│ {} |\n".format(" " * string_len) 213 | out += "└{}┘".format("─" * (box_width - 2)) 214 | return out 215 | 216 | 217 | class ListToDictAction(argparse.Action): 218 | """ 219 | `argparse` action to convert `[['key', 'val'], ['key2', 'val2']]` to 220 | `{'key': 'val', 'key2': 'val2'}`. 221 | 222 | This will also do the following conversions: 223 | - `[[]]` -> `{}` 224 | - `[['k': 'v'], []]` -> `{'k': 'v'}` 225 | - `[['k': 'v'], ['']]` -> `{'k': 'v'}` 226 | - `[['k': 'v'], ['a']]` -> `{'k': 'v', 'a': ''}` 227 | """ 228 | 229 | def __call__(self, parser, namespace, values, option_string=None): 230 | # Remove any empty values. 231 | values = list(filter(None, values)) 232 | values = list(filter(lambda x: len(x[0]), values)) 233 | 234 | # Correct any bad values. 235 | for item in values: 236 | print(item) 237 | if len(item) == 1: 238 | item.append("") 239 | elif len(item) > 2: 240 | item = item[0:2] 241 | 242 | # Convert to dict and set as argument attribute. 243 | setattr(namespace, self.dest, dict(values)) 244 | 245 | 246 | def print_flash(address, flash): 247 | """ 248 | Return binary data in a nice hexdump format. 249 | """ 250 | 251 | def chunks(l, n): 252 | for i in range(0, len(l), n): 253 | yield l[i : i + n] 254 | 255 | def dump_line(addr, bytes): 256 | k = binascii.hexlify(bytes).decode("utf-8") 257 | b = " ".join(list(chunks(k, 2))) 258 | if len(b) >= 26: 259 | # add middle space 260 | b = "{} {}".format(b[0:24], b[24:]) 261 | # Add right padding for not full lines 262 | if len(b) < 48: 263 | b = "{0: <48}".format(b) 264 | printable = string.ascii_letters + string.digits + string.punctuation + " " 265 | t = "".join([chr(i) if chr(i) in printable else "." for i in bytes]) 266 | return "{:08x} {} |{}|\n".format(addr, b, t) 267 | 268 | out = "" 269 | for i, chunk in enumerate(chunks(flash, 16)): 270 | out += dump_line(address + (i * 16), chunk) 271 | 272 | return out 273 | -------------------------------------------------------------------------------- /tockloader/kernel_attributes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parse kernel attributes at the end of the kernel flash region. 3 | """ 4 | 5 | import struct 6 | 7 | 8 | class KATLV: 9 | TYPE_APP_MEMORY = 0x0101 10 | TYPE_KERNEL_BINARY = 0x0102 11 | 12 | def get_tlvid(self): 13 | return self.TLVID 14 | 15 | def get_size(self): 16 | return len(self.pack()) 17 | 18 | 19 | class KATLVAppMemory(KATLV): 20 | TLVID = KATLV.TYPE_APP_MEMORY 21 | SIZE = 8 22 | 23 | def __init__(self, buffer): 24 | self.valid = False 25 | 26 | if len(buffer) == 8: 27 | base = struct.unpack(" 4: 131 | # Now try to parse TLVs, but going backwards in flash. 132 | t, l = struct.unpack("= katlv_len and l == katlv_len: 139 | self.tlvs.append(katlv_type(buffer[-1 * katlv_len :])) 140 | buffer = buffer[: -1 * katlv_len] 141 | break 142 | else: 143 | break 144 | 145 | def get_app_memory_region(self): 146 | """ 147 | Get a tuple of the RAM region for apps. If unknown, return None. 148 | 149 | (app_memory_start_address, app_memory_length_bytes) 150 | """ 151 | tlv = self._get_tlv(KATLV.TYPE_APP_MEMORY) 152 | if tlv: 153 | return (tlv.app_memory_start, tlv.app_memory_len) 154 | else: 155 | return None 156 | 157 | return "" 158 | 159 | def _get_tlv(self, tlvid): 160 | """ 161 | Return the TLV from the self.tlvs array if it exists. 162 | """ 163 | for tlv in self.tlvs: 164 | if tlv.get_tlvid() == tlvid: 165 | return tlv 166 | return None 167 | 168 | def __str__(self): 169 | return self.info() 170 | 171 | def info(self): 172 | out = "" 173 | 174 | # Check if we found kernel attributes at all. If not, return early. 175 | if not hasattr(self, "version"): 176 | return out 177 | 178 | address = self.address 179 | index = 0 180 | 181 | ka = "Kernel Attributes" 182 | absolute_address = " [{:<#9x}]".format(address + index) if address else "" 183 | out += "{:<48}[{:<#5x}]{}\n".format(ka, index, absolute_address) 184 | index -= 4 185 | 186 | sentinel = " {:<20}: {}".format("sentinel", "TOCK") 187 | absolute_address = " [{:<#9x}]".format(address + index) if address else "" 188 | out += "{:<48}[{:<#5x}]{}\n".format(sentinel, index, absolute_address) 189 | index -= 4 190 | 191 | version = " {:<20}: {}".format("version", self.version) 192 | absolute_address = " [{:<#9x}]".format(address + index) if address else "" 193 | out += "{:<48}[{:<#5x}]{}\n".format(version, index, absolute_address) 194 | 195 | for tlv in self.tlvs: 196 | # Decrement the byte index FIRST so we get the beginning address. 197 | index -= tlv.get_size() 198 | 199 | # Format the offset so we know the size. 200 | offset = "[{:<#5x}]".format(index) 201 | absolute_address = " [{:<#9x}]".format(address + index) if address else "" 202 | # Create the base TLV format. 203 | tlv_str = str(tlv) 204 | # Insert the address at the end of the first line of the TLV str. 205 | lines = tlv_str.split("\n") 206 | lines[0] = "{:<48}{}{}".format(lines[0], offset, absolute_address) 207 | # Recreate string. 208 | out += "\n".join(lines) 209 | 210 | return out 211 | -------------------------------------------------------------------------------- /tockloader/nrfjprog.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface for boards using nrfjprog. 3 | """ 4 | 5 | import logging 6 | import struct 7 | 8 | import pynrfjprog 9 | from pynrfjprog import LowLevel, Parameters 10 | 11 | from .board_interface import BoardInterface 12 | from .exceptions import TockLoaderException 13 | 14 | 15 | class nrfjprog(BoardInterface): 16 | def __init__(self, args): 17 | # Must call the generic init first. 18 | super().__init__(args) 19 | 20 | def open_link_to_board(self): 21 | qspi_size = 0 22 | self.qspi_address = 0 23 | 24 | # Get board-specific properties we need. 25 | if self.board and self.board in self.KNOWN_BOARDS: 26 | logging.info('Using settings from KNOWN_BOARDS["{}"]'.format(self.board)) 27 | board = self.KNOWN_BOARDS[self.board] 28 | 29 | if "nrfjprog" in board: 30 | if "qspi_size" in board["nrfjprog"]: 31 | qspi_size = board["nrfjprog"]["qspi_size"] 32 | if "qspi_address" in board["nrfjprog"]: 33 | self.qspi_address = board["nrfjprog"]["qspi_address"] 34 | 35 | # pynrfjprog does a lot of logging, at this point too much, so we don't 36 | # enable it. 37 | nrfjprog_logging = False 38 | # if self.args.debug: 39 | # nrfjprog_logging = True 40 | 41 | # First create the base API, this is how pynrfjprog works. 42 | api = pynrfjprog.LowLevel.API() 43 | if not api.is_open(): 44 | api.open() 45 | # try: 46 | # api.open() 47 | # except: 48 | # # If this is called twice it throws an exception that we can ignore. 49 | # pass 50 | 51 | api.connect_to_emu_without_snr() 52 | api.qspi_configure() 53 | api.qspi_init() 54 | 55 | self.nrfjprog = api 56 | 57 | def flash_binary(self, address, binary, pad=False): 58 | """ 59 | Write using nrfjprog. 60 | """ 61 | self.nrfjprog.qspi_write(address - self.qspi_address, binary) 62 | 63 | def read_range(self, address, length): 64 | """ 65 | Read using nrfjprog. 66 | """ 67 | return self.nrfjprog.qspi_read(address - self.qspi_address, length) 68 | 69 | def clear_bytes(self, address): 70 | logging.debug("Clearing bytes starting at {:#0x}".format(address)) 71 | 72 | binary = bytes([0xFF] * 8) 73 | self.flash_binary(address, binary) 74 | -------------------------------------------------------------------------------- /tockloader/static/bscan_spi_xc7a100t.bit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tock/tockloader/a514e818da9c781725a55cbefd64eae411baf591/tockloader/static/bscan_spi_xc7a100t.bit -------------------------------------------------------------------------------- /tockloader/static/bscan_spi_xc7a35t.bit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tock/tockloader/a514e818da9c781725a55cbefd64eae411baf591/tockloader/static/bscan_spi_xc7a35t.bit -------------------------------------------------------------------------------- /tockloader/stlink.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface for boards using STLink. 3 | """ 4 | 5 | import logging 6 | import platform 7 | import shlex 8 | import socket 9 | import subprocess 10 | import tempfile 11 | import time 12 | 13 | from .board_interface import BoardInterface 14 | from .exceptions import TockLoaderException 15 | 16 | # global static variable for collecting temp files for Windows 17 | collect_temp_files = [] 18 | 19 | 20 | class STLink(BoardInterface): 21 | def __init__(self, args): 22 | # Must call the generic init first. 23 | super().__init__(args) 24 | 25 | # Command can be passed in as an argument, otherwise use default. 26 | self.stinfo_cmd = getattr(self.args, "stinfo_cmd") 27 | self.stflash_cmd = getattr(self.args, "stflash_cmd") 28 | 29 | def attached_board_exists(self): 30 | # Get a list of attached devices, check if that list has at least 31 | # one entry. 32 | emulators = self._list_emulators() 33 | return len(emulators) > 0 34 | 35 | def open_link_to_board(self): 36 | # It's very important that we know the board. There are three ways we 37 | # can learn that: 1) use the known boards struct, 2) have it passed in 38 | # via a command line option, 3) guess it from what we can see attached 39 | # to the host. If options 1 and 2 aren't done, then we try number 3! 40 | if self.board == None: 41 | emulators = self._list_emulators() 42 | if len(emulators) > 0: 43 | # Just use the first one. Should be good enough to just assume 44 | # there is only one for now. 45 | self.board = emulators[0] 46 | 47 | # If the user specified a board, use that configuration to fill in any 48 | # missing settings. 49 | if self.board and self.board in self.KNOWN_BOARDS: 50 | logging.info('Using settings from KNOWN_BOARDS["{}"]'.format(self.board)) 51 | board = self.KNOWN_BOARDS[self.board] 52 | 53 | # And we may need to setup other common board settings. 54 | self._configure_from_known_boards() 55 | 56 | def _gather_stlink_cmdline(self, command, binary, write=True): 57 | """ 58 | - `command`: st-flash command. Use {binary} for where the name of the 59 | binary file should be substituted. 60 | - `binary`: A bytes() object that will be used to write to the board. 61 | - `write`: Set to true if the command writes binaries to the board. Set 62 | to false if the command will read bits from the board. 63 | """ 64 | 65 | # in Windows, you can't mark delete bc they delete too fast 66 | delete = platform.system() != "Windows" 67 | if self.args.debug: 68 | delete = False 69 | 70 | if binary or not write: 71 | temp_bin = tempfile.NamedTemporaryFile( 72 | mode="w+b", suffix=".bin", delete=delete 73 | ) 74 | if write: 75 | temp_bin.write(binary) 76 | 77 | temp_bin.flush() 78 | 79 | if platform.system() == "Windows": 80 | # For Windows, forward slashes need to be escaped 81 | temp_bin.name = temp_bin.name.replace("\\", "\\\\\\") 82 | # For Windows, files need to be manually deleted 83 | global collect_temp_files 84 | collect_temp_files += [temp_bin.name] 85 | 86 | # Update the command with the name of the binary file 87 | command = command.format(binary=temp_bin.name) 88 | else: 89 | temp_bin = None 90 | 91 | return ( 92 | "{stflash_cmd} --connect-under-reset {cmd}".format( 93 | stflash_cmd=self.stflash_cmd, 94 | cmd=command, 95 | ), 96 | temp_bin, 97 | ) 98 | 99 | def _run_stlink_command(self, command, binary, write=True): 100 | stlink_command, temp_bin = self._gather_stlink_cmdline(command, binary, write) 101 | 102 | logging.debug('Running "{}".'.format(stlink_command.replace("$", "\$"))) 103 | 104 | def print_output(subp): 105 | response = "" 106 | if subp.stdout: 107 | response += subp.stdout.decode("utf-8") 108 | if subp.stderr: 109 | response += subp.stderr.decode("utf-8") 110 | logging.info(response) 111 | return response 112 | 113 | p = subprocess.run( 114 | shlex.split(stlink_command), stdout=subprocess.PIPE, stderr=subprocess.PIPE 115 | ) 116 | if p.returncode != 0: 117 | logging.error( 118 | "ERROR: st-flash returned with error code " + str(p.returncode) 119 | ) 120 | out = print_output(p) 121 | raise TockLoaderException("st-flash error") 122 | elif self.args.debug: 123 | print_output(p) 124 | 125 | # check that there was a JTAG programmer and that it found a device 126 | stdout = p.stdout.decode("utf-8") 127 | if "Couldn't find any ST-Link devices" in stdout: 128 | raise TockLoaderException("ERROR: Cannot find hardware. Is USB attached?") 129 | 130 | if write == False: 131 | # Wanted to read binary, so lets pull that 132 | temp_bin.seek(0, 0) 133 | return temp_bin.read() 134 | 135 | def _list_emulators(self): 136 | """ 137 | Return a list of board names that are attached to the host. 138 | """ 139 | stlink_command = "{stinfo_cmd} --descr --connect-under-reset".format( 140 | stinfo_cmd=self.stinfo_cmd 141 | ) 142 | 143 | # These are the magic strings in the output of st-info we are looking 144 | # for. 145 | magic_strings_boards = [ 146 | ("STM32F42x_F43x", "stm32f4discovery"), 147 | ] 148 | 149 | emulators = [] 150 | 151 | def print_output(subp): 152 | response = "" 153 | if subp.stdout: 154 | response += subp.stdout.decode("utf-8") 155 | if subp.stderr: 156 | response += subp.stderr.decode("utf-8") 157 | logging.info(response) 158 | return response 159 | 160 | try: 161 | logging.debug('Running "{}".'.format(stlink_command)) 162 | p = subprocess.run( 163 | shlex.split(stlink_command), 164 | stdout=subprocess.PIPE, 165 | stderr=subprocess.PIPE, 166 | ) 167 | if self.args.debug: 168 | print_output(p) 169 | 170 | # Parse all output to look for a device. 171 | stdouterr = p.stdout.decode("utf-8") + p.stderr.decode("utf-8") 172 | 173 | for magic_string, board in magic_strings_boards: 174 | if magic_string in stdouterr: 175 | emulators.append(board) 176 | except FileNotFoundError as e: 177 | if self.args.debug: 178 | logging.debug("st-info does not seem to exist.") 179 | logging.debug(e) 180 | except: 181 | # Any other error just ignore...this is only for convenience. 182 | pass 183 | 184 | return emulators 185 | 186 | def flash_binary(self, address, binary, pad=False): 187 | """ 188 | Write using st-flash `write` command. 189 | """ 190 | # st-flash write command 191 | command = "write {{binary}} {address:#x}" 192 | 193 | # Substitute the key arguments. 194 | command = command.format(address=address) 195 | 196 | logging.debug('Expanded program command: "{}"'.format(command)) 197 | 198 | self._run_stlink_command(command, binary) 199 | 200 | def read_range(self, address, length): 201 | # st-flash read 202 | command = "read {{binary}} {address:#x} {length}" 203 | 204 | logging.debug('Using read command: "{}"'.format(command)) 205 | 206 | # Substitute the key arguments. 207 | command = command.format(address=address, length=length) 208 | 209 | logging.debug('Expanded read command: "{}"'.format(command)) 210 | 211 | # Always return a valid byte array (like the serial version does) 212 | read = bytes() 213 | result = self._run_stlink_command(command, None, write=False) 214 | if result: 215 | read += result 216 | 217 | # Check to make sure we didn't get too many 218 | if len(read) > length: 219 | read = read[0:length] 220 | 221 | return read 222 | 223 | def clear_bytes(self, address): 224 | logging.debug("Clearing bytes starting at {:#0x}".format(address)) 225 | 226 | binary = bytes([0xFF] * 8) 227 | self.flash_binary(address, binary) 228 | 229 | def determine_current_board(self): 230 | if self.board and self.arch and self.page_size > 0: 231 | # These are already set! Yay we are done. 232 | return 233 | 234 | # If we get to here, we still have unknown settings and we need to 235 | # retrieve them from the board itself. If they exist, they will be 236 | # stored as attributes in the flash of the board. 237 | attributes = self.get_all_attributes() 238 | for attribute in attributes: 239 | if attribute and attribute["key"] == "board" and self.board == None: 240 | self.board = attribute["value"] 241 | if attribute and attribute["key"] == "arch" and self.arch == None: 242 | self.arch = attribute["value"] 243 | if attribute and attribute["key"] == "pagesize" and self.page_size == 0: 244 | self.page_size = attribute["value"] 245 | 246 | # We might need to fill in if we only got a "board" attribute. 247 | self._configure_from_known_boards() 248 | 249 | # Check that we learned what we needed to learn. 250 | if self.board is None: 251 | raise TockLoaderException("Could not determine the current board") 252 | if self.arch is None: 253 | raise TockLoaderException("Could not determine the current arch") 254 | if self.page_size == 0: 255 | raise TockLoaderException("Could not determine the current page size") 256 | -------------------------------------------------------------------------------- /tockloader/tab.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import io 3 | import logging 4 | import os 5 | import shutil 6 | import struct 7 | import tarfile 8 | import tempfile 9 | import textwrap 10 | import urllib.request 11 | 12 | import toml 13 | 14 | from .app_tab import TabApp 15 | from .app_tab import TabTbf 16 | from .exceptions import TockLoaderException 17 | from .tbfh import TBFHeader 18 | from .tbfh import TBFFooter 19 | 20 | 21 | class TAB: 22 | """ 23 | Tock Application Bundle object. This class handles the TAB format. 24 | """ 25 | 26 | def __init__(self, tab_path, args=argparse.Namespace()): 27 | self.args = args 28 | self.tab_path = tab_path 29 | 30 | if os.path.exists(tab_path): 31 | # Fetch it from the local filesystem. 32 | self.tab = tarfile.open(tab_path) 33 | else: 34 | try: 35 | # Otherwise download it as a URL. 36 | with urllib.request.urlopen(tab_path) as response: 37 | tmp_file = tempfile.TemporaryFile() 38 | # Copy the downloaded response to our temporary file. 39 | shutil.copyfileobj(response, tmp_file) 40 | # Need to seek to the beginning of the file for tarfile 41 | # to work. 42 | tmp_file.seek(0) 43 | self.tab = tarfile.open(fileobj=tmp_file) 44 | except Exception as e: 45 | if self.args.debug: 46 | logging.error( 47 | "Could not download .tab file. This may have happened because:" 48 | ) 49 | logging.error(" - An HTTPS connection could not be established.") 50 | logging.error(" - A temporary file could not be created.") 51 | logging.error(" - Untarring the TAB failed.") 52 | logging.error("Exception: {}".format(e)) 53 | raise TockLoaderException("Could not download .tab file.") 54 | 55 | def extract_app(self, arch): 56 | """ 57 | Return a `TabApp` object from this TAB, or `None` if the requested 58 | architecture is not present in the TAB. You must specify the desired MCU 59 | architecture so the correct App object can be retrieved. Note that an 60 | architecture may have multiple TBF files if the app is compiled for a 61 | fixed address, and multiple fixed address versions are included in the 62 | TAB. 63 | """ 64 | # Find all filenames that start with the architecture name. 65 | matching_tbf_filenames = [] 66 | contained_files = self.tab.getnames() 67 | # A TBF name is in the format: ..tbf 68 | for contained_file in contained_files: 69 | name_pieces = contained_file.split(".") 70 | if len(name_pieces) >= 2 and name_pieces[-1] == "tbf": 71 | if name_pieces[0] == arch: 72 | matching_tbf_filenames.append(contained_file) 73 | 74 | if len(matching_tbf_filenames) == 0: 75 | # No match for this architecture! Just return None. 76 | return None 77 | 78 | # Get all of the TBF headers and app binaries to create a TabApp. 79 | tbfs = [] 80 | for tbf_filename in matching_tbf_filenames: 81 | binary_tarinfo = self.tab.getmember(tbf_filename) 82 | binary = self.tab.extractfile(binary_tarinfo).read() 83 | 84 | # Parse binary and add TBF to our list of TBFs for this app. 85 | tabtbf = self._extract_tbf_from_filebuffer(tbf_filename, binary) 86 | tbfs.append(tabtbf) 87 | 88 | return TabApp(tbfs) 89 | 90 | def extract_tbf(self, tbf_name): 91 | """ 92 | Return a `TabApp` object from this TAB. You must specify the 93 | desired TBF name, and only that TBF will be returned. 94 | """ 95 | tbf_filename = "{}.tbf".format(tbf_name) 96 | binary_tarinfo = self.tab.getmember(tbf_filename) 97 | binary = self.tab.extractfile(binary_tarinfo).read() 98 | 99 | # Parse binary and app with just this one TBF. 100 | tabtbf = self._extract_tbf_from_filebuffer(tbf_filename, binary) 101 | return TabApp([tabtbf]) 102 | 103 | def update_tbf(self, app): 104 | """ 105 | Inserts a new or modified `TabApp` into the .tab file. 106 | 107 | Only works with .tab files stored locally. 108 | """ 109 | 110 | # First need to extract everything from the existing tab so we can 111 | # replace the files that have not been modified. 112 | allmembers = self.tab.getmembers() 113 | allfiles = [] 114 | for member in allmembers: 115 | allfiles.append(self.tab.extractfile(member).read()) 116 | 117 | # Close the tab we have open since we need to re-open it for writing. 118 | self.tab.close() 119 | 120 | # Now need to open tab for writing. 121 | tab = tarfile.open(self.tab_path, mode="w") 122 | 123 | # Track which files we replaced with new versions. 124 | replaced_names = [] 125 | 126 | # Iterate the new versions and add them to the tab 127 | names_and_binaries = app.get_names_and_binaries() 128 | for filename, binary in names_and_binaries: 129 | tarinfo = tarfile.TarInfo(filename) 130 | tarinfo.size = len(binary) 131 | tab.addfile(tarinfo, io.BytesIO(binary)) 132 | # Keep track of what we now have. 133 | replaced_names.append(filename) 134 | 135 | # Replace what we didn't change. 136 | for i, member in enumerate(allmembers): 137 | if not member.name in replaced_names: 138 | tab.addfile(member, io.BytesIO(allfiles[i])) 139 | 140 | # Close the version for writing. 141 | tab.close() 142 | 143 | # Re-open the read version 144 | self.tab = tarfile.open(self.tab_path) 145 | 146 | def is_compatible_with_board(self, board): 147 | """ 148 | Check if the Tock app is compatible with a particular Tock board. 149 | """ 150 | only_for_boards = self._get_metadata_key("only-for-boards") 151 | 152 | # If no boards are set, unconditionally return True. 153 | if only_for_boards == None or only_for_boards == "": 154 | return True 155 | 156 | # If boards are set, it better be in the list. 157 | if board and board in only_for_boards: 158 | return True 159 | 160 | # Otherwise, incompatible. 161 | return False 162 | 163 | def get_compatible_boards(self): 164 | """ 165 | Return a list of compatible boards from the metadata file. 166 | """ 167 | only_for_boards = self._get_metadata_key("only-for-boards") or "" 168 | return [b.strip() for b in only_for_boards.split(",")] 169 | 170 | def is_compatible_with_kernel_version(self, kernel_version): 171 | """ 172 | Check if the Tock app is compatible with the version of the kernel. 173 | Default to yes unless we can be confident the answer is no. 174 | 175 | `kernel_version` should be a string, or None if the kernel API version 176 | is unknown. 177 | """ 178 | if kernel_version == None: 179 | # Bail out early if kernel version is unknown. 180 | return True 181 | 182 | tock_kernel_version = self._get_metadata_key("tock-kernel-version") 183 | return ( 184 | tock_kernel_version == None 185 | or tock_kernel_version == kernel_version 186 | or tock_kernel_version == "" 187 | ) 188 | 189 | def get_supported_architectures(self): 190 | """ 191 | Return a list of architectures that this TAB has compiled binaries for. 192 | Note that this will return all architectures that have any TBF binary, 193 | but some of those TBF binaries may be compiled for very specific 194 | addresses. That is, there isn't a guarantee that the TBF file will work 195 | on any chip with one of the supported architectures. 196 | """ 197 | archs = set() 198 | contained_files = self.tab.getnames() 199 | # A TBF name is in the format: ..tbf 200 | for contained_file in contained_files: 201 | name_pieces = contained_file.split(".") 202 | if len(name_pieces) >= 2 and name_pieces[-1] == "tbf": 203 | archs.add(name_pieces[0]) 204 | 205 | # We used to use the format .bin, so for backwards 206 | # compatibility check that too. 207 | if len(archs) == 0: 208 | archs = set([i[:-4] for i in contained_files if i[-4:] == ".bin"]) 209 | 210 | return sorted(archs) 211 | 212 | def get_tbf_names(self): 213 | """ 214 | Returns a list of the names of all of the .tbf files contained in the 215 | TAB, without the extension. 216 | """ 217 | tbfs = [] 218 | for f in self.tab.getnames(): 219 | if f[-4:] == ".tbf": 220 | tbfs.append(f[:-4]) 221 | return sorted(tbfs) 222 | 223 | def get_app_name(self): 224 | """ 225 | Return the app name from the metadata file. 226 | """ 227 | return self._get_metadata_key("name") or "" 228 | 229 | def _extract_tbf_from_filebuffer(self, tbf_filename, binary): 230 | # First get the TBF header from the binary and check that it is valid. 231 | tbfh = TBFHeader(binary) 232 | if tbfh.is_valid(): 233 | # Check that total size actually matches the binary that we got. 234 | if tbfh.get_app_size() < len(binary): 235 | # It's fine if the binary is smaller, but the binary cannot 236 | # be longer than the amount of reserved space (`total_size` 237 | # in the TBF header) for the app. 238 | raise TockLoaderException( 239 | "Invalid TAB, the app binary length ({} bytes) is longer \ 240 | than its defined total_size ({} bytes)".format( 241 | len(binary), tbfh.get_app_size() 242 | ) 243 | ) 244 | 245 | # Get indices into the TBF file binary on where elements are 246 | # located. 247 | start_of_app_binary = tbfh.get_size_before_app() 248 | start_of_footers = tbfh.get_binary_end_offset() 249 | 250 | # Get application binary code. 251 | app_binary = binary[start_of_app_binary:start_of_footers] 252 | 253 | # Extract the footer if any should exist. It is OK if the footer 254 | # buffer is zero length, the footer object will just be empty. 255 | tbff = TBFFooter(tbfh, app_binary, binary[start_of_footers:]) 256 | 257 | # Finally we can return the TBF. 258 | return TabTbf( 259 | tbf_filename, 260 | tbfh, 261 | app_binary, 262 | tbff, 263 | ) 264 | else: 265 | raise TockLoaderException("Invalid TBF found in app in TAB: {}", tbfh) 266 | 267 | def _parse_metadata(self): 268 | """ 269 | Open and parse the included metadata file in the TAB, returning the 270 | key-value pairs as a dict. 271 | """ 272 | # Use cached value. 273 | if hasattr(self, "metadata"): 274 | return self.metadata 275 | 276 | # Otherwise parse f.toml file. 277 | metadata_tarinfo = self.tab.getmember("metadata.toml") 278 | metadata_str = self.tab.extractfile(metadata_tarinfo).read().decode("utf-8") 279 | self.metadata = toml.loads(metadata_str) 280 | return self.metadata 281 | 282 | def _get_metadata_key(self, key): 283 | """ 284 | Return the value for a specific key from the metadata file. 285 | """ 286 | metadata = self._parse_metadata() 287 | if metadata["tab-version"] == 1: 288 | if key in metadata: 289 | return metadata[key].strip() 290 | else: 291 | # If unknown version, print warning. 292 | logging.warning( 293 | "Unknown TAB version. You probably need to update tockloader." 294 | ) 295 | 296 | # Default to returning None if key is not found or file is not understood. 297 | return None 298 | 299 | def __str__(self): 300 | out = "" 301 | metadata = self._parse_metadata() 302 | out += "TAB: {}\n".format(metadata["name"]) 303 | for k, v in sorted(metadata.items()): 304 | if k == "name": 305 | continue 306 | out += " {}: {}\n".format(k, v) 307 | out += " included architectures: {}\n".format( 308 | ", ".join(self.get_supported_architectures()) 309 | ) 310 | return out 311 | --------------------------------------------------------------------------------