arduino-pico/ota/ota.c
Earle F. Philhower, III b473139758
Enable use of uninitialized_ram() macro (#1199)
The uninitted RAM macro would fail because the OTA code would clear out the
first 50KB or so of RAM to copy its code and global values.

Move all global values to the end of RAM in OTA, and then align any uninitted
vars to a 16KB unit to ensure it won't appear in the 1st part of RAM that is
still needed to copy the OTA executable.

In the normal case, without any uninit_ram vars, no additional space is used.
When uninit_ram is used, up to 16KB of space may be lost to get that alignment,
but it will work properly.

Fixes #1188
2023-02-16 17:59:15 -08:00

219 lines
6.6 KiB
C

/*
ota.c - OTA stub that copies from LittleFS to flash
Copyright (c) 2022 Earle F. Philhower, III. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdint.h>
#include <string.h>
#include <hardware/irq.h>
#include <hardware/structs/scb.h>
#include <hardware/sync.h>
#include <hardware/flash.h>
#include <pico/time.h>
#include <hardware/gpio.h>
#include <hardware/uart.h>
#include <hardware/watchdog.h>
#include "ota_lfs.h"
#include "ota_command.h"
//#define DEBUG 1
#ifndef DEBUG
#define uart_putc(a, b)
#define uart_puts(a, b)
#endif
uint8_t **__FS_START__ = (uint8_t **)(XIP_BASE + 0x3000 - 0x10 + 0x0);
uint8_t **__FS_END__ = (uint8_t **)(XIP_BASE + 0x3000 - 0x10 + 0x4);
void dumphex(uint32_t x) {
#ifndef DEBUG
(void) x;
#else
uart_puts(uart0, "0x");
for (int nibble = 7; nibble >= 0; nibble--) {
uint32_t n = 0x0f & (x >> (nibble * 4));
char c = n < 10 ? '0' + n : 'A' + n - 10;
uart_putc(uart0, c);
}
#endif
}
extern OTACmdPage _ota_cmd;
void do_ota() {
if (*__FS_START__ == *__FS_END__) {
return;
}
if (!lfsMount(*__FS_START__, 4096, *__FS_END__ - *__FS_START__)) {
uart_puts(uart0, "mount failed\n");
return;
}
// We are very naughty and record the last block read, since it should be the actual data block of the
// OTA structure. We'll erase it behind the scenes to avoid bringing in all of LittleFS write infra.
uint32_t blockToErase;
if (!lfsReadOTA(&_ota_cmd, &blockToErase)) {
return;
}
if (memcmp(_ota_cmd.sign, "Pico OTA", 8)) {
return; // No signature
}
uint32_t crc = 0xffffffff;
const uint8_t *data = (const uint8_t *)&_ota_cmd;
for (uint32_t i = 0; i < offsetof(OTACmdPage, crc32); i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 1) {
crc = (crc >> 1) ^ 0xedb88320;
} else {
crc >>= 1;
}
}
}
crc = ~crc;
if (crc != _ota_cmd.crc32) {
uart_puts(uart0, "\ncrc32 mismatch\n");
return;
}
if (!_ota_cmd.count) {
uart_puts(uart0, "\nno ota count\n");
return;
}
for (uint32_t i = 0; i < _ota_cmd.count; i++) {
switch (_ota_cmd.cmd[i].command) {
case _OTA_WRITE:
uart_puts(uart0, "write: open ");
uart_puts(uart0, _ota_cmd.cmd[i].write.filename);
uart_puts(uart0, " = ");
if (!lfsOpen(_ota_cmd.cmd[i].write.filename)) {
uart_puts(uart0, "failed\n");
return;
}
uart_puts(uart0, "success\n");
uart_puts(uart0, "seek ");
dumphex(_ota_cmd.cmd[i].write.fileOffset);
uart_puts(uart0, " = ");
if (!lfsSeek(_ota_cmd.cmd[i].write.fileOffset)) {
uart_puts(uart0, "failed\n");
return;
}
uart_puts(uart0, "success\n");
uint32_t toRead = _ota_cmd.cmd[i].write.fileLength;
uint32_t toWrite = _ota_cmd.cmd[i].write.flashAddress;
while (toRead) {
uint32_t len = (toRead < 4096) ? toRead : 4096;
uint8_t *p = lfsRead(len);
if (!p) {
uart_puts(uart0, "read failed\n");
return;
}
uart_puts(uart0, "toread = ");
dumphex(toRead);
uart_puts(uart0, "\ntowrite = ");
dumphex(toWrite);
uart_puts(uart0, "\n");
// Only write pages which differ (i.e. preserve OTA pages unless the OTA shim changes)
if (memcmp(p, (void*)toWrite, 4096)) {
uart_puts(uart0, "writing\n");
int save = save_and_disable_interrupts();
flash_range_erase((intptr_t)toWrite - XIP_BASE, 4096);
flash_range_program((intptr_t)toWrite - XIP_BASE, (const uint8_t *)p, 4096);
restore_interrupts(save);
} else {
uart_puts(uart0, "identical to flash, skipping\n");
}
toRead -= len;
toWrite += 4096;
}
lfsClose();
break;
default:
// TODO - verify
break;
}
}
uart_puts(uart0, "\nota completed\n");
// Work completed, erase record.
lfsEraseBlock(blockToErase);
// Do a hard reset just in case the start up sequence is not the same
watchdog_reboot(0, 0, 100);
}
#pragma GCC push_options
#pragma GCC optimize("O0")
int main(int a, char **b) {
(void) a;
(void) b;
#ifdef DEBUG
uart_init(uart0, 115200);
gpio_set_function(0, GPIO_FUNC_UART);
gpio_set_function(1, GPIO_FUNC_UART);
uart_set_hw_flow(uart0, false, false);
uart_set_format(uart0, 8, 1, UART_PARITY_NONE);
#endif
do_ota();
// Reset the interrupt/etc. vectors to the real app. Will be copied to RAM in app's runtime_init
scb_hw->vtor = (uint32_t)0x10003000;
// Jump to it
register uint32_t* sp asm("sp");
register uint32_t _sp = *(uint32_t *)0x10003000;
register void (*fcn)(void) = (void (*)(void)) *(uint32_t *)0x10003004;
sp = (uint32_t *)_sp;
fcn();
// Should never get here!
return *sp;
}
#pragma GCC pop_options
// Clear out some unwanted extra code
int __wrap_atexit(void (*function)(void)) {
(void) function;
return 0;
}
// Clear out some unwanted extra code
void __wrap_exit(int status) {
(void) status;
while (1) continue;
}
void __wrap_panic(const char *x) {
(void) x;
while (1) continue;
}
void __wrap_panic_unsupported() {
while (1) continue;
}
void __wrap_hard_assertion_failure() {
while (1) continue;
}