diff --git a/CMakeLists.txt b/CMakeLists.txt index c78ad9449..fb40e924e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,9 @@ function(add_subdirectory_exclude_platforms NAME) add_subdirectory(${NAME}) endfunction() +# Set signing file for entire project +set_property(GLOBAL PROPERTY PICOTOOL_SIGFILE "${CMAKE_CURRENT_LIST_DIR}/sample_keys/private.pem") + # Add blink example add_subdirectory_exclude_platforms(blink) add_subdirectory_exclude_platforms(blink_simple) diff --git a/bootloaders/encrypted/CMakeLists.txt b/bootloaders/encrypted/CMakeLists.txt index f29f0efe2..6b3fe74a0 100644 --- a/bootloaders/encrypted/CMakeLists.txt +++ b/bootloaders/encrypted/CMakeLists.txt @@ -53,7 +53,7 @@ add_linker_script(enc_bootloader "0x20070000" "64k") pico_set_otp_key_output_file(enc_bootloader ${CMAKE_CURRENT_BINARY_DIR}/otp.json) # sign, hash, and clear SRAM -pico_sign_binary(enc_bootloader ${CMAKE_CURRENT_LIST_DIR}/private.pem) +pico_sign_binary(enc_bootloader) pico_hash_binary(enc_bootloader) pico_load_map_clear_sram(enc_bootloader) @@ -87,7 +87,7 @@ pico_set_binary_type(hello_serial_enc no_flash) add_linker_script(hello_serial_enc "0x20000000" "448k") # sign, hash, and encrypt -pico_sign_binary(hello_serial_enc ${CMAKE_CURRENT_LIST_DIR}/private.pem) +pico_sign_binary(hello_serial_enc) pico_hash_binary(hello_serial_enc) pico_encrypt_binary(hello_serial_enc ${CMAKE_CURRENT_LIST_DIR}/privateaes.bin) diff --git a/pico_w/wifi/CMakeLists.txt b/pico_w/wifi/CMakeLists.txt index 31797084b..badc762f0 100644 --- a/pico_w/wifi/CMakeLists.txt +++ b/pico_w/wifi/CMakeLists.txt @@ -21,8 +21,9 @@ else() add_subdirectory_exclude_platforms(mqtt) if (NOT PICO_MBEDTLS_PATH) - message("Skipping tls examples as PICO_MBEDTLS_PATH is not defined") + message("Skipping some examples as PICO_MBEDTLS_PATH is not defined") else() add_subdirectory_exclude_platforms(tls_client) + add_subdirectory_exclude_platforms(ota_update rp2040) endif() endif() diff --git a/pico_w/wifi/ota_update/CMakeLists.txt b/pico_w/wifi/ota_update/CMakeLists.txt new file mode 100644 index 000000000..accba9236 --- /dev/null +++ b/pico_w/wifi/ota_update/CMakeLists.txt @@ -0,0 +1,55 @@ +add_executable(picow_ota_update_background + picow_ota_update.c + ) +target_compile_definitions(picow_ota_update_background PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + PICO_CRT0_IMAGE_TYPE_TBYB=1 + ) +target_include_directories(picow_ota_update_background PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts + ) +target_link_libraries(picow_ota_update_background + pico_cyw43_arch_lwip_threadsafe_background + pico_stdlib + pico_sha256 + boot_uf2_headers + ) + +pico_use_wifi_firmware_partition(picow_ota_update_background) + +pico_hash_binary(picow_ota_update_background) +pico_sign_binary(picow_ota_update_background) +# pico_set_binary_type(picow_ota_update_background no_flash) +# pico_package_uf2_output(picow_ota_update_background 0x10000000) + +pico_add_extra_outputs(picow_ota_update_background) + +add_executable(picow_ota_update_poll + picow_ota_update.c + ) +target_compile_definitions(picow_ota_update_poll PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\" + PICO_CRT0_IMAGE_TYPE_TBYB=1 + ) +target_include_directories(picow_ota_update_poll PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts + ) +target_link_libraries(picow_ota_update_poll + pico_cyw43_arch_lwip_poll + pico_stdlib + pico_sha256 + boot_uf2_headers + ) + +pico_use_wifi_firmware_partition(picow_ota_update_poll) + +pico_hash_binary(picow_ota_update_poll) +pico_sign_binary(picow_ota_update_poll) +# pico_set_binary_type(picow_ota_update_background no_flash) +# pico_package_uf2_output(picow_ota_update_background 0x10000000) + +pico_add_extra_outputs(picow_ota_update_poll) diff --git a/pico_w/wifi/ota_update/lwipopts.h b/pico_w/wifi/ota_update/lwipopts.h new file mode 100644 index 000000000..8571ed509 --- /dev/null +++ b/pico_w/wifi/ota_update/lwipopts.h @@ -0,0 +1,10 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// Generally you would define your own explicit list of lwIP options +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html) +// +// This example uses a common include to avoid repetition +#include "lwipopts_examples_common.h" + +#endif diff --git a/pico_w/wifi/ota_update/main.json b/pico_w/wifi/ota_update/main.json new file mode 100644 index 000000000..c9e989bd6 --- /dev/null +++ b/pico_w/wifi/ota_update/main.json @@ -0,0 +1,64 @@ +{ + "version": [1, 0], + "unpartitioned": { + "families": ["absolute"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + } + }, + "partitions": [ + { + "name": "Main A", + "id": 0, + "size": "1744K", + "families": ["rp2350-arm-s", "rp2350-riscv"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + } + }, + { + "name": "Main B", + "id": 0, + "size": "1744K", + "families": ["rp2350-arm-s", "rp2350-riscv"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + }, + "link": ["a", 0] + }, + { + "name": "Firmware A", + "id": "0x776966696669726d", + "start": "3500k", + "size": "240K", + "families": ["data"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + }, + "ignored_during_riscv_boot": true, + "no_reboot_on_uf2_download": true + }, + { + "name": "Firmware B", + "id": 12345, + "size": "240K", + "families": ["data"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + }, + "link": ["a", 2], + "ignored_during_riscv_boot": true, + "no_reboot_on_uf2_download": true + } + ] +} diff --git a/pico_w/wifi/ota_update/picow_ota_update.c b/pico_w/wifi/ota_update/picow_ota_update.c new file mode 100644 index 000000000..8904417ab --- /dev/null +++ b/pico_w/wifi/ota_update/picow_ota_update.c @@ -0,0 +1,363 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "pico/stdlib.h" +#include "pico/cyw43_arch.h" +#include "pico/sha256.h" +#include "pico/bootrom.h" +#include "boot/picobin.h" +#include "boot/picoboot.h" +#include "boot/uf2.h" + +#include "lwip/pbuf.h" +#include "lwip/tcp.h" + +#define TCP_PORT 4242 +// #define DEBUG_printf(...) printf(__VA_ARGS__) +#define DEBUG_printf(...) +#define BUF_SIZE 2048 +#define POLL_TIME_S 5 + +#define FLASH_SECTOR_ERASE_SIZE 4096u + +typedef struct TCP_UPDATE_SERVER_T_ { + struct tcp_pcb *server_pcb; + struct tcp_pcb *client_pcb; + bool complete; + uint8_t buffer_sent[SHA256_RESULT_BYTES]; + uint8_t buffer_recv[BUF_SIZE]; + int sent_len; + int recv_len; + int num_blocks; + int blocks_done; + uint32_t family_id; + uint32_t flash_update; + int32_t write_offset; + uint32_t write_size; + uint32_t highest_erased_sector; +} TCP_UPDATE_SERVER_T; + +typedef struct uf2_block uf2_block_t; + +static TCP_UPDATE_SERVER_T* tcp_update_server_init(void) { + TCP_UPDATE_SERVER_T *state = calloc(1, sizeof(TCP_UPDATE_SERVER_T)); + if (!state) { + DEBUG_printf("failed to allocate state\n"); + return NULL; + } + return state; +} + + +static __attribute__((aligned(4))) uint8_t workarea[4 * 1024]; + +static err_t tcp_update_server_close(void *arg) { + TCP_UPDATE_SERVER_T *state = (TCP_UPDATE_SERVER_T*)arg; + err_t err = ERR_OK; + if (state->client_pcb != NULL) { + tcp_arg(state->client_pcb, NULL); + tcp_poll(state->client_pcb, NULL, 0); + tcp_sent(state->client_pcb, NULL); + tcp_recv(state->client_pcb, NULL); + tcp_err(state->client_pcb, NULL); + err = tcp_close(state->client_pcb); + if (err != ERR_OK) { + DEBUG_printf("close failed %d, calling abort\n", err); + tcp_abort(state->client_pcb); + err = ERR_ABRT; + } + state->client_pcb = NULL; + } + if (state->server_pcb) { + tcp_arg(state->server_pcb, NULL); + tcp_close(state->server_pcb); + state->server_pcb = NULL; + } + return err; +} + +static err_t tcp_update_server_result(void *arg, int status) { + TCP_UPDATE_SERVER_T *state = (TCP_UPDATE_SERVER_T*)arg; + if (status == 0) { + DEBUG_printf("test success\n"); + } else { + DEBUG_printf("test failed %d\n", status); + } + state->complete = true; + return tcp_update_server_close(arg); +} + +static err_t tcp_update_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) { + TCP_UPDATE_SERVER_T *state = (TCP_UPDATE_SERVER_T*)arg; + DEBUG_printf("tcp_update_server_sent %u\n", len); + state->sent_len += len; + + if (state->sent_len >= SHA256_RESULT_BYTES) { + + // We should get the data back from the client + state->recv_len = 0; + DEBUG_printf("Waiting for buffer from client\n"); + } + + return ERR_OK; +} + +err_t tcp_update_server_send_data(void *arg, struct tcp_pcb *tpcb) +{ + TCP_UPDATE_SERVER_T *state = (TCP_UPDATE_SERVER_T*)arg; + + state->sent_len = 0; + DEBUG_printf("Writing %ld bytes to client\n", SHA256_RESULT_BYTES); + // this method is callback from lwIP, so cyw43_arch_lwip_begin is not required, however you + // can use this method to cause an assertion in debug mode, if this method is called when + // cyw43_arch_lwip_begin IS needed + cyw43_arch_lwip_check(); + err_t err = tcp_write(tpcb, state->buffer_sent, SHA256_RESULT_BYTES, TCP_WRITE_FLAG_COPY); + if (err != ERR_OK) { + DEBUG_printf("Failed to write data %d\n", err); + return tcp_update_server_result(arg, -1); + } + return ERR_OK; +} + +err_t tcp_update_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { + TCP_UPDATE_SERVER_T *state = (TCP_UPDATE_SERVER_T*)arg; + if (!p) { + return tcp_update_server_result(arg, -1); + } + // this method is callback from lwIP, so cyw43_arch_lwip_begin is not required, however you + // can use this method to cause an assertion in debug mode, if this method is called when + // cyw43_arch_lwip_begin IS needed + cyw43_arch_lwip_check(); + if (p->tot_len > 0) { + DEBUG_printf("tcp_update_server_recv %d/%d err %d\n", p->tot_len, state->recv_len, err); + + // Receive the buffer + const uint16_t buffer_left = BUF_SIZE - state->recv_len; + state->recv_len += pbuf_copy_partial(p, state->buffer_recv + state->recv_len, + p->tot_len > buffer_left ? buffer_left : p->tot_len, 0); + tcp_recved(tpcb, p->tot_len); + } + pbuf_free(p); + + // Have we have received the whole buffer + if (state->recv_len == BUF_SIZE) { + + for (int i=0; i < BUF_SIZE/sizeof(uf2_block_t); i++) { + // check it matches + uf2_block_t* block; + block = (uf2_block_t*)(state->buffer_recv + i * sizeof(uf2_block_t)); + + if (state->num_blocks == 0) { + state->num_blocks = block->num_blocks; + state->family_id = block->file_size; // or familyID; + + resident_partition_t uf2_target_partition; + rom_flash_flush_cache(); + rom_get_uf2_target_partition(workarea, sizeof(workarea), state->family_id, &uf2_target_partition); + printf("Code Target partition is %lx %lx\n", uf2_target_partition.permissions_and_location, uf2_target_partition.permissions_and_flags); + + uint16_t first_sector_number = (uf2_target_partition.permissions_and_location & PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_BITS) >> PICOBIN_PARTITION_LOCATION_FIRST_SECTOR_LSB; + uint16_t last_sector_number = (uf2_target_partition.permissions_and_location & PICOBIN_PARTITION_LOCATION_LAST_SECTOR_BITS) >> PICOBIN_PARTITION_LOCATION_LAST_SECTOR_LSB; + uint32_t code_start_addr = first_sector_number * 0x1000; + uint32_t code_end_addr = (last_sector_number + 1) * 0x1000; + uint32_t code_size = code_end_addr - code_start_addr; + printf("Start %lx, End %lx, Size %lx\n", code_start_addr, code_end_addr, code_size); + + state->flash_update = code_start_addr + XIP_BASE; + state->write_offset = code_start_addr + XIP_BASE - block->target_addr; + state->write_size = code_size; + DEBUG_printf("Write Offset %lx, Size %lx\n", state->write_offset, state->write_size); + } + + if (state->blocks_done != block->block_no) { + DEBUG_printf("block number mismatch - expected %d, got %d\n", state->blocks_done, block->block_no); + return tcp_update_server_result(arg, -1); + } + if (state->family_id != block->file_size) { + DEBUG_printf("family id mismatch\n"); + return tcp_update_server_result(arg, -1); + } + DEBUG_printf("tcp_update_server_recv buffer ok\n"); + + // Write to flash + struct cflash_flags flags; + int8_t ret; + (void)ret; + if (block->target_addr / FLASH_SECTOR_ERASE_SIZE > state->highest_erased_sector) { + flags.flags = + (CFLASH_OP_VALUE_ERASE << CFLASH_OP_LSB) | + (CFLASH_SECLEVEL_VALUE_SECURE << CFLASH_SECLEVEL_LSB) | + (CFLASH_ASPACE_VALUE_STORAGE << CFLASH_ASPACE_LSB); + ret = rom_flash_op(flags, + block->target_addr + state->write_offset, + FLASH_SECTOR_ERASE_SIZE, NULL); + state->highest_erased_sector = block->target_addr / FLASH_SECTOR_ERASE_SIZE; + DEBUG_printf("Checked Erase Returned %d, start %x, size %x, highest erased %x\n", ret, block->target_addr + state->write_offset, FLASH_SECTOR_ERASE_SIZE, state->highest_erased_sector); + } + flags.flags = + (CFLASH_OP_VALUE_PROGRAM << CFLASH_OP_LSB) | + (CFLASH_SECLEVEL_VALUE_SECURE << CFLASH_SECLEVEL_LSB) | + (CFLASH_ASPACE_VALUE_STORAGE << CFLASH_ASPACE_LSB); + ret = rom_flash_op(flags, + block->target_addr + state->write_offset, + 256, (void*)block->data); + DEBUG_printf("Checked Program Returned %d, start %x, size %x\n", ret, block->target_addr + state->write_offset, 256); + + // Download complete? + state->blocks_done++; + if (state->blocks_done >= state->num_blocks) { + tcp_update_server_result(arg, 0); + return ERR_OK; + } + } + + // Hash the received data + pico_sha256_state_t sha_state; + int rc = pico_sha256_start_blocking(&sha_state, SHA256_BIG_ENDIAN, true); // using some DMA system resources + hard_assert(rc == PICO_OK); + pico_sha256_update_blocking(&sha_state, (const uint8_t*)state->buffer_recv, sizeof(state->buffer_recv)); + + // Get the result of the sha256 calculation + sha256_result_t* result; + result = (sha256_result_t*)state->buffer_sent; + pico_sha256_finish(&sha_state, result); + + // Send another buffer + return tcp_update_server_send_data(arg, state->client_pcb); + } + return ERR_OK; +} + +static err_t tcp_update_server_poll(void *arg, struct tcp_pcb *tpcb) { + DEBUG_printf("tcp_update_server_poll_fn\n"); + return tcp_update_server_result(arg, -1); // no response is an error? +} + +static void tcp_update_server_err(void *arg, err_t err) { + if (err != ERR_ABRT) { + DEBUG_printf("tcp_client_err_fn %d\n", err); + tcp_update_server_result(arg, err); + } +} + +static err_t tcp_update_server_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) { + TCP_UPDATE_SERVER_T *state = (TCP_UPDATE_SERVER_T*)arg; + if (err != ERR_OK || client_pcb == NULL) { + DEBUG_printf("Failure in accept\n"); + tcp_update_server_result(arg, err); + return ERR_VAL; + } + DEBUG_printf("Client connected\n"); + + state->client_pcb = client_pcb; + tcp_arg(client_pcb, state); + tcp_sent(client_pcb, tcp_update_server_sent); + tcp_recv(client_pcb, tcp_update_server_recv); + tcp_poll(client_pcb, tcp_update_server_poll, POLL_TIME_S * 2); + tcp_err(client_pcb, tcp_update_server_err); + + return ERR_OK; +} + +static bool tcp_update_server_open(void *arg) { + TCP_UPDATE_SERVER_T *state = (TCP_UPDATE_SERVER_T*)arg; + printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), TCP_PORT); + + struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); + if (!pcb) { + DEBUG_printf("failed to create pcb\n"); + return false; + } + + err_t err = tcp_bind(pcb, NULL, TCP_PORT); + if (err) { + DEBUG_printf("failed to bind to port %u\n", TCP_PORT); + return false; + } + + state->server_pcb = tcp_listen_with_backlog(pcb, 1); + if (!state->server_pcb) { + DEBUG_printf("failed to listen\n"); + if (pcb) { + tcp_close(pcb); + } + return false; + } + + tcp_arg(state->server_pcb, state); + tcp_accept(state->server_pcb, tcp_update_server_accept); + + return true; +} + +int main() { + stdio_init_all(); + + if (cyw43_arch_init()) { + printf("failed to initialise\n"); + return 1; + } + + cyw43_arch_enable_sta_mode(); + + printf("Connecting to Wi-Fi...\n"); + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { + printf("failed to connect.\n"); + return 1; + } else { + printf("Connected.\n"); + } + + boot_info_t boot_info = {}; + int ret = rom_get_boot_info(&boot_info); + printf("Boot partition was %d\n", boot_info.partition); + + if (rom_get_last_boot_type() == BOOT_TYPE_FLASH_UPDATE) { + printf("Someone updated into me\n"); + if (boot_info.reboot_params[0]) printf("Flash update base was %x\n", boot_info.reboot_params[0]); + if (boot_info.tbyb_and_update_info) printf("Update info %x\n", boot_info.tbyb_and_update_info); + ret = rom_explicit_buy(workarea, sizeof(workarea)); + if (ret) printf("Buy returned %d\n", ret); + ret = rom_get_boot_info(&boot_info); + if (boot_info.tbyb_and_update_info) printf("Update info now %x\n", boot_info.tbyb_and_update_info); + } + + + TCP_UPDATE_SERVER_T *state = tcp_update_server_init(); + if (!state) { + return -1; + } + if (!tcp_update_server_open(state)) { + tcp_update_server_result(state, -1); + return -1; + } + while(!state->complete) { + // the following #ifdef is only here so this same example can be used in multiple modes; + // you do not need it in your code +#if PICO_CYW43_ARCH_POLL + // if you are using pico_cyw43_arch_poll, then you must poll periodically from your + // main loop (not from a timer) to check for Wi-Fi driver or lwIP work that needs to be done. + cyw43_arch_poll(); +#endif + + // Do your application code here + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1); + sleep_ms(250); + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0); + sleep_ms(250); + } + + cyw43_arch_deinit(); + ret = rom_reboot(REBOOT2_FLAG_REBOOT_TYPE_FLASH_UPDATE, 1000, state->flash_update, 0); + printf("Done - rebooting for a flash update boot %d\n", ret); + free(state); + sleep_ms(2000); + return 0; +} diff --git a/pico_w/wifi/python_test_tcp/python_ota_update.py b/pico_w/wifi/python_test_tcp/python_ota_update.py new file mode 100644 index 000000000..b49453e85 --- /dev/null +++ b/pico_w/wifi/python_test_tcp/python_ota_update.py @@ -0,0 +1,77 @@ +#!/usr/bin/python + +import socket +import sys +import hashlib + +# Check server ip address set +if len(sys.argv) < 2: + raise RuntimeError('pass IP address of the server') + +# Check file is set +if len(sys.argv) < 3: + raise RuntimeError('pass UF2 file for update') + +# Set the server address here like 1.2.3.4 +SERVER_ADDR = sys.argv[1] + +# These constants should match the server +BUF_SIZE = 2048 +UF2_BLOCK = 512 +SERVER_PORT = 4242 + +# Open socket to the server +sock = socket.socket() +addr = (SERVER_ADDR, SERVER_PORT) +sock.connect(addr) + +file = sys.argv[2] + +with open(file, 'rb') as f: + data = f.read() + +data = bytearray(data) + +# Skip abs block +data = data[UF2_BLOCK:] + +print("Len data", len(data), f"/{BUF_SIZE}", len(data) / BUF_SIZE) + +while (len(data) % BUF_SIZE != 0): + data.extend([0] * UF2_BLOCK) + +print("Len data now", len(data), f"/{BUF_SIZE}", len(data) / BUF_SIZE) + +# Repeat test for a number of iterations +for i in range(len(data) // BUF_SIZE): + print("I", i, "of", len(data) // BUF_SIZE, ' '*10, end='\r') + uf2_buf = data[i*BUF_SIZE:(i+1)*BUF_SIZE] + + # Send the data back to the server + write_len = sock.send(uf2_buf) + if write_len != BUF_SIZE: + raise RuntimeError('wrong amount of data written') + + if i < (len(data) // BUF_SIZE) - 1: + # Read 32 bytes from the server + total_size = 32 + read_buf = b'' + while total_size > 0: + buf = sock.recv(32) + # print('read %d bytes from server' % len(buf)) + total_size -= len(buf) + read_buf += buf + + # Check size of data received + if len(read_buf) != 32: + raise RuntimeError('wrong amount of data read %d', len(read_buf)) + + # Check the hash matches + h = hashlib.new('sha256') + h.update(uf2_buf) + if read_buf != h.digest(): + raise RuntimeError('buffer mismatch') + +# All done +sock.close() +print("\nupload completed") diff --git a/bootloaders/encrypted/private.pem b/sample_keys/private.pem similarity index 100% rename from bootloaders/encrypted/private.pem rename to sample_keys/private.pem