Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ In OTA DFU mode:

The OTA fallback is intentional and prevents devices from getting stuck in UF2 mode after failed OTA updates.

If the bootloader recorded a fault since the last UF2 drive session, `INFO_UF2.TXT` shows a `Last-Fault:` line with the fault id, program counter, and info word. It appears only if the next boot reaches the drive before an application boots, since the application's RAM use wipes the record.

---

### OTA update fails with `Error: Operation Failed`
Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ it and is upstream Adafruit history, kept for provenance.

## OTAFIX 2.6

- `INFO_UF2.TXT` shows a `Last-Fault: id=0x.. pc=0x.. info=0x..` line for a SoftDevice or SDK fault the bootloader recorded since the last UF2 drive session. It appears only if the next boot reaches the drive before an application boots, since the application's RAM use wipes the record. The record lives in a 16-byte RAM region behind a magic word and is cleared once printed; for an SDK error `info` is the error code.
- A device with no valid application that is plugged into a USB host comes up as a UF2 drive showing NO FIRMWARE instead of waiting for a phone. On battery (no VBUS) it goes straight to the BLE OTA wait, and with VBUS but no host enumerating within 3 s (a charger or power bank) it falls through to the same wait. A cabled board with no application returns to the drive after an aborted copy, and after an eject on boards built with `UF2_EXIT_ON_EJECT` (`mesh_tracker_x1`, `t1000_e`). When the cable is pulled while the drive is up (VBUS gone for 250 ms), it goes to the BLE OTA wait. Plugging a cable into a board with no application that is waiting for BLE, with no phone connected, brings the drive up (VBUS present for 250 ms); a cable already present when the wait began, or a phone connected, leaves the wait alone.
- A UF2 application copy marks `bank_0` invalid when its first application block arrives, so a cable pulled mid-copy leaves the device with no application instead of an old `bank_0` marked valid over a half-written image. The completion path records the real application size as before.
- A bootloader self-update whose staged image would overwrite the tail of the installed application (one larger than 744 KB on S140 6.1.1 or 740 KB on 7.3.0) marks `bank_0` invalid first, so the device comes up in DFU with no application instead of booting a corrupted one.
Expand Down
12 changes: 11 additions & 1 deletion linker/nrf52.ld
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ MEMORY


/** RAM Region for bootloader. */
RAM (rwx) : ORIGIN = 0x20008000, LENGTH = 0x20010000-0x20008000
RAM (rwx) : ORIGIN = 0x20008010, LENGTH = 0x20010000-0x20008010

/* Written by the fault handler and read after the next reset; above the SoftDevice RAM, outside .data and .bss. */
FAULT_RECORD (rwx) : ORIGIN = 0x20008000, LENGTH = 0x10

/* Location for double reset detection, no init */
DBL_RESET (rwx) : ORIGIN = 0x20007F7C, LENGTH = 0x04
Expand Down Expand Up @@ -83,6 +86,13 @@ SECTIONS
{

} > NOINIT

.fault_record (NOLOAD) :
{
KEEP(*(.fault_record))
} > FAULT_RECORD

__sd_ram_end__ = ORIGIN(FAULT_RECORD);
/* other placements follow here... */
}

Expand Down
12 changes: 11 additions & 1 deletion linker/nrf52833.ld
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ MEMORY

/** RAM Region for bootloader. */
/* Avoid conflict with NOINIT for OTA bond sharing */
RAM (rwx) : ORIGIN = 0x20008000, LENGTH = 0x20020000-0x20008000
RAM (rwx) : ORIGIN = 0x20008010, LENGTH = 0x20020000-0x20008010

/* Written by the fault handler and read after the next reset; above the SoftDevice RAM, outside .data and .bss. */
FAULT_RECORD (rwx) : ORIGIN = 0x20008000, LENGTH = 0x10

/* Location for double reset detection, no init */
DBL_RESET (rwx) : ORIGIN = 0x20007F7C, LENGTH = 0x04
Expand Down Expand Up @@ -92,6 +95,13 @@ SECTIONS

} > NOINIT

.fault_record (NOLOAD) :
{
KEEP(*(.fault_record))
} > FAULT_RECORD

__sd_ram_end__ = ORIGIN(FAULT_RECORD);

/* other placements follow here... */
}

Expand Down
12 changes: 11 additions & 1 deletion linker/nrf52833_debug.ld
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ MEMORY

