Files
OpenSK/deploy.py
T

996 lines
34 KiB
Python
Raw Normal View History

2020-02-19 11:34:43 +01:00
#!/usr/bin/env python3
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Lint as: python3
2020-03-03 14:56:11 +01:00
# pylint: disable=C0111
2020-02-19 11:34:43 +01:00
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
2020-03-11 16:51:26 +01:00
import collections
2020-02-19 11:34:43 +01:00
import copy
import os
import shutil
import subprocess
import sys
2020-02-21 17:41:40 +01:00
import colorama
2020-03-11 17:27:11 +01:00
from six.moves import input
2020-04-28 10:02:47 +02:00
import tockloader
2020-03-03 14:56:11 +01:00
from tockloader import tab
from tockloader import tbfh
from tockloader import tockloader as loader
2020-02-21 14:41:17 +01:00
from tockloader.exceptions import TockLoaderException
2020-02-19 11:34:43 +01:00
2020-03-11 16:51:26 +01:00
PROGRAMMERS = frozenset(("jlink", "openocd", "pyocd", "nordicdfu", "none"))
# This structure allows us to support out-of-tree boards as well as (in the
# future) more achitectures.
OpenSKBoard = collections.namedtuple(
"OpenSKBoard",
[
2020-03-12 12:12:45 +01:00
# Location of the Tock board (where the Makefile file is)
2020-03-11 16:51:26 +01:00
"path",
2020-03-12 12:12:45 +01:00
# Target architecture (e.g. thumbv7em-none-eabi)
2020-03-11 16:51:26 +01:00
"arch",
# Size of 1 page of flash memory
"page_size",
# Flash address at which the kernel will be written
"kernel_address",
2020-03-12 12:12:45 +01:00
# Set to None is padding is not required for the board.
# This creates a fake Tock OS application that starts at the
# address specified by this parameter (must match the `prog` value
# specified on the board's `layout.ld` file) and will end at
# `app_address`.
2020-03-11 16:51:26 +01:00
"padding_address",
# Linker script to produce a working app for this board
"app_ldscript",
# Flash address at which the app should be written
"app_address",
2020-05-30 12:05:50 +02:00
# Flash address of the storage
"storage_address",
# Size of the storage
"storage_size",
2020-03-11 16:51:26 +01:00
# Target name for flashing the board using pyOCD
"pyocd_target",
# The cfg file in OpenOCD board folder
"openocd_board",
# Options to tell Tockloader how to work with OpenOCD
# Default: []
"openocd_options",
# Dictionnary specifying custom commands for OpenOCD
# Default is an empty dict
# Valid keys are: program, read, erase
"openocd_commands",
# Interface to use with JLink (e.g. swd, jtag, etc.)
"jlink_if",
# Device name as supported by JLinkExe
"jlink_device",
# Whether Nordic DFU flashing method is supported
"nordic_dfu",
])
2020-02-19 11:34:43 +01:00
SUPPORTED_BOARDS = {
2021-06-28 14:55:20 +02:00
"nrf52840dk_opensk":
2020-03-11 16:51:26 +01:00
OpenSKBoard(
2021-06-28 14:55:20 +02:00
path="third_party/tock/boards/nordic/nrf52840dk_opensk",
2020-03-11 16:51:26 +01:00
arch="thumbv7em-none-eabi",
page_size=4096,
kernel_address=0,
padding_address=0x30000,
2020-04-30 16:04:28 +02:00
app_ldscript="nrf52840_layout.ld",
2020-03-11 16:51:26 +01:00
app_address=0x40000,
2020-05-30 12:05:50 +02:00
storage_address=0xC0000,
storage_size=0x40000,
2020-03-11 16:51:26 +01:00
pyocd_target="nrf52840",
openocd_board="nordic_nrf52840_dongle.cfg",
openocd_options=[],
openocd_commands={},
jlink_if="swd",
jlink_device="nrf52840_xxaa",
nordic_dfu=False,
),
2021-06-28 14:55:20 +02:00
"nrf52840_dongle_opensk":
2020-03-11 16:51:26 +01:00
OpenSKBoard(
2021-06-28 14:55:20 +02:00
path="third_party/tock/boards/nordic/nrf52840_dongle_opensk",
2020-03-11 16:51:26 +01:00
arch="thumbv7em-none-eabi",
page_size=4096,
kernel_address=0,
padding_address=0x30000,
2020-04-30 16:04:28 +02:00
app_ldscript="nrf52840_layout.ld",
2020-03-11 16:51:26 +01:00
app_address=0x40000,
2020-05-30 12:05:50 +02:00
storage_address=0xC0000,
storage_size=0x40000,
2020-03-11 16:51:26 +01:00
pyocd_target="nrf52840",
openocd_board="nordic_nrf52840_dongle.cfg",
openocd_options=[],
openocd_commands={},
jlink_if="swd",
jlink_device="nrf52840_xxaa",
nordic_dfu=False,
),
"nrf52840_dongle_dfu":
OpenSKBoard(
path="third_party/tock/boards/nordic/nrf52840_dongle_dfu",
2020-03-11 16:51:26 +01:00
arch="thumbv7em-none-eabi",
page_size=4096,
kernel_address=0x1000,
padding_address=0x30000,
2020-04-30 16:04:28 +02:00
app_ldscript="nrf52840_layout.ld",
2020-03-11 16:51:26 +01:00
app_address=0x40000,
2020-05-30 12:05:50 +02:00
storage_address=0xC0000,
storage_size=0x40000,
2020-03-11 16:51:26 +01:00
pyocd_target="nrf52840",
openocd_board="nordic_nrf52840_dongle.cfg",
openocd_options=[],
openocd_commands={},
jlink_if="swd",
jlink_device="nrf52840_xxaa",
nordic_dfu=True,
),
"nrf52840_mdk_dfu":
OpenSKBoard(
path="third_party/tock/boards/nordic/nrf52840_mdk_dfu",
2020-03-11 16:51:26 +01:00
arch="thumbv7em-none-eabi",
page_size=4096,
kernel_address=0x1000,
padding_address=0x30000,
2020-04-30 16:04:28 +02:00
app_ldscript="nrf52840_layout.ld",
2020-03-11 16:51:26 +01:00
app_address=0x40000,
2020-05-30 12:05:50 +02:00
storage_address=0xC0000,
storage_size=0x40000,
2020-03-11 16:51:26 +01:00
pyocd_target="nrf52840",
openocd_board="nordic_nrf52840_dongle.cfg",
openocd_options=[],
openocd_commands={},
jlink_if="swd",
jlink_device="nrf52840_xxaa",
nordic_dfu=True,
),
2020-02-19 11:34:43 +01:00
}
# The STACK_SIZE value below must match the one used in the linker script
# used by the board.
2020-04-30 16:04:28 +02:00
# e.g. for Nordic nRF52840 boards the file is `nrf52840_layout.ld`.
2020-02-19 11:34:43 +01:00
STACK_SIZE = 0x4000
# The following value must match the one used in the file
# `src/entry_point.rs`
APP_HEAP_SIZE = 90000
def get_supported_boards():
2020-02-20 11:28:49 +01:00
boards = []
2020-03-11 16:51:26 +01:00
for name, props in SUPPORTED_BOARDS.items():
if all((os.path.exists(os.path.join(props.path, "Cargo.toml")),
(props.app_ldscript and os.path.exists(props.app_ldscript)))):
2020-02-20 11:28:49 +01:00
boards.append(name)
return tuple(set(boards))
2020-02-19 11:34:43 +01:00
def fatal(msg):
2020-02-20 11:28:49 +01:00
print("{style_begin}fatal:{style_end} {message}".format(
style_begin=colorama.Fore.RED + colorama.Style.BRIGHT,
style_end=colorama.Style.RESET_ALL,
message=msg))
sys.exit(1)
2020-02-19 11:34:43 +01:00
def error(msg):
2020-02-20 11:28:49 +01:00
print("{style_begin}error:{style_end} {message}".format(
style_begin=colorama.Fore.RED,
style_end=colorama.Style.RESET_ALL,
message=msg))
2020-02-19 11:34:43 +01:00
def info(msg):
2020-02-20 11:28:49 +01:00
print("{style_begin}info:{style_end} {message}".format(
style_begin=colorama.Fore.GREEN + colorama.Style.BRIGHT,
style_end=colorama.Style.RESET_ALL,
message=msg))
2020-02-19 11:34:43 +01:00
2020-03-11 16:51:26 +01:00
def assert_mandatory_binary(binary):
if not shutil.which(binary):
fatal(("Couldn't find {} binary. Make sure it is installed and "
"that your PATH is set correctly.").format(binary))
def assert_python_library(module):
try:
__import__(module)
except ModuleNotFoundError:
fatal(("Couldn't load python3 module {name}. "
"Try to run: pip3 install {name}").format(name=module))
2020-02-19 11:34:43 +01:00
class RemoveConstAction(argparse.Action):
2020-03-11 16:51:26 +01:00
# pylint: disable=redefined-builtin
2020-02-20 11:28:49 +01:00
def __init__(self,
option_strings,
dest,
const,
default=None,
required=False,
help=None,
metavar=None):
2020-09-15 11:29:03 +02:00
super().__init__(
2020-02-20 11:28:49 +01:00
option_strings=option_strings,
dest=dest,
nargs=0,
const=const,
default=default,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
# Code is simply a modified version of the AppendConstAction from argparse
# https://github.com/python/cpython/blob/master/Lib/argparse.py#L138-L147
# https://github.com/python/cpython/blob/master/Lib/argparse.py#L1028-L1052
items = getattr(namespace, self.dest, [])
2021-06-09 22:52:52 +02:00
if items is None:
items = []
2020-02-21 17:41:40 +01:00
if isinstance(items, list):
2020-02-20 11:28:49 +01:00
items = items[:]
else:
items = copy.copy(items)
if self.const in items:
2020-02-21 14:41:17 +01:00
items.remove(self.const)
2020-02-20 11:28:49 +01:00
setattr(namespace, self.dest, items)
2020-02-19 11:34:43 +01:00
2020-02-21 17:41:40 +01:00
class OpenSKInstaller:
2020-02-19 11:34:43 +01:00
2020-02-20 11:28:49 +01:00
def __init__(self, args):
self.args = args
# Where all the TAB files should go
self.tab_folder = os.path.join("target", "tab")
2020-03-11 16:51:26 +01:00
board = SUPPORTED_BOARDS[self.args.board]
2020-02-20 11:28:49 +01:00
self.tockloader_default_args = argparse.Namespace(
2020-03-11 16:51:26 +01:00
arch=board.arch,
board=self.args.board,
bundle_apps=False,
2020-02-20 11:28:49 +01:00
debug=False,
force=False,
2020-04-28 10:02:47 +02:00
jlink_cmd="JLinkExe",
2020-03-11 16:51:26 +01:00
jlink=self.args.programmer == "jlink",
jlink_device=board.jlink_device,
jlink_if=board.jlink_if,
2020-02-20 11:28:49 +01:00
jlink_speed=1200,
2020-03-11 16:51:26 +01:00
openocd=self.args.programmer == "openocd",
openocd_board=board.openocd_board,
2020-04-28 10:02:47 +02:00
openocd_cmd="openocd",
openocd_commands=copy.copy(board.openocd_commands),
openocd_options=copy.copy(board.openocd_options),
2020-02-20 11:28:49 +01:00
jtag=False,
no_bootloader_entry=False,
2020-03-11 16:51:26 +01:00
page_size=board.page_size,
2020-02-20 11:28:49 +01:00
port=None,
)
2020-02-19 11:34:43 +01:00
def checked_command(self, cmd, env=None, cwd=None):
stdout = None if self.args.verbose_build else subprocess.DEVNULL
try:
subprocess.run(
2020-04-28 14:05:36 +02:00
cmd, stdout=stdout, timeout=None, check=True, env=env, cwd=cwd)
except subprocess.CalledProcessError as e:
fatal("Failed to execute {}: {}".format(cmd[0], str(e)))
2020-03-11 16:51:26 +01:00
def checked_command_output(self, cmd, env=None, cwd=None):
2020-02-20 11:28:49 +01:00
cmd_output = ""
try:
2020-03-11 16:51:26 +01:00
cmd_output = subprocess.run(
cmd,
stdout=subprocess.PIPE,
timeout=None,
check=True,
env=env,
cwd=cwd).stdout
2020-02-20 11:28:49 +01:00
except subprocess.CalledProcessError as e:
fatal("Failed to execute {}: {}".format(cmd[0], str(e)))
# Unreachable because fatal() will exit
return cmd_output.decode()
2020-02-19 11:34:43 +01:00
2020-02-20 11:28:49 +01:00
def update_rustc_if_needed(self):
target_toolchain_fullstring = "stable"
with open("rust-toolchain", "r") as f:
target_toolchain_fullstring = f.readline().strip()
target_toolchain = target_toolchain_fullstring.split("-", maxsplit=1)
if len(target_toolchain) == 1:
# If we target the stable version of rust, we won't have a date
# associated to the version and split will only return 1 item.
# To avoid failing later when accessing the date, we insert an
# empty value.
2020-03-03 14:56:11 +01:00
target_toolchain.append("")
2020-02-20 11:28:49 +01:00
current_version = self.checked_command_output(["rustc", "--version"])
2020-04-24 12:45:58 +02:00
if not (target_toolchain[0] in current_version and
target_toolchain[1] in current_version):
2020-02-20 11:28:49 +01:00
info("Updating rust toolchain to {}".format("-".join(target_toolchain)))
# Need to update
2020-04-28 14:02:24 +02:00
rustup_install = ["rustup"]
if self.args.verbose_build:
2020-04-28 18:04:41 +02:00
rustup_install.append("--verbose")
2020-04-28 14:02:24 +02:00
rustup_install.extend(["install", target_toolchain_fullstring])
self.checked_command(rustup_install)
rustup_target = ["rustup"]
if self.args.verbose_build:
2020-04-28 18:04:41 +02:00
rustup_target.append("--verbose")
2020-04-28 14:05:36 +02:00
rustup_target.extend(
["target", "add", SUPPORTED_BOARDS[self.args.board].arch])
2020-04-28 14:02:24 +02:00
self.checked_command(rustup_target)
2020-02-20 11:28:49 +01:00
info("Rust toolchain up-to-date")
2020-02-19 11:34:43 +01:00
2020-03-11 16:51:26 +01:00
def build_tockos(self):
info("Building Tock OS for board {}".format(self.args.board))
props = SUPPORTED_BOARDS[self.args.board]
2020-04-08 15:55:51 +02:00
out_directory = os.path.join("third_party", "tock", "target", props.arch,
"release")
2020-03-11 16:51:26 +01:00
os.makedirs(out_directory, exist_ok=True)
env = os.environ.copy()
if self.args.verbose_build:
env["V"] = "1"
self.checked_command(["make"], cwd=props.path, env=env)
2020-02-19 11:34:43 +01:00
2020-03-11 16:51:26 +01:00
def build_example(self):
info("Building example {}".format(self.args.application))
self._build_app_or_example(is_example=True)
def build_opensk(self):
2020-02-20 11:28:49 +01:00
info("Building OpenSK application")
self._check_invariants()
2020-03-11 16:51:26 +01:00
self._build_app_or_example(is_example=False)
def _build_app_or_example(self, is_example):
assert self.args.application
# Ideally we would build a TAB file for all boards at once but depending on
# the chip on the board, the link script could be totally different.
# And elf2tab doesn't seem to let us set the boards a TAB file has been
# created for. So at the moment we only build for the selected board.
props = SUPPORTED_BOARDS[self.args.board]
rust_flags = [
"-C",
"link-arg=-T{}".format(props.app_ldscript),
"-C",
"relocation-model=static",
"-D",
"warnings",
"--remap-path-prefix={}=".format(os.getcwd()),
2021-06-18 14:52:03 +02:00
"-C",
"link-arg=-icf=all",
"-C",
"force-frame-pointers=no",
2020-03-11 16:51:26 +01:00
]
env = os.environ.copy()
env["RUSTFLAGS"] = " ".join(rust_flags)
2020-07-10 10:41:01 +02:00
env["APP_HEAP_SIZE"] = str(APP_HEAP_SIZE)
2020-03-11 16:51:26 +01:00
command = [
"cargo", "build", "--release", "--target={}".format(props.arch),
"--features={}".format(",".join(self.args.features))
]
if is_example:
command.extend(["--example", self.args.application])
if self.args.verbose_build:
2020-04-28 18:04:41 +02:00
command.append("--verbose")
self.checked_command(command, env=env)
2020-03-11 16:51:26 +01:00
app_path = os.path.join("target", props.arch, "release")
if is_example:
app_path = os.path.join(app_path, "examples")
app_path = os.path.join(app_path, self.args.application)
# Create a TAB file
self.create_tab_file({props.arch: app_path})
2020-02-19 11:34:43 +01:00
def _check_invariants(self):
print("Testing invariants in customization.rs...")
self.checked_command_output(
["cargo", "test", "--features=std", "--lib", "customization"])
2020-02-20 11:28:49 +01:00
def generate_crypto_materials(self, force_regenerate):
has_error = subprocess.call([
os.path.join("tools", "gen_key_materials.sh"),
"Y" if force_regenerate else "N",
])
if has_error:
error(("Something went wrong while trying to generate ECC "
"key and/or certificate for OpenSK"))
2020-02-19 11:34:43 +01:00
2020-03-11 16:51:26 +01:00
def create_tab_file(self, binaries):
assert binaries
2020-02-21 17:41:40 +01:00
assert self.args.application
2020-03-11 16:51:26 +01:00
info("Generating Tock TAB file for application/example {}".format(
self.args.application))
2020-09-29 12:42:48 +02:00
elf2tab_ver = self.checked_command_output(
["elf2tab/bin/elf2tab", "--version"]).split(
"\n", maxsplit=1)[0]
2020-09-01 17:23:26 +02:00
if elf2tab_ver != "elf2tab 0.6.0":
2020-05-13 12:35:10 +02:00
error(
("Detected unsupported elf2tab version {!a}. The following "
2020-09-01 17:23:26 +02:00
"commands may fail. Please use 0.6.0 instead.").format(elf2tab_ver))
2020-02-20 11:28:49 +01:00
os.makedirs(self.tab_folder, exist_ok=True)
tab_filename = os.path.join(self.tab_folder,
"{}.tab".format(self.args.application))
2020-03-11 16:51:26 +01:00
elf2tab_args = [
2020-09-29 12:42:48 +02:00
"elf2tab/bin/elf2tab", "--deterministic", "--package-name",
self.args.application, "-o", tab_filename
2020-03-11 16:51:26 +01:00
]
if self.args.verbose_build:
2020-04-28 18:04:41 +02:00
elf2tab_args.append("--verbose")
2020-03-11 16:51:26 +01:00
for arch, app_file in binaries.items():
dest_file = os.path.join(self.tab_folder, "{}.elf".format(arch))
shutil.copyfile(app_file, dest_file)
elf2tab_args.append(dest_file)
elf2tab_args.extend([
"--stack={}".format(STACK_SIZE), "--app-heap={}".format(APP_HEAP_SIZE),
"--kernel-heap=1024", "--protected-region-size=64"
2020-02-20 11:28:49 +01:00
])
if self.args.elf2tab_output:
output = self.checked_command_output(elf2tab_args)
self.args.elf2tab_output.write(output)
else:
self.checked_command(elf2tab_args)
2020-03-11 16:51:26 +01:00
def install_tab_file(self, tab_filename):
assert self.args.application
2020-02-20 11:28:49 +01:00
info("Installing Tock application {}".format(self.args.application))
2020-03-11 16:51:26 +01:00
board_props = SUPPORTED_BOARDS[self.args.board]
2020-02-20 11:28:49 +01:00
args = copy.copy(self.tockloader_default_args)
2020-03-11 16:51:26 +01:00
setattr(args, "app_address", board_props.app_address)
2020-02-20 11:28:49 +01:00
setattr(args, "erase", self.args.clear_apps)
setattr(args, "make", False)
setattr(args, "no_replace", False)
2020-03-03 14:56:11 +01:00
tock = loader.TockLoader(args)
2020-04-28 10:02:47 +02:00
tock.open()
2020-02-20 11:28:49 +01:00
tabs = [tab.TAB(tab_filename)]
try:
tock.install(tabs, replace="yes", erase=args.erase)
2020-02-21 14:41:17 +01:00
except TockLoaderException as e:
2020-02-20 11:28:49 +01:00
fatal("Couldn't install Tock application {}: {}".format(
self.args.application, str(e)))
2020-02-19 11:34:43 +01:00
2020-03-11 16:51:26 +01:00
def get_padding(self):
2020-08-07 16:28:39 +02:00
padding = tbfh.TBFHeaderPadding(
2020-03-11 16:51:26 +01:00
SUPPORTED_BOARDS[self.args.board].app_address -
SUPPORTED_BOARDS[self.args.board].padding_address)
2020-08-07 16:28:39 +02:00
return padding.get_binary()
2020-03-11 16:51:26 +01:00
def install_tock_os(self):
board_props = SUPPORTED_BOARDS[self.args.board]
2020-04-08 15:55:51 +02:00
kernel_file = os.path.join("third_party", "tock", "target",
board_props.arch, "release",
"{}.bin".format(self.args.board))
2020-03-11 16:51:26 +01:00
info("Flashing file {}.".format(kernel_file))
with open(kernel_file, "rb") as f:
kernel = f.read()
args = copy.copy(self.tockloader_default_args)
setattr(args, "address", board_props.app_address)
tock = loader.TockLoader(args)
2020-04-28 10:02:47 +02:00
tock.open()
2020-03-11 16:51:26 +01:00
try:
tock.flash_binary(kernel, board_props.kernel_address)
except TockLoaderException as e:
2020-03-12 12:12:45 +01:00
fatal("Couldn't install Tock OS: {}".format(str(e)))
2020-03-11 16:51:26 +01:00
def install_padding(self):
padding = self.get_padding()
board_props = SUPPORTED_BOARDS[self.args.board]
2020-02-20 11:28:49 +01:00
info("Flashing padding application")
args = copy.copy(self.tockloader_default_args)
2020-03-11 16:51:26 +01:00
setattr(args, "address", board_props.padding_address)
2020-03-03 14:56:11 +01:00
tock = loader.TockLoader(args)
2020-04-28 10:02:47 +02:00
tock.open()
2020-02-20 11:28:49 +01:00
try:
tock.flash_binary(padding, args.address)
2020-02-21 14:41:17 +01:00
except TockLoaderException as e:
2020-02-20 11:28:49 +01:00
fatal("Couldn't install padding: {}".format(str(e)))
2020-02-19 11:34:43 +01:00
2020-02-20 11:28:49 +01:00
def clear_apps(self):
args = copy.copy(self.tockloader_default_args)
2020-03-11 16:51:26 +01:00
board_props = SUPPORTED_BOARDS[self.args.board]
setattr(args, "app_address", board_props.app_address)
2020-04-28 10:02:47 +02:00
# Ensure we don't force erase all apps but only the apps starting
# at `board.app_address`. This makes sure we don't erase the padding.
setattr(args, "force", False)
2020-02-20 11:28:49 +01:00
info("Erasing all installed applications")
2020-03-03 14:56:11 +01:00
tock = loader.TockLoader(args)
2020-04-28 10:02:47 +02:00
tock.open()
2020-02-20 11:28:49 +01:00
try:
2020-04-28 10:02:47 +02:00
tock.erase_apps()
2020-02-21 14:41:17 +01:00
except TockLoaderException as e:
2020-02-20 11:28:49 +01:00
# Erasing apps is not critical
2020-03-11 16:51:26 +01:00
info(("A non-critical error occurred while erasing "
2020-02-20 11:28:49 +01:00
"apps: {}".format(str(e))))
2020-02-19 11:34:43 +01:00
2020-05-30 12:05:50 +02:00
def clear_storage(self):
if self.args.programmer == "none":
return 0
2020-06-02 15:53:42 +02:00
info("Erasing the persistent storage")
2020-05-30 12:05:50 +02:00
board_props = SUPPORTED_BOARDS[self.args.board]
# Use tockloader if possible
if self.args.programmer in ("jlink", "openocd"):
2020-06-02 15:53:42 +02:00
storage = bytes([0xFF] * board_props.storage_size)
2020-05-30 12:05:50 +02:00
tock = loader.TockLoader(self.tockloader_default_args)
tock.open()
try:
tock.flash_binary(storage, board_props.storage_address)
except TockLoaderException as e:
fatal("Couldn't erase the persistent storage: {}".format(str(e)))
return 0
if self.args.programmer == "pyocd":
self.checked_command([
2020-06-02 15:53:42 +02:00
"pyocd", "erase", "--target={}".format(board_props.pyocd_target),
"--sector", "{}+{}".format(board_props.storage_address,
board_props.storage_size)
2020-05-30 12:05:50 +02:00
])
return 0
fatal("Programmer {} is not supported.".format(self.args.programmer))
2020-03-11 16:51:26 +01:00
# pylint: disable=protected-access
2020-02-20 11:28:49 +01:00
def verify_flashed_app(self, expected_app):
2020-03-11 16:51:26 +01:00
if self.args.programmer not in ("jlink", "openocd"):
return False
2020-02-20 11:28:49 +01:00
args = copy.copy(self.tockloader_default_args)
2020-03-03 14:56:11 +01:00
tock = loader.TockLoader(args)
2020-04-28 10:02:47 +02:00
tock.open()
2020-02-20 11:28:49 +01:00
app_found = False
with tock._start_communication_with_board():
2020-08-07 16:28:39 +02:00
apps = [app.get_name() for app in tock._extract_all_app_headers()]
2020-02-20 11:28:49 +01:00
app_found = expected_app in apps
return app_found
2020-02-19 11:34:43 +01:00
2020-03-11 16:51:26 +01:00
def create_hex_file(self, dest_file):
# We produce an intelhex file with everything in it
2020-03-11 17:47:14 +01:00
# https://en.wikipedia.org/wiki/Intel_HEX
2020-03-11 16:51:26 +01:00
# pylint: disable=g-import-not-at-top,import-outside-toplevel
import intelhex
board_props = SUPPORTED_BOARDS[self.args.board]
final_hex = intelhex.IntelHex()
if self.args.tockos:
# Process kernel
2020-04-08 15:55:51 +02:00
kernel_path = os.path.join("third_party", "tock", "target",
board_props.arch, "release",
"{}.bin".format(self.args.board))
2020-03-11 16:51:26 +01:00
with open(kernel_path, "rb") as kernel:
kern_hex = intelhex.IntelHex()
kern_hex.frombytes(kernel.read(), offset=board_props.kernel_address)
final_hex.merge(kern_hex, overlap="error")
if self.args.application:
# Add padding
if board_props.padding_address:
padding_hex = intelhex.IntelHex()
padding_hex.frombytes(
self.get_padding(), offset=board_props.padding_address)
final_hex.merge(padding_hex, overlap="error")
2020-03-11 16:51:26 +01:00
# Now we can add the application from the TAB file
app_tab_path = "target/tab/{}.tab".format(self.args.application)
assert os.path.exists(app_tab_path)
app_tab = tab.TAB(app_tab_path)
if board_props.arch not in app_tab.get_supported_architectures():
fatal(("It seems that the TAB file was not produced for the "
"architecture {}".format(board_props.arch)))
app_hex = intelhex.IntelHex()
app_hex.frombytes(
2020-08-07 16:28:39 +02:00
app_tab.extract_app(board_props.arch).get_binary(
board_props.app_address),
offset=board_props.app_address)
final_hex.merge(app_hex)
2020-03-11 16:51:26 +01:00
info("Generating all-merged HEX file: {}".format(dest_file))
final_hex.tofile(dest_file, format="hex")
def check_prerequisites(self):
2020-08-07 16:28:39 +02:00
if not tockloader.__version__.startswith("1.5."):
fatal(("Your version of tockloader seems incompatible: found {}, "
2020-08-07 16:28:39 +02:00
"expected 1.5.x.".format(tockloader.__version__)))
2020-04-28 10:02:47 +02:00
2020-03-11 16:51:26 +01:00
if self.args.programmer == "jlink":
assert_mandatory_binary("JLinkExe")
if self.args.programmer == "openocd":
assert_mandatory_binary("openocd")
if self.args.programmer == "pyocd":
assert_mandatory_binary("pyocd")
assert_python_library("intelhex")
if not SUPPORTED_BOARDS[self.args.board].pyocd_target:
fatal("This board doesn't seem to support flashing through pyocd.")
if self.args.programmer == "nordicdfu":
assert_mandatory_binary("nrfutil")
assert_python_library("intelhex")
assert_python_library("nordicsemi.lister")
2020-03-13 15:26:54 +01:00
nrfutil_version = __import__("nordicsemi.version").version.NRFUTIL_VERSION
2020-03-13 11:48:08 +01:00
if not nrfutil_version.startswith("6."):
fatal(("You need to install nrfutil python3 package v6.0 or above. "
"Found: {}".format(nrfutil_version)))
2020-03-11 16:51:26 +01:00
if not SUPPORTED_BOARDS[self.args.board].nordic_dfu:
fatal("This board doesn't support flashing over DFU.")
if self.args.programmer == "none":
assert_python_library("intelhex")
2020-02-20 11:28:49 +01:00
def run(self):
2020-03-11 16:51:26 +01:00
self.check_prerequisites()
2020-02-20 11:28:49 +01:00
self.update_rustc_if_needed()
2020-05-30 12:05:50 +02:00
if not (self.args.tockos or self.args.application or
self.args.clear_storage):
2020-03-11 18:31:09 +01:00
info("Nothing to do.")
return 0
2020-03-11 16:51:26 +01:00
# Compile what needs to be compiled
if self.args.tockos:
self.build_tockos()
if self.args.application == "ctap2":
self.generate_crypto_materials(self.args.regenerate_keys)
self.build_opensk()
elif self.args.application is None:
info("No application selected.")
2020-03-11 16:51:26 +01:00
else:
self.build_example()
2020-05-30 12:05:50 +02:00
# Erase persistent storage
if self.args.clear_storage:
self.clear_storage()
2020-03-11 16:51:26 +01:00
# Flashing
board_props = SUPPORTED_BOARDS[self.args.board]
if self.args.programmer in ("jlink", "openocd"):
# We rely on Tockloader to do the job
2020-02-20 11:28:49 +01:00
if self.args.clear_apps:
self.clear_apps()
2020-03-11 16:51:26 +01:00
if self.args.tockos:
# Install Tock OS
self.install_tock_os()
# Install padding and application if needed
if self.args.application:
self.install_padding()
self.install_tab_file("target/tab/{}.tab".format(self.args.application))
if self.verify_flashed_app(self.args.application):
info("You're all set!")
return 0
error(
("It seems that something went wrong. App/example not found "
2020-03-11 16:51:26 +01:00
"on your board. Ensure the connections between the programmer and "
"the board are correct."))
return 1
return 0
2020-03-11 16:51:26 +01:00
if self.args.programmer in ("pyocd", "nordicdfu", "none"):
dest_file = "target/{}_merged.hex".format(self.args.board)
2020-03-12 13:35:43 +01:00
os.makedirs("target", exist_ok=True)
2020-03-11 16:51:26 +01:00
self.create_hex_file(dest_file)
if self.args.programmer == "pyocd":
info("Flashing HEX file")
self.checked_command([
2020-03-11 16:51:26 +01:00
"pyocd", "flash", "--target={}".format(board_props.pyocd_target),
"--format=hex", "--erase=auto", dest_file
])
if self.args.programmer == "nordicdfu":
info("Creating DFU package")
dfu_pkg_file = "target/{}_dfu.zip".format(self.args.board)
self.checked_command([
2020-03-11 16:51:26 +01:00
"nrfutil", "pkg", "generate", "--hw-version=52", "--sd-req=0",
"--application-version=1", "--application={}".format(dest_file),
dfu_pkg_file
])
info(
"Please insert the dongle and switch it to DFU mode by keeping the "
"button pressed while inserting...")
info("Press [ENTER] when ready.")
_ = input()
# Search for the DFU devices
serial_number = []
# pylint: disable=g-import-not-at-top,import-outside-toplevel
from nordicsemi.lister import device_lister
for device in device_lister.DeviceLister().enumerate():
if device.vendor_id == "1915" and device.product_id == "521F":
serial_number.append(device.serial_number)
if not serial_number:
fatal("Couldn't find any DFU device on your system.")
if len(serial_number) > 1:
fatal("Multiple DFU devices are detected. Please only connect one.")
# Run the command without capturing stdout so that we show progress
info("Flashing device using DFU...")
return subprocess.run(
[
"nrfutil", "dfu", "usb-serial",
"--package={}".format(dfu_pkg_file),
"--serial-number={}".format(serial_number[0])
],
check=False,
timeout=None,
).returncode
2020-12-10 16:17:09 +01:00
# Configure OpenSK through vendor specific command if needed
if any([
self.args.lock_device,
self.args.config_cert,
self.args.config_pkey,
]):
# pylint: disable=g-import-not-at-top,import-outside-toplevel
import tools.configure
tools.configure.main(
argparse.Namespace(
batch=False,
certificate=self.args.config_cert,
priv_key=self.args.config_pkey,
lock=self.args.lock_device,
))
2020-02-21 17:41:40 +01:00
return 0
2020-02-19 11:34:43 +01:00
def main(args):
2020-08-12 11:20:37 +02:00
colorama.init()
2020-02-20 11:28:49 +01:00
# Make sure the current working directory is the right one before running
os.chdir(os.path.realpath(os.path.dirname(__file__)))
2020-02-19 11:50:52 +01:00
2020-08-12 11:20:37 +02:00
if args.listing == "boards":
print(os.linesep.join(get_supported_boards()))
return 0
if args.listing == "programmers":
print(os.linesep.join(PROGRAMMERS))
return 0
if args.listing:
# Missing check?
fatal("Listing {} is not implemented.".format(args.listing))
2020-02-20 11:28:49 +01:00
OpenSKInstaller(args).run()
2020-02-19 11:34:43 +01:00
2020-03-03 14:56:11 +01:00
if __name__ == "__main__":
2020-03-11 16:51:26 +01:00
main_parser = argparse.ArgumentParser()
action_group = main_parser.add_mutually_exclusive_group(required=True)
action_group.add_argument(
"--list",
metavar="WHAT",
choices=("boards", "programmers"),
default=None,
dest="listing",
2020-04-28 17:57:10 +02:00
help="List supported boards or programmers, 1 per line and then exit.",
2020-03-11 16:51:26 +01:00
)
action_group.add_argument(
"--board",
metavar="BOARD_NAME",
dest="board",
default=None,
choices=get_supported_boards(),
help="Indicates which board Tock OS will be compiled for.",
)
main_parser.add_argument(
2020-02-20 11:28:49 +01:00
"--dont-clear-apps",
action="store_false",
default=True,
dest="clear_apps",
help=("When installing an application, previously installed "
"applications won't be erased from the board."),
)
2020-05-30 12:05:50 +02:00
main_parser.add_argument(
"--clear-storage",
action="store_true",
default=False,
dest="clear_storage",
help=("Erases the persistent storage when installing an application. "
"All stored data will be permanently lost."),
)
2020-12-10 16:17:09 +01:00
main_parser.add_argument(
"--lock-device",
action="store_true",
default=False,
dest="lock_device",
help=("Try to disable JTAG at the end of the operations. This "
"operation may fail if the device is already locked or if "
"the certificate/private key are not programmed."),
)
main_parser.add_argument(
"--inject-certificate",
default=None,
metavar="PEM_FILE",
type=argparse.FileType("rb"),
dest="config_cert",
help=("If this option is set, the corresponding certificate "
"will be programmed into the key as the last operation."),
)
main_parser.add_argument(
"--inject-private-key",
default=None,
metavar="PEM_FILE",
type=argparse.FileType("rb"),
dest="config_pkey",
help=("If this option is set, the corresponding private key "
"will be programmed into the key as the last operation."),
)
2020-03-11 16:51:26 +01:00
main_parser.add_argument(
"--programmer",
metavar="METHOD",
dest="programmer",
choices=PROGRAMMERS,
default="jlink",
help=("Sets the method to be used to flash Tock OS or the application "
"on the target board."),
2020-02-20 11:28:49 +01:00
)
2020-02-19 11:34:43 +01:00
2020-03-11 16:51:26 +01:00
main_parser.add_argument(
"--no-tockos",
action="store_false",
default=True,
dest="tockos",
help=("Only compiles and flash the application/example. "
"Otherwise TockOS will also be bundled and flashed."),
)
main_parser.add_argument(
"--verbose-build",
action="store_true",
default=False,
dest="verbose_build",
2020-04-28 17:57:10 +02:00
help="Build everything in verbose mode.",
)
2020-03-11 16:51:26 +01:00
main_parser.add_argument(
2020-02-20 11:28:49 +01:00
"--panic-console",
action="append_const",
const="panic_console",
dest="features",
help=("In case of application panic, the console will be used to "
2020-02-19 11:34:43 +01:00
"output messages before starting blinking the LEDs on the "
2020-02-20 11:28:49 +01:00
"board."),
)
main_parser.add_argument(
"--debug",
action="append_const",
const="debug_ctap",
dest="features",
help=("Compiles and installs the OpenSK application in debug mode "
"(i.e. more debug messages will be sent over the console port "
"such as hexdumps of packets)."),
)
2020-03-11 16:51:26 +01:00
main_parser.add_argument(
"--debug-allocations",
action="append_const",
const="debug_allocations",
dest="features",
help=("The console will be used to output allocator statistics every "
2020-03-02 16:09:33 +01:00
"time an allocation/deallocation happens."),
)
main_parser.add_argument(
"--verbose",
action="append_const",
const="verbose",
dest="features",
help=("The console will be used to output verbose information about the "
"OpenSK application. This also automatically activates --debug."),
)
2020-03-11 16:51:26 +01:00
main_parser.add_argument(
2020-02-20 11:28:49 +01:00
"--no-u2f",
action=RemoveConstAction,
const="with_ctap1",
dest="features",
help=("Compiles the OpenSK application without backward compatible "
"support for U2F/CTAP1 protocol."),
)
2020-10-23 19:25:36 +02:00
main_parser.add_argument(
"--nfc",
action="append_const",
const="with_nfc",
dest="features",
help=("Compiles the OpenSK application with support for nfc."),
)
2020-03-11 16:51:26 +01:00
main_parser.add_argument(
2020-02-20 11:28:49 +01:00
"--regen-keys",
action="store_true",
default=False,
dest="regenerate_keys",
help=("Forces the generation of files (certificates and private keys) "
2020-02-19 11:34:43 +01:00
"under the crypto_data/ directory. "
"This is useful to allow flashing multiple OpenSK authenticators "
2020-02-20 11:28:49 +01:00
"in a row without them being considered clones."),
)
2020-03-11 16:51:26 +01:00
main_parser.add_argument(
"--elf2tab-output",
metavar="FILE",
type=argparse.FileType("a"),
dest="elf2tab_output",
default=None,
help=("When set, the output of elf2tab is appended to this file."),
)
2021-06-09 22:52:52 +02:00
main_parser.set_defaults(features=["with_ctap1"])
2020-08-12 11:20:37 +02:00
# Start parsing to know if we're going to list things or not.
partial_args, _ = main_parser.parse_known_args()
# We only need the apps_group if we have a board set
apps_group = main_parser.add_mutually_exclusive_group(
required=(partial_args.board is not None))
apps_group.add_argument(
"--no-app",
dest="application",
action="store_const",
const=None,
help=("Doesn't compile nor install any application. Useful when you only "
"want to update Tock OS kernel."))
2020-02-21 17:41:40 +01:00
apps_group.add_argument(
2020-02-20 11:28:49 +01:00
"--opensk",
dest="application",
action="store_const",
const="ctap2",
help="Compiles and installs the OpenSK application.")
2020-02-21 17:41:40 +01:00
apps_group.add_argument(
2020-02-20 11:28:49 +01:00
"--crypto_bench",
dest="application",
action="store_const",
const="crypto_bench",
2020-07-10 10:41:55 +02:00
help=("Compiles and installs the crypto_bench example that benchmarks "
2020-02-20 11:28:49 +01:00
"the performance of the cryptographic algorithms on the board."))
2020-12-10 13:31:25 +01:00
apps_group.add_argument(
"--store_latency",
dest="application",
action="store_const",
const="store_latency",
2021-01-06 19:24:56 +01:00
help=("Compiles and installs the store_latency example which prints "
2020-12-22 15:33:14 +01:00
"latency statistics of the persistent store library."))
apps_group.add_argument(
"--erase_storage",
dest="application",
action="store_const",
const="erase_storage",
help=("Compiles and installs the erase_storage example which erases "
"the storage. During operation the dongle red light is on. Once "
"the operation is completed the dongle green light is on."))
2020-07-10 10:41:55 +02:00
apps_group.add_argument(
"--panic_test",
dest="application",
action="store_const",
const="panic_test",
help=("Compiles and installs the panic_test example that immediately "
"triggers a panic."))
apps_group.add_argument(
"--oom_test",
dest="application",
action="store_const",
const="oom_test",
help=("Compiles and installs the oom_test example that tests the "
"allocator until an out-of-memory error occurs."))
apps_group.add_argument(
"--console_test",
dest="application",
action="store_const",
const="console_test",
help=("Compiles and installs the console_test example that tests the "
"console driver with messages of various lengths."))
2020-11-02 11:13:58 +02:00
apps_group.add_argument(
"--nfct_test",
dest="application",
action="store_const",
const="nfct_test",
help=("Compiles and installs the nfct_test example that tests the "
"NFC driver."))
2020-02-19 11:34:43 +01:00
2020-02-21 17:41:40 +01:00
main(main_parser.parse_args())