218 lines
9.6 KiB
C++
218 lines
9.6 KiB
C++
#pragma once
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "ota/OtaBootloaderUpdate.h"
|
|
|
|
namespace mesh {
|
|
|
|
struct Nrf52BootloaderRegion {
|
|
uint32_t start = 0;
|
|
uint32_t end = 0;
|
|
};
|
|
|
|
// The MBR's flash word takes precedence over UICR, just as it does at boot.
|
|
// Exclude the MBR-parameter and settings pages. Never search the application,
|
|
// an OTA staging slot, external flash, or beyond the chip's physical flash.
|
|
inline bool nrf52BootloaderRegion(uint32_t page_size, uint32_t page_count,
|
|
uint32_t mbr_start, uint32_t uicr_start,
|
|
Nrf52BootloaderRegion& region) {
|
|
region = Nrf52BootloaderRegion();
|
|
if (page_size != 4096u || page_count < 16u || page_count > 256u) return false;
|
|
const uint32_t flash_size = page_size * page_count;
|
|
const uint32_t end = flash_size - 2u * page_size;
|
|
uint32_t start = mbr_start != UINT32_MAX ? mbr_start : uicr_start;
|
|
// Historical Adafruit/OTAFIX installations can have both words erased.
|
|
// Their 40 KiB layout is accepted only with valid vectors by the reader.
|
|
if (start == UINT32_MAX) start = end - 0xA000u;
|
|
if (start < 0x10000u || start >= end || start % page_size != 0u) return false;
|
|
region.start = start;
|
|
region.end = end;
|
|
return true;
|
|
}
|
|
|
|
inline bool nrf52BootloaderVectorsValid(const uint8_t* image, size_t size,
|
|
uint32_t start) {
|
|
if (!image || size < 8u || size > UINT32_MAX - start) return false;
|
|
const uint32_t sp = ota::ota_boot_rd32(image);
|
|
const uint32_t reset = ota::ota_boot_rd32(image + 4u);
|
|
return sp > 0x20000000u && sp <= 0x20040000u && (sp & 7u) == 0u &&
|
|
(reset & 1u) != 0u && (reset & ~1u) >= start &&
|
|
(reset & ~1u) < start + size;
|
|
}
|
|
|
|
inline bool nrf52BootVersionChar(uint8_t c) {
|
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
|
|
(c >= 'a' && c <= 'z') || c == '.' || c == '-' || c == '_' || c == '+';
|
|
}
|
|
|
|
// Return the complete token, never a truncated (and possibly different) version.
|
|
// An embedded marker literal with no actual version is not an INFO_UF2 record.
|
|
inline size_t nrf52BootVersionTokenSize(const uint8_t* token, size_t available) {
|
|
size_t pos = available && token[0] == 'v' ? 1u : 0u;
|
|
for (unsigned component = 0; component < 3u; ++component) {
|
|
const size_t begin = pos;
|
|
while (pos < available && token[pos] >= '0' && token[pos] <= '9') ++pos;
|
|
if (pos == begin) return 0;
|
|
if (component != 2u && (pos >= available || token[pos++] != '.')) return 0;
|
|
}
|
|
size_t n = 0;
|
|
for (; n < available && n <= 127u; ++n) {
|
|
const uint8_t c = token[n];
|
|
if (c == 0 || c == ' ' || c == '\t' || c == '\r' || c == '\n')
|
|
return n;
|
|
if (!nrf52BootVersionChar(c)) return 0;
|
|
}
|
|
return 0; // unterminated or too long
|
|
}
|
|
|
|
inline bool nrf52BootDecimalComponent(const uint8_t* token, size_t length, size_t& pos) {
|
|
const size_t begin = pos;
|
|
while (pos < length && token[pos] >= '0' && token[pos] <= '9') ++pos;
|
|
return pos != begin;
|
|
}
|
|
|
|
// Recent OTAFIX images embed the INFO_UF2 version value without the older
|
|
// "UF2 Bootloader " label. Match only that explicit version shape, not an
|
|
// arbitrary dotted number or a SoftDevice/application string.
|
|
inline bool nrf52BootStandaloneOtafixToken(const uint8_t* token, size_t length) {
|
|
if (!token || length == 0u || token[0] != 'v') return false;
|
|
size_t pos = 1u;
|
|
for (unsigned component = 0; component < 3u; ++component) {
|
|
if (!nrf52BootDecimalComponent(token, length, pos)) return false;
|
|
if (component != 2u && (pos >= length || token[pos++] != '.')) return false;
|
|
}
|
|
static const char marker[] = "-OTAFIX";
|
|
const size_t marker_size = sizeof(marker) - 1u;
|
|
if (length - pos < marker_size || memcmp(token + pos, marker, marker_size) != 0)
|
|
return false;
|
|
pos += marker_size;
|
|
if (!nrf52BootDecimalComponent(token, length, pos) ||
|
|
pos >= length || token[pos++] != '.' ||
|
|
!nrf52BootDecimalComponent(token, length, pos)) return false;
|
|
if (pos < length && token[pos] == '.') {
|
|
++pos;
|
|
if (!nrf52BootDecimalComponent(token, length, pos)) return false;
|
|
}
|
|
return pos == length || token[pos] == '-' || token[pos] == '+' || token[pos] == '_';
|
|
}
|
|
|
|
inline bool nrf52BootloaderVersion(const uint8_t* image, size_t size,
|
|
uint32_t start, uint32_t cached_base_version,
|
|
char* out, size_t capacity) {
|
|
if (!out || capacity == 0u) return false;
|
|
out[0] = 0;
|
|
if (capacity < 2u || !nrf52BootloaderVectorsValid(image, size, start)) return false;
|
|
|
|
static const char marker[] = "UF2 Bootloader ";
|
|
const size_t marker_size = sizeof(marker) - 1u;
|
|
const uint8_t* selected = nullptr;
|
|
size_t selected_size = 0;
|
|
bool conflicting = false;
|
|
for (size_t offset = 0; offset + marker_size < size; ++offset) {
|
|
if (image[offset] != 'U' || memcmp(image + offset, marker, marker_size) != 0) continue;
|
|
const uint8_t* token = image + offset + marker_size;
|
|
const size_t length = nrf52BootVersionTokenSize(token, size - offset - marker_size);
|
|
if (length == 0u) continue;
|
|
if (selected && (length != selected_size || memcmp(selected, token, length) != 0))
|
|
conflicting = true;
|
|
selected = token;
|
|
selected_size = length;
|
|
}
|
|
if (selected && !conflicting) {
|
|
if (selected_size >= capacity) return false;
|
|
memcpy(out, selected, selected_size);
|
|
out[selected_size] = 0;
|
|
return true;
|
|
}
|
|
|
|
// Reuse the installer's whole-image CRC and unique BLMF/BLM2 validation.
|
|
// Metadata contains the OTAFIX version, not Adafruit's base release number;
|
|
// do not fabricate a 0.x.y prefix when INFO_UF2 is absent or ambiguous.
|
|
ota::OtaBootloaderIdentity identity;
|
|
if (start == ota::OTA_BOOT_IMAGE_START && size == ota::OTA_BOOT_IMAGE_SIZE &&
|
|
ota::ota_bootloader_identity_from_image(image, size, identity) &&
|
|
identity.continuity_present) {
|
|
const uint32_t version = identity.boot_version;
|
|
const unsigned preview = version & 0xffu;
|
|
int written;
|
|
if (preview == 0xffu) {
|
|
written = snprintf(out, capacity, "OTAFIX%u.%u.%u",
|
|
(unsigned)(version >> 24), (unsigned)((version >> 16) & 0xffu),
|
|
(unsigned)((version >> 8) & 0xffu));
|
|
} else {
|
|
written = snprintf(out, capacity, "OTAFIX%u.%u.%u-preview.%u",
|
|
(unsigned)(version >> 24), (unsigned)((version >> 16) & 0xffu),
|
|
(unsigned)((version >> 8) & 0xffu), preview);
|
|
}
|
|
if (written > 0 && (size_t)written < capacity) return true;
|
|
out[0] = 0;
|
|
return false;
|
|
}
|
|
if (conflicting) return false;
|
|
|
|
// Only as a fallback, inspect at most the first 40 KiB of the already
|
|
// identified bootloader region. A unique standalone OTAFIX version is useful
|
|
// on boards whose installed image lacks a reader-recognized BLMF identity.
|
|
// If a canonical identity is present but corrupt, do not let its printable
|
|
// version string make that image look healthy.
|
|
bool allow_standalone = true;
|
|
if (size == ota::OTA_BOOT_IMAGE_SIZE) {
|
|
const size_t offset = ota::OTA_BOOT_CANDIDATE_MANIFEST_OFFSET;
|
|
const bool magic0 = ota::ota_boot_rd32(image + offset) == ota::OTA_BOOT_MANIFEST_MAGIC0;
|
|
const bool magic1 = ota::ota_boot_rd32(image + offset + 4u) == ota::OTA_BOOT_MANIFEST_MAGIC1;
|
|
if (magic0 || magic1) {
|
|
ota::OtaBootloaderIdentity continuity;
|
|
allow_standalone =
|
|
magic0 && magic1 &&
|
|
ota::ota_boot_rd16(image + offset + 8u) == ota::OTA_BOOT_MANIFEST_VERSION &&
|
|
ota::ota_boot_rd16(image + offset + 10u) == ota::OTA_BOOT_MANIFEST_SIZE &&
|
|
ota::ota_boot_rd32(image + offset + 12u) == ota::OTA_BOOT_IMAGE_START &&
|
|
ota::ota_boot_rd32(image + offset + 16u) == ota::OTA_BOOT_IMAGE_SIZE &&
|
|
ota::ota_bootloader_board_id_valid(ota::ota_boot_rd32(image + offset + 20u)) &&
|
|
ota::ota_boot_image_crc32(image, size, offset + 40u) ==
|
|
ota::ota_boot_rd32(image + offset + 40u) &&
|
|
ota::ota_bootloader_continuity_parse(image + offset, size - offset,
|
|
continuity) &&
|
|
continuity.continuity_present;
|
|
}
|
|
}
|
|
const size_t scan_size = size < ota::OTA_BOOT_IMAGE_SIZE ? size : ota::OTA_BOOT_IMAGE_SIZE;
|
|
const uint8_t* standalone = nullptr;
|
|
size_t standalone_size = 0;
|
|
for (size_t offset = 0; allow_standalone && offset < scan_size; ++offset) {
|
|
if (image[offset] != 'v' ||
|
|
(offset != 0u && nrf52BootVersionChar(image[offset - 1u]))) continue;
|
|
const uint8_t* token = image + offset;
|
|
const size_t length = nrf52BootVersionTokenSize(token, scan_size - offset);
|
|
if (length == 0u || !nrf52BootStandaloneOtafixToken(token, length)) continue;
|
|
if (standalone &&
|
|
(length != standalone_size || memcmp(standalone, token, length) != 0)) return false;
|
|
standalone = token;
|
|
standalone_size = length;
|
|
}
|
|
if (standalone) {
|
|
if (standalone_size >= capacity) return false;
|
|
memcpy(out, standalone, standalone_size);
|
|
out[standalone_size] = 0;
|
|
return true;
|
|
}
|
|
|
|
// Adafruit's core captures TIMER2->CC[0] at startup, before the timer can
|
|
// be reused. This is only a base version (no OTAFIX/fork suffix). Never read
|
|
// the live timer here or reinterpret Nordic settings with a different layout.
|
|
if (cached_base_version != 0u && cached_base_version <= 0x00ffffffu) {
|
|
const int written = snprintf(out, capacity, "%u.%u.%u (base)",
|
|
(unsigned)(cached_base_version >> 16),
|
|
(unsigned)((cached_base_version >> 8) & 0xffu),
|
|
(unsigned)(cached_base_version & 0xffu));
|
|
if (written > 0 && (size_t)written < capacity) return true;
|
|
out[0] = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace mesh
|