From a5dc7487bedb4d56fc51733abbbcf4de2899d109 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Mon, 4 Mar 2024 12:06:49 +0100 Subject: [PATCH 01/27] drv/upgate: add initial OTA FPGA update implementation --- dist/scripts/upgate/.gitignore | 2 + dist/scripts/upgate/dotbot-upgate.py | 163 +++++++++++++++++++++++++ dist/scripts/upgate/generate_keys.py | 54 +++++++++ dist/scripts/upgate/requirements.txt | 4 + drv/drv.emProject | 9 ++ drv/upgate.h | 90 ++++++++++++++ drv/upgate/public_key.h | 16 +++ drv/upgate/upgate.c | 171 +++++++++++++++++++++++++++ nrf52833dk.emProject | 1 + nrf52840dk.emProject | 1 + upgate/application/main.c | 93 +++++++++++++++ upgate/upgate.emProject | 43 +++++++ 12 files changed, 647 insertions(+) create mode 100644 dist/scripts/upgate/.gitignore create mode 100755 dist/scripts/upgate/dotbot-upgate.py create mode 100755 dist/scripts/upgate/generate_keys.py create mode 100644 dist/scripts/upgate/requirements.txt create mode 100644 drv/upgate.h create mode 100644 drv/upgate/public_key.h create mode 100644 drv/upgate/upgate.c create mode 100644 upgate/application/main.c create mode 100644 upgate/upgate.emProject diff --git a/dist/scripts/upgate/.gitignore b/dist/scripts/upgate/.gitignore new file mode 100644 index 00000000..ad4b63c2 --- /dev/null +++ b/dist/scripts/upgate/.gitignore @@ -0,0 +1,2 @@ +private_key +public_key.h diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py new file mode 100755 index 00000000..8814b28d --- /dev/null +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python + +import os +import logging +import time + +from enum import Enum + +import click +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 + + +BAUDRATE = 1000000 +CHUNK_SIZE = 128 +PRIVATE_KEY_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "private_key") + +class MessageType(Enum): + """Types of bootloader message.""" + + UPGATE_MESSAGE_TYPE_START = 0 + UPGATE_MESSAGE_TYPE_START_ACK = 1 + UPGATE_MESSAGE_TYPE_CHUNK = 2 + UPGATE_MESSAGE_TYPE_CHUNK_ACK = 3 + + +class DotBotUpgate: + """Class used to send an FPGA bitstream.""" + + def __init__(self, port, baudrate, image): + self.serial = SerialInterface(port, baudrate, self.on_byte_received) + 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 + # Just write a single byte to fake a DotBot gateway handshake + self.serial.write(int(PROTOCOL_VERSION).to_bytes(length=1)) + + def on_byte_received(self, byte): + self.hdlc_handler.handle_byte(byte) + if self.hdlc_handler.state == HDLCState.READY: + payload = self.hdlc_handler.payload + if not payload: + return + if payload[0] == MessageType.UPGATE_MESSAGE_TYPE_START_ACK.value: + self.start_ack_received = True + elif payload[0] == MessageType.UPGATE_MESSAGE_TYPE_CHUNK_ACK.value: + self.last_acked_chunk = int.from_bytes(payload[1:5], byteorder="little") + + def init(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 + buffer = bytearray() + buffer += int(MessageType.UPGATE_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)) + timeout = 0 # ms + while self.start_ack_received is False and timeout < 60000: + timeout += 1 + time.sleep(0.01) + return self.start_ack_received is True + + def run(self): + pos = 0 + progress = tqdm( + total=len(self.image), unit="B", unit_scale=False, colour="green", ncols=100 + ) + progress.set_description(f"Flashing firmware ({int(len(self.image) / 1024)}kB)") + while pos + CHUNK_SIZE <= len(self.image) + 1: + chunk_index = int(pos / CHUNK_SIZE) + while self.last_acked_chunk != chunk_index: + buffer = bytearray() + buffer += int(MessageType.UPGATE_MESSAGE_TYPE_CHUNK.value).to_bytes( + length=1, byteorder="little" + ) + buffer += int(chunk_index).to_bytes(length=4, byteorder="little") + buffer += int((len(self.image) - 1) / CHUNK_SIZE).to_bytes( + length=4, byteorder="little" + ) + buffer += self.image[pos : pos + CHUNK_SIZE] + self.serial.write(hdlc_encode(buffer)) + time.sleep(0.005) + pos += CHUNK_SIZE + progress.update(CHUNK_SIZE) + progress.update(1) + progress.close() + + +@click.command() +@click.option( + "-p", + "--port", + default="/dev/ttyACM0", + help="Serial port to use to send the bitstream to the gateway.", +) +@click.option( + "-s", + "--secure", + is_flag=True, + help="Use cryptographic security (hash and signature).", +) +@click.option( + "-y", + "--yes", + is_flag=True, + help="Continue the upgate without prompt.", +) +@click.argument("bitstream", type=click.File(mode="rb", lazy=True)) +def main(port, secure, yes, bitstream): + # Disable logging configure in PyDotBot + structlog.configure( + wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL), + ) + try: + upgater = DotBotUpgate(port, BAUDRATE, bytearray(bitstream.read())) + except ( + SerialInterfaceException, + serial.serialutil.SerialException, + ) as exc: + print(f"Error: {exc}") + return + print(f"Image size: {len(upgater.image)}B") + print("") + if yes is False: + click.confirm("Do you want to continue?", default=True, abort=True) + ret = upgater.init(secure) + if ret is False: + print("Error: No start acknowledment received. Aborting.") + return + upgater.run() + print("Done") + + +if __name__ == "__main__": + main() diff --git a/dist/scripts/upgate/generate_keys.py b/dist/scripts/upgate/generate_keys.py new file mode 100755 index 00000000..124e8c6e --- /dev/null +++ b/dist/scripts/upgate/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/upgate/requirements.txt b/dist/scripts/upgate/requirements.txt new file mode 100644 index 00000000..df144dc1 --- /dev/null +++ b/dist/scripts/upgate/requirements.txt @@ -0,0 +1,4 @@ +click==8.1.7 +cryptography==41.0.5 +tqdm==4.66.1 +pydotbot diff --git a/drv/drv.emProject b/drv/drv.emProject index 9c3eb1d1..8af869b6 100644 --- a/drv/drv.emProject +++ b/drv/drv.emProject @@ -137,6 +137,15 @@ + + + + + + * @copyright Inria, 2024-present + * @} + */ + +#include +#include "n25q128.h" + +//=========================== defines ========================================== + +#define DB_UPGATE_CHUNK_SIZE (128U) ///< Size of an FPGA bitstream chunk +#define DB_UPGATE_SHA256_LENGTH (32U) +#define DB_UPGATE_SIGNATURE_LENGTH (64U) + +typedef void (*db_upgate_reply_t)(const uint8_t *, size_t); ///< Transport agnostic function used to reply to the flasher script + +///< FPGA bitstream update configuration +typedef struct { + db_upgate_reply_t reply; ///< Pointer to the function used to reply to the upgate script + const n25q128_conf_t *n25q128_conf; +} db_upgate_conf_t; + +///< FPGA bitstream update start notification packet +typedef struct __attribute__((packed, aligned(4))) { + uint32_t chunk_count; ///< Number of chunks +#if defined(UPGATE_USE_CRYPTO) + uint8_t hash[DB_UPGATE_SHA256_LENGTH]; ///< SHA256 hash of the bitsream + uint8_t signature[DB_UPGATE_SIGNATURE_LENGTH]; ///< Signature of the bitstream hash +#endif +} db_upgate_start_notification_t; + +///< Firmware update packet +typedef struct __attribute__((packed, aligned(4))) { + uint32_t index; ///< Index of the chunk + uint32_t chunk_count; ///< Total number of chunks + uint8_t upgate_chunk[DB_UPGATE_CHUNK_SIZE]; ///< Bytes array of the firmware chunk +} db_upgate_pkt_t; + +///< Message type +typedef enum { + DB_UPGATE_MESSAGE_TYPE_START, + DB_UPGATE_MESSAGE_TYPE_START_ACK, + DB_UPGATE_MESSAGE_TYPE_CHUNK, + DB_UPGATE_MESSAGE_TYPE_CHUNK_ACK, +} db_upgate_message_type_t; + +//=========================== prototypes ======================================= + +/** + * @brief Initialize the upgate FPGA bitstream update + * + * @param[in] config Pointer to the upgate configuration + */ +void db_upgate_init(const db_upgate_conf_t *config); + +/** + * @brief Start the upgate process + */ +void db_upgate_start(uint32_t chunk_count); + +/** + * @brief Finalize the upgate process + */ +void db_upgate_finish(void); + +/** + * @brief Write a chunk of the FPGA bistream to the external flash memory + * + * @param[in] pkt Pointer to the upgate packet + */ +void db_upgate_write_chunk(const db_upgate_pkt_t *pkt); + +/** + * @brief Handle received upgate message + * + * @param[in] message The message to handle + */ +void db_upgate_handle_message(const uint8_t *message); + +#endif diff --git a/drv/upgate/public_key.h b/drv/upgate/public_key.h new file mode 100644 index 00000000..572662b7 --- /dev/null +++ b/drv/upgate/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/drv/upgate/upgate.c b/drv/upgate/upgate.c new file mode 100644 index 00000000..58d66675 --- /dev/null +++ b/drv/upgate/upgate.c @@ -0,0 +1,171 @@ +/** + * @file + * @ingroup drv_upgate + * + * @brief nRF52833-specific definition of the "upgate" drv module. + * + * @author Alexandre Abadie + * + * @copyright Inria, 2024-present + */ +#include +#include +#include +#include +#include +#include + +#include "n25q128.h" +#include "upgate.h" + +#if defined(UPGATE_USE_CRYPTO) +#include "ed25519.h" +#include "sha256.h" +#include "public_key.h" +#endif + +//=========================== defines ========================================== + +typedef struct { + const db_upgate_conf_t *config; + uint8_t reply_buffer[UINT8_MAX]; + uint32_t target_partition; + uint32_t addr; + uint32_t last_index_acked; + uint8_t hash[DB_UPGATE_SHA256_LENGTH]; + uint8_t read_buf[DB_UPGATE_CHUNK_SIZE * 2]; + uint8_t write_buf[DB_UPGATE_CHUNK_SIZE * 2]; +} db_upgate_vars_t; + +//=========================== variables ======================================== + +static db_upgate_vars_t _upgate_vars = { 0 }; + +//============================ public ========================================== + +void db_upgate_init(const db_upgate_conf_t *config) { + n25q128_init(config->n25q128_conf); + _upgate_vars.config = config; +} + +void db_upgate_start(uint32_t chunk_count) { + (void)chunk_count; + n25q128_base_address(&_upgate_vars.addr); + // Erase the corresponding sectors. + uint32_t total_bytes = chunk_count * DB_UPGATE_CHUNK_SIZE; + uint32_t sector_count = (total_bytes / N25Q128_SECTOR_SIZE) + 1; + printf("Sectors to erase: %u\n", sector_count); + for (uint32_t sector = 0; sector < sector_count; sector++) { + uint32_t addr = _upgate_vars.addr + sector * N25Q128_SECTOR_SIZE; + printf("Erasing sector %u at %p\n", sector, addr); + n25q128_sector_erase(addr); + } + puts(""); + _upgate_vars.last_index_acked = UINT32_MAX; + printf("Starting upgate at %p\n\n", _upgate_vars.addr); +} + +void db_upgate_finish(void) { +#if defined(UPGATE_USE_CRYPTO) + uint8_t hash_result[DB_UPGATE_SHA256_LENGTH] = { 0 }; + crypto_sha256(hash_result); + + if (memcmp(hash_result, _upgate_vars.hash, DB_UPGATE_SHA256_LENGTH) != 0) { + return; + } +#endif + + // TODO: Reset the FPGA +} + +void db_upgate_write_chunk(const db_upgate_pkt_t *pkt) { + printf("Received chunk %u:\n"); + //for (uint8_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE; idx++) { + // printf("%d ", pkt->upgate_chunk[idx]); + // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { + // puts(""); + // } + //} + //puts(""); + memcpy(&_upgate_vars.write_buf[(pkt->index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); + if (pkt->index % 2 == 0) { + return; + } + uint32_t addr = _upgate_vars.addr + (pkt->index - 1) * DB_UPGATE_CHUNK_SIZE; + + printf("Programming 256 bytes at %p\n", addr); + //for (uint16_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE * 2; idx++) { + // printf("%d ", _upgate_vars.write_buf[idx]); + // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { + // puts(""); + // } + //} + //puts(""); + n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); + n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); + //n25q128_read(addr + DB_UPGATE_CHUNK_SIZE, &_upgate_vars.read_buf[DB_UPGATE_CHUNK_SIZE], DB_UPGATE_CHUNK_SIZE); + + //printf("Reading back the 256 bytes written to flash\n"); + //for (uint16_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE * 2; idx++) { + // printf("%d ", _upgate_vars.read_buf[idx]); + // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { + // puts(""); + // } + //} + //puts(""); + uint16_t delay = 0xffff; + while (delay--) {} + if (memcmp(pkt->upgate_chunk, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { + puts("packet doesn't match!!"); + } +} + +void db_upgate_handle_message(const uint8_t *message) { + db_upgate_message_type_t message_type = (db_upgate_message_type_t)message[0]; + switch (message_type) { + case DB_UPGATE_MESSAGE_TYPE_START: + { + const db_upgate_start_notification_t *upgate_start = (const db_upgate_start_notification_t *)&message[1]; +#if defined(UPGATE_USE_CRYPTO) + const uint8_t *hash = upgate_start->hash; + if (!crypto_ed25519_verify(upgate_start->signature, DB_UPGATE_SIGNATURE_LENGTH, (const uint8_t *)upgate_start, sizeof(db_upgate_start_notification_t) - DB_UPGATE_SIGNATURE_LENGTH, public_key)) { + break; + } + memcpy(_upgate_vars.hash, hash, DB_UPGATE_SHA256_LENGTH); + crypto_sha256_init(); +#endif + db_upgate_start(upgate_start->chunk_count); + // Acknowledge the update start + _upgate_vars.reply_buffer[0] = DB_UPGATE_MESSAGE_TYPE_START_ACK; + _upgate_vars.config->reply(_upgate_vars.reply_buffer, sizeof(db_upgate_message_type_t)); + } break; + case DB_UPGATE_MESSAGE_TYPE_CHUNK: + { + const db_upgate_pkt_t *upgate_pkt = (const db_upgate_pkt_t *)&message[1]; + const uint32_t chunk_index = upgate_pkt->index; + const uint32_t chunk_count = upgate_pkt->chunk_count; + + if (_upgate_vars.last_index_acked != chunk_index) { + //printf("\rWriting packet %u", chunk_index); + // Skip writing the chunk if already acked + db_upgate_write_chunk(upgate_pkt); +#if defined(UPGATE_USE_CRYPTO) + crypto_sha256_update((const uint8_t *)upgate_pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); +#endif + } + + // Acknowledge the received chunk + _upgate_vars.reply_buffer[0] = DB_UPGATE_MESSAGE_TYPE_CHUNK_ACK; + memcpy(&_upgate_vars.reply_buffer[1], &chunk_index, sizeof(uint32_t)); + _upgate_vars.config->reply(_upgate_vars.reply_buffer, sizeof(db_upgate_message_type_t) + sizeof(uint32_t)); + _upgate_vars.last_index_acked = chunk_index; + + if (chunk_index == chunk_count - 1) { + puts(""); + db_upgate_finish(); + } + } break; + default: + break; + } +} diff --git a/nrf52833dk.emProject b/nrf52833dk.emProject index 8a9517ac..86eb1d35 100644 --- a/nrf52833dk.emProject +++ b/nrf52833dk.emProject @@ -66,5 +66,6 @@ + diff --git a/nrf52840dk.emProject b/nrf52840dk.emProject index bb478ff2..227aef65 100644 --- a/nrf52840dk.emProject +++ b/nrf52840dk.emProject @@ -66,5 +66,6 @@ + diff --git a/upgate/application/main.c b/upgate/application/main.c new file mode 100644 index 00000000..3215fb66 --- /dev/null +++ b/upgate/application/main.c @@ -0,0 +1,93 @@ +/** + * @file + * @author Alexandre Abadie + * @brief This application can be flashed on partition 0 (at 0x2000) + * + * @copyright Inria, 2023 + * + */ + +#include +#include +#include +#include +#include + +#include "board_config.h" +#include "gpio.h" +#include "radio.h" +#include "timer.h" + +#include "upgate.h" + +//=========================== defines ========================================== + +typedef struct { + bool packet_received; + uint8_t message_buffer[UINT8_MAX]; +} application_vars_t; + +//=========================== variables ======================================== + +static application_vars_t _app_vars = { 0 }; + +#if defined(BOARD_NRF52833DK) +static const gpio_t _sck_pin = { .port = 0, .pin = 23 }; +static const gpio_t _miso_pin = { .port = 0, .pin = 22 }; +static const gpio_t _mosi_pin = { .port = 0, .pin = 21 }; +static const gpio_t _cs_pin = { .port = 0, .pin = 20 }; +#else +static const gpio_t _sck_pin = { .port = 1, .pin = 15 }; +static const gpio_t _miso_pin = { .port = 1, .pin = 14 }; +static const gpio_t _mosi_pin = { .port = 1, .pin = 13 }; +static const gpio_t _cs_pin = { .port = 1, .pin = 12 }; +#endif + +static const n25q128_conf_t _n25q128_conf = { + .mosi = &_mosi_pin, + .sck = &_sck_pin, + .miso = &_miso_pin, + .cs = &_cs_pin, +}; + +//=========================== callbacks ======================================== + +static void _radio_callback(uint8_t *pkt, uint8_t len) { + memcpy(&_app_vars.message_buffer, pkt, len); + _app_vars.packet_received = true; +} + +//=========================== private ========================================== + +static void _upgate_reply(const uint8_t *message, size_t len) { + db_radio_disable(); + db_radio_tx(message, len); +} + +static const db_upgate_conf_t _upgate_config = { + .reply = _upgate_reply, + .n25q128_conf = &_n25q128_conf, +}; + +//================================ main ======================================== + +int main(void) { + db_upgate_init(&_upgate_config); + + db_radio_init(&_radio_callback, DB_RADIO_BLE_1MBit); + db_radio_set_frequency(8); + db_radio_rx(); + + db_gpio_init(&db_led1, DB_GPIO_OUT); + db_gpio_set(&db_led1); + + while (1) { + __WFE(); + + if (_app_vars.packet_received) { + _app_vars.packet_received = false; + db_gpio_toggle(&db_led1); + db_upgate_handle_message(_app_vars.message_buffer); + } + } +} diff --git a/upgate/upgate.emProject b/upgate/upgate.emProject new file mode 100644 index 00000000..cfad326f --- /dev/null +++ b/upgate/upgate.emProject @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 03ae9b76994921428fc017f0ab4349050ca88041 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 6 Mar 2024 10:37:21 +0100 Subject: [PATCH 02/27] debugging --- drv/upgate.h | 2 +- drv/upgate/upgate.c | 60 ++++++++++++++++++++++----------------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/drv/upgate.h b/drv/upgate.h index 4f3d7b56..b9d9938f 100644 --- a/drv/upgate.h +++ b/drv/upgate.h @@ -26,7 +26,7 @@ typedef void (*db_upgate_reply_t)(const uint8_t *, size_t); ///< Transport agno ///< FPGA bitstream update configuration typedef struct { - db_upgate_reply_t reply; ///< Pointer to the function used to reply to the upgate script + db_upgate_reply_t reply; ///< Pointer to the function used to reply to the upgate script const n25q128_conf_t *n25q128_conf; } db_upgate_conf_t; diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 58d66675..f84cce20 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -52,7 +52,7 @@ void db_upgate_start(uint32_t chunk_count) { (void)chunk_count; n25q128_base_address(&_upgate_vars.addr); // Erase the corresponding sectors. - uint32_t total_bytes = chunk_count * DB_UPGATE_CHUNK_SIZE; + uint32_t total_bytes = chunk_count * DB_UPGATE_CHUNK_SIZE; uint32_t sector_count = (total_bytes / N25Q128_SECTOR_SIZE) + 1; printf("Sectors to erase: %u\n", sector_count); for (uint32_t sector = 0; sector < sector_count; sector++) { @@ -80,13 +80,13 @@ void db_upgate_finish(void) { void db_upgate_write_chunk(const db_upgate_pkt_t *pkt) { printf("Received chunk %u:\n"); - //for (uint8_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE; idx++) { - // printf("%d ", pkt->upgate_chunk[idx]); - // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { - // puts(""); - // } - //} - //puts(""); + // for (uint8_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE; idx++) { + // printf("%d ", pkt->upgate_chunk[idx]); + // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { + // puts(""); + // } + // } + // puts(""); memcpy(&_upgate_vars.write_buf[(pkt->index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); if (pkt->index % 2 == 0) { return; @@ -94,25 +94,25 @@ void db_upgate_write_chunk(const db_upgate_pkt_t *pkt) { uint32_t addr = _upgate_vars.addr + (pkt->index - 1) * DB_UPGATE_CHUNK_SIZE; printf("Programming 256 bytes at %p\n", addr); - //for (uint16_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE * 2; idx++) { - // printf("%d ", _upgate_vars.write_buf[idx]); - // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { - // puts(""); - // } - //} - //puts(""); + // for (uint16_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE * 2; idx++) { + // printf("%d ", _upgate_vars.write_buf[idx]); + // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { + // puts(""); + // } + // } + // puts(""); n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); - //n25q128_read(addr + DB_UPGATE_CHUNK_SIZE, &_upgate_vars.read_buf[DB_UPGATE_CHUNK_SIZE], DB_UPGATE_CHUNK_SIZE); - - //printf("Reading back the 256 bytes written to flash\n"); - //for (uint16_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE * 2; idx++) { - // printf("%d ", _upgate_vars.read_buf[idx]); - // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { - // puts(""); - // } - //} - //puts(""); + // n25q128_read(addr + DB_UPGATE_CHUNK_SIZE, &_upgate_vars.read_buf[DB_UPGATE_CHUNK_SIZE], DB_UPGATE_CHUNK_SIZE); + + // printf("Reading back the 256 bytes written to flash\n"); + // for (uint16_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE * 2; idx++) { + // printf("%d ", _upgate_vars.read_buf[idx]); + // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { + // puts(""); + // } + // } + // puts(""); uint16_t delay = 0xffff; while (delay--) {} if (memcmp(pkt->upgate_chunk, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { @@ -141,13 +141,13 @@ void db_upgate_handle_message(const uint8_t *message) { } break; case DB_UPGATE_MESSAGE_TYPE_CHUNK: { - const db_upgate_pkt_t *upgate_pkt = (const db_upgate_pkt_t *)&message[1]; - const uint32_t chunk_index = upgate_pkt->index; - const uint32_t chunk_count = upgate_pkt->chunk_count; + const db_upgate_pkt_t *upgate_pkt = (const db_upgate_pkt_t *)&message[1]; + const uint32_t chunk_index = upgate_pkt->index; + const uint32_t chunk_count = upgate_pkt->chunk_count; if (_upgate_vars.last_index_acked != chunk_index) { - //printf("\rWriting packet %u", chunk_index); - // Skip writing the chunk if already acked + // printf("\rWriting packet %u", chunk_index); + // Skip writing the chunk if already acked db_upgate_write_chunk(upgate_pkt); #if defined(UPGATE_USE_CRYPTO) crypto_sha256_update((const uint8_t *)upgate_pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); From c8478cb37dae9106ef63e90144fba16b7c47d9da Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Fri, 8 Mar 2024 09:07:01 +0100 Subject: [PATCH 03/27] dist/scripts/upgate: adapt with lz4 compression --- dist/scripts/upgate/dotbot-upgate.py | 20 +++++++++++++------- dist/scripts/upgate/requirements.txt | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py index 8814b28d..75be7d76 100755 --- a/dist/scripts/upgate/dotbot-upgate.py +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -10,6 +10,8 @@ import serial import structlog +import lz4.block + from tqdm import tqdm from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey @@ -41,8 +43,7 @@ def __init__(self, port, baudrate, image): 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.image = image self.last_acked_chunk = -1 # Just write a single byte to fake a DotBot gateway handshake self.serial.write(int(PROTOCOL_VERSION).to_bytes(length=1)) @@ -68,7 +69,6 @@ def init(self, secure): fw_hash = digest.finalize() private_key_bytes = open(PRIVATE_KEY_PATH, "rb").read() private_key = Ed25519PrivateKey.from_private_bytes(private_key_bytes) - attempts = 0 buffer = bytearray() buffer += int(MessageType.UPGATE_MESSAGE_TYPE_START.value).to_bytes( length=1, byteorder="little" @@ -89,12 +89,18 @@ def init(self, secure): return self.start_ack_received is True def run(self): + # Compress the image by blocks and pad it to the next block size + compressed = lz4.block.compress(self.image) + pad_length = CHUNK_SIZE - (len(compressed) % CHUNK_SIZE) + compressed += bytearray(b"\xff") * (pad_length + 1) + + # Send the compressed image by chunks pos = 0 progress = tqdm( total=len(self.image), unit="B", unit_scale=False, colour="green", ncols=100 ) - progress.set_description(f"Flashing firmware ({int(len(self.image) / 1024)}kB)") - while pos + CHUNK_SIZE <= len(self.image) + 1: + progress.set_description(f"Flashing compressed firmware ({int(len(compressed) / 1024)}kB)") + while pos + CHUNK_SIZE <= len(compressed) + 1: chunk_index = int(pos / CHUNK_SIZE) while self.last_acked_chunk != chunk_index: buffer = bytearray() @@ -102,10 +108,10 @@ def run(self): length=1, byteorder="little" ) buffer += int(chunk_index).to_bytes(length=4, byteorder="little") - buffer += int((len(self.image) - 1) / CHUNK_SIZE).to_bytes( + buffer += int((len(compressed) - 1) / CHUNK_SIZE).to_bytes( length=4, byteorder="little" ) - buffer += self.image[pos : pos + CHUNK_SIZE] + buffer += compressed[pos : pos + CHUNK_SIZE] self.serial.write(hdlc_encode(buffer)) time.sleep(0.005) pos += CHUNK_SIZE diff --git a/dist/scripts/upgate/requirements.txt b/dist/scripts/upgate/requirements.txt index df144dc1..b827c50d 100644 --- a/dist/scripts/upgate/requirements.txt +++ b/dist/scripts/upgate/requirements.txt @@ -1,4 +1,5 @@ click==8.1.7 cryptography==41.0.5 +lz4==4.3.3 tqdm==4.66.1 pydotbot From 98626b52715e4834f4a343aa92a4ac9dc72419fb Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Fri, 8 Mar 2024 09:08:24 +0100 Subject: [PATCH 04/27] drv/upgate: adapt for lz4 compression --- drv/drv.emProject | 2 +- drv/upgate/upgate.c | 85 ++++++++++++++++++++++++--------------------- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/drv/drv.emProject b/drv/drv.emProject index 8af869b6..f3dd04d1 100644 --- a/drv/drv.emProject +++ b/drv/drv.emProject @@ -140,7 +140,7 @@ diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index f84cce20..38b35961 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -15,6 +15,7 @@ #include #include +#include "lz4.h" #include "n25q128.h" #include "upgate.h" @@ -26,6 +27,9 @@ //=========================== defines ========================================== +#define LZ4F_VERSION 100 +#define DECOMPRESS_BUFFER_SIZE (4096) + typedef struct { const db_upgate_conf_t *config; uint8_t reply_buffer[UINT8_MAX]; @@ -35,17 +39,21 @@ typedef struct { uint8_t hash[DB_UPGATE_SHA256_LENGTH]; uint8_t read_buf[DB_UPGATE_CHUNK_SIZE * 2]; uint8_t write_buf[DB_UPGATE_CHUNK_SIZE * 2]; + uint8_t write_buf_pos; + uint8_t decompress_buffer[DECOMPRESS_BUFFER_SIZE]; } db_upgate_vars_t; //=========================== variables ======================================== static db_upgate_vars_t _upgate_vars = { 0 }; +//static LZ4F_decompressionContext_t dctx; //============================ public ========================================== void db_upgate_init(const db_upgate_conf_t *config) { n25q128_init(config->n25q128_conf); _upgate_vars.config = config; + //LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION); } void db_upgate_start(uint32_t chunk_count) { @@ -62,10 +70,12 @@ void db_upgate_start(uint32_t chunk_count) { } puts(""); _upgate_vars.last_index_acked = UINT32_MAX; + //LZ4F_resetDecompressionContext(dctx); printf("Starting upgate at %p\n\n", _upgate_vars.addr); } void db_upgate_finish(void) { + puts("Finishing upgate"); #if defined(UPGATE_USE_CRYPTO) uint8_t hash_result[DB_UPGATE_SHA256_LENGTH] = { 0 }; crypto_sha256(hash_result); @@ -79,45 +89,41 @@ void db_upgate_finish(void) { } void db_upgate_write_chunk(const db_upgate_pkt_t *pkt) { - printf("Received chunk %u:\n"); - // for (uint8_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE; idx++) { - // printf("%d ", pkt->upgate_chunk[idx]); - // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { - // puts(""); - // } - // } - // puts(""); - memcpy(&_upgate_vars.write_buf[(pkt->index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); - if (pkt->index % 2 == 0) { - return; - } - uint32_t addr = _upgate_vars.addr + (pkt->index - 1) * DB_UPGATE_CHUNK_SIZE; - - printf("Programming 256 bytes at %p\n", addr); - // for (uint16_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE * 2; idx++) { - // printf("%d ", _upgate_vars.write_buf[idx]); - // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { - // puts(""); - // } - // } - // puts(""); - n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); - n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); - // n25q128_read(addr + DB_UPGATE_CHUNK_SIZE, &_upgate_vars.read_buf[DB_UPGATE_CHUNK_SIZE], DB_UPGATE_CHUNK_SIZE); - - // printf("Reading back the 256 bytes written to flash\n"); - // for (uint16_t idx = 0; idx < DB_UPGATE_CHUNK_SIZE * 2; idx++) { - // printf("%d ", _upgate_vars.read_buf[idx]); - // if (idx > 0 && (idx + 1) % (DB_UPGATE_CHUNK_SIZE / 2) == 0) { - // puts(""); - // } - // } - // puts(""); - uint16_t delay = 0xffff; - while (delay--) {} - if (memcmp(pkt->upgate_chunk, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { - puts("packet doesn't match!!"); - } + int32_t decompressed_length = LZ4_decompress_safe( + (const char *)pkt->upgate_chunk, (char *)_upgate_vars.decompress_buffer, DB_UPGATE_CHUNK_SIZE, DECOMPRESS_BUFFER_SIZE); + assert(decompressed_length < DECOMPRESS_BUFFER_SIZE); + uint32_t decompress_buffer_pos = 0; + do { + uint16_t write_buf_available = (DB_UPGATE_CHUNK_SIZE * 2) - _upgate_vars.write_buf_pos; + memcpy(&_upgate_vars.write_buf[_upgate_vars.write_buf_pos], &_upgate_vars.decompress_buffer[decompress_buffer_pos], write_buf_available); + decompressed_length -= write_buf_available; + decompress_buffer_pos += write_buf_available; + _upgate_vars.write_buf_pos = 0; + uint32_t addr = _upgate_vars.addr + (pkt->index - 1) * DB_UPGATE_CHUNK_SIZE; + + printf("Programming 256 bytes at %p\n", addr); + n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); + n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); + if (memcmp(_upgate_vars.write_buf, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { + puts("packet doesn't match!!"); + } + } while (decompressed_length >= (int32_t)DB_UPGATE_CHUNK_SIZE * 2); + memcpy(&_upgate_vars.write_buf[_upgate_vars.write_buf_pos], &_upgate_vars.decompress_buffer[decompress_buffer_pos], decompressed_length); + _upgate_vars.write_buf_pos = decompressed_length; + + + //memcpy(&_upgate_vars.write_buf[(pkt->index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); + //if (pkt->index % 2 == 0) { + // return; + //} + //uint32_t addr = _upgate_vars.addr + (pkt->index - 1) * DB_UPGATE_CHUNK_SIZE; + + //printf("Programming 256 bytes at %p\n", addr); + //n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); + //n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); + //if (memcmp(_upgate_vars.write_buf, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { + // puts("packet doesn't match!!"); + //} } void db_upgate_handle_message(const uint8_t *message) { @@ -146,7 +152,6 @@ void db_upgate_handle_message(const uint8_t *message) { const uint32_t chunk_count = upgate_pkt->chunk_count; if (_upgate_vars.last_index_acked != chunk_index) { - // printf("\rWriting packet %u", chunk_index); // Skip writing the chunk if already acked db_upgate_write_chunk(upgate_pkt); #if defined(UPGATE_USE_CRYPTO) From 6ef9072e328128d7d7bb2e2e429568bb46d1af6b Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Fri, 8 Mar 2024 12:04:06 +0100 Subject: [PATCH 05/27] drv/upgate: make compression optional --- dist/scripts/upgate/dotbot-upgate.py | 14 ++++-- drv/upgate.h | 3 +- drv/upgate/upgate.c | 69 ++++++++++++---------------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py index 75be7d76..c5fe9d2a 100755 --- a/dist/scripts/upgate/dotbot-upgate.py +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -59,7 +59,7 @@ def on_byte_received(self, byte): elif payload[0] == MessageType.UPGATE_MESSAGE_TYPE_CHUNK_ACK.value: self.last_acked_chunk = int.from_bytes(payload[1:5], byteorder="little") - def init(self, secure): + def init(self, secure, use_compression): if secure is True: digest = hashes.Hash(hashes.SHA256()) pos = 0 @@ -76,6 +76,8 @@ def init(self, secure): buffer += int((len(self.image) - 1) / CHUNK_SIZE).to_bytes( length=4, byteorder="little" ) + if use_compression is True: + buffer += int(use_compression).to_bytes(length=1, byteorder="little") if secure is True: buffer += fw_hash signature = private_key.sign(bytes(buffer[1:])) @@ -133,6 +135,12 @@ def run(self): is_flag=True, help="Use cryptographic security (hash and signature).", ) +@click.option( + "-c", + "--use-compression", + is_flag=True, + help="Compress the bitstream before sending it.", +) @click.option( "-y", "--yes", @@ -140,7 +148,7 @@ def run(self): help="Continue the upgate without prompt.", ) @click.argument("bitstream", type=click.File(mode="rb", lazy=True)) -def main(port, secure, yes, bitstream): +def main(port, secure, use_compression, yes, bitstream): # Disable logging configure in PyDotBot structlog.configure( wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL), @@ -157,7 +165,7 @@ def main(port, secure, yes, bitstream): print("") if yes is False: click.confirm("Do you want to continue?", default=True, abort=True) - ret = upgater.init(secure) + ret = upgater.init(secure, use_compression) if ret is False: print("Error: No start acknowledment received. Aborting.") return diff --git a/drv/upgate.h b/drv/upgate.h index b9d9938f..d0c04782 100644 --- a/drv/upgate.h +++ b/drv/upgate.h @@ -32,7 +32,8 @@ typedef struct { ///< FPGA bitstream update start notification packet typedef struct __attribute__((packed, aligned(4))) { - uint32_t chunk_count; ///< Number of chunks + uint32_t chunk_count; ///< Number of chunks + bool use_compression; ///< True if bitstream is compressed #if defined(UPGATE_USE_CRYPTO) uint8_t hash[DB_UPGATE_SHA256_LENGTH]; ///< SHA256 hash of the bitsream uint8_t signature[DB_UPGATE_SIGNATURE_LENGTH]; ///< Signature of the bitstream hash diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 38b35961..79c98327 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -27,8 +27,8 @@ //=========================== defines ========================================== -#define LZ4F_VERSION 100 -#define DECOMPRESS_BUFFER_SIZE (4096) +#define LZ4F_VERSION 100 +#define DECOMPRESS_BUFFER_SIZE (4096) typedef struct { const db_upgate_conf_t *config; @@ -39,6 +39,7 @@ typedef struct { uint8_t hash[DB_UPGATE_SHA256_LENGTH]; uint8_t read_buf[DB_UPGATE_CHUNK_SIZE * 2]; uint8_t write_buf[DB_UPGATE_CHUNK_SIZE * 2]; + bool use_compression; uint8_t write_buf_pos; uint8_t decompress_buffer[DECOMPRESS_BUFFER_SIZE]; } db_upgate_vars_t; @@ -46,14 +47,12 @@ typedef struct { //=========================== variables ======================================== static db_upgate_vars_t _upgate_vars = { 0 }; -//static LZ4F_decompressionContext_t dctx; //============================ public ========================================== void db_upgate_init(const db_upgate_conf_t *config) { n25q128_init(config->n25q128_conf); _upgate_vars.config = config; - //LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION); } void db_upgate_start(uint32_t chunk_count) { @@ -70,7 +69,6 @@ void db_upgate_start(uint32_t chunk_count) { } puts(""); _upgate_vars.last_index_acked = UINT32_MAX; - //LZ4F_resetDecompressionContext(dctx); printf("Starting upgate at %p\n\n", _upgate_vars.addr); } @@ -89,41 +87,33 @@ void db_upgate_finish(void) { } void db_upgate_write_chunk(const db_upgate_pkt_t *pkt) { - int32_t decompressed_length = LZ4_decompress_safe( - (const char *)pkt->upgate_chunk, (char *)_upgate_vars.decompress_buffer, DB_UPGATE_CHUNK_SIZE, DECOMPRESS_BUFFER_SIZE); - assert(decompressed_length < DECOMPRESS_BUFFER_SIZE); - uint32_t decompress_buffer_pos = 0; - do { - uint16_t write_buf_available = (DB_UPGATE_CHUNK_SIZE * 2) - _upgate_vars.write_buf_pos; - memcpy(&_upgate_vars.write_buf[_upgate_vars.write_buf_pos], &_upgate_vars.decompress_buffer[decompress_buffer_pos], write_buf_available); - decompressed_length -= write_buf_available; - decompress_buffer_pos += write_buf_available; - _upgate_vars.write_buf_pos = 0; - uint32_t addr = _upgate_vars.addr + (pkt->index - 1) * DB_UPGATE_CHUNK_SIZE; - - printf("Programming 256 bytes at %p\n", addr); - n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); - n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); - if (memcmp(_upgate_vars.write_buf, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { - puts("packet doesn't match!!"); + if (_upgate_vars.use_compression) { + int32_t decompressed_length = LZ4_decompress_safe( + (const char *)pkt->upgate_chunk, (char *)_upgate_vars.decompress_buffer, DB_UPGATE_CHUNK_SIZE, DECOMPRESS_BUFFER_SIZE); + assert(decompressed_length < DECOMPRESS_BUFFER_SIZE); + uint32_t decompress_buffer_pos = 0; + do { + uint16_t write_buf_available = (DB_UPGATE_CHUNK_SIZE * 2) - _upgate_vars.write_buf_pos; + memcpy(&_upgate_vars.write_buf[_upgate_vars.write_buf_pos], &_upgate_vars.decompress_buffer[decompress_buffer_pos], write_buf_available); + decompressed_length -= write_buf_available; + decompress_buffer_pos += write_buf_available; + _upgate_vars.write_buf_pos = 0; + } while (decompressed_length >= (int32_t)DB_UPGATE_CHUNK_SIZE * 2); + memcpy(&_upgate_vars.write_buf[_upgate_vars.write_buf_pos], &_upgate_vars.decompress_buffer[decompress_buffer_pos], decompressed_length); + _upgate_vars.write_buf_pos = decompressed_length; + } else { + memcpy(&_upgate_vars.write_buf[(pkt->index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); + if (pkt->index % 2 == 0) { + return; } - } while (decompressed_length >= (int32_t)DB_UPGATE_CHUNK_SIZE * 2); - memcpy(&_upgate_vars.write_buf[_upgate_vars.write_buf_pos], &_upgate_vars.decompress_buffer[decompress_buffer_pos], decompressed_length); - _upgate_vars.write_buf_pos = decompressed_length; - - - //memcpy(&_upgate_vars.write_buf[(pkt->index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); - //if (pkt->index % 2 == 0) { - // return; - //} - //uint32_t addr = _upgate_vars.addr + (pkt->index - 1) * DB_UPGATE_CHUNK_SIZE; - - //printf("Programming 256 bytes at %p\n", addr); - //n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); - //n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); - //if (memcmp(_upgate_vars.write_buf, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { - // puts("packet doesn't match!!"); - //} + } + uint32_t addr = _upgate_vars.addr + (pkt->index - 1) * DB_UPGATE_CHUNK_SIZE; + printf("Programming 256 bytes at %p\n", addr); + n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); + n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); + if (memcmp(_upgate_vars.write_buf, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { + puts("packet doesn't match!!"); + } } void db_upgate_handle_message(const uint8_t *message) { @@ -140,6 +130,7 @@ void db_upgate_handle_message(const uint8_t *message) { memcpy(_upgate_vars.hash, hash, DB_UPGATE_SHA256_LENGTH); crypto_sha256_init(); #endif + _upgate_vars.use_compression = upgate_start->use_compression; db_upgate_start(upgate_start->chunk_count); // Acknowledge the update start _upgate_vars.reply_buffer[0] = DB_UPGATE_MESSAGE_TYPE_START_ACK; From 492c199e54ac808cf84108ecc3b162d4628356a4 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Fri, 8 Mar 2024 12:06:24 +0100 Subject: [PATCH 06/27] drv/upgate: used hard coded base address --- drv/upgate/upgate.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 79c98327..22844a47 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -27,8 +27,9 @@ //=========================== defines ========================================== -#define LZ4F_VERSION 100 -#define DECOMPRESS_BUFFER_SIZE (4096) +#define LZ4F_VERSION 100 +#define DECOMPRESS_BUFFER_SIZE (4096) +#define UPGATE_BASE_ADDRESS (0x00000000) typedef struct { const db_upgate_conf_t *config; @@ -56,8 +57,7 @@ void db_upgate_init(const db_upgate_conf_t *config) { } void db_upgate_start(uint32_t chunk_count) { - (void)chunk_count; - n25q128_base_address(&_upgate_vars.addr); + _upgate_vars.addr = UPGATE_BASE_ADDRESS; // Erase the corresponding sectors. uint32_t total_bytes = chunk_count * DB_UPGATE_CHUNK_SIZE; uint32_t sector_count = (total_bytes / N25Q128_SECTOR_SIZE) + 1; From f93e6cc249ccc1ac9f014bddbae8e4184ff24e8e Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 12 Mar 2024 15:40:34 +0100 Subject: [PATCH 07/27] drv/upgate: reset SPIM GPIOS at the end of upgate to leave the flash in usable state by the FPGA --- drv/upgate/upgate.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 22844a47..1bc4cad9 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -15,6 +15,7 @@ #include #include +#include "gpio.h" #include "lz4.h" #include "n25q128.h" #include "upgate.h" @@ -27,9 +28,9 @@ //=========================== defines ========================================== -#define LZ4F_VERSION 100 -#define DECOMPRESS_BUFFER_SIZE (4096) -#define UPGATE_BASE_ADDRESS (0x00000000) +#define LZ4F_VERSION 100 +#define DECOMPRESS_BUFFER_SIZE (4096) +#define UPGATE_BASE_ADDRESS (0x00000000) typedef struct { const db_upgate_conf_t *config; @@ -52,11 +53,11 @@ static db_upgate_vars_t _upgate_vars = { 0 }; //============================ public ========================================== void db_upgate_init(const db_upgate_conf_t *config) { - n25q128_init(config->n25q128_conf); _upgate_vars.config = config; } void db_upgate_start(uint32_t chunk_count) { + n25q128_init(_upgate_vars.config->n25q128_conf); _upgate_vars.addr = UPGATE_BASE_ADDRESS; // Erase the corresponding sectors. uint32_t total_bytes = chunk_count * DB_UPGATE_CHUNK_SIZE; @@ -74,6 +75,11 @@ void db_upgate_start(uint32_t chunk_count) { void db_upgate_finish(void) { puts("Finishing upgate"); + + // Put SPIM GPIOS as input otherwise the FPGA ends up in a broken state + db_gpio_init(_upgate_vars.config->n25q128_conf->mosi, DB_GPIO_IN); + db_gpio_init(_upgate_vars.config->n25q128_conf->sck, DB_GPIO_IN); + db_gpio_init(_upgate_vars.config->n25q128_conf->miso, DB_GPIO_IN); #if defined(UPGATE_USE_CRYPTO) uint8_t hash_result[DB_UPGATE_SHA256_LENGTH] = { 0 }; crypto_sha256(hash_result); From 72db5c638c5a5e52ba683cd409de477eb73a2d38 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 12 Mar 2024 15:41:35 +0100 Subject: [PATCH 08/27] dist/scripts/upgate: use gzip instead of lz4 --- dist/scripts/upgate/dotbot-upgate.py | 46 ++++++++++++++++------------ dist/scripts/upgate/requirements.txt | 1 - 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py index c5fe9d2a..7d35ea68 100755 --- a/dist/scripts/upgate/dotbot-upgate.py +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import gzip import os import logging import time @@ -10,8 +11,6 @@ import serial import structlog -import lz4.block - from tqdm import tqdm from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey @@ -37,9 +36,11 @@ class MessageType(Enum): class DotBotUpgate: """Class used to send an FPGA bitstream.""" - def __init__(self, port, baudrate, image): + def __init__(self, port, baudrate, image, use_compression=False, secure=False): self.serial = SerialInterface(port, baudrate, self.on_byte_received) self.hdlc_handler = HDLCHandler() + self.use_compression = use_compression + self.secure = secure self.device_info = None self.device_info_received = False self.start_ack_received = False @@ -59,8 +60,8 @@ def on_byte_received(self, byte): elif payload[0] == MessageType.UPGATE_MESSAGE_TYPE_CHUNK_ACK.value: self.last_acked_chunk = int.from_bytes(payload[1:5], byteorder="little") - def init(self, secure, use_compression): - if secure is True: + def init(self): + if self.secure is True: digest = hashes.Hash(hashes.SHA256()) pos = 0 while pos + CHUNK_SIZE <= len(self.image) + 1: @@ -76,9 +77,9 @@ def init(self, secure, use_compression): buffer += int((len(self.image) - 1) / CHUNK_SIZE).to_bytes( length=4, byteorder="little" ) - if use_compression is True: - buffer += int(use_compression).to_bytes(length=1, byteorder="little") - if secure is True: + if self.use_compression is True: + buffer += int(self.use_compression).to_bytes(length=1, byteorder="little") + if self.secure is True: buffer += fw_hash signature = private_key.sign(bytes(buffer[1:])) buffer += signature @@ -91,18 +92,20 @@ def init(self, secure, use_compression): return self.start_ack_received is True def run(self): - # Compress the image by blocks and pad it to the next block size - compressed = lz4.block.compress(self.image) - pad_length = CHUNK_SIZE - (len(compressed) % CHUNK_SIZE) - compressed += bytearray(b"\xff") * (pad_length + 1) + data = self.image + if self.use_compression: + # Compress the image by blocks and pad it to the next block size + data = gzip.compress(data) + pad_length = CHUNK_SIZE - (len(data) % CHUNK_SIZE) + data += bytearray(b"\xff") * (pad_length + 1) # Send the compressed image by chunks pos = 0 progress = tqdm( - total=len(self.image), unit="B", unit_scale=False, colour="green", ncols=100 + total=len(data), unit="B", unit_scale=False, colour="green", ncols=100 ) - progress.set_description(f"Flashing compressed firmware ({int(len(compressed) / 1024)}kB)") - while pos + CHUNK_SIZE <= len(compressed) + 1: + progress.set_description(f"Flashing compressed firmware ({int(len(data) / 1024)}kB)") + while pos + CHUNK_SIZE <= len(data) + 1: chunk_index = int(pos / CHUNK_SIZE) while self.last_acked_chunk != chunk_index: buffer = bytearray() @@ -110,10 +113,10 @@ def run(self): length=1, byteorder="little" ) buffer += int(chunk_index).to_bytes(length=4, byteorder="little") - buffer += int((len(compressed) - 1) / CHUNK_SIZE).to_bytes( + buffer += int((len(data) - 1) / CHUNK_SIZE).to_bytes( length=4, byteorder="little" ) - buffer += compressed[pos : pos + CHUNK_SIZE] + buffer += data[pos : pos + CHUNK_SIZE] self.serial.write(hdlc_encode(buffer)) time.sleep(0.005) pos += CHUNK_SIZE @@ -154,7 +157,12 @@ def main(port, secure, use_compression, yes, bitstream): wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL), ) try: - upgater = DotBotUpgate(port, BAUDRATE, bytearray(bitstream.read())) + upgater = DotBotUpgate( + port, + BAUDRATE, + bytearray(bitstream.read()), + use_compression=use_compression + ) except ( SerialInterfaceException, serial.serialutil.SerialException, @@ -165,7 +173,7 @@ def main(port, secure, use_compression, yes, bitstream): print("") if yes is False: click.confirm("Do you want to continue?", default=True, abort=True) - ret = upgater.init(secure, use_compression) + ret = upgater.init() if ret is False: print("Error: No start acknowledment received. Aborting.") return diff --git a/dist/scripts/upgate/requirements.txt b/dist/scripts/upgate/requirements.txt index b827c50d..df144dc1 100644 --- a/dist/scripts/upgate/requirements.txt +++ b/dist/scripts/upgate/requirements.txt @@ -1,5 +1,4 @@ click==8.1.7 cryptography==41.0.5 -lz4==4.3.3 tqdm==4.66.1 pydotbot From d21ced14ec353a17f4dc424100884b60241aa4bc Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 13 Mar 2024 16:26:51 +0100 Subject: [PATCH 09/27] drv/upgate: wire up the FPGA prog pin --- drv/upgate.h | 2 ++ drv/upgate/upgate.c | 16 ++++++++++++---- upgate/application/main.c | 3 +++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/drv/upgate.h b/drv/upgate.h index d0c04782..1dcddb7a 100644 --- a/drv/upgate.h +++ b/drv/upgate.h @@ -14,6 +14,7 @@ */ #include +#include "gpio.h" #include "n25q128.h" //=========================== defines ========================================== @@ -28,6 +29,7 @@ typedef void (*db_upgate_reply_t)(const uint8_t *, size_t); ///< Transport agno typedef struct { db_upgate_reply_t reply; ///< Pointer to the function used to reply to the upgate script const n25q128_conf_t *n25q128_conf; + const gpio_t *prog; } db_upgate_conf_t; ///< FPGA bitstream update start notification packet diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 1bc4cad9..1eab0074 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -54,6 +54,9 @@ static db_upgate_vars_t _upgate_vars = { 0 }; void db_upgate_init(const db_upgate_conf_t *config) { _upgate_vars.config = config; + db_gpio_init(config->prog, DB_GPIO_OUT); + nrf_port[config->prog->port]->PIN_CNF[config->prog->pin] |= GPIO_PIN_CNF_DRIVE_H0D1 << GPIO_PIN_CNF_DRIVE_Pos; + db_gpio_set(_upgate_vars.config->prog); } void db_upgate_start(uint32_t chunk_count) { @@ -76,10 +79,6 @@ void db_upgate_start(uint32_t chunk_count) { void db_upgate_finish(void) { puts("Finishing upgate"); - // Put SPIM GPIOS as input otherwise the FPGA ends up in a broken state - db_gpio_init(_upgate_vars.config->n25q128_conf->mosi, DB_GPIO_IN); - db_gpio_init(_upgate_vars.config->n25q128_conf->sck, DB_GPIO_IN); - db_gpio_init(_upgate_vars.config->n25q128_conf->miso, DB_GPIO_IN); #if defined(UPGATE_USE_CRYPTO) uint8_t hash_result[DB_UPGATE_SHA256_LENGTH] = { 0 }; crypto_sha256(hash_result); @@ -89,6 +88,15 @@ void db_upgate_finish(void) { } #endif + // Put SPIM GPIOS as input otherwise the FPGA ends up in a broken state + db_gpio_init(_upgate_vars.config->n25q128_conf->mosi, DB_GPIO_IN); + db_gpio_init(_upgate_vars.config->n25q128_conf->sck, DB_GPIO_IN); + db_gpio_init(_upgate_vars.config->n25q128_conf->miso, DB_GPIO_IN); + + // Trigger FPGA program + db_gpio_clear(_upgate_vars.config->prog); + db_gpio_set(_upgate_vars.config->prog); + // TODO: Reset the FPGA } diff --git a/upgate/application/main.c b/upgate/application/main.c index 3215fb66..9ed5968f 100644 --- a/upgate/application/main.c +++ b/upgate/application/main.c @@ -36,11 +36,13 @@ static const gpio_t _sck_pin = { .port = 0, .pin = 23 }; static const gpio_t _miso_pin = { .port = 0, .pin = 22 }; static const gpio_t _mosi_pin = { .port = 0, .pin = 21 }; static const gpio_t _cs_pin = { .port = 0, .pin = 20 }; +static const gpio_t _prog_pin = { .port = 0, .pin = 19 }; #else static const gpio_t _sck_pin = { .port = 1, .pin = 15 }; static const gpio_t _miso_pin = { .port = 1, .pin = 14 }; static const gpio_t _mosi_pin = { .port = 1, .pin = 13 }; static const gpio_t _cs_pin = { .port = 1, .pin = 12 }; +static const gpio_t _prog_pin = { .port = 0, .pin = 11 }; #endif static const n25q128_conf_t _n25q128_conf = { @@ -67,6 +69,7 @@ static void _upgate_reply(const uint8_t *message, size_t len) { static const db_upgate_conf_t _upgate_config = { .reply = _upgate_reply, .n25q128_conf = &_n25q128_conf, + .prog = &_prog_pin, }; //================================ main ======================================== From b713cff9cd92326306683c824cd51c9ed8d4fbfb Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Sun, 17 Mar 2024 22:10:17 +0100 Subject: [PATCH 10/27] dist/scripts/upgate: refactor with independent compressed chunks --- dist/scripts/upgate/dotbot-upgate.py | 185 ++++++++++++++++++++------- 1 file changed, 141 insertions(+), 44 deletions(-) diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py index 7d35ea68..f33c2eaa 100755 --- a/dist/scripts/upgate/dotbot-upgate.py +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -3,8 +3,10 @@ import gzip import os import logging +import secrets import time +from dataclasses import dataclass from enum import Enum import click @@ -22,6 +24,7 @@ BAUDRATE = 1000000 CHUNK_SIZE = 128 +COMPRESSED_CHUNK_SIZE = 8192 PRIVATE_KEY_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "private_key") class MessageType(Enum): @@ -29,8 +32,28 @@ class MessageType(Enum): UPGATE_MESSAGE_TYPE_START = 0 UPGATE_MESSAGE_TYPE_START_ACK = 1 - UPGATE_MESSAGE_TYPE_CHUNK = 2 - UPGATE_MESSAGE_TYPE_CHUNK_ACK = 3 + UPGATE_MESSAGE_TYPE_PACKET = 2 + UPGATE_MESSAGE_TYPE_PACKET_ACK = 3 + UPGATE_MESSAGE_TYPE_FINALIZE = 4 + UPGATE_MESSAGE_TYPE_FINALIZE_ACK = 5 + +@dataclass +class RadioPacket: + """Class that holds radio packets.""" + + index: int + token: bytes + data: bytes + + +@dataclass +class DataChunk: + """Class that holds data chunks.""" + + index: int + dsize: int + csize: int + packets: bytes class DotBotUpgate: @@ -44,8 +67,10 @@ def __init__(self, port, baudrate, image, use_compression=False, secure=False): self.device_info = None self.device_info_received = False self.start_ack_received = False + self.finalize_ack_received = False self.image = image - self.last_acked_chunk = -1 + self.last_acked_token = -1 + self.chunks = [] # Just write a single byte to fake a DotBot gateway handshake self.serial.write(int(PROTOCOL_VERSION).to_bytes(length=1)) @@ -57,10 +82,67 @@ def on_byte_received(self, byte): return if payload[0] == MessageType.UPGATE_MESSAGE_TYPE_START_ACK.value: self.start_ack_received = True - elif payload[0] == MessageType.UPGATE_MESSAGE_TYPE_CHUNK_ACK.value: - self.last_acked_chunk = int.from_bytes(payload[1:5], byteorder="little") + if payload[0] == MessageType.UPGATE_MESSAGE_TYPE_FINALIZE_ACK.value: + self.finalize_ack_received = True + elif payload[0] == MessageType.UPGATE_MESSAGE_TYPE_PACKET_ACK.value: + self.last_acked_token = int.from_bytes(payload[1:5], byteorder="little") def init(self): + if self.use_compression is True: + chunks_count = int(len(self.image) / COMPRESSED_CHUNK_SIZE) + int(len(self.image) % COMPRESSED_CHUNK_SIZE != 0) + for chunk in range(chunks_count): + if chunk == chunks_count - 1: + compressed = gzip.compress( + self.image[chunk * COMPRESSED_CHUNK_SIZE:] + ) + dsize = len(self.image) % COMPRESSED_CHUNK_SIZE + else: + compressed = gzip.compress( + self.image[chunk * COMPRESSED_CHUNK_SIZE : (chunk + 1) * COMPRESSED_CHUNK_SIZE] + ) + dsize = COMPRESSED_CHUNK_SIZE + packets_count = int(len(compressed) / CHUNK_SIZE) + int(len(compressed) % CHUNK_SIZE != 0) + packets = [] + for packet_idx in range(packets_count): + if packet_idx == packets_count - 1: + packet_data = compressed[packet_idx * CHUNK_SIZE:] + else: + packet_data = compressed[packet_idx * CHUNK_SIZE : (packet_idx + 1) * CHUNK_SIZE] + packets.append( + RadioPacket(index=packet_idx, token=secrets.token_bytes(4), data=packet_data) + ) + self.chunks.append( + DataChunk( + index=chunk, + dsize=dsize, + csize=len(compressed), + packets=packets, + ) + ) + image_size = len(self.image) + compressed_size = sum([c.csize for c in self.chunks]) + print(f"Compression ratio: {(1 - compressed_size / image_size) * 100:.2f}% ({image_size}B -> {compressed_size}B)") + print(f"Compressed chunks ({COMPRESSED_CHUNK_SIZE}B): {len(self.chunks)}") + else: + chunks_count = int(len(self.image) / CHUNK_SIZE) + int(len(self.image) % CHUNK_SIZE != 0) + for chunk in range(chunks_count): + if chunk == chunks_count - 1: + data=self.image[chunks_count * CHUNK_SIZE:] + dsize = len(self.image) % CHUNK_SIZE + else: + data = self.image[chunk * CHUNK_SIZE : (chunk + 1) * CHUNK_SIZE] + dsize = CHUNK_SIZE + self.chunks.append( + DataChunk( + index=chunk, + dsize=dsize, + csize=dsize, + packets=[ + RadioPacket(index=0, token=secrets.token_bytes(4), data=data) + ], + ) + ) + print(f"Radio packets ({CHUNK_SIZE}B): {sum([len(c.packets) for c in self.chunks])}") if self.secure is True: digest = hashes.Hash(hashes.SHA256()) pos = 0 @@ -70,20 +152,18 @@ def init(self): fw_hash = digest.finalize() private_key_bytes = open(PRIVATE_KEY_PATH, "rb").read() private_key = Ed25519PrivateKey.from_private_bytes(private_key_bytes) + buffer = bytearray() buffer += int(MessageType.UPGATE_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 self.use_compression is True: - buffer += int(self.use_compression).to_bytes(length=1, byteorder="little") + buffer += len(self.image).to_bytes(length=4, byteorder="little") + buffer += int(self.use_compression).to_bytes(length=1, byteorder="little") if self.secure is True: buffer += fw_hash signature = private_key.sign(bytes(buffer[1:])) buffer += signature - print("Sending start update notification...") + print("Sending start upgate notification...") self.serial.write(hdlc_encode(buffer)) timeout = 0 # ms while self.start_ack_received is False and timeout < 60000: @@ -91,40 +171,52 @@ def init(self): time.sleep(0.01) return self.start_ack_received is True - def run(self): - data = self.image - if self.use_compression: - # Compress the image by blocks and pad it to the next block size - data = gzip.compress(data) - pad_length = CHUNK_SIZE - (len(data) % CHUNK_SIZE) - data += bytearray(b"\xff") * (pad_length + 1) - - # Send the compressed image by chunks - pos = 0 - progress = tqdm( - total=len(data), unit="B", unit_scale=False, colour="green", ncols=100 + def finalize(self): + buffer = bytearray() + buffer += int(MessageType.UPGATE_MESSAGE_TYPE_FINALIZE.value).to_bytes( + length=1, byteorder="little" ) - progress.set_description(f"Flashing compressed firmware ({int(len(data) / 1024)}kB)") - while pos + CHUNK_SIZE <= len(data) + 1: - chunk_index = int(pos / CHUNK_SIZE) - while self.last_acked_chunk != chunk_index: - buffer = bytearray() - buffer += int(MessageType.UPGATE_MESSAGE_TYPE_CHUNK.value).to_bytes( - length=1, byteorder="little" - ) - buffer += int(chunk_index).to_bytes(length=4, byteorder="little") - buffer += int((len(data) - 1) / CHUNK_SIZE).to_bytes( - length=4, byteorder="little" - ) - buffer += data[pos : pos + CHUNK_SIZE] - self.serial.write(hdlc_encode(buffer)) - time.sleep(0.005) - pos += CHUNK_SIZE - progress.update(CHUNK_SIZE) + buffer += int((len(self.image) - 1) / CHUNK_SIZE).to_bytes( + length=4, byteorder="little" + ) + print("Sending upgate finalize...") + self.serial.write(hdlc_encode(buffer)) + timeout = 0 # ms + while self.finalize_ack_received is False and timeout < 60000: + timeout += 1 + time.sleep(0.01) + return self.finalize_ack_received is True + + def send_packet(self, chunk_index, packet, packet_count): + while self.last_acked_token != int.from_bytes(packet.token, byteorder="little"): + buffer = bytearray() + buffer += int(MessageType.UPGATE_MESSAGE_TYPE_PACKET.value).to_bytes( + length=1, byteorder="little" + ) + buffer += int(chunk_index).to_bytes(length=4, byteorder="little") + buffer += packet.token + buffer += int(packet.index).to_bytes(length=1, byteorder="little") + buffer += int(packet_count).to_bytes(length=1, byteorder="little") + buffer += int(len(packet.data)).to_bytes(length=1, byteorder="little") + buffer += packet.data + self.serial.write(hdlc_encode(buffer)) + time.sleep(0.005) + + def transfer(self): + if self.use_compression is True: + data_size = sum([c.csize for c in self.chunks]) + else: + data_size = len(self.image) + progress = tqdm(total=data_size, unit="B", unit_scale=False, colour="green", ncols=100) + progress.set_description(f"Flashing compressed firmware ({int(data_size / 1024)}kB)") + for idx, chunk in enumerate(self.chunks): + for packet in chunk.packets: + self.send_packet(chunk.index, packet, len(chunk.packets)) + if idx < len(self.chunks) - 1: + progress.update(chunk.csize if self.use_compression is True else chunk.dsize) progress.update(1) progress.close() - @click.command() @click.option( "-p", @@ -161,7 +253,8 @@ def main(port, secure, use_compression, yes, bitstream): port, BAUDRATE, bytearray(bitstream.read()), - use_compression=use_compression + use_compression=use_compression, + secure=secure, ) except ( SerialInterfaceException, @@ -175,9 +268,13 @@ def main(port, secure, use_compression, yes, bitstream): click.confirm("Do you want to continue?", default=True, abort=True) ret = upgater.init() if ret is False: - print("Error: No start acknowledment received. Aborting.") + print("Error: No start acknowledgment received. Aborting.") + return + upgater.transfer() + ret = upgater.finalize() + if ret is False: + print("Error: No finalize acknowledgment received. Upgate failed.") return - upgater.run() print("Done") From f4c49a23faaa65376e996ba6af0118a5b1ef0d0f Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Sun, 17 Mar 2024 22:11:46 +0100 Subject: [PATCH 11/27] drv/upgate: use uzlib for decompression --- drv/drv.emProject | 4 +- drv/upgate.h | 23 +++++--- drv/upgate/upgate.c | 137 ++++++++++++++++++++++++++------------------ 3 files changed, 98 insertions(+), 66 deletions(-) diff --git a/drv/drv.emProject b/drv/drv.emProject index f3dd04d1..b507eb07 100644 --- a/drv/drv.emProject +++ b/drv/drv.emProject @@ -140,7 +140,7 @@ @@ -160,7 +160,7 @@ - + diff --git a/drv/upgate.h b/drv/upgate.h index 1dcddb7a..2a0797dc 100644 --- a/drv/upgate.h +++ b/drv/upgate.h @@ -34,7 +34,7 @@ typedef struct { ///< FPGA bitstream update start notification packet typedef struct __attribute__((packed, aligned(4))) { - uint32_t chunk_count; ///< Number of chunks + uint32_t bitstream_size; ///< Size of the bitstream in bytes bool use_compression; ///< True if bitstream is compressed #if defined(UPGATE_USE_CRYPTO) uint8_t hash[DB_UPGATE_SHA256_LENGTH]; ///< SHA256 hash of the bitsream @@ -44,17 +44,22 @@ typedef struct __attribute__((packed, aligned(4))) { ///< Firmware update packet typedef struct __attribute__((packed, aligned(4))) { - uint32_t index; ///< Index of the chunk - uint32_t chunk_count; ///< Total number of chunks - uint8_t upgate_chunk[DB_UPGATE_CHUNK_SIZE]; ///< Bytes array of the firmware chunk + uint32_t chunk_index; ///< Index of the chunk + uint32_t packet_token; ///< Random token of the packet + uint8_t packet_index; ///< Index of the packet in the chunk + uint8_t packet_count; ///< Number of packet composing the chunk + uint8_t packet_size; ///< Size of the packet + uint8_t data[DB_UPGATE_CHUNK_SIZE]; ///< Bytes array containing the chunk data } db_upgate_pkt_t; ///< Message type typedef enum { DB_UPGATE_MESSAGE_TYPE_START, DB_UPGATE_MESSAGE_TYPE_START_ACK, - DB_UPGATE_MESSAGE_TYPE_CHUNK, - DB_UPGATE_MESSAGE_TYPE_CHUNK_ACK, + DB_UPGATE_MESSAGE_TYPE_PACKET, + DB_UPGATE_MESSAGE_TYPE_PACKET_ACK, + DB_UPGATE_MESSAGE_TYPE_FINALIZE, + DB_UPGATE_MESSAGE_TYPE_FINALIZE_ACK, } db_upgate_message_type_t; //=========================== prototypes ======================================= @@ -69,7 +74,7 @@ void db_upgate_init(const db_upgate_conf_t *config); /** * @brief Start the upgate process */ -void db_upgate_start(uint32_t chunk_count); +void db_upgate_start(void); /** * @brief Finalize the upgate process @@ -77,11 +82,11 @@ void db_upgate_start(uint32_t chunk_count); void db_upgate_finish(void); /** - * @brief Write a chunk of the FPGA bistream to the external flash memory + * @brief Handle a received bitstream packet * * @param[in] pkt Pointer to the upgate packet */ -void db_upgate_write_chunk(const db_upgate_pkt_t *pkt); +void db_upgate_handle_packet(const db_upgate_pkt_t *pkt); /** * @brief Handle received upgate message diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 1eab0074..3dc1c798 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -16,8 +16,8 @@ #include #include "gpio.h" -#include "lz4.h" #include "n25q128.h" +#include "uzlib.h" #include "upgate.h" #if defined(UPGATE_USE_CRYPTO) @@ -28,22 +28,25 @@ //=========================== defines ========================================== -#define LZ4F_VERSION 100 -#define DECOMPRESS_BUFFER_SIZE (4096) -#define UPGATE_BASE_ADDRESS (0x00000000) +#define LZ4F_VERSION 100 +#define BUFFER_SIZE (8192) +#define UPGATE_BASE_ADDRESS (0x00000000) typedef struct { const db_upgate_conf_t *config; uint8_t reply_buffer[UINT8_MAX]; uint32_t target_partition; uint32_t addr; - uint32_t last_index_acked; + uint32_t last_packet_acked; + uint32_t bistream_size; uint8_t hash[DB_UPGATE_SHA256_LENGTH]; uint8_t read_buf[DB_UPGATE_CHUNK_SIZE * 2]; uint8_t write_buf[DB_UPGATE_CHUNK_SIZE * 2]; bool use_compression; - uint8_t write_buf_pos; - uint8_t decompress_buffer[DECOMPRESS_BUFFER_SIZE]; + uint8_t temp_buffer[BUFFER_SIZE]; + uint16_t compressed_length; + uint8_t decompressed_buffer[BUFFER_SIZE]; + struct uzlib_uncomp d; } db_upgate_vars_t; //=========================== variables ======================================== @@ -59,12 +62,11 @@ void db_upgate_init(const db_upgate_conf_t *config) { db_gpio_set(_upgate_vars.config->prog); } -void db_upgate_start(uint32_t chunk_count) { +void db_upgate_start(void) { n25q128_init(_upgate_vars.config->n25q128_conf); _upgate_vars.addr = UPGATE_BASE_ADDRESS; // Erase the corresponding sectors. - uint32_t total_bytes = chunk_count * DB_UPGATE_CHUNK_SIZE; - uint32_t sector_count = (total_bytes / N25Q128_SECTOR_SIZE) + 1; + uint32_t sector_count = (_upgate_vars.bistream_size / N25Q128_SECTOR_SIZE) + (_upgate_vars.bistream_size % N25Q128_SECTOR_SIZE != 0); printf("Sectors to erase: %u\n", sector_count); for (uint32_t sector = 0; sector < sector_count; sector++) { uint32_t addr = _upgate_vars.addr + sector * N25Q128_SECTOR_SIZE; @@ -72,7 +74,8 @@ void db_upgate_start(uint32_t chunk_count) { n25q128_sector_erase(addr); } puts(""); - _upgate_vars.last_index_acked = UINT32_MAX; + uzlib_init(); + _upgate_vars.last_packet_acked = UINT32_MAX; printf("Starting upgate at %p\n\n", _upgate_vars.addr); } @@ -93,40 +96,61 @@ void db_upgate_finish(void) { db_gpio_init(_upgate_vars.config->n25q128_conf->sck, DB_GPIO_IN); db_gpio_init(_upgate_vars.config->n25q128_conf->miso, DB_GPIO_IN); - // Trigger FPGA program + // Reset the FPGA by triggerring the FPGA prog pin db_gpio_clear(_upgate_vars.config->prog); db_gpio_set(_upgate_vars.config->prog); - - // TODO: Reset the FPGA } -void db_upgate_write_chunk(const db_upgate_pkt_t *pkt) { +void db_upgate_handle_packet(const db_upgate_pkt_t *pkt) { if (_upgate_vars.use_compression) { - int32_t decompressed_length = LZ4_decompress_safe( - (const char *)pkt->upgate_chunk, (char *)_upgate_vars.decompress_buffer, DB_UPGATE_CHUNK_SIZE, DECOMPRESS_BUFFER_SIZE); - assert(decompressed_length < DECOMPRESS_BUFFER_SIZE); - uint32_t decompress_buffer_pos = 0; - do { - uint16_t write_buf_available = (DB_UPGATE_CHUNK_SIZE * 2) - _upgate_vars.write_buf_pos; - memcpy(&_upgate_vars.write_buf[_upgate_vars.write_buf_pos], &_upgate_vars.decompress_buffer[decompress_buffer_pos], write_buf_available); - decompressed_length -= write_buf_available; - decompress_buffer_pos += write_buf_available; - _upgate_vars.write_buf_pos = 0; - } while (decompressed_length >= (int32_t)DB_UPGATE_CHUNK_SIZE * 2); - memcpy(&_upgate_vars.write_buf[_upgate_vars.write_buf_pos], &_upgate_vars.decompress_buffer[decompress_buffer_pos], decompressed_length); - _upgate_vars.write_buf_pos = decompressed_length; + uint8_t packet_size = pkt->packet_size; + _upgate_vars.compressed_length += packet_size; + if (pkt->packet_index == 0) { + memset(_upgate_vars.temp_buffer, 0, sizeof(_upgate_vars.temp_buffer)); + memset(_upgate_vars.decompressed_buffer, 0, sizeof(_upgate_vars.decompressed_buffer)); + uzlib_uncompress_init(&_upgate_vars.d, NULL, 0); + _upgate_vars.d.source = _upgate_vars.temp_buffer; + _upgate_vars.d.source_limit = _upgate_vars.temp_buffer + sizeof(_upgate_vars.temp_buffer); + _upgate_vars.d.source_read_cb = NULL; + _upgate_vars.d.dest_start = _upgate_vars.d.dest = _upgate_vars.decompressed_buffer; + } + memcpy(&_upgate_vars.temp_buffer[pkt->packet_index * DB_UPGATE_CHUNK_SIZE], pkt->data, packet_size); + if (pkt->packet_index == pkt->packet_count - 1) { + _upgate_vars.d.source_limit = _upgate_vars.temp_buffer + _upgate_vars.compressed_length; + int ret = uzlib_gzip_parse_header(&_upgate_vars.d); + assert(ret == TINF_OK); + uint16_t remaining_data = BUFFER_SIZE; + while (remaining_data) { + uint8_t block_len = remaining_data < DB_UPGATE_CHUNK_SIZE ? remaining_data : DB_UPGATE_CHUNK_SIZE; + _upgate_vars.d.dest_limit = _upgate_vars.d.dest + block_len; + int ret = uzlib_uncompress(&_upgate_vars.d); + if (ret != TINF_OK) { + break; + } + remaining_data -= block_len; + } + uint32_t base_addr = _upgate_vars.addr + pkt->chunk_index * BUFFER_SIZE; + for (uint32_t block = 0; block < BUFFER_SIZE / N25Q128_PAGE_SIZE; block++) { + printf("Programming %d bytes at %p\n", N25Q128_PAGE_SIZE, base_addr + block * N25Q128_PAGE_SIZE); + n25q128_program_page(base_addr + block * N25Q128_PAGE_SIZE, &_upgate_vars.decompressed_buffer[block * N25Q128_PAGE_SIZE], N25Q128_PAGE_SIZE); + n25q128_read(base_addr + block * N25Q128_PAGE_SIZE, _upgate_vars.temp_buffer, N25Q128_PAGE_SIZE); + if (memcmp(&_upgate_vars.decompressed_buffer[block * N25Q128_PAGE_SIZE], _upgate_vars.temp_buffer, N25Q128_PAGE_SIZE) != 0) { + puts("packet doesn't match!!"); + } + } + } } else { - memcpy(&_upgate_vars.write_buf[(pkt->index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); - if (pkt->index % 2 == 0) { + memcpy(&_upgate_vars.write_buf[(pkt->chunk_index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->data, DB_UPGATE_CHUNK_SIZE); + if (pkt->chunk_index % 2 == 0) { return; } - } - uint32_t addr = _upgate_vars.addr + (pkt->index - 1) * DB_UPGATE_CHUNK_SIZE; - printf("Programming 256 bytes at %p\n", addr); - n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); - n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); - if (memcmp(_upgate_vars.write_buf, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { - puts("packet doesn't match!!"); + uint32_t addr = _upgate_vars.addr + (pkt->chunk_index - 1) * DB_UPGATE_CHUNK_SIZE; + printf("Programming 256 bytes at %p\n", addr); + n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); + n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); + if (memcmp(_upgate_vars.write_buf, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { + puts("packet doesn't match!!"); + } } } @@ -144,37 +168,40 @@ void db_upgate_handle_message(const uint8_t *message) { memcpy(_upgate_vars.hash, hash, DB_UPGATE_SHA256_LENGTH); crypto_sha256_init(); #endif - _upgate_vars.use_compression = upgate_start->use_compression; - db_upgate_start(upgate_start->chunk_count); + bool use_compression = upgate_start->use_compression; + uint32_t bitstream_size = upgate_start->bitstream_size; + _upgate_vars.use_compression = use_compression; + _upgate_vars.bistream_size = bitstream_size; + db_upgate_start(); // Acknowledge the update start _upgate_vars.reply_buffer[0] = DB_UPGATE_MESSAGE_TYPE_START_ACK; _upgate_vars.config->reply(_upgate_vars.reply_buffer, sizeof(db_upgate_message_type_t)); } break; - case DB_UPGATE_MESSAGE_TYPE_CHUNK: + case DB_UPGATE_MESSAGE_TYPE_PACKET: { - const db_upgate_pkt_t *upgate_pkt = (const db_upgate_pkt_t *)&message[1]; - const uint32_t chunk_index = upgate_pkt->index; - const uint32_t chunk_count = upgate_pkt->chunk_count; - - if (_upgate_vars.last_index_acked != chunk_index) { + const db_upgate_pkt_t *upgate_pkt = (const db_upgate_pkt_t *)&message[1]; + const uint32_t token = upgate_pkt->packet_token; + if (_upgate_vars.last_packet_acked != token) { // Skip writing the chunk if already acked - db_upgate_write_chunk(upgate_pkt); + db_upgate_handle_packet(upgate_pkt); #if defined(UPGATE_USE_CRYPTO) crypto_sha256_update((const uint8_t *)upgate_pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); #endif } - // Acknowledge the received chunk - _upgate_vars.reply_buffer[0] = DB_UPGATE_MESSAGE_TYPE_CHUNK_ACK; - memcpy(&_upgate_vars.reply_buffer[1], &chunk_index, sizeof(uint32_t)); + // Acknowledge the received packet + _upgate_vars.reply_buffer[0] = DB_UPGATE_MESSAGE_TYPE_PACKET_ACK; + memcpy(&_upgate_vars.reply_buffer[1], &token, sizeof(uint32_t)); _upgate_vars.config->reply(_upgate_vars.reply_buffer, sizeof(db_upgate_message_type_t) + sizeof(uint32_t)); - _upgate_vars.last_index_acked = chunk_index; - - if (chunk_index == chunk_count - 1) { - puts(""); - db_upgate_finish(); - } + _upgate_vars.last_packet_acked = token; } break; + case DB_UPGATE_MESSAGE_TYPE_FINALIZE: + puts(""); + db_upgate_finish(); + // Acknowledge the finalize step + _upgate_vars.reply_buffer[0] = DB_UPGATE_MESSAGE_TYPE_FINALIZE_ACK; + _upgate_vars.config->reply(_upgate_vars.reply_buffer, sizeof(db_upgate_message_type_t)); + break; default: break; } From 0d3dd14387c85b937283304bea89f19265fb3b00 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 19 Mar 2024 09:09:04 +0100 Subject: [PATCH 12/27] dist/scripts/upgate: add lz4 compression support --- dist/scripts/upgate/dotbot-upgate.py | 68 ++++++++++++++++++---------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py index f33c2eaa..f74146e7 100755 --- a/dist/scripts/upgate/dotbot-upgate.py +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -10,6 +10,7 @@ from enum import Enum import click +import lz4.block import serial import structlog @@ -37,6 +38,22 @@ class MessageType(Enum): UPGATE_MESSAGE_TYPE_FINALIZE = 4 UPGATE_MESSAGE_TYPE_FINALIZE_ACK = 5 + +class CompressionMode(Enum): + """Types of compression.""" + + UPGATE_COMPRESSION_NONE = 0 + UPGATE_COMPRESSION_GZIP = 1 + UPGATE_COMPRESSION_LZ4 = 2 + + +COMPRESSION_MODES_MAP = { + "none": CompressionMode.UPGATE_COMPRESSION_NONE, + "gzip": CompressionMode.UPGATE_COMPRESSION_GZIP, + "lz4": CompressionMode.UPGATE_COMPRESSION_LZ4, +} + + @dataclass class RadioPacket: """Class that holds radio packets.""" @@ -59,10 +76,10 @@ class DataChunk: class DotBotUpgate: """Class used to send an FPGA bitstream.""" - def __init__(self, port, baudrate, image, use_compression=False, secure=False): + def __init__(self, port, baudrate, image, compression="none", secure=False): self.serial = SerialInterface(port, baudrate, self.on_byte_received) self.hdlc_handler = HDLCHandler() - self.use_compression = use_compression + self.compression = compression self.secure = secure self.device_info = None self.device_info_received = False @@ -88,19 +105,20 @@ def on_byte_received(self, byte): self.last_acked_token = int.from_bytes(payload[1:5], byteorder="little") def init(self): - if self.use_compression is True: + if self.compression != "none": chunks_count = int(len(self.image) / COMPRESSED_CHUNK_SIZE) + int(len(self.image) % COMPRESSED_CHUNK_SIZE != 0) for chunk in range(chunks_count): if chunk == chunks_count - 1: - compressed = gzip.compress( - self.image[chunk * COMPRESSED_CHUNK_SIZE:] - ) dsize = len(self.image) % COMPRESSED_CHUNK_SIZE else: - compressed = gzip.compress( - self.image[chunk * COMPRESSED_CHUNK_SIZE : (chunk + 1) * COMPRESSED_CHUNK_SIZE] - ) dsize = COMPRESSED_CHUNK_SIZE + data = self.image[chunk * COMPRESSED_CHUNK_SIZE : chunk * COMPRESSED_CHUNK_SIZE + dsize] + if self.compression == "gzip": + compressed = gzip.compress(data) + elif self.compression == "lz4": + compressed = lz4.block.compress(data, mode="high_compression", store_size=False) + else: + compressed = [] packets_count = int(len(compressed) / CHUNK_SIZE) + int(len(compressed) % CHUNK_SIZE != 0) packets = [] for packet_idx in range(packets_count): @@ -158,7 +176,7 @@ def init(self): length=1, byteorder="little" ) buffer += len(self.image).to_bytes(length=4, byteorder="little") - buffer += int(self.use_compression).to_bytes(length=1, byteorder="little") + buffer += int(COMPRESSION_MODES_MAP[self.compression].value).to_bytes(length=1, byteorder="little") if self.secure is True: buffer += fw_hash signature = private_key.sign(bytes(buffer[1:])) @@ -187,34 +205,33 @@ def finalize(self): time.sleep(0.01) return self.finalize_ack_received is True - def send_packet(self, chunk_index, packet, packet_count): + def send_packet(self, chunk, packet): while self.last_acked_token != int.from_bytes(packet.token, byteorder="little"): buffer = bytearray() buffer += int(MessageType.UPGATE_MESSAGE_TYPE_PACKET.value).to_bytes( length=1, byteorder="little" ) - buffer += int(chunk_index).to_bytes(length=4, byteorder="little") + buffer += int(chunk.index).to_bytes(length=4, byteorder="little") buffer += packet.token + buffer += int(chunk.dsize).to_bytes(length=2, byteorder="little") buffer += int(packet.index).to_bytes(length=1, byteorder="little") - buffer += int(packet_count).to_bytes(length=1, byteorder="little") + buffer += int(len(chunk.packets)).to_bytes(length=1, byteorder="little") buffer += int(len(packet.data)).to_bytes(length=1, byteorder="little") buffer += packet.data self.serial.write(hdlc_encode(buffer)) time.sleep(0.005) def transfer(self): - if self.use_compression is True: + if self.compression in ["gzip", "lz4"]: data_size = sum([c.csize for c in self.chunks]) else: data_size = len(self.image) - progress = tqdm(total=data_size, unit="B", unit_scale=False, colour="green", ncols=100) + progress = tqdm(range(0, data_size), unit="B", unit_scale=False, colour="green", ncols=100) progress.set_description(f"Flashing compressed firmware ({int(data_size / 1024)}kB)") - for idx, chunk in enumerate(self.chunks): + for chunk in self.chunks: for packet in chunk.packets: - self.send_packet(chunk.index, packet, len(chunk.packets)) - if idx < len(self.chunks) - 1: - progress.update(chunk.csize if self.use_compression is True else chunk.dsize) - progress.update(1) + self.send_packet(chunk, packet) + progress.update(chunk.dsize if self.compression == "none" else chunk.csize) progress.close() @click.command() @@ -232,9 +249,10 @@ def transfer(self): ) @click.option( "-c", - "--use-compression", - is_flag=True, - help="Compress the bitstream before sending it.", + "--compression", + type=click.Choice(['none', 'gzip', 'lz4']), + default="none", + help="Bitstream compression mode.", ) @click.option( "-y", @@ -243,7 +261,7 @@ def transfer(self): help="Continue the upgate without prompt.", ) @click.argument("bitstream", type=click.File(mode="rb", lazy=True)) -def main(port, secure, use_compression, yes, bitstream): +def main(port, secure, compression, yes, bitstream): # Disable logging configure in PyDotBot structlog.configure( wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL), @@ -253,7 +271,7 @@ def main(port, secure, use_compression, yes, bitstream): port, BAUDRATE, bytearray(bitstream.read()), - use_compression=use_compression, + compression=compression, secure=secure, ) except ( From a58e8b27f6362784b87ad1a930f648d4d41c2eeb Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 19 Mar 2024 11:36:10 +0100 Subject: [PATCH 13/27] drv/upgate: add support for lz4 compression --- drv/drv.emProject | 2 +- drv/upgate.h | 12 +++++-- drv/upgate/upgate.c | 86 ++++++++++++++++++++++++++------------------- 3 files changed, 60 insertions(+), 40 deletions(-) diff --git a/drv/drv.emProject b/drv/drv.emProject index b507eb07..65de3591 100644 --- a/drv/drv.emProject +++ b/drv/drv.emProject @@ -140,7 +140,7 @@ diff --git a/drv/upgate.h b/drv/upgate.h index 2a0797dc..1e0cc3df 100644 --- a/drv/upgate.h +++ b/drv/upgate.h @@ -25,6 +25,13 @@ typedef void (*db_upgate_reply_t)(const uint8_t *, size_t); ///< Transport agnostic function used to reply to the flasher script +///< Compression modes +typedef enum { + DB_UPGATE_COMPRESSION_NONE, + DB_UPGATE_COMPRESSION_GZIP, + DB_UPGATE_COMPRESSION_LZ4, +} db_upgate_compression_mode_t; + ///< FPGA bitstream update configuration typedef struct { db_upgate_reply_t reply; ///< Pointer to the function used to reply to the upgate script @@ -34,8 +41,8 @@ typedef struct { ///< FPGA bitstream update start notification packet typedef struct __attribute__((packed, aligned(4))) { - uint32_t bitstream_size; ///< Size of the bitstream in bytes - bool use_compression; ///< True if bitstream is compressed + uint32_t bitstream_size; ///< Size of the bitstream in bytes + db_upgate_compression_mode_t compression; ///< Compression mode used #if defined(UPGATE_USE_CRYPTO) uint8_t hash[DB_UPGATE_SHA256_LENGTH]; ///< SHA256 hash of the bitsream uint8_t signature[DB_UPGATE_SIGNATURE_LENGTH]; ///< Signature of the bitstream hash @@ -46,6 +53,7 @@ typedef struct __attribute__((packed, aligned(4))) { typedef struct __attribute__((packed, aligned(4))) { uint32_t chunk_index; ///< Index of the chunk uint32_t packet_token; ///< Random token of the packet + uint16_t original_size; ///< Original size uint8_t packet_index; ///< Index of the packet in the chunk uint8_t packet_count; ///< Number of packet composing the chunk uint8_t packet_size; ///< Size of the packet diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 3dc1c798..2492197b 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -17,6 +17,7 @@ #include "gpio.h" #include "n25q128.h" +#include "lz4.h" #include "uzlib.h" #include "upgate.h" @@ -33,20 +34,20 @@ #define UPGATE_BASE_ADDRESS (0x00000000) typedef struct { - const db_upgate_conf_t *config; - uint8_t reply_buffer[UINT8_MAX]; - uint32_t target_partition; - uint32_t addr; - uint32_t last_packet_acked; - uint32_t bistream_size; - uint8_t hash[DB_UPGATE_SHA256_LENGTH]; - uint8_t read_buf[DB_UPGATE_CHUNK_SIZE * 2]; - uint8_t write_buf[DB_UPGATE_CHUNK_SIZE * 2]; - bool use_compression; - uint8_t temp_buffer[BUFFER_SIZE]; - uint16_t compressed_length; - uint8_t decompressed_buffer[BUFFER_SIZE]; - struct uzlib_uncomp d; + const db_upgate_conf_t *config; + uint8_t reply_buffer[UINT8_MAX]; + uint32_t target_partition; + uint32_t addr; + uint32_t last_packet_acked; + uint32_t bistream_size; + uint8_t hash[DB_UPGATE_SHA256_LENGTH]; + uint8_t read_buf[DB_UPGATE_CHUNK_SIZE * 2]; + uint8_t write_buf[DB_UPGATE_CHUNK_SIZE * 2]; + db_upgate_compression_mode_t compression; + uint8_t temp_buffer[BUFFER_SIZE]; + uint16_t compressed_length; + uint8_t decompressed_buffer[BUFFER_SIZE]; + struct uzlib_uncomp d; } db_upgate_vars_t; //=========================== variables ======================================== @@ -102,34 +103,45 @@ void db_upgate_finish(void) { } void db_upgate_handle_packet(const db_upgate_pkt_t *pkt) { - if (_upgate_vars.use_compression) { + if (_upgate_vars.compression != DB_UPGATE_COMPRESSION_NONE) { uint8_t packet_size = pkt->packet_size; _upgate_vars.compressed_length += packet_size; if (pkt->packet_index == 0) { memset(_upgate_vars.temp_buffer, 0, sizeof(_upgate_vars.temp_buffer)); - memset(_upgate_vars.decompressed_buffer, 0, sizeof(_upgate_vars.decompressed_buffer)); - uzlib_uncompress_init(&_upgate_vars.d, NULL, 0); - _upgate_vars.d.source = _upgate_vars.temp_buffer; - _upgate_vars.d.source_limit = _upgate_vars.temp_buffer + sizeof(_upgate_vars.temp_buffer); - _upgate_vars.d.source_read_cb = NULL; - _upgate_vars.d.dest_start = _upgate_vars.d.dest = _upgate_vars.decompressed_buffer; + memset(_upgate_vars.decompressed_buffer, 0xff, sizeof(_upgate_vars.decompressed_buffer)); + if (_upgate_vars.compression == DB_UPGATE_COMPRESSION_GZIP) { + uzlib_uncompress_init(&_upgate_vars.d, NULL, 0); + _upgate_vars.d.source = _upgate_vars.temp_buffer; + _upgate_vars.d.source_limit = _upgate_vars.temp_buffer + sizeof(_upgate_vars.temp_buffer); + _upgate_vars.d.source_read_cb = NULL; + _upgate_vars.d.dest_start = _upgate_vars.d.dest = _upgate_vars.decompressed_buffer; + } } memcpy(&_upgate_vars.temp_buffer[pkt->packet_index * DB_UPGATE_CHUNK_SIZE], pkt->data, packet_size); if (pkt->packet_index == pkt->packet_count - 1) { - _upgate_vars.d.source_limit = _upgate_vars.temp_buffer + _upgate_vars.compressed_length; - int ret = uzlib_gzip_parse_header(&_upgate_vars.d); - assert(ret == TINF_OK); - uint16_t remaining_data = BUFFER_SIZE; - while (remaining_data) { - uint8_t block_len = remaining_data < DB_UPGATE_CHUNK_SIZE ? remaining_data : DB_UPGATE_CHUNK_SIZE; - _upgate_vars.d.dest_limit = _upgate_vars.d.dest + block_len; - int ret = uzlib_uncompress(&_upgate_vars.d); - if (ret != TINF_OK) { - break; + if (_upgate_vars.compression == DB_UPGATE_COMPRESSION_GZIP) { + _upgate_vars.d.source_limit = _upgate_vars.temp_buffer + _upgate_vars.compressed_length; + int ret = uzlib_gzip_parse_header(&_upgate_vars.d); + assert(ret == TINF_OK); + uint16_t remaining_data = BUFFER_SIZE; + while (remaining_data) { + uint8_t block_len = remaining_data < DB_UPGATE_CHUNK_SIZE ? remaining_data : DB_UPGATE_CHUNK_SIZE; + _upgate_vars.d.dest_limit = _upgate_vars.d.dest + block_len; + int ret = uzlib_uncompress(&_upgate_vars.d); + if (ret != TINF_OK) { + break; + } + remaining_data -= block_len; } - remaining_data -= block_len; + } else if (_upgate_vars.compression == DB_UPGATE_COMPRESSION_LZ4) { + const uint32_t decompressed_len = LZ4_decompress_safe_partial((const char *)_upgate_vars.temp_buffer, (char *)_upgate_vars.decompressed_buffer, _upgate_vars.compressed_length, pkt->original_size, BUFFER_SIZE); + (void)decompressed_len; + assert(decompressed_len == pkt->original_size); + } else { // Invalid compression + assert(0); } - uint32_t base_addr = _upgate_vars.addr + pkt->chunk_index * BUFFER_SIZE; + _upgate_vars.compressed_length = 0; + uint32_t base_addr = _upgate_vars.addr + pkt->chunk_index * BUFFER_SIZE; for (uint32_t block = 0; block < BUFFER_SIZE / N25Q128_PAGE_SIZE; block++) { printf("Programming %d bytes at %p\n", N25Q128_PAGE_SIZE, base_addr + block * N25Q128_PAGE_SIZE); n25q128_program_page(base_addr + block * N25Q128_PAGE_SIZE, &_upgate_vars.decompressed_buffer[block * N25Q128_PAGE_SIZE], N25Q128_PAGE_SIZE); @@ -168,10 +180,10 @@ void db_upgate_handle_message(const uint8_t *message) { memcpy(_upgate_vars.hash, hash, DB_UPGATE_SHA256_LENGTH); crypto_sha256_init(); #endif - bool use_compression = upgate_start->use_compression; - uint32_t bitstream_size = upgate_start->bitstream_size; - _upgate_vars.use_compression = use_compression; - _upgate_vars.bistream_size = bitstream_size; + db_upgate_compression_mode_t compression = upgate_start->compression; + uint32_t bitstream_size = upgate_start->bitstream_size; + _upgate_vars.compression = compression; + _upgate_vars.bistream_size = bitstream_size; db_upgate_start(); // Acknowledge the update start _upgate_vars.reply_buffer[0] = DB_UPGATE_MESSAGE_TYPE_START_ACK; From d79cec3f44da3267d54131df63cf87b11ebf8664 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 19 Mar 2024 14:46:29 +0100 Subject: [PATCH 14/27] dist/scripts/upgate: refactor to fix crypto --- dist/scripts/upgate/dotbot-upgate.py | 56 +++++++++++++--------------- dist/scripts/upgate/generate_keys.py | 2 +- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py index f74146e7..bac5a8dc 100755 --- a/dist/scripts/upgate/dotbot-upgate.py +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -105,7 +105,29 @@ def on_byte_received(self, byte): self.last_acked_token = int.from_bytes(payload[1:5], byteorder="little") def init(self): - if self.compression != "none": + private_key_bytes = open(PRIVATE_KEY_PATH, "rb").read() + private_key = Ed25519PrivateKey.from_private_bytes(private_key_bytes) + digest = hashes.Hash(hashes.SHA256()) + if self.compression == "none": + chunks_count = int(len(self.image) / CHUNK_SIZE) + int(len(self.image) % CHUNK_SIZE != 0) + for chunk in range(chunks_count): + if chunk == chunks_count - 1: + dsize = len(self.image) % CHUNK_SIZE + else: + dsize = CHUNK_SIZE + data = self.image[chunk * CHUNK_SIZE : chunk * CHUNK_SIZE + dsize] + digest.update(data) + self.chunks.append( + DataChunk( + index=chunk, + dsize=dsize, + csize=dsize, + packets=[ + RadioPacket(index=0, token=secrets.token_bytes(4), data=data) + ], + ) + ) + else: chunks_count = int(len(self.image) / COMPRESSED_CHUNK_SIZE) + int(len(self.image) % COMPRESSED_CHUNK_SIZE != 0) for chunk in range(chunks_count): if chunk == chunks_count - 1: @@ -113,6 +135,7 @@ def init(self): else: dsize = COMPRESSED_CHUNK_SIZE data = self.image[chunk * COMPRESSED_CHUNK_SIZE : chunk * COMPRESSED_CHUNK_SIZE + dsize] + digest.update(data) if self.compression == "gzip": compressed = gzip.compress(data) elif self.compression == "lz4": @@ -141,35 +164,8 @@ def init(self): compressed_size = sum([c.csize for c in self.chunks]) print(f"Compression ratio: {(1 - compressed_size / image_size) * 100:.2f}% ({image_size}B -> {compressed_size}B)") print(f"Compressed chunks ({COMPRESSED_CHUNK_SIZE}B): {len(self.chunks)}") - else: - chunks_count = int(len(self.image) / CHUNK_SIZE) + int(len(self.image) % CHUNK_SIZE != 0) - for chunk in range(chunks_count): - if chunk == chunks_count - 1: - data=self.image[chunks_count * CHUNK_SIZE:] - dsize = len(self.image) % CHUNK_SIZE - else: - data = self.image[chunk * CHUNK_SIZE : (chunk + 1) * CHUNK_SIZE] - dsize = CHUNK_SIZE - self.chunks.append( - DataChunk( - index=chunk, - dsize=dsize, - csize=dsize, - packets=[ - RadioPacket(index=0, token=secrets.token_bytes(4), data=data) - ], - ) - ) print(f"Radio packets ({CHUNK_SIZE}B): {sum([len(c.packets) for c in self.chunks])}") - if self.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) + fw_hash = digest.finalize() buffer = bytearray() buffer += int(MessageType.UPGATE_MESSAGE_TYPE_START.value).to_bytes( @@ -231,7 +227,7 @@ def transfer(self): for chunk in self.chunks: for packet in chunk.packets: self.send_packet(chunk, packet) - progress.update(chunk.dsize if self.compression == "none" else chunk.csize) + progress.update(chunk.csize) progress.close() @click.command() diff --git a/dist/scripts/upgate/generate_keys.py b/dist/scripts/upgate/generate_keys.py index 124e8c6e..b266ca51 100755 --- a/dist/scripts/upgate/generate_keys.py +++ b/dist/scripts/upgate/generate_keys.py @@ -11,7 +11,7 @@ ) 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") +PUBLIC_KEY_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../../drv/upgate/public_key.h") HEADER_FORMAT = """/* * PLEASE DON'T EDIT From d351f89f4308e08a1188051485442431adc726b0 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 19 Mar 2024 14:46:48 +0100 Subject: [PATCH 15/27] drv/upgate: fix crypto support --- drv/upgate.h | 2 +- drv/upgate/public_key.h | 2 +- drv/upgate/upgate.c | 35 ++++++++++++++++++++++------------- nrf52833dk.emProject | 2 +- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/drv/upgate.h b/drv/upgate.h index 1e0cc3df..42267bbe 100644 --- a/drv/upgate.h +++ b/drv/upgate.h @@ -40,7 +40,7 @@ typedef struct { } db_upgate_conf_t; ///< FPGA bitstream update start notification packet -typedef struct __attribute__((packed, aligned(4))) { +typedef struct __attribute__((packed)) { uint32_t bitstream_size; ///< Size of the bitstream in bytes db_upgate_compression_mode_t compression; ///< Compression mode used #if defined(UPGATE_USE_CRYPTO) diff --git a/drv/upgate/public_key.h b/drv/upgate/public_key.h index 572662b7..67215561 100644 --- a/drv/upgate/public_key.h +++ b/drv/upgate/public_key.h @@ -10,7 +10,7 @@ #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 + 0xd4, 0x0b, 0x70, 0x94, 0xe5, 0x71, 0x39, 0x26, 0x8b, 0x88, 0xf8, 0x17, 0x78, 0xcc, 0x24, 0x13, 0x7e, 0xfe, 0x93, 0xf4, 0x31, 0xeb, 0x17, 0xbd, 0x44, 0xa9, 0xdd, 0xe9, 0x96, 0xaa, 0x26, 0x55 }; #endif /* __PUBLIC_KEY_H */ diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 2492197b..0b7ec373 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -81,17 +81,18 @@ void db_upgate_start(void) { } void db_upgate_finish(void) { - puts("Finishing upgate"); - #if defined(UPGATE_USE_CRYPTO) uint8_t hash_result[DB_UPGATE_SHA256_LENGTH] = { 0 }; crypto_sha256(hash_result); if (memcmp(hash_result, _upgate_vars.hash, DB_UPGATE_SHA256_LENGTH) != 0) { + puts("Bitstream hashes don't match!"); return; } + puts("Bitstream hashes match!"); #endif + puts("Finishing upgate"); // Put SPIM GPIOS as input otherwise the FPGA ends up in a broken state db_gpio_init(_upgate_vars.config->n25q128_conf->mosi, DB_GPIO_IN); db_gpio_init(_upgate_vars.config->n25q128_conf->sck, DB_GPIO_IN); @@ -142,7 +143,7 @@ void db_upgate_handle_packet(const db_upgate_pkt_t *pkt) { } _upgate_vars.compressed_length = 0; uint32_t base_addr = _upgate_vars.addr + pkt->chunk_index * BUFFER_SIZE; - for (uint32_t block = 0; block < BUFFER_SIZE / N25Q128_PAGE_SIZE; block++) { + for (uint32_t block = 0; block < pkt->original_size / N25Q128_PAGE_SIZE; block++) { printf("Programming %d bytes at %p\n", N25Q128_PAGE_SIZE, base_addr + block * N25Q128_PAGE_SIZE); n25q128_program_page(base_addr + block * N25Q128_PAGE_SIZE, &_upgate_vars.decompressed_buffer[block * N25Q128_PAGE_SIZE], N25Q128_PAGE_SIZE); n25q128_read(base_addr + block * N25Q128_PAGE_SIZE, _upgate_vars.temp_buffer, N25Q128_PAGE_SIZE); @@ -150,19 +151,27 @@ void db_upgate_handle_packet(const db_upgate_pkt_t *pkt) { puts("packet doesn't match!!"); } } +#if defined(UPGATE_USE_CRYPTO) + crypto_sha256_update((const uint8_t *)_upgate_vars.decompressed_buffer, pkt->original_size); +#endif } } else { - memcpy(&_upgate_vars.write_buf[(pkt->chunk_index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->data, DB_UPGATE_CHUNK_SIZE); - if (pkt->chunk_index % 2 == 0) { + memcpy(&_upgate_vars.write_buf[(pkt->chunk_index % 2) * DB_UPGATE_CHUNK_SIZE], pkt->data, pkt->original_size); + uint32_t chunk_count = (_upgate_vars.bistream_size / DB_UPGATE_CHUNK_SIZE) + ((_upgate_vars.bistream_size % DB_UPGATE_CHUNK_SIZE) != 0); + if (pkt->chunk_index % 2 == 0 && pkt->chunk_index != chunk_count - 1) { return; } - uint32_t addr = _upgate_vars.addr + (pkt->chunk_index - 1) * DB_UPGATE_CHUNK_SIZE; - printf("Programming 256 bytes at %p\n", addr); - n25q128_program_page(addr, _upgate_vars.write_buf, DB_UPGATE_CHUNK_SIZE * 2); - n25q128_read(addr, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2); - if (memcmp(_upgate_vars.write_buf, _upgate_vars.read_buf, DB_UPGATE_CHUNK_SIZE * 2) != 0) { + uint32_t addr = _upgate_vars.addr + (pkt->chunk_index - 1) * DB_UPGATE_CHUNK_SIZE; + size_t data_size = (pkt->chunk_index == chunk_count - 1 && chunk_count % 2 == 1) ? pkt->original_size : DB_UPGATE_CHUNK_SIZE + pkt->original_size; + printf("Programming %d bytes at %p\n", data_size, addr); + n25q128_program_page(addr, _upgate_vars.write_buf, data_size); + n25q128_read(addr, _upgate_vars.read_buf, data_size); + if (memcmp(_upgate_vars.write_buf, _upgate_vars.read_buf, data_size) != 0) { puts("packet doesn't match!!"); } +#if defined(UPGATE_USE_CRYPTO) + crypto_sha256_update((const uint8_t *)_upgate_vars.read_buf, data_size); +#endif } } @@ -173,10 +182,13 @@ void db_upgate_handle_message(const uint8_t *message) { { const db_upgate_start_notification_t *upgate_start = (const db_upgate_start_notification_t *)&message[1]; #if defined(UPGATE_USE_CRYPTO) + printf("Verifying ed25519 signature: "); const uint8_t *hash = upgate_start->hash; if (!crypto_ed25519_verify(upgate_start->signature, DB_UPGATE_SIGNATURE_LENGTH, (const uint8_t *)upgate_start, sizeof(db_upgate_start_notification_t) - DB_UPGATE_SIGNATURE_LENGTH, public_key)) { + puts("Failed!"); break; } + puts("Success!"); memcpy(_upgate_vars.hash, hash, DB_UPGATE_SHA256_LENGTH); crypto_sha256_init(); #endif @@ -196,9 +208,6 @@ void db_upgate_handle_message(const uint8_t *message) { if (_upgate_vars.last_packet_acked != token) { // Skip writing the chunk if already acked db_upgate_handle_packet(upgate_pkt); -#if defined(UPGATE_USE_CRYPTO) - crypto_sha256_update((const uint8_t *)upgate_pkt->upgate_chunk, DB_UPGATE_CHUNK_SIZE); -#endif } // Acknowledge the received packet diff --git a/nrf52833dk.emProject b/nrf52833dk.emProject index 86eb1d35..0ca65b25 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;OTA_USE_CRYPTO" + c_preprocessor_definitions="ARM_MATH_CM4;NRF52833_XXAA;__nRF_FAMILY;CONFIG_NFCT_PINS_AS_GPIOS;FLASH_PLACEMENT=1;BOARD_NRF52833DK;OTA_USE_CRYPTO;UPGATE_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__" From f72bfd3c352ac69f29482e662de4fef383595179 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 20 Mar 2024 15:06:29 +0100 Subject: [PATCH 16/27] drv/upgate: use defines instead of enum for compression modes --- drv/upgate.h | 12 +++++------- drv/upgate/upgate.c | 36 ++++++++++++++++++------------------ 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/drv/upgate.h b/drv/upgate.h index 42267bbe..8e67514b 100644 --- a/drv/upgate.h +++ b/drv/upgate.h @@ -26,11 +26,9 @@ typedef void (*db_upgate_reply_t)(const uint8_t *, size_t); ///< Transport agnostic function used to reply to the flasher script ///< Compression modes -typedef enum { - DB_UPGATE_COMPRESSION_NONE, - DB_UPGATE_COMPRESSION_GZIP, - DB_UPGATE_COMPRESSION_LZ4, -} db_upgate_compression_mode_t; +#define DB_UPGATE_COMPRESSION_NONE 0x00 +#define DB_UPGATE_COMPRESSION_GZIP 0x01 +#define DB_UPGATE_COMPRESSION_LZ4 0x02 ///< FPGA bitstream update configuration typedef struct { @@ -41,8 +39,8 @@ typedef struct { ///< FPGA bitstream update start notification packet typedef struct __attribute__((packed)) { - uint32_t bitstream_size; ///< Size of the bitstream in bytes - db_upgate_compression_mode_t compression; ///< Compression mode used + uint32_t bitstream_size; ///< Size of the bitstream in bytes + uint8_t compression; ///< Compression mode used #if defined(UPGATE_USE_CRYPTO) uint8_t hash[DB_UPGATE_SHA256_LENGTH]; ///< SHA256 hash of the bitsream uint8_t signature[DB_UPGATE_SIGNATURE_LENGTH]; ///< Signature of the bitstream hash diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 0b7ec373..3737219e 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -34,20 +34,20 @@ #define UPGATE_BASE_ADDRESS (0x00000000) typedef struct { - const db_upgate_conf_t *config; - uint8_t reply_buffer[UINT8_MAX]; - uint32_t target_partition; - uint32_t addr; - uint32_t last_packet_acked; - uint32_t bistream_size; - uint8_t hash[DB_UPGATE_SHA256_LENGTH]; - uint8_t read_buf[DB_UPGATE_CHUNK_SIZE * 2]; - uint8_t write_buf[DB_UPGATE_CHUNK_SIZE * 2]; - db_upgate_compression_mode_t compression; - uint8_t temp_buffer[BUFFER_SIZE]; - uint16_t compressed_length; - uint8_t decompressed_buffer[BUFFER_SIZE]; - struct uzlib_uncomp d; + const db_upgate_conf_t *config; + uint8_t reply_buffer[UINT8_MAX]; + uint32_t target_partition; + uint32_t addr; + uint32_t last_packet_acked; + uint32_t bistream_size; + uint8_t hash[DB_UPGATE_SHA256_LENGTH]; + uint8_t read_buf[DB_UPGATE_CHUNK_SIZE * 2]; + uint8_t write_buf[DB_UPGATE_CHUNK_SIZE * 2]; + uint8_t compression; + uint8_t temp_buffer[BUFFER_SIZE]; + uint16_t compressed_length; + uint8_t decompressed_buffer[BUFFER_SIZE]; + struct uzlib_uncomp d; } db_upgate_vars_t; //=========================== variables ======================================== @@ -192,10 +192,10 @@ void db_upgate_handle_message(const uint8_t *message) { memcpy(_upgate_vars.hash, hash, DB_UPGATE_SHA256_LENGTH); crypto_sha256_init(); #endif - db_upgate_compression_mode_t compression = upgate_start->compression; - uint32_t bitstream_size = upgate_start->bitstream_size; - _upgate_vars.compression = compression; - _upgate_vars.bistream_size = bitstream_size; + uint8_t compression = upgate_start->compression; + uint32_t bitstream_size = upgate_start->bitstream_size; + _upgate_vars.compression = compression; + _upgate_vars.bistream_size = bitstream_size; db_upgate_start(); // Acknowledge the update start _upgate_vars.reply_buffer[0] = DB_UPGATE_MESSAGE_TYPE_START_ACK; From 5e5cd3264b9629ed979bd63c4061a5d7aa85a03e Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 20 Mar 2024 15:06:52 +0100 Subject: [PATCH 17/27] nrf52840dk.emProject: enable crypto with upgate --- nrf52840dk.emProject | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrf52840dk.emProject b/nrf52840dk.emProject index 227aef65..cfbabaa9 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;OTA_USE_CRYPTO;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;UPGATE_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/nrf52840_Registers.xml" debug_stack_pointer_start="__stack_end__" From ff1f987c500a045930253dc4daa45b9967c299d3 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 20 Mar 2024 15:07:25 +0100 Subject: [PATCH 18/27] upgate: fix prog pin with non nrf52840 boards --- upgate/application/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/upgate/application/main.c b/upgate/application/main.c index 9ed5968f..2068a840 100644 --- a/upgate/application/main.c +++ b/upgate/application/main.c @@ -42,7 +42,7 @@ static const gpio_t _sck_pin = { .port = 1, .pin = 15 }; static const gpio_t _miso_pin = { .port = 1, .pin = 14 }; static const gpio_t _mosi_pin = { .port = 1, .pin = 13 }; static const gpio_t _cs_pin = { .port = 1, .pin = 12 }; -static const gpio_t _prog_pin = { .port = 0, .pin = 11 }; +static const gpio_t _prog_pin = { .port = 1, .pin = 11 }; #endif static const n25q128_conf_t _n25q128_conf = { @@ -78,7 +78,7 @@ int main(void) { db_upgate_init(&_upgate_config); db_radio_init(&_radio_callback, DB_RADIO_BLE_1MBit); - db_radio_set_frequency(8); + db_radio_set_frequency(20); db_radio_rx(); db_gpio_init(&db_led1, DB_GPIO_OUT); From a4934bac254785d52f13a784a2cf122f046bc2b5 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 20 Mar 2024 16:32:45 +0100 Subject: [PATCH 19/27] dist/scripts/upgate: make serial baudrate configurable --- dist/scripts/upgate/dotbot-upgate.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py index bac5a8dc..71cce9b8 100755 --- a/dist/scripts/upgate/dotbot-upgate.py +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -23,6 +23,7 @@ from dotbot.serial_interface import SerialInterface, SerialInterfaceException +SERIAL_PORT = "/dev/ttyACM0" BAUDRATE = 1000000 CHUNK_SIZE = 128 COMPRESSED_CHUNK_SIZE = 8192 @@ -234,8 +235,14 @@ def transfer(self): @click.option( "-p", "--port", - default="/dev/ttyACM0", - help="Serial port to use to send the bitstream to the gateway.", + default=SERIAL_PORT, + help=f"Serial port to use to send the bitstream to the gateway. Default: {SERIAL_PORT}.", +) +@click.option( + "-b", + "--baudrate", + default=BAUDRATE, + help=f"Serial port baudrate. Default: {BAUDRATE}.", ) @click.option( "-s", @@ -257,7 +264,7 @@ def transfer(self): help="Continue the upgate without prompt.", ) @click.argument("bitstream", type=click.File(mode="rb", lazy=True)) -def main(port, secure, compression, yes, bitstream): +def main(port, baudrate, secure, compression, yes, bitstream): # Disable logging configure in PyDotBot structlog.configure( wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL), @@ -265,7 +272,7 @@ def main(port, secure, compression, yes, bitstream): try: upgater = DotBotUpgate( port, - BAUDRATE, + baudrate, bytearray(bitstream.read()), compression=compression, secure=secure, From efb8a5e2463d00f89406a7e3546dec3548633164 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 20 Mar 2024 16:38:07 +0100 Subject: [PATCH 20/27] dist/scripts/upgate: increase delay between resend to avoid breaking the UART --- dist/scripts/upgate/dotbot-upgate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py index 71cce9b8..2c8f7978 100755 --- a/dist/scripts/upgate/dotbot-upgate.py +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -216,7 +216,7 @@ def send_packet(self, chunk, packet): buffer += int(len(packet.data)).to_bytes(length=1, byteorder="little") buffer += packet.data self.serial.write(hdlc_encode(buffer)) - time.sleep(0.005) + time.sleep(0.01) def transfer(self): if self.compression in ["gzip", "lz4"]: From 3bb6c9fd81d8fa07e2b766db19551964acfe2fe9 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 20 Mar 2024 16:38:38 +0100 Subject: [PATCH 21/27] drv/upgate: fix build in release mode --- drv/upgate/upgate.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 3737219e..9f40b1eb 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -123,6 +123,7 @@ void db_upgate_handle_packet(const db_upgate_pkt_t *pkt) { if (_upgate_vars.compression == DB_UPGATE_COMPRESSION_GZIP) { _upgate_vars.d.source_limit = _upgate_vars.temp_buffer + _upgate_vars.compressed_length; int ret = uzlib_gzip_parse_header(&_upgate_vars.d); + (void)ret; assert(ret == TINF_OK); uint16_t remaining_data = BUFFER_SIZE; while (remaining_data) { From da61e75bcac8050310ad805a25e5e5b875b155e3 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 20 Mar 2024 16:39:11 +0100 Subject: [PATCH 22/27] upgate: add support for upgate over UART --- upgate/application/main.c | 45 ++++++++++++++++++++++++++++++++++++++- upgate/upgate.emProject | 2 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/upgate/application/main.c b/upgate/application/main.c index 2068a840..42c22754 100644 --- a/upgate/application/main.c +++ b/upgate/application/main.c @@ -15,16 +15,23 @@ #include "board_config.h" #include "gpio.h" +#include "hdlc.h" #include "radio.h" #include "timer.h" +#include "uart.h" #include "upgate.h" //=========================== defines ========================================== +#define DB_UART_BAUDRATE (1000000U) + typedef struct { bool packet_received; uint8_t message_buffer[UINT8_MAX]; + uint8_t uart_byte; + bool uart_byte_received; + uint8_t hdlc_buffer[UINT8_MAX]; } application_vars_t; //=========================== variables ======================================== @@ -59,11 +66,19 @@ static void _radio_callback(uint8_t *pkt, uint8_t len) { _app_vars.packet_received = true; } +static void _uart_callback(uint8_t byte) { + _app_vars.uart_byte = byte; + _app_vars.uart_byte_received = true; +} + //=========================== private ========================================== static void _upgate_reply(const uint8_t *message, size_t len) { db_radio_disable(); db_radio_tx(message, len); + + size_t frame_len = db_hdlc_encode(message, len, _app_vars.hdlc_buffer); + db_uart_write(0, _app_vars.hdlc_buffer, frame_len); } static const db_upgate_conf_t _upgate_config = { @@ -78,15 +93,43 @@ int main(void) { db_upgate_init(&_upgate_config); db_radio_init(&_radio_callback, DB_RADIO_BLE_1MBit); - db_radio_set_frequency(20); + db_radio_set_frequency(8); db_radio_rx(); + db_uart_init(0, &db_uart_rx, &db_uart_tx, DB_UART_BAUDRATE, &_uart_callback); + + // Radio feedback LED db_gpio_init(&db_led1, DB_GPIO_OUT); db_gpio_set(&db_led1); + // UART feedback LED + db_gpio_init(&db_led2, DB_GPIO_OUT); + db_gpio_set(&db_led2); + while (1) { __WFE(); + if (_app_vars.uart_byte_received) { + _app_vars.uart_byte_received = false; + db_hdlc_state_t hdlc_state = db_hdlc_rx_byte(_app_vars.uart_byte); + switch ((uint8_t)hdlc_state) { + case DB_HDLC_STATE_IDLE: + case DB_HDLC_STATE_RECEIVING: + case DB_HDLC_STATE_ERROR: + break; + case DB_HDLC_STATE_READY: + { + size_t msg_len = db_hdlc_decode(_app_vars.hdlc_buffer); + if (msg_len) { + db_gpio_toggle(&db_led2); + db_upgate_handle_message(_app_vars.hdlc_buffer); + } + } break; + default: + break; + } + } + if (_app_vars.packet_received) { _app_vars.packet_received = false; db_gpio_toggle(&db_led1); diff --git a/upgate/upgate.emProject b/upgate/upgate.emProject index cfad326f..742f7294 100644 --- a/upgate/upgate.emProject +++ b/upgate/upgate.emProject @@ -5,7 +5,7 @@ Name="Common" linker_additional_files="$(SolutionDir)/../crypto/nrf_cc310/lib/libnrf_cc310_0.9.13-hard-float-no-interrupts-$(Target).a" linker_output_format="bin" - project_dependencies="00bsp_gpio(bsp);00drv_upgate(drv);00bsp_radio(bsp)" + project_dependencies="00bsp_gpio(bsp);00drv_upgate(drv);00bsp_radio(bsp);00bsp_uart(bsp);00drv_dotbot_hdlc(drv)" project_directory="application" project_type="Executable" /> From 9f1e72088b773097fe1edea33f4d302acc8956b3 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 20 Mar 2024 16:59:45 +0100 Subject: [PATCH 23/27] drv/upgate: improve packet matching in the compressed cases --- drv/upgate/upgate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 9f40b1eb..10ddaddc 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -147,8 +147,8 @@ void db_upgate_handle_packet(const db_upgate_pkt_t *pkt) { for (uint32_t block = 0; block < pkt->original_size / N25Q128_PAGE_SIZE; block++) { printf("Programming %d bytes at %p\n", N25Q128_PAGE_SIZE, base_addr + block * N25Q128_PAGE_SIZE); n25q128_program_page(base_addr + block * N25Q128_PAGE_SIZE, &_upgate_vars.decompressed_buffer[block * N25Q128_PAGE_SIZE], N25Q128_PAGE_SIZE); - n25q128_read(base_addr + block * N25Q128_PAGE_SIZE, _upgate_vars.temp_buffer, N25Q128_PAGE_SIZE); - if (memcmp(&_upgate_vars.decompressed_buffer[block * N25Q128_PAGE_SIZE], _upgate_vars.temp_buffer, N25Q128_PAGE_SIZE) != 0) { + n25q128_read(base_addr + block * N25Q128_PAGE_SIZE, &_upgate_vars.temp_buffer[block * N25Q128_PAGE_SIZE], N25Q128_PAGE_SIZE); + if (memcmp(&_upgate_vars.temp_buffer[block * N25Q128_PAGE_SIZE], &_upgate_vars.temp_buffer[block * N25Q128_PAGE_SIZE], N25Q128_PAGE_SIZE) != 0) { puts("packet doesn't match!!"); } } From e38969815e6609d56a7c8e0af713ac2edee8e736 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Fri, 22 Mar 2024 14:41:13 +0100 Subject: [PATCH 24/27] dist/scripts/upgate: refactor packet send --- dist/scripts/upgate/dotbot-upgate.py | 51 ++++++++++++++++++---------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/dist/scripts/upgate/dotbot-upgate.py b/dist/scripts/upgate/dotbot-upgate.py index 2c8f7978..eebe1b26 100755 --- a/dist/scripts/upgate/dotbot-upgate.py +++ b/dist/scripts/upgate/dotbot-upgate.py @@ -103,7 +103,7 @@ def on_byte_received(self, byte): if payload[0] == MessageType.UPGATE_MESSAGE_TYPE_FINALIZE_ACK.value: self.finalize_ack_received = True elif payload[0] == MessageType.UPGATE_MESSAGE_TYPE_PACKET_ACK.value: - self.last_acked_token = int.from_bytes(payload[1:5], byteorder="little") + self.last_acked_token = payload[1:5].hex() def init(self): private_key_bytes = open(PRIVATE_KEY_PATH, "rb").read() @@ -181,7 +181,7 @@ def init(self): print("Sending start upgate notification...") self.serial.write(hdlc_encode(buffer)) timeout = 0 # ms - while self.start_ack_received is False and timeout < 60000: + while self.start_ack_received is False and timeout < 10000: timeout += 1 time.sleep(0.01) return self.start_ack_received is True @@ -197,26 +197,37 @@ def finalize(self): print("Sending upgate finalize...") self.serial.write(hdlc_encode(buffer)) timeout = 0 # ms - while self.finalize_ack_received is False and timeout < 60000: + while self.finalize_ack_received is False and timeout < 10000: timeout += 1 time.sleep(0.01) return self.finalize_ack_received is True def send_packet(self, chunk, packet): - while self.last_acked_token != int.from_bytes(packet.token, byteorder="little"): - buffer = bytearray() - buffer += int(MessageType.UPGATE_MESSAGE_TYPE_PACKET.value).to_bytes( - length=1, byteorder="little" - ) - buffer += int(chunk.index).to_bytes(length=4, byteorder="little") - buffer += packet.token - buffer += int(chunk.dsize).to_bytes(length=2, byteorder="little") - buffer += int(packet.index).to_bytes(length=1, byteorder="little") - buffer += int(len(chunk.packets)).to_bytes(length=1, byteorder="little") - buffer += int(len(packet.data)).to_bytes(length=1, byteorder="little") - buffer += packet.data - self.serial.write(hdlc_encode(buffer)) - time.sleep(0.01) + send_time = time.time() + send = True + tries = 0 + while tries < 3: + if self.last_acked_token == packet.token.hex(): + break + if send is True: + buffer = bytearray() + buffer += int(MessageType.UPGATE_MESSAGE_TYPE_PACKET.value).to_bytes( + length=1, byteorder="little" + ) + buffer += int(chunk.index).to_bytes(length=4, byteorder="little") + buffer += packet.token + buffer += int(chunk.dsize).to_bytes(length=2, byteorder="little") + buffer += int(packet.index).to_bytes(length=1, byteorder="little") + buffer += int(len(chunk.packets)).to_bytes(length=1, byteorder="little") + buffer += int(len(packet.data)).to_bytes(length=1, byteorder="little") + buffer += packet.data + self.serial.write(hdlc_encode(buffer)) + send_time = time.time() + tries += 1 + time.sleep(0.0001) + send = time.time() - send_time > 0.1 + else: + raise Exception(f"packet #{chunk.index} ({packet.token.hex()}) not acknowledged. Aborting.") def transfer(self): if self.compression in ["gzip", "lz4"]: @@ -291,7 +302,11 @@ def main(port, baudrate, secure, compression, yes, bitstream): if ret is False: print("Error: No start acknowledgment received. Aborting.") return - upgater.transfer() + try: + upgater.transfer() + except Exception as exc: + print(f"Error during transfer: {exc}") + return ret = upgater.finalize() if ret is False: print("Error: No finalize acknowledgment received. Upgate failed.") From fb1693d832008a4d6e3966d51f86283dae45e438 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 29 May 2024 11:52:05 +0200 Subject: [PATCH 25/27] dist/scripts/upgate: bump cryptography version --- dist/scripts/upgate/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/scripts/upgate/requirements.txt b/dist/scripts/upgate/requirements.txt index df144dc1..2393b7e2 100644 --- a/dist/scripts/upgate/requirements.txt +++ b/dist/scripts/upgate/requirements.txt @@ -1,4 +1,4 @@ click==8.1.7 -cryptography==41.0.5 +cryptography==42.0.7 tqdm==4.66.1 pydotbot From 16f83633e3023538c2d58430be75f59bc6fc0d5d Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 29 May 2024 11:52:27 +0200 Subject: [PATCH 26/27] drv/upgate: use printf instead of puts --- drv/upgate/upgate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drv/upgate/upgate.c b/drv/upgate/upgate.c index 10ddaddc..4ebfea5b 100644 --- a/drv/upgate/upgate.c +++ b/drv/upgate/upgate.c @@ -186,10 +186,10 @@ void db_upgate_handle_message(const uint8_t *message) { printf("Verifying ed25519 signature: "); const uint8_t *hash = upgate_start->hash; if (!crypto_ed25519_verify(upgate_start->signature, DB_UPGATE_SIGNATURE_LENGTH, (const uint8_t *)upgate_start, sizeof(db_upgate_start_notification_t) - DB_UPGATE_SIGNATURE_LENGTH, public_key)) { - puts("Failed!"); + printf("Failed!\n"); break; } - puts("Success!"); + printf("Success!\n"); memcpy(_upgate_vars.hash, hash, DB_UPGATE_SHA256_LENGTH); crypto_sha256_init(); #endif From ca0ed3368925e1bbf1901afb620e94e541cb223a Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 29 May 2024 12:06:44 +0200 Subject: [PATCH 27/27] doc: add missing upgate references --- doc/sphinx/drv.md | 1 + doc/sphinx/index.md | 1 + doc/sphinx/upgate.md | 4 ++++ upgate/README.md | 16 ++++++++++++++++ 4 files changed, 22 insertions(+) create mode 100644 doc/sphinx/upgate.md create mode 100644 upgate/README.md diff --git a/doc/sphinx/drv.md b/doc/sphinx/drv.md index a9b5e45a..32327536 100644 --- a/doc/sphinx/drv.md +++ b/doc/sphinx/drv.md @@ -24,5 +24,6 @@ _api/drv_ota _api/drv_pid _api/drv_protocol _api/drv_rgbled_pwm +_api/drv_upgate _api/drv_uzlib ``` diff --git a/doc/sphinx/index.md b/doc/sphinx/index.md index 2e5c51a4..5c1161ab 100644 --- a/doc/sphinx/index.md +++ b/doc/sphinx/index.md @@ -5,6 +5,7 @@ getting_started applications examples otap +upgate api ``` diff --git a/doc/sphinx/upgate.md b/doc/sphinx/upgate.md new file mode 100644 index 00000000..1aa4b815 --- /dev/null +++ b/doc/sphinx/upgate.md @@ -0,0 +1,4 @@ +```{include} ../../upgate/README.md +:relative-images: +:relative-docs: ../ +``` diff --git a/upgate/README.md b/upgate/README.md new file mode 100644 index 00000000..0c894f9b --- /dev/null +++ b/upgate/README.md @@ -0,0 +1,16 @@ +# upGate + +Over-the-air dynamic reconfiguration of FPGA library and example application. + +Use the [dotbot-upgate.py](../dist/scripts/upgate/dotbot-upgate.py) Python script to +reconfigure an FPGA with a new bitstream. For better performances, the bitstream +can be sent compressed (Gzip andn LZ4 compression methods available). +Among different common Python packages, this script requires the +[pydotbot](https://pypi.org/project/pydotbot/) package to be installed on the +system. + +To install all the Python dependencies (pydotbot, cryptography, click and tqdm), run: + +``` +pip install -r dist/scripts/otap/requirements.txt +```