/** RAM Region for bootloader. */
/* Avoid conflict with NOINIT for OTA bond sharing */
RAM (rwx) : ORIGIN = 0x20008000, LENGTH = 0x20020000-0x20008000
RAM (rwx) : ORIGIN = 0x20008010, LENGTH = 0x20020000-0x20008010

/* Written by the fault handler and read after the next reset; above the SoftDevice RAM, outside .data and .bss. */
FAULT_RECORD (rwx) : ORIGIN = 0x20008000, LENGTH = 0x10

/* Location for double reset detection, no init */
DBL_RESET (rwx) : ORIGIN = 0x20007F7C, LENGTH = 0x04
Expand Down Expand Up @@ -93,6 +96,13 @@ SECTIONS

} > NOINIT

.fault_record (NOLOAD) :
{
KEEP(*(.fault_record))
} > FAULT_RECORD

__sd_ram_end__ = ORIGIN(FAULT_RECORD);

/* other placements follow here... */
}

Expand Down
12 changes: 11 additions & 1 deletion linker/nrf52840.ld
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ MEMORY

/** RAM Region for bootloader. */
/* Avoid conflict with NOINIT for OTA bond sharing */
RAM (rwx) : ORIGIN = 0x20008000, LENGTH = 0x20040000-0x20008000
RAM (rwx) : ORIGIN = 0x20008010, LENGTH = 0x20040000-0x20008010

/* Written by the fault handler and read after the next reset; above the SoftDevice RAM, outside .data and .bss. */
FAULT_RECORD (rwx) : ORIGIN = 0x20008000, LENGTH = 0x10

/* Location for double reset detection, no init */
DBL_RESET (rwx) : ORIGIN = 0x20007F7C, LENGTH = 0x04
Expand Down Expand Up @@ -92,6 +95,13 @@ SECTIONS

} > NOINIT

.fault_record (NOLOAD) :
{
KEEP(*(.fault_record))
} > FAULT_RECORD

__sd_ram_end__ = ORIGIN(FAULT_RECORD);

/* other placements follow here... */
}

Expand Down
12 changes: 11 additions & 1 deletion linker/nrf52840_debug.ld
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ MEMORY

/** RAM Region for bootloader. */
/* Avoid conflict with NOINIT for OTA bond sharing */
RAM (rwx) : ORIGIN = 0x20008000, LENGTH = 0x20040000-0x20008000
RAM (rwx) : ORIGIN = 0x20008010, LENGTH = 0x20040000-0x20008010

/* Written by the fault handler and read after the next reset; above the SoftDevice RAM, outside .data and .bss. */
FAULT_RECORD (rwx) : ORIGIN = 0x20008000, LENGTH = 0x10

/* Location for double reset detection, no init */
DBL_RESET (rwx) : ORIGIN = 0x20007F7C, LENGTH = 0x04
Expand Down Expand Up @@ -93,6 +96,13 @@ SECTIONS

} > NOINIT

.fault_record (NOLOAD) :
{
KEEP(*(.fault_record))
} > FAULT_RECORD

__sd_ram_end__ = ORIGIN(FAULT_RECORD);

/* other placements follow here... */
}

Expand Down
12 changes: 11 additions & 1 deletion linker/nrf52_debug.ld
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ MEMORY


/** RAM Region for bootloader. */
RAM (rwx) : ORIGIN = 0x20008000, LENGTH = 0x20010000-0x20008000
RAM (rwx) : ORIGIN = 0x20008010, LENGTH = 0x20010000-0x20008010

/* Written by the fault handler and read after the next reset; above the SoftDevice RAM, outside .data and .bss. */
FAULT_RECORD (rwx) : ORIGIN = 0x20008000, LENGTH = 0x10

/* Location for double reset detection, no init */
DBL_RESET (rwx) : ORIGIN = 0x20007F7C, LENGTH = 0x04
Expand Down Expand Up @@ -83,6 +86,13 @@ SECTIONS
{

} > NOINIT

.fault_record (NOLOAD) :
{
KEEP(*(.fault_record))
} > FAULT_RECORD

__sd_ram_end__ = ORIGIN(FAULT_RECORD);
/* other placements follow here... */
}

Expand Down
17 changes: 17 additions & 0 deletions src/fault_record.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef FAULT_RECORD_H
#define FAULT_RECORD_H

#include <stdint.h>

// The magic word tells a real record from power-on RAM garbage.
#define FAULT_RECORD_MAGIC 0x4641554Cu

