diff --git a/Makefile b/Makefile index 0a186a96c..d2f04889f 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ SEGGER_DIR ?= /opt/segger BUILD_CONFIG ?= Debug BUILD_TARGET ?= dotbot-v1 PROJECT_FILE ?= $(BUILD_TARGET).emProject +BOOTLOADER ?= bootloader ifeq (nrf5340dk-app,$(BUILD_TARGET)) PROJECTS ?= \ @@ -56,30 +57,30 @@ else ifeq (nrf5340dk-net,$(BUILD_TARGET)) 03app_log_dump \ 03app_nrf5340_net \ # + # Bootloader not supported on nrf5340 network core + BOOTLOADER := else PROJECTS ?= $(shell find projects/ -maxdepth 1 -mindepth 1 -type d | tr -d "/" | sed -e s/projects// | sort) endif -TESTBED_APPS ?= $(shell find testbed/ -maxdepth 1 -mindepth 1 -type d | tr -d "/" | sed -e s/testbed// | sort) +OTAP_APPS ?= $(shell find otap/ -maxdepth 1 -mindepth 1 -type d | tr -d "/" | sed -e s/otap// | sort) +OTAP_APPS := $(filter-out bootloader,$(OTAP_APPS)) # remove incompatible apps (nrf5340, sailbot gateway) for dotbot (v1, v2) builds ifneq (,$(filter dotbot-v1,$(BUILD_TARGET))) PROJECTS := $(filter-out 01bsp_qdec 01drv_lis3mdl 01drv_move 03app_dotbot_gateway 03app_dotbot_gateway_lr 03app_sailbot 03app_nrf5340_%,$(PROJECTS)) ARTIFACT_PROJECTS := 03app_dotbot - TESTBED_APPS := $(filter-out bootloader partition0 partition1,$(TESTBED_APPS)) endif ifneq (,$(filter dotbot-v2,$(BUILD_TARGET))) PROJECTS := $(filter-out 03app_dotbot_gateway 03app_dotbot_gateway_lr 03app_sailbot 03app_nrf5340_net,$(PROJECTS)) ARTIFACT_PROJECTS := 03app_dotbot - TESTBED_APPS := $(filter-out bootloader partition0 partition1,$(TESTBED_APPS)) endif # remove incompatible apps (nrf5340, dotbot, gateway) for sailbot-v1 build ifeq (sailbot-v1,$(BUILD_TARGET)) PROJECTS := $(filter-out 01bsp_qdec 01drv_lis3mdl 01drv_move 03app_dotbot_gateway 03app_dotbot_gateway_lr 03app_dotbot 03app_nrf5340_%,$(PROJECTS)) ARTIFACT_PROJECTS := 03app_sailbot - TESTBED_APPS := $(filter-out bootloader partition0 partition1,$(TESTBED_APPS)) endif # remove incompatible apps (nrf5340) for nrf52833dk/nrf52840dk build @@ -96,7 +97,7 @@ ifneq (,$(filter nrf5340dk-net,$(BUILD_TARGET))) ARTIFACT_PROJECTS := 03app_nrf5340_net endif -SRCS ?= $(shell find bsp/ -name "*.[c|h]") $(shell find crypto/ -name "*.[c|h]") $(shell find drv/ -name "*.[c|h]") $(shell find projects/ -name "*.[c|h]") $(shell find testbed/ -name "*.[c|h]") +SRCS ?= $(shell find bsp/ -name "*.[c|h]") $(shell find crypto/ -name "*.[c|h]") $(shell find drv/ -name "*.[c|h]") $(shell find projects/ -name "*.[c|h]") $(shell find otap/ -name "*.[c|h]") CLANG_FORMAT ?= clang-format CLANG_FORMAT_TYPE ?= file @@ -107,16 +108,21 @@ ARTIFACTS = $(ARTIFACT_ELF) $(ARTIFACT_HEX) .PHONY: $(PROJECTS) $(ARTIFACT_PROJECTS) artifacts docker docker-release format check-format -all: $(PROJECTS) $(TESTBED_APPS) +all: $(PROJECTS) $(OTAP_APPS) $(BOOTLOADER) $(PROJECTS): @echo "\e[1mBuilding project $@\e[0m" "$(SEGGER_DIR)/bin/emBuild" $(PROJECT_FILE) -project $@ -config $(BUILD_CONFIG) $(PACKAGES_DIR_OPT) -rebuild -verbose @echo "\e[1mDone\e[0m\n" -$(TESTBED_APPS): - @echo "\e[1mBuilding testbed application $@\e[0m" - "$(SEGGER_DIR)/bin/emBuild" $(PROJECT_FILE) -project $@ -config Release $(PACKAGES_DIR_OPT) -rebuild -verbose +$(OTAP_APPS): + @echo "\e[1mBuilding otap application $@\e[0m" + "$(SEGGER_DIR)/bin/emBuild" $(PROJECT_FILE) -project $@ -config $(BUILD_CONFIG) $(PACKAGES_DIR_OPT) -rebuild -verbose + @echo "\e[1mDone\e[0m\n" + +$(BOOTLOADER): + @echo "\e[1mBuilding bootloader application $@\e[0m" + "$(SEGGER_DIR)/bin/emBuild" otap/$(BUILD_TARGET)-bootloader.emProject -project $@ -config Release $(PACKAGES_DIR_OPT) -rebuild -verbose @echo "\e[1mDone\e[0m\n" list-projects: diff --git a/bsp/nrf/nvmc.c b/bsp/nrf/nvmc.c index 0894c9eed..3da4349bf 100644 --- a/bsp/nrf/nvmc.c +++ b/bsp/nrf/nvmc.c @@ -53,7 +53,7 @@ void db_nvmc_write(const uint32_t *addr, const void *data, size_t len) { // Length must be a multiple of 4 bytes assert(len % 4 == 0); // writes must be 4 bytes aligned - assert(!((uint32_t)addr % 4) && !((uint32_t)data % 4)); + assert(((uint32_t)addr % 4) && ((uint32_t)data % 4)); uint32_t *dest_addr = (uint32_t *)addr; const uint32_t *data_addr = data; diff --git a/dist/scripts/otap/.gitignore b/dist/scripts/otap/.gitignore new file mode 100644 index 000000000..ad4b63c2e --- /dev/null +++ b/dist/scripts/otap/.gitignore @@ -0,0 +1,2 @@ +private_key +public_key.h diff --git a/dist/scripts/testbed/dotbot-flash.py b/dist/scripts/otap/dotbot-flash.py similarity index 72% rename from dist/scripts/testbed/dotbot-flash.py rename to dist/scripts/otap/dotbot-flash.py index d9ccfb7da..300d9ffe0 100755 --- a/dist/scripts/testbed/dotbot-flash.py +++ b/dist/scripts/otap/dotbot-flash.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import os import logging import time @@ -8,10 +9,13 @@ from enum import Enum import click -from tqdm import tqdm - import serial import structlog + +from tqdm import tqdm +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + from dotbot.hdlc import hdlc_encode, HDLCHandler, HDLCState from dotbot.protocol import PROTOCOL_VERSION from dotbot.serial_interface import SerialInterface, SerialInterfaceException @@ -25,14 +29,16 @@ "nrf52840": 4096, "nrf5340-app": 4096, } - +PRIVATE_KEY_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "private_key") class MessageType(Enum): """Types of bootloader message.""" - OTA_MESSAGE_TYPE_FW = 0 - OTA_MESSAGE_TYPE_FW_ACK = 1 - OTA_MESSAGE_TYPE_INFO = 2 + OTA_MESSAGE_TYPE_START = 0 + OTA_MESSAGE_TYPE_START_ACK = 1 + OTA_MESSAGE_TYPE_FW = 2 + OTA_MESSAGE_TYPE_FW_ACK = 3 + OTA_MESSAGE_TYPE_INFO = 4 class CpuType(Enum): @@ -109,6 +115,7 @@ def __init__(self, port, baudrate, image): self.hdlc_handler = HDLCHandler() self.device_info = None self.device_info_received = False + self.start_ack_received = False pad_length = CHUNK_SIZE - (len(image) % CHUNK_SIZE) self.image = image + bytearray(b"\xff") * (pad_length + 1) self.last_acked_chunk = -1 @@ -121,21 +128,60 @@ def on_byte_received(self, byte): payload = self.hdlc_handler.payload if not payload: return - if payload[0] == MessageType.OTA_MESSAGE_TYPE_FW_ACK.value: + if payload[0] == MessageType.OTA_MESSAGE_TYPE_START_ACK.value: + self.start_ack_received = True + elif payload[0] == MessageType.OTA_MESSAGE_TYPE_FW_ACK.value: self.last_acked_chunk = int.from_bytes(payload[1:5], byteorder="little") elif payload[0] == MessageType.OTA_MESSAGE_TYPE_INFO.value: self.device_info = DeviceInfo.from_bytes(payload[1:]) self.device_info_received = True def fetch_device_info(self): - while self.device_info_received is False: + # while self.device_info_received is False: + buffer = bytearray() + buffer += int(MessageType.OTA_MESSAGE_TYPE_INFO.value).to_bytes( + length=1, byteorder="little" + ) + print("Fetching device info...") + self.serial.write(hdlc_encode(buffer)) + timeout = 0 # ms + while self.device_info_received is False and timeout < 100: + timeout += 1 + time.sleep(0.01) + + def send_start_update(self, secure): + if secure is True: + digest = hashes.Hash(hashes.SHA256()) + pos = 0 + while pos + CHUNK_SIZE <= len(self.image) + 1: + digest.update(self.image[pos : pos + CHUNK_SIZE]) + pos += CHUNK_SIZE + fw_hash = digest.finalize() + private_key_bytes = open(PRIVATE_KEY_PATH, "rb").read() + private_key = Ed25519PrivateKey.from_private_bytes(private_key_bytes) + attempts = 0 + while attempts < 3: buffer = bytearray() - buffer += int(MessageType.OTA_MESSAGE_TYPE_INFO.value).to_bytes( + buffer += int(MessageType.OTA_MESSAGE_TYPE_START.value).to_bytes( length=1, byteorder="little" ) + buffer += int((len(self.image) - 1) / CHUNK_SIZE).to_bytes( + length=4, byteorder="little" + ) + if secure is True: + buffer += fw_hash + signature = private_key.sign(bytes(buffer[1:])) + buffer += signature + print("Sending start update notification...") self.serial.write(hdlc_encode(buffer)) - time.sleep(0.2) - print("Fetching device info...") + attempts += 1 + timeout = 0 # ms + while self.start_ack_received is False and timeout < 1000: + timeout += 1 + time.sleep(0.01) + if self.start_ack_received is True: + break + return attempts < 3 def flash(self): page_size = PAGE_SIZE_MAP[self.device_info.cpu] @@ -172,6 +218,12 @@ def flash(self): default="/dev/ttyACM0", help="Serial port to use to send the firmware.", ) +@click.option( + "-s", + "--secure", + is_flag=True, + help="Use cryptographic security (hash and signature).", +) @click.option( "-y", "--yes", @@ -179,7 +231,7 @@ def flash(self): help="Continue flashing without prompt.", ) @click.argument("image", type=click.File(mode="rb", lazy=True)) -def main(port, yes, image): +def main(port, secure, yes, image): # Disable logging configure in PyDotBot structlog.configure( wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL), @@ -216,6 +268,10 @@ def main(port, yes, image): return if yes is False: click.confirm("Do you want to continue?", default=True, abort=True) + ret = flasher.send_start_update(secure) + if ret is False: + print("Error: No start acknowledment received. Aborting.") + return flasher.flash() print("Done") diff --git a/dist/scripts/otap/generate_keys.py b/dist/scripts/otap/generate_keys.py new file mode 100755 index 000000000..124e8c6e0 --- /dev/null +++ b/dist/scripts/otap/generate_keys.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import os + +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from cryptography.hazmat.primitives.serialization import ( + Encoding, + PrivateFormat, + PublicFormat, + NoEncryption, +) + +PRIVATE_KEY_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "private_key") +PUBLIC_KEY_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../../drv/ota/public_key.h") + +HEADER_FORMAT = """/* + * PLEASE DON'T EDIT + * + * This file was automatically generated + */ + +#ifndef __{name_upper}_H +#define __{name_upper}_H + +#include + +const uint8_t {name}[] = {{ + {data} +}}; + +#endif /* __{name_upper}_H */ +""" + + +def save_as_c_array(path, name, data): + data_str = ", ".join([f"0x{data[i:i+2]}" for i in range(0, len(data), 2)]) + print(f"Saving '{name}' to {os.path.abspath(path)}") + with open(path, "w") as f: + f.write(HEADER_FORMAT.format(name=name, name_upper=name.upper(), data=data_str)) + + +def main(): + key_pair = Ed25519PrivateKey.generate() + private_key = key_pair.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption()) + public_key = key_pair.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw) + + print(f"Generated keys:\n - private key\t: {private_key.hex()}\n - public key\t: {public_key.hex()}") + print(f"Saving 'private key' to {os.path.abspath(PRIVATE_KEY_PATH)}") + with open(PRIVATE_KEY_PATH, "wb") as f: + f.write(private_key) + save_as_c_array(PUBLIC_KEY_PATH, "public_key", public_key.hex()) + +if __name__ == "__main__": + main() diff --git a/dist/scripts/testbed/requirements.txt b/dist/scripts/otap/requirements.txt similarity index 62% rename from dist/scripts/testbed/requirements.txt rename to dist/scripts/otap/requirements.txt index f6c4839a3..df144dc15 100644 --- a/dist/scripts/testbed/requirements.txt +++ b/dist/scripts/otap/requirements.txt @@ -1,3 +1,4 @@ click==8.1.7 +cryptography==41.0.5 tqdm==4.66.1 pydotbot diff --git a/doc/doxygen/Doxyfile b/doc/doxygen/Doxyfile index a21d03e0d..fd1fbe883 100644 --- a/doc/doxygen/Doxyfile +++ b/doc/doxygen/Doxyfile @@ -126,8 +126,8 @@ INPUT = \ ../../bsp \ ../../crypto \ ../../drv \ + ../../otap \ ../../projects \ - ../../testbed \ # INPUT_ENCODING = UTF-8 FILE_PATTERNS = \ diff --git a/doc/sphinx/index.md b/doc/sphinx/index.md index 9c21018d2..2e5c51a48 100644 --- a/doc/sphinx/index.md +++ b/doc/sphinx/index.md @@ -4,7 +4,7 @@ getting_started applications examples -testbed +otap api ``` diff --git a/doc/sphinx/testbed.md b/doc/sphinx/otap.md similarity index 53% rename from doc/sphinx/testbed.md rename to doc/sphinx/otap.md index c94635ac5..f65979b73 100644 --- a/doc/sphinx/testbed.md +++ b/doc/sphinx/otap.md @@ -1,4 +1,4 @@ -```{include} ../../testbed/README.md +```{include} ../../otap/README.md :relative-images: :relative-docs: ../ ``` diff --git a/dotbot-v1.emProject b/dotbot-v1.emProject index f212614f1..94a3dd7e0 100644 --- a/dotbot-v1.emProject +++ b/dotbot-v1.emProject @@ -26,7 +26,7 @@ build_output_file_name="$(OutDir)/$(ProjectName)-$(BuildTarget)$(EXE)" build_treat_warnings_as_errors="Yes" c_additional_options="-Wno-missing-field-initializers" - c_preprocessor_definitions="ARM_MATH_CM4;NRF52833_XXAA;__nRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_DOTBOT_V1" + c_preprocessor_definitions="ARM_MATH_CM4;NRF52833_XXAA;__nRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_DOTBOT_V1;OTA_USE_CRYPTO" c_user_include_directories="$(SolutionDir)/../bsp;$(SolutionDir)/../crypto;$(SolutionDir)/../drv;$(PackagesDir)/nRF/Device/Include;$(PackagesDir)/CMSIS_5/CMSIS/Core/Include" debug_register_definition_file="$(PackagesDir)/nRF/XML/nrf52833_Registers.xml" debug_stack_pointer_start="__stack_end__" @@ -61,4 +61,5 @@ + diff --git a/dotbot-v2.emProject b/dotbot-v2.emProject index 0a1a7cad9..2985fc63e 100644 --- a/dotbot-v2.emProject +++ b/dotbot-v2.emProject @@ -23,7 +23,7 @@ build_output_file_name="$(OutDir)/$(ProjectName)-$(BuildTarget)$(EXE)" build_treat_warnings_as_errors="Yes" c_additional_options="-Wno-strict-prototypes" - c_preprocessor_definitions="ARM_MATH_ARMV8MML;NRF5340_XXAA;NRF_APPLICATION;__NRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_DOTBOT_V2" + c_preprocessor_definitions="ARM_MATH_ARMV8MML;NRF5340_XXAA;NRF_APPLICATION;__NRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_DOTBOT_V2;OTA_USE_CRYPTO" c_user_include_directories="$(SolutionDir)/../bsp;$(SolutionDir)/../crypto;$(SolutionDir)/../drv;$(PackagesDir)/nRF/Device/Include;$(PackagesDir)/CMSIS_5/CMSIS/Core/Include" debug_register_definition_file="$(PackagesDir)/nRF/XML/nrf5340_application_Registers.xml" debug_stack_pointer_start="__stack_end__" @@ -62,4 +62,5 @@ + diff --git a/drv/drv.emProject b/drv/drv.emProject index 7a9c3c483..885ee62a9 100644 --- a/drv/drv.emProject +++ b/drv/drv.emProject @@ -88,7 +88,7 @@ diff --git a/drv/ota.h b/drv/ota.h index 012ab4b2e..92ab7f4b4 100644 --- a/drv/ota.h +++ b/drv/ota.h @@ -18,7 +18,9 @@ //=========================== defines ========================================== -#define DB_OTA_CHUNK_SIZE (128U) ///< Size of a firmware chunk +#define DB_OTA_CHUNK_SIZE (128U) ///< Size of a firmware chunk +#define DB_OTA_SHA256_LENGTH (32U) +#define DB_OTA_SIGNATURE_LENGTH (64U) typedef void (*db_ota_reply_t)(const uint8_t *, size_t); ///< Transport agnostic function used to reply to the flasher script @@ -34,6 +36,15 @@ typedef struct { db_ota_mode_t mode; ///< Firmware update mode } db_ota_conf_t; +///< Firmware update start notification packet +typedef struct __attribute__((packed, aligned(4))) { + uint32_t chunk_count; ///< Number of chunks +#if defined(OTA_USE_CRYPTO) + uint8_t hash[DB_OTA_SHA256_LENGTH]; ///< SHA256 hash of the firmware + uint8_t signature[DB_OTA_SIGNATURE_LENGTH]; ///< Signature of the firmware hash +#endif +} db_ota_start_notification_t; + ///< Firmware update packet typedef struct __attribute__((packed, aligned(4))) { uint32_t index; ///< Index of the chunk @@ -51,6 +62,8 @@ typedef enum { ///< Message type typedef enum { + DB_OTA_MESSAGE_TYPE_START, + DB_OTA_MESSAGE_TYPE_START_ACK, DB_OTA_MESSAGE_TYPE_FW, DB_OTA_MESSAGE_TYPE_FW_ACK, DB_OTA_MESSAGE_TYPE_INFO, diff --git a/drv/ota/ota.c b/drv/ota/ota.c index 83ae55907..8e7913bc8 100644 --- a/drv/ota/ota.c +++ b/drv/ota/ota.c @@ -17,6 +17,12 @@ #include "ota.h" #include "partition.h" +#if defined(OTA_USE_CRYPTO) +#include "ed25519.h" +#include "sha256.h" +#include "public_key.h" +#endif + //=========================== defines ========================================== typedef struct { @@ -27,6 +33,7 @@ typedef struct { uint32_t target_partition; uint32_t addr; uint32_t last_index_acked; + uint8_t hash[DB_OTA_SHA256_LENGTH]; } db_ota_vars_t; //=========================== variables ======================================== @@ -62,7 +69,15 @@ void db_ota_start(void) { void db_ota_finish(void) { // Switch active image in partition table before resetting the device - // TODO: do more verifications (CRC, etc) before rebooting +#if defined(OTA_USE_CRYPTO) + uint8_t hash_result[DB_OTA_SHA256_LENGTH] = { 0 }; + crypto_sha256(hash_result); + + if (memcmp(hash_result, _ota_vars.hash, DB_OTA_SHA256_LENGTH) != 0) { + return; + } +#endif + if (_ota_vars.table.active_image != _ota_vars.target_partition) { _ota_vars.table.active_image = _ota_vars.target_partition; db_write_partitions_table(&_ota_vars.table); @@ -92,18 +107,36 @@ void db_ota_handle_message(const uint8_t *message) { memcpy(&_ota_vars.reply_buffer[1], &message_info, sizeof(db_ota_message_info_t)); _ota_vars.config->reply(_ota_vars.reply_buffer, sizeof(db_ota_message_type_t) + sizeof(db_ota_message_info_t)); } break; + case DB_OTA_MESSAGE_TYPE_START: + { + const db_ota_start_notification_t *ota_start = (const db_ota_start_notification_t *)&message[1]; +#if defined(OTA_USE_CRYPTO) + const uint8_t *hash = ota_start->hash; + if (!crypto_ed25519_verify(ota_start->signature, DB_OTA_SIGNATURE_LENGTH, (const uint8_t *)ota_start, sizeof(db_ota_start_notification_t) - DB_OTA_SIGNATURE_LENGTH, public_key)) { + break; + } + memcpy(_ota_vars.hash, hash, DB_OTA_SHA256_LENGTH); + crypto_sha256_init(); +#else + (void)ota_start; +#endif + db_ota_start(); + // Acknowledge the update start + _ota_vars.reply_buffer[0] = DB_OTA_MESSAGE_TYPE_START_ACK; + _ota_vars.config->reply(_ota_vars.reply_buffer, sizeof(db_ota_message_type_t)); + } break; case DB_OTA_MESSAGE_TYPE_FW: { const db_ota_pkt_t *ota_pkt = (const db_ota_pkt_t *)&message[1]; const uint32_t chunk_index = ota_pkt->index; const uint32_t chunk_count = ota_pkt->chunk_count; - if (chunk_index == 0) { - db_ota_start(); - } if (_ota_vars.last_index_acked != chunk_index) { // Skip writing the chunk if already acked db_ota_write_chunk(ota_pkt); +#if defined(OTA_USE_CRYPTO) + crypto_sha256_update((const uint8_t *)ota_pkt->fw_chunk, DB_OTA_CHUNK_SIZE); +#endif } // Acknowledge the received chunk diff --git a/drv/ota/public_key.h b/drv/ota/public_key.h new file mode 100644 index 000000000..572662b71 --- /dev/null +++ b/drv/ota/public_key.h @@ -0,0 +1,16 @@ +/* + * PLEASE DON'T EDIT + * + * This file was automatically generated + */ + +#ifndef __PUBLIC_KEY_H +#define __PUBLIC_KEY_H + +#include + +const uint8_t public_key[] = { + 0xe7, 0xb6, 0x65, 0xb0, 0x97, 0xad, 0xde, 0x27, 0xd8, 0x6b, 0xb1, 0x7b, 0x23, 0x00, 0x9f, 0x22, 0x96, 0x40, 0x7a, 0x25, 0x46, 0x69, 0x7d, 0x1b, 0x0c, 0x54, 0x3e, 0x27, 0xbb, 0xe9, 0x8e, 0xa0 +}; + +#endif /* __PUBLIC_KEY_H */ diff --git a/nrf52833dk.emProject b/nrf52833dk.emProject index 885268122..0a165f6fc 100644 --- a/nrf52833dk.emProject +++ b/nrf52833dk.emProject @@ -26,7 +26,7 @@ build_output_file_name="$(OutDir)/$(ProjectName)-$(BuildTarget)$(EXE)" build_treat_warnings_as_errors="Yes" c_additional_options="-Wno-missing-field-initializers" - c_preprocessor_definitions="ARM_MATH_CM4;NRF52833_XXAA;__nRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_NRF52833DK" + c_preprocessor_definitions="ARM_MATH_CM4;NRF52833_XXAA;__nRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_NRF52833DK;OTA_USE_CRYPTO" c_user_include_directories="$(SolutionDir)/../bsp;$(SolutionDir)/../crypto;$(SolutionDir)/../drv;$(PackagesDir)/nRF/Device/Include;$(PackagesDir)/CMSIS_5/CMSIS/Core/Include" debug_register_definition_file="$(PackagesDir)/nRF/XML/nrf52833_Registers.xml" debug_stack_pointer_start="__stack_end__" @@ -63,5 +63,5 @@ - + diff --git a/nrf52840dk.emProject b/nrf52840dk.emProject index b58a6ceb5..4f930c86a 100644 --- a/nrf52840dk.emProject +++ b/nrf52840dk.emProject @@ -26,7 +26,7 @@ build_output_file_name="$(OutDir)/$(ProjectName)-$(BuildTarget)$(EXE)" build_treat_warnings_as_errors="Yes" c_additional_options="-Wno-missing-field-initializers" - c_preprocessor_definitions="ARM_MATH_CM4;NRF52840_XXAA;__nRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_NRF52840DK;USE_CRYPTOCELL" + c_preprocessor_definitions="ARM_MATH_CM4;NRF52840_XXAA;__nRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_NRF52840DK;OTA_USE_CRYPTO;USE_CRYPTOCELL" c_user_include_directories="$(SolutionDir)/../bsp;$(SolutionDir)/../crypto;$(SolutionDir)/../drv;$(PackagesDir)/nRF/Device/Include;$(PackagesDir)/CMSIS_5/CMSIS/Core/Include" debug_register_definition_file="$(PackagesDir)/nRF/XML/nrf52840_Registers.xml" debug_stack_pointer_start="__stack_end__" @@ -63,5 +63,5 @@ - + diff --git a/nrf5340dk-app.emProject b/nrf5340dk-app.emProject index d532b0040..a5ab636f6 100644 --- a/nrf5340dk-app.emProject +++ b/nrf5340dk-app.emProject @@ -23,7 +23,7 @@ build_output_file_name="$(OutDir)/$(ProjectName)-$(BuildTarget)$(EXE)" build_treat_warnings_as_errors="Yes" c_additional_options="-Wno-strict-prototypes" - c_preprocessor_definitions="ARM_MATH_ARMV8MML;NRF5340_XXAA;NRF_APPLICATION;__NRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_NRF5340DK" + c_preprocessor_definitions="ARM_MATH_ARMV8MML;NRF5340_XXAA;NRF_APPLICATION;__NRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_NRF5340DK;OTA_USE_CRYPTO" c_user_include_directories="$(SolutionDir)/../bsp;$(SolutionDir)/../crypto;$(SolutionDir)/../drv;$(PackagesDir)/nRF/Device/Include;$(PackagesDir)/CMSIS_5/CMSIS/Core/Include" debug_register_definition_file="$(PackagesDir)/nRF/XML/nrf5340_application_Registers.xml" debug_stack_pointer_start="__stack_end__" @@ -64,5 +64,5 @@ - + diff --git a/nrf5340dk-net.emProject b/nrf5340dk-net.emProject index c2c4f3985..d02124546 100644 --- a/nrf5340dk-net.emProject +++ b/nrf5340dk-net.emProject @@ -59,5 +59,5 @@ - + diff --git a/testbed/README.md b/otap/README.md similarity index 86% rename from testbed/README.md rename to otap/README.md index 73a249f67..c69fc58e9 100644 --- a/testbed/README.md +++ b/otap/README.md @@ -1,7 +1,8 @@ -# Testbed support +# OTAP -This directory contains 3 applications that be used for the DotBot testbed support: -- **testbed/bootloader** contains a flexible bootloader application that +This directory contains 3 applications that be used for over-the-air +programming (OTAP) of DotBots: +- **otap/bootloader** contains a flexible bootloader application that can be used to boot an image written at a given address on the flash memory. The bootloader application must be compiled in **Release** mode to make it as small as possible so that it stays under 4kiB. By default, the bootloader reads @@ -13,19 +14,19 @@ This directory contains 3 applications that be used for the DotBot testbed suppo tries to boot the image in partition 0 the next time, - pressing button 2 will set partition 1 as active and reset the device so it tries to boot the image in partition 1 the next time, - - using the [dotbot-flash.py](../dist/scripts/testbed/dotbot-flash.py) (see below) - provided in `dist/scripts/testbed`, a new firmware + - using the [dotbot-flash.py](../dist/scripts/otap/dotbot-flash.py) (see below) + provided in `dist/scripts/otap`, a new firmware image can be sent over UART to the active partition. Once completed the device has to be reset manually so it boots on the newly flashed image. -- **testbed/partition0** +- **otap/partition0** contains a sample application that is linked so that it can be written at address 0x2000, after the bootloader and partition tables pages. This application simply blinks an LED (LED1 on nRF DKs) and also contains the logic to download 128B chunks of an image and to write them on partition 1, -- **testbed/partition1** +- **otap/partition1** contains another sample application that is linked so that it can be written at address 0x41000 on nrf52833 or 0x81000 on nrf52840/nrf5340-app, after the image on partition 0. This application also @@ -60,7 +61,7 @@ The start address of partition 0 and partition 1 applications is defined in the match the start address of each partition defined in the bootloader `main.c`. -The [dotbot-flash.py](../dist/scripts/testbed/dotbot-flash.py) Python script is also provided: +The [dotbot-flash.py](../dist/scripts/otap/dotbot-flash.py) Python script is also provided: it reads a .bin firmware file and sends to a device over UART. The device can either be: - rebooted in bootloader mode (hold button 4 pressed during reset). @@ -71,7 +72,7 @@ be: application, which is just forwarding the bytes received from UART over radio. In this case, the firmware is split in 128B chunks and reconstructed on flash by an application running a code like in - **testbed/partition0**/**testbed/partition1** + **otap/partition0**/**otap/partition1** applications. Make sure that the firmware was built with the right offset on flash, following this rule: if the active image is on partition 0, the new firmware has to be built for partition 1 and vice versa. @@ -83,5 +84,5 @@ system. To install all the Python dependencies (pydotbot, click and tqdm), run: ``` -pip install -r dist/scripts/testbed/requirements.txt +pip install -r dist/scripts/otap/requirements.txt ``` diff --git a/otap/bootloader.emProject b/otap/bootloader.emProject new file mode 100644 index 000000000..a3e389150 --- /dev/null +++ b/otap/bootloader.emProject @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testbed/bootloader/main.c b/otap/bootloader/main.c similarity index 97% rename from testbed/bootloader/main.c rename to otap/bootloader/main.c index ef9955c55..13f3e774d 100644 --- a/testbed/bootloader/main.c +++ b/otap/bootloader/main.c @@ -99,10 +99,13 @@ static void _write_partitions_table(const db_partitions_table_t *table) { //=========================== callbacks ======================================== +#ifdef DB_LED4_PIN static void _blink_led4(void) { db_gpio_toggle(&db_led4); } +#endif +#ifdef DB_BTN4_PIN static void _uart_callback(uint8_t byte) { _bootloader_vars.uart_byte = byte; _bootloader_vars.uart_byte_received = true; @@ -119,6 +122,7 @@ static const db_ota_conf_t _bootloader_ota_config = { .mode = DB_OTA_MODE_BOOTLOADER, .reply = _bootloader_reply, }; +#endif //================================= main ======================================= @@ -133,7 +137,9 @@ int main(void) { uint32_t active_image = (_bootloader_vars.table.active_image < DB_PARTITIONS_MAX_COUNT) ? _bootloader_vars.table.active_image : 0; +#ifdef DB_BTN4_PIN db_gpio_init(&db_btn4, DB_GPIO_IN_PU); + uint8_t keep_active = !db_gpio_read(&db_btn4); if (keep_active) { @@ -145,6 +151,9 @@ int main(void) { db_uart_init(0, &db_uart_rx, &db_uart_tx, DB_UART_BAUDRATE, &_uart_callback); db_ota_init(&_bootloader_ota_config); } +#else + uint8_t keep_active = 0; +#endif while (keep_active) { if (_bootloader_vars.uart_byte_received) { @@ -167,15 +176,18 @@ int main(void) { } } +#ifdef DB_BTN1_PIN if (!db_gpio_read(&db_btn1)) { _bootloader_vars.table.active_image = 0; _write_partitions_table(&_bootloader_vars.table); } - +#endif +#ifdef DB_BTN2_PIN if (!db_gpio_read(&db_btn2)) { _bootloader_vars.table.active_image = 1; _write_partitions_table(&_bootloader_vars.table); } +#endif } _jump_to_image((uint32_t *)_bootloader_vars.table.partitions[active_image].address); diff --git a/testbed/bootloader/nRF52833_xxAA_MemoryMap.xml b/otap/bootloader/nRF52833_xxAA_MemoryMap.xml similarity index 100% rename from testbed/bootloader/nRF52833_xxAA_MemoryMap.xml rename to otap/bootloader/nRF52833_xxAA_MemoryMap.xml diff --git a/testbed/bootloader/nRF52840_xxAA_MemoryMap.xml b/otap/bootloader/nRF52840_xxAA_MemoryMap.xml similarity index 100% rename from testbed/bootloader/nRF52840_xxAA_MemoryMap.xml rename to otap/bootloader/nRF52840_xxAA_MemoryMap.xml diff --git a/testbed/bootloader/nRF5340_xxAA_Application_MemoryMap.xml b/otap/bootloader/nRF5340_xxAA_Application_MemoryMap.xml similarity index 100% rename from testbed/bootloader/nRF5340_xxAA_Application_MemoryMap.xml rename to otap/bootloader/nRF5340_xxAA_Application_MemoryMap.xml diff --git a/testbed/bootloader/nRF5340_xxAA_Network_MemoryMap.xml b/otap/bootloader/nRF5340_xxAA_Network_MemoryMap.xml similarity index 100% rename from testbed/bootloader/nRF5340_xxAA_Network_MemoryMap.xml rename to otap/bootloader/nRF5340_xxAA_Network_MemoryMap.xml diff --git a/otap/dotbot-v1-bootloader.emProject b/otap/dotbot-v1-bootloader.emProject new file mode 100644 index 000000000..3bad2fe19 --- /dev/null +++ b/otap/dotbot-v1-bootloader.emProject @@ -0,0 +1,61 @@ + + + + + + + + + + diff --git a/otap/dotbot-v2-bootloader.emProject b/otap/dotbot-v2-bootloader.emProject new file mode 100644 index 000000000..5dda0f4e0 --- /dev/null +++ b/otap/dotbot-v2-bootloader.emProject @@ -0,0 +1,61 @@ + + + + + + + + + + diff --git a/otap/nrf52833dk-bootloader.emProject b/otap/nrf52833dk-bootloader.emProject new file mode 100644 index 000000000..0d168cda2 --- /dev/null +++ b/otap/nrf52833dk-bootloader.emProject @@ -0,0 +1,61 @@ + + + + + + + + + + diff --git a/otap/nrf52840dk-bootloader.emProject b/otap/nrf52840dk-bootloader.emProject new file mode 100644 index 000000000..01f1868d4 --- /dev/null +++ b/otap/nrf52840dk-bootloader.emProject @@ -0,0 +1,61 @@ + + + + + + + + + + diff --git a/otap/nrf5340dk-app-bootloader.emProject b/otap/nrf5340dk-app-bootloader.emProject new file mode 100644 index 000000000..3d3deac5d --- /dev/null +++ b/otap/nrf5340dk-app-bootloader.emProject @@ -0,0 +1,61 @@ + + + + + + + + + + diff --git a/testbed/testbed.emProject b/otap/otap.emProject similarity index 63% rename from testbed/testbed.emProject rename to otap/otap.emProject index 554f20b4f..697acf10d 100644 --- a/testbed/testbed.emProject +++ b/otap/otap.emProject @@ -1,55 +1,13 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -88,6 +46,7 @@ + + + + + + + + + diff --git a/sailbot-v1.emProject b/sailbot-v1.emProject index eec2b7ed4..1687ca010 100644 --- a/sailbot-v1.emProject +++ b/sailbot-v1.emProject @@ -26,7 +26,7 @@ build_output_file_name="$(OutDir)/$(ProjectName)-$(BuildTarget)$(EXE)" build_treat_warnings_as_errors="Yes" c_additional_options="-Wno-missing-field-initializers" - c_preprocessor_definitions="ARM_MATH_CM4;NRF52833_XXAA;__nRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_SAILBOT_V1" + c_preprocessor_definitions="ARM_MATH_CM4;NRF52833_XXAA;__nRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_SAILBOT_V1;OTA_USE_CRYPTO" c_user_include_directories="$(SolutionDir)/../bsp;$(SolutionDir)/../crypto;$(SolutionDir)/../drv;$(PackagesDir)/nRF/Device/Include;$(PackagesDir)/CMSIS_5/CMSIS/Core/Include" debug_register_definition_file="$(PackagesDir)/nRF/XML/nrf52833_Registers.xml" debug_stack_pointer_start="__stack_end__" @@ -61,4 +61,5 @@ +