#include #include #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "pico/sha256.h" #include "hardware/flash.h" #include "hardware/sync.h" #include "hardware/structs/scb.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" // ---- OTA-Wire-Protokoll (siehe server/server.py) ---- #define OTA_MAGIC 0xC0FFEE01u #define OTA_STATUS_NONE 0x00u #define OTA_STATUS_UPDATE 0x01u #define SERVER_IP "192.168.161.25" #define SERVER_PORT 9000 // ---- Flash-Layout ---- #define APP_BASE 0x10080000u #define APP_FLASH_OFFSET (APP_BASE - XIP_BASE) // = 0x80000, fuer flash_range_* #define APP_SLOT_SIZE (3584u * 1024u) // 3.5 MB, muss zum Server passen #define SHA256_BYTES 32 #ifndef PICO_ID #error "Bitte PICO_ID per -DPICO_ID= setzen" #endif // ---------- Hilfen ---------- static void print_hash(const char *label, const uint8_t *hash) { printf("%s ", label); for (int i = 0; i < SHA256_BYTES; i++) { printf("%02x", hash[i]); } printf("\n"); } static bool app_present(uint32_t base) { uint32_t sp = *(volatile uint32_t *)base; return sp > 0x20000000u && sp <= 0x20082000u; } __attribute__((noreturn)) static void jump_to_app(uint32_t base) { uint32_t sp = *(volatile uint32_t *)base; uint32_t reset = *(volatile uint32_t *)(base + 4u); cyw43_arch_deinit(); __asm volatile ("cpsid i" ::: "memory"); scb_hw->vtor = base; __asm volatile ("dsb 0xf" ::: "memory"); __asm volatile ("isb 0xf" ::: "memory"); __asm volatile ( "msr msp, %0\n\t" "bx %1\n" :: "r" (sp), "r" (reset) : "memory" ); __builtin_unreachable(); } __attribute__((noreturn)) static void halt_with_led(void) { cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true); while (true) { tight_loop_contents(); } } // ---------- Flash-Operationen mit IRQ-Maskierung ---------- static void flash_erase_sector(uint32_t flash_offset) { uint32_t irq = save_and_disable_interrupts(); flash_range_erase(flash_offset, FLASH_SECTOR_SIZE); restore_interrupts(irq); } static void flash_program_page(uint32_t flash_offset, const uint8_t *data) { uint32_t irq = save_and_disable_interrupts(); flash_range_program(flash_offset, data, FLASH_PAGE_SIZE); restore_interrupts(irq); } // ---------- App-Slot hashen ---------- static bool hash_app_slot(uint8_t out[SHA256_BYTES]) { pico_sha256_state_t st; if (pico_sha256_start_blocking(&st, SHA256_BIG_ENDIAN, true) != PICO_OK) { printf("pico_sha256_start fehlgeschlagen\n"); return false; } pico_sha256_update_blocking(&st, (const uint8_t *)APP_BASE, APP_SLOT_SIZE); sha256_result_t result; pico_sha256_finish(&st, &result); memcpy(out, result.bytes, SHA256_BYTES); return true; } // ---------- OTA-Client ---------- typedef enum { OTA_PHASE_REQUEST, // Request raus, warte auf Status-Byte OTA_PHASE_HEADER, // Status=Update gelesen, sammle size + new_hash (36 Byte) OTA_PHASE_PAYLOAD, // sammle payload (size Byte) — und schreibe ins Flash OTA_PHASE_DONE, OTA_PHASE_ERROR, } ota_phase_t; typedef struct { struct tcp_pcb *pcb; ip_addr_t remote; ota_phase_t phase; uint8_t request[40]; // Update-Header uint32_t expected_size; uint8_t new_hash[SHA256_BYTES]; uint32_t header_have; uint8_t header_buf[4 + SHA256_BYTES]; // Payload + Flash uint32_t payload_have; uint8_t page_buf[FLASH_PAGE_SIZE]; uint32_t page_filled; volatile bool done; bool update_pending; } ota_t; static void ota_finish(ota_t *o) { if (o->pcb) { tcp_arg(o->pcb, NULL); tcp_recv(o->pcb, NULL); tcp_err(o->pcb, NULL); tcp_close(o->pcb); o->pcb = NULL; } o->done = true; } // Schreibt eine voll gefuellte page_buf an den Slot. Erase passiert genau dann, // wenn das die erste Page eines neuen Sektors ist. static void flush_page_to_flash(ota_t *o, uint32_t offset_in_slot) { uint32_t flash_off = APP_FLASH_OFFSET + offset_in_slot; if ((offset_in_slot % FLASH_SECTOR_SIZE) == 0) { flash_erase_sector(flash_off); } flash_program_page(flash_off, o->page_buf); } static void ota_handle_bytes(ota_t *o, const uint8_t *data, uint16_t len) { uint16_t i = 0; while (i < len && o->phase != OTA_PHASE_DONE && o->phase != OTA_PHASE_ERROR) { if (o->phase == OTA_PHASE_REQUEST) { uint8_t status = data[i++]; printf("<- status=0x%02x\n", status); if (status == OTA_STATUS_NONE) { o->phase = OTA_PHASE_DONE; break; } if (status != OTA_STATUS_UPDATE) { printf("unerwartetes Statusbyte\n"); o->phase = OTA_PHASE_ERROR; break; } o->update_pending = true; o->phase = OTA_PHASE_HEADER; o->header_have = 0; } else if (o->phase == OTA_PHASE_HEADER) { uint16_t take = (uint16_t)(sizeof(o->header_buf) - o->header_have); if (take > (uint16_t)(len - i)) take = (uint16_t)(len - i); memcpy(&o->header_buf[o->header_have], &data[i], take); o->header_have += take; i += take; if (o->header_have == sizeof(o->header_buf)) { memcpy(&o->expected_size, &o->header_buf[0], 4); memcpy(o->new_hash, &o->header_buf[4], SHA256_BYTES); printf("Update-Header: size=%u\n", (unsigned)o->expected_size); print_hash("new_hash:", o->new_hash); if (o->expected_size == 0 || o->expected_size > APP_SLOT_SIZE) { printf("Size ausserhalb gueltigem Bereich\n"); o->phase = OTA_PHASE_ERROR; break; } o->phase = OTA_PHASE_PAYLOAD; o->payload_have = 0; o->page_filled = 0; } } else if (o->phase == OTA_PHASE_PAYLOAD) { uint32_t rest_total = o->expected_size - o->payload_have; uint32_t rest_chunk = (uint32_t)(len - i); uint32_t free_in_page = FLASH_PAGE_SIZE - o->page_filled; uint32_t take = rest_chunk; if (take > rest_total) take = rest_total; if (take > free_in_page) take = free_in_page; memcpy(&o->page_buf[o->page_filled], &data[i], take); o->page_filled += take; o->payload_have += take; i += (uint16_t)take; bool page_done = (o->page_filled == FLASH_PAGE_SIZE); bool last_byte = (o->payload_have == o->expected_size); if (page_done || last_byte) { uint32_t offset_in_slot = o->payload_have - o->page_filled; if (last_byte && o->page_filled < FLASH_PAGE_SIZE) { // Letzte Page: mit 0xFF auffuellen, damit der Slot-Hash passt memset(&o->page_buf[o->page_filled], 0xFF, FLASH_PAGE_SIZE - o->page_filled); } flush_page_to_flash(o, offset_in_slot); // Fortschritt alle 64 KB als Lebenszeichen if (((o->payload_have & 0xFFFF) == 0) || last_byte) { printf(" geschrieben: %u/%u Byte\n", (unsigned)o->payload_have, (unsigned)o->expected_size); } o->page_filled = 0; if (last_byte) { o->phase = OTA_PHASE_DONE; break; } } } } } static err_t on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { ota_t *o = (ota_t *)arg; if (!p) { if (o->phase == OTA_PHASE_PAYLOAD && o->payload_have < o->expected_size) { printf("Verbindung zu frueh geschlossen (%u/%u)\n", (unsigned)o->payload_have, (unsigned)o->expected_size); o->phase = OTA_PHASE_ERROR; } else if (o->phase != OTA_PHASE_DONE) { o->phase = OTA_PHASE_DONE; } ota_finish(o); return ERR_OK; } struct pbuf *q = p; while (q) { ota_handle_bytes(o, (const uint8_t *)q->payload, q->len); q = q->next; } tcp_recved(pcb, p->tot_len); pbuf_free(p); if (o->phase == OTA_PHASE_DONE || o->phase == OTA_PHASE_ERROR) { ota_finish(o); } return ERR_OK; } static void on_err(void *arg, err_t err) { ota_t *o = (ota_t *)arg; printf("TCP-Fehler: %d\n", err); o->pcb = NULL; o->phase = OTA_PHASE_ERROR; o->done = true; } static err_t on_connected(void *arg, struct tcp_pcb *pcb, err_t err) { ota_t *o = (ota_t *)arg; if (err != ERR_OK) { printf("connect-callback err=%d\n", err); o->phase = OTA_PHASE_ERROR; ota_finish(o); return err; } err_t w = tcp_write(pcb, o->request, sizeof(o->request), TCP_WRITE_FLAG_COPY); if (w != ERR_OK) { printf("tcp_write err=%d\n", w); o->phase = OTA_PHASE_ERROR; ota_finish(o); return w; } tcp_output(pcb); return ERR_OK; } // Statisch, damit das halbe KB nicht jedesmal auf dem Stack steht. static ota_t g_ota; static void run_ota_request(const uint8_t current_hash[SHA256_BYTES], bool *update_was_received, uint32_t *received_size, uint8_t out_new_hash[SHA256_BYTES], bool *failed) { *update_was_received = false; *received_size = 0; *failed = false; ota_t *o = &g_ota; memset(o, 0, sizeof(*o)); if (!ip4addr_aton(SERVER_IP, &o->remote)) { printf("ungueltige Server-IP\n"); *failed = true; return; } uint32_t magic = OTA_MAGIC; uint32_t pid = (uint32_t)PICO_ID; memcpy(&o->request[0], &magic, 4); memcpy(&o->request[4], &pid, 4); memcpy(&o->request[8], current_hash, SHA256_BYTES); o->pcb = tcp_new_ip_type(IP_GET_TYPE(&o->remote)); if (!o->pcb) { printf("tcp_new fehlgeschlagen\n"); *failed = true; return; } tcp_arg(o->pcb, o); tcp_recv(o->pcb, on_recv); tcp_err(o->pcb, on_err); o->phase = OTA_PHASE_REQUEST; printf("Verbinde TCP %s:%d\n", SERVER_IP, SERVER_PORT); cyw43_arch_lwip_begin(); err_t err = tcp_connect(o->pcb, &o->remote, SERVER_PORT, on_connected); cyw43_arch_lwip_end(); if (err != ERR_OK) { printf("tcp_connect err=%d\n", err); ota_finish(o); *failed = true; return; } // 60 s Gesamttimeout. Reicht fuer 260 KB Download inkl. ein paar Erase-Pausen. const uint32_t TIMEOUT_MS = 60000; uint32_t waited = 0; while (!o->done && waited < TIMEOUT_MS) { sleep_ms(50); waited += 50; } if (!o->done) { printf("OTA-Timeout, breche ab\n"); ota_finish(o); *failed = true; return; } if (o->phase == OTA_PHASE_ERROR) { *failed = true; return; } *update_was_received = o->update_pending; if (o->update_pending) { *received_size = o->expected_size; memcpy(out_new_hash, o->new_hash, SHA256_BYTES); } } // ---------- Nach-Download: Tail-Erase + Hash-Verify ---------- // Erased alle Sektoren ab `start_offset` (im Slot, relativ) bis Slot-Ende. static void erase_tail(uint32_t start_offset) { // Auf Sektor-Grenze hoch runden. uint32_t aligned = (start_offset + FLASH_SECTOR_SIZE - 1) & ~(FLASH_SECTOR_SIZE - 1); uint32_t total_sectors = APP_SLOT_SIZE / FLASH_SECTOR_SIZE; uint32_t first_sector = aligned / FLASH_SECTOR_SIZE; printf("Tail-Erase: Sektoren %u..%u (= %u KB)\n", (unsigned)first_sector, (unsigned)(total_sectors - 1), (unsigned)((total_sectors - first_sector) * FLASH_SECTOR_SIZE / 1024)); for (uint32_t s = first_sector; s < total_sectors; s++) { flash_erase_sector(APP_FLASH_OFFSET + s * FLASH_SECTOR_SIZE); // Optisches Feedback: LED toggeln alle 32 Sektoren (~ alle 1.4 s). if ((s & 0x1F) == 0) { cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, (s & 0x20) != 0); } } cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false); printf("Tail-Erase fertig\n"); } // ---------- main ---------- int main(void) { stdio_init_all(); sleep_ms(2500); printf("\n=== Bootloader Etappe C (pico_id=%d) ===\n", (int)PICO_ID); if (cyw43_arch_init()) { printf("cyw43_arch_init fehlgeschlagen\n"); halt_with_led(); } cyw43_arch_enable_sta_mode(); uint8_t current_hash[SHA256_BYTES]; printf("Hashe App-Slot (%u Byte)...\n", (unsigned)APP_SLOT_SIZE); absolute_time_t t0 = get_absolute_time(); if (!hash_app_slot(current_hash)) { halt_with_led(); } int64_t took = absolute_time_diff_us(t0, get_absolute_time()) / 1000; printf("SHA256 fertig in %lld ms\n", took); print_hash("current:", current_hash); printf("Verbinde mit WLAN \"%s\"...\n", WIFI_SSID); if (cyw43_arch_wifi_connect_timeout_ms( WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 15000) != 0) { printf("WLAN-Verbindung fehlgeschlagen — boote alte App\n"); goto boot; } printf("WLAN OK\n"); bool update_received = false; bool fail = false; uint32_t received_size = 0; uint8_t expected_new_hash[SHA256_BYTES]; run_ota_request(current_hash, &update_received, &received_size, expected_new_hash, &fail); if (fail) { printf("OTA-Request fehlgeschlagen — boote alte App\n"); goto boot; } if (!update_received) { printf("Server: kein Update\n"); goto boot; } // Update wurde komplett ins Flash geschrieben. Jetzt Tail erasen // und den ganzen Slot neu hashen. erase_tail(received_size); uint8_t post_hash[SHA256_BYTES]; printf("Verifiziere durch erneutes Hashen des Slots...\n"); t0 = get_absolute_time(); if (!hash_app_slot(post_hash)) { printf("Post-Hash fehlgeschlagen — bleibe stehen\n"); halt_with_led(); } took = absolute_time_diff_us(t0, get_absolute_time()) / 1000; printf("Post-Hash fertig in %lld ms\n", took); print_hash("got: ", post_hash); print_hash("expected:", expected_new_hash); if (memcmp(post_hash, expected_new_hash, SHA256_BYTES) != 0) { printf("HASH-MISMATCH nach Flash-Write — bleibe stehen, springe nicht.\n"); halt_with_led(); } printf("Update verifiziert. \\o/\n"); boot: if (!app_present(APP_BASE)) { printf("Kein gueltiges App-Image bei 0x%08x — bleibe stehen\n", (unsigned)APP_BASE); halt_with_led(); } printf("Springe in die App...\n"); sleep_ms(100); jump_to_app(APP_BASE); }