typedef struct {
uint32_t magic, id, pc, info;
} fault_record_t;

// Lives in the linker's 16-byte FAULT_RECORD region at the start of bootloader
// RAM: above the SoftDevice RAM, outside .data and .bss, so reset leaves it alone.
extern fault_record_t g_fault_record;

#endif
17 changes: 15 additions & 2 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
#include "nrf_mbr.h"
#include "pstorage.h"
#include "nrfx_nvmc.h"
#include "fault_record.h"
#include "dfu_ble_svc.h"

#ifdef NRF_USBD

Expand Down Expand Up @@ -450,8 +452,8 @@ static uint32_t ble_stack_init(void) {
sd_nvic_EnableIRQ(SD_EVT_IRQn);

/*------------- Configure BLE params -------------*/
extern uint32_t __data_start__[]; // defined in linker
uint32_t ram_start = (uint32_t) __data_start__;
extern uint32_t __sd_ram_end__[]; // defined in linker: the SoftDevice owns RAM below it
uint32_t ram_start = (uint32_t) __sd_ram_end__;

ble_cfg_t blecfg;

Expand Down Expand Up @@ -498,7 +500,18 @@ static uint32_t ble_stack_init(void) {
//--------------------------------------------------------------------+
// Error Handler
//--------------------------------------------------------------------+
// The peer data and its CRC (dfu_ble_svc.c) occupy the first 0x40 bytes of NOINIT.
STATIC_ASSERT(sizeof(dfu_ble_peer_data_t) + sizeof(uint16_t) <= 0x40);
STATIC_ASSERT(sizeof(fault_record_t) <= 0x10);
fault_record_t g_fault_record __attribute__((section(".fault_record")));

void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info) {
// For SDK errors info points at a stack error_info_t that will not survive the reset.
if (id == NRF_FAULT_ID_SDK_ERROR && info != 0) info = ((error_info_t const*) info)->err_code;
g_fault_record.id = id;
g_fault_record.pc = pc;
g_fault_record.info = info;
g_fault_record.magic = FAULT_RECORD_MAGIC;
volatile uint32_t* ARM_CM_DHCSR = ((volatile uint32_t*) 0xE000EDF0UL); /* Cortex M CoreDebug->DHCSR */
if ((*ARM_CM_DHCSR) & 1UL) __asm("BKPT #0\n"); /* Only halt mcu if debugger is attached */
NVIC_SystemReset();
Expand Down
23 changes: 20 additions & 3 deletions src/usb/uf2/ghostfat.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "bootloader_settings.h"
#include "bootloader.h"
#include "boards.h"
#include "fault_record.h"

//--------------------------------------------------------------------+
//
Expand Down Expand Up @@ -134,9 +135,9 @@ STATIC_ASSERT(FAT_ENTRIES_PER_SECTOR == 256); // FAT

static const char infoUf2Text[] = INFO_UF2_TEXT;
char infoUf2File[128*3];
// uf2_init() appends "SoftDevice: S140 7.3.0\r\n" at runtime; 40 covers the
// widest values the id/version fields can print
STATIC_ASSERT(sizeof(INFO_UF2_TEXT) + 40 <= sizeof(infoUf2File));
// uf2_init() appends "SoftDevice: S140 7.3.0\r\n" (40 covers the widest id/version
// fields) and "Last-Fault: id=0x<8> pc=0x<8> info=0x<8>\r\n" (57 bytes)
STATIC_ASSERT(sizeof(INFO_UF2_TEXT) + 40 + 57 <= sizeof(infoUf2File));

const char indexFile[] =
"<!doctype html>\n"
Expand Down Expand Up @@ -345,6 +346,22 @@ void uf2_init(void)
{
strcat(infoUf2File, "not found\r\n");
}

if (g_fault_record.magic == FAULT_RECORD_MAGIC)
{
char str[10];
strcat(infoUf2File, "Last-Fault: id=0x");
utoa(g_fault_record.id, str, 16);
strcat(infoUf2File, str);
strcat(infoUf2File, " pc=0x");
utoa(g_fault_record.pc, str, 16);
strcat(infoUf2File, str);
strcat(infoUf2File, " info=0x");
utoa(g_fault_record.info, str, 16);
strcat(infoUf2File, str);
strcat(infoUf2File, "\r\n");
g_fault_record.magic = 0;
}
}

/*------------------------------------------------------------------*/
Expand Down
Loading