From 65ac4d0ee6f825b621b3b0c8d0fdea713bebc9e9 Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Mon, 13 Jul 2026 19:09:25 +0200 Subject: [PATCH 01/10] fix(tx16s): execute USB/SD interrupt-path code from RAM On some TX16S MK1 units, instruction fetches from the upper flash bank return corrupted data under heavy IRQ/DMA load. USB joystick or mass-storage traffic then hard-faults inside the OTG/DMA interrupt handlers (UNDEFINSTR/NOCP on provably valid instructions) and the radio reboots into DFU. This is the long-standing "radio switches to DFU when plugged into USB" issue (#5899, #7356, #6047, #6414): unit- dependent (marginal flash), version-dependent (the linker moves the hot handlers across the bank boundary as the firmware grows), and the bootloader is immune (its code sits at the start of bank 1). Fix: link the USB device stack, PCD/LL drivers, MSC storage glue and SDIO diskio into a .fastcode section placed in RAM (copied at startup, ~14KB), so the hot interrupt paths never fetch from flash. Validated on an affected TX16S: continuous mass-storage stress-reads crashed after ~1 min with the code in flash, and ran clean with the code in RAM (same flash config otherwise). Build with -DNO_LTO=ON (or drop -flto from the USB object libraries): linker file-pattern matching cannot see through LTO partitions. --- .../generic_stm32/linker/definitions.ld | 6 +++++ .../boards/generic_stm32/linker/firmware.ld | 25 +++++++++++++++++++ .../targets/common/arm/stm32/system_init.c | 9 +++++++ 3 files changed, 40 insertions(+) diff --git a/radio/src/boards/generic_stm32/linker/definitions.ld b/radio/src/boards/generic_stm32/linker/definitions.ld index 47d1ac07bce..936ba752c85 100644 --- a/radio/src/boards/generic_stm32/linker/definitions.ld +++ b/radio/src/boards/generic_stm32/linker/definitions.ld @@ -11,3 +11,9 @@ _main_stack_start = _estack - MAIN_STACK_SIZE; /* Higest heap address */ _heap_end = HEAP_ADDRESS; + +/* Fastcode defaults for scripts that do not define the section + (bootloader): the startup copy loop becomes a no-op */ +PROVIDE(_sfastcode = 0); +PROVIDE(_efastcode = 0); +PROVIDE(_fastcode_load = 0); diff --git a/radio/src/boards/generic_stm32/linker/firmware.ld b/radio/src/boards/generic_stm32/linker/firmware.ld index d20169298bd..af3ba72fbad 100644 --- a/radio/src/boards/generic_stm32/linker/firmware.ld +++ b/radio/src/boards/generic_stm32/linker/firmware.ld @@ -33,6 +33,31 @@ SECTIONS _isr_load = LOADADDR(.isr_vector); + /* Hot interrupt-path code (USB device stack, MSC storage glue, SDIO + diskio) executed from RAM: on some TX16S units, instruction + fetches from the upper flash bank get corrupted under heavy + IRQ/DMA load (USB crash, EdgeTX#5899); claimed before .text so + these input sections land here. Requires non-LTO objects. */ + .fastcode : + { + . = ALIGN(4); + _sfastcode = .; + *ll_usb*(.text .text.*) + *hal_pcd*(.text .text.*) + *usb_driver*(.text .text.*) + *usbd_conf*(.text .text.*) + *usbd_core*(.text .text.*) + *usbd_ioreq*(.text .text.*) + *usbd_ctlreq*(.text .text.*) + *usbd_msc*(.text .text.*) + *usbd_storage_msd*(.text .text.*) + *diskio_sdio*(.text .text.*) + . = ALIGN(4); + _efastcode = .; + } > REGION_BSS AT> REGION_TEXT_STORAGE + + _fastcode_load = LOADADDR(.fastcode); + /* Main code section */ .text (READONLY) : { diff --git a/radio/src/targets/common/arm/stm32/system_init.c b/radio/src/targets/common/arm/stm32/system_init.c index 72e530e69c9..0e69f7578f6 100644 --- a/radio/src/targets/common/arm/stm32/system_init.c +++ b/radio/src/targets/common/arm/stm32/system_init.c @@ -71,6 +71,15 @@ NAKED BOOTSTRAP void Reset_Handler() "bl naked_copy \n" ); + // Copy hot interrupt-path code into RAM (no-op when the linker + // script does not define a .fastcode section) + asm inline ( + "ldr r0, =_sfastcode \n" + "ldr r1, =_efastcode \n" + "ldr r2, =_fastcode_load \n" + "bl naked_copy \n" + ); + #if defined(BOOT) && defined(REQUIRE_MPU_CONFIG) asm inline ( "bl MPU_Config \n" From b4f4d88ddb8bbc376cf49a4b6e2a9a138ac067f0 Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Mon, 13 Jul 2026 19:37:43 +0200 Subject: [PATCH 02/10] fix(tx16s): keep USB/SDIO object libraries out of LTO Linker-script file-pattern matching (used by the .fastcode RAM section) cannot see through LTO partitions; without this the hot interrupt code silently stays in flash. --- radio/src/bootloader/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/radio/src/bootloader/CMakeLists.txt b/radio/src/bootloader/CMakeLists.txt index 25d5aa39c36..6d4c25bbf98 100644 --- a/radio/src/bootloader/CMakeLists.txt +++ b/radio/src/bootloader/CMakeLists.txt @@ -41,12 +41,12 @@ remove_definitions(-DUSB_SERIAL) if(NOT NO_LTO) message("-- Use LTO to compile bootloader") - target_compile_options(stm32cube_ll PRIVATE -flto) - target_compile_options(stm32usb PRIVATE -flto) + # stm32cube_ll, stm32usb, stm32usb_device_fw and + # stm32_drivers_w_dbg_fw are kept out of LTO: their objects must + # stay pattern-matchable by the .fastcode output section (hot + # USB/SD interrupt code executed from RAM, see firmware.ld) target_compile_options(stm32usb_device_bl PRIVATE -flto) - target_compile_options(stm32usb_device_fw PRIVATE -flto) target_compile_options(stm32_drivers PRIVATE -flto) - target_compile_options(stm32_drivers_w_dbg_fw PRIVATE -flto) target_compile_options(stm32_drivers_w_dbg_bl PRIVATE -flto) target_compile_options(board_bl PRIVATE -flto) endif() From 823fa8063293a6da6c931a25b544139c0325c1dd Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Tue, 14 Jul 2026 10:34:17 +0200 Subject: [PATCH 03/10] fix(tx16s): guarantee all hot code lives in flash bank 1 (linker layout) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On some STM32F42x units (flash rev < 3), USB D+ (PA12) switching noise corrupts reads from flash bank 2 (ST errata ES0166 §2.4.6, EdgeTX#5899). Restrict the FLASH region to bank 1 (overflow = link error) and relegate UI code/rodata (.uicode: colorlcd, lvgl, libopenui) and assets (.flash) to bank 2 via a per-target pre_text_sections.ld, claimed before the .text catch-all. Shared C++ comdats (std::) are pinned to bank 1 (.text.shared) with their own startup copy (no-op on XIP targets), so link order can never strand a template instantiation used by hot code in bank 2. Symbols called from non-UI tasks but living in UI objects (lv_tick_inc from SysTick, R9M inline helpers from the pulses path, popup mailbox from telemetry/log timers, SET_SCREEN from the mixer) are pinned individually (.text.pinned). bench/check_bank1.sh verifies after every link: no executable section in bank 2 except .uicode, USB handlers in RAM, and no direct call path from USB-active contexts (ISRs, mixer, audio, timers, perMain head) into bank 2. Co-Authored-By: Claude Fable 5 --- bench/check_bank1.sh | 132 ++++++++++++++++++ .../generic_stm32/linker/definitions.ld | 5 + .../boards/generic_stm32/linker/firmware.ld | 36 ++++- .../generic_stm32/linker/stm32f20x/layout.ld | 1 + .../linker/stm32f20x/pre_text_sections.ld | 1 + .../generic_stm32/linker/stm32f40x/layout.ld | 1 + .../linker/stm32f40x/pre_text_sections.ld | 1 + .../generic_stm32/linker/stm32f413/layout.ld | 1 + .../linker/stm32f413/pre_text_sections.ld | 1 + .../linker/stm32f429_sdram/layout.ld | 20 ++- .../stm32f429_sdram/pre_text_sections.ld | 43 ++++++ .../linker/stm32h750_sdram/layout.ld | 1 + .../stm32h750_sdram/pre_text_sections.ld | 1 + .../linker/stm32h7rs_sdram/layout.ld | 1 + .../stm32h7rs_sdram/pre_text_sections.ld | 1 + .../targets/common/arm/stm32/system_init.c | 9 ++ 16 files changed, 247 insertions(+), 8 deletions(-) create mode 100755 bench/check_bank1.sh create mode 100644 radio/src/boards/generic_stm32/linker/stm32f20x/pre_text_sections.ld create mode 100644 radio/src/boards/generic_stm32/linker/stm32f40x/pre_text_sections.ld create mode 100644 radio/src/boards/generic_stm32/linker/stm32f413/pre_text_sections.ld create mode 100644 radio/src/boards/generic_stm32/linker/stm32f429_sdram/pre_text_sections.ld create mode 100644 radio/src/boards/generic_stm32/linker/stm32h750_sdram/pre_text_sections.ld create mode 100644 radio/src/boards/generic_stm32/linker/stm32h7rs_sdram/pre_text_sections.ld diff --git a/bench/check_bank1.sh b/bench/check_bank1.sh new file mode 100755 index 00000000000..e71aa3a3b2d --- /dev/null +++ b/bench/check_bank1.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# Verification post-link du layout "code en bank 1" (TX16S, EdgeTX#5899, +# errata ST ES0166 §2.4.6 : bruit PA12/USB-D+ -> lectures corrompues en +# flash bank 2 pendant une session USB). +# +# Garanties verifiees : +# 1. aucune section executable au-dessus de 0x08100000, sauf .uicode +# (UI gelee pendant l'USB) ; le depassement de la bank 1 est deja +# une erreur de link (region FLASH = 1024K) +# 2. le code chaud USB est bien en RAM (.fastcode) +# 3. aucun chemin d'appel DIRECT depuis un contexte actif pendant +# l'USB (ISR, mixer, audio, timers telemetrie/logs, tete de +# perMain) vers la bank 2 — hors aretes gardees a l'execution +# (liste blanche ci-dessous) +# +# Limites connues : les appels indirects (pointeurs de fonction) ne +# sont pas suivis ; la couverture est completee a l'execution par la +# barriere MPU du build de validation (DIAG_BANK2_FENCE). +# +# Usage: bench/check_bank1.sh [chemin/firmware.elf] + +set -e +ELF=${1:-"$(dirname "$0")/../build-bank1/arm-none-eabi/firmware.elf"} +[ -f "$ELF" ] || { echo "ELF introuvable: $ELF"; exit 1; } + +echo "== 1. sections executables ==" +BAD_SECTIONS=$(arm-none-eabi-objdump -h "$ELF" | awk ' + /CODE/ { print prev } + { if ($2 ~ /^\./) prev = $2" "$4 }' | while read name vma; do + case "$name" in .uicode) continue ;; esac + v=$((16#$vma)) + if [ $v -ge $((0x08100000)) ] && [ $v -lt $((0x08200000)) ]; then + echo "$name@0x$vma" + fi + done) +if [ -n "$BAD_SECTIONS" ]; then + echo "ERREUR: sections code en bank 2: $BAD_SECTIONS"; exit 1 +fi +echo "OK (seule .uicode est en bank 2)" + +echo "== 2. code USB en RAM ==" +ADDR=$(arm-none-eabi-nm "$ELF" | awk '/ T OTG_FS_IRQHandler/ {print $1}') +case "$ADDR" in + 2000*) echo "OK: OTG_FS_IRQHandler @ 0x$ADDR" ;; + *) echo "ERREUR: OTG_FS_IRQHandler @ 0x$ADDR (attendu 0x2000xxxx)"; exit 1 ;; +esac + +echo "== 3. joignabilite bank 2 depuis les contextes actifs pendant l'USB ==" +DISAS=$(mktemp) +trap 'rm -f "$DISAS"' EXIT +arm-none-eabi-objdump -d "$ELF" > "$DISAS" +python3 - "$DISAS" <<'PYEOF' +import sys, re, collections +sys.stdin = open(sys.argv[1]) + +BANK2 = range(0x08100000, 0x08200000) + +# Racines : tout ce qui peut s'executer pendant une session USB active. +# - toutes les ISR (suffixe _IRQHandler / Handler du vecteur) +# - taches et timers qui ne sont pas geles par le gel UI +# - la tete de perMain (avant le retour anticipe du gel) +ROOT_PATTERNS = [ + r'.*_IRQHandler$', r'^HardFault_Handler$', r'^SVC_Handler$', + r'^PendSV_Handler$', r'^SysTick_Handler$', + r'.*mixerTask.*', r'.*audioTask.*', r'^telemetryTimerCb$', + r'.*logsTimerCb.*', r'^_Z9logsWritev$', r'^_Z15telemetryWakeupv$', + r'^_Z13evalFunctions.*', + # tete de perMain : ce qui s'execute AVANT le retour anticipe du + # gel USB (perMain lui-meme est tronque a l'execution) + r'^_Z17checkSpeakerVolumev$', r'^_Z16initLoggingTimerv$', + r'^_Z20checkTrainerSettingsv$', r'^_Z12periodicTickv$', + r'^_Z14checkBacklightv$', r'^_Z11flightResetv$', +] + +# Fonctions gardees a l'execution : ne s'executent jamais pendant une +# session USB active (etat USB teste en entree, ou garde +# _usb_ui_frozen) -> sous-arbre entier elague +PRUNED = { + '_Z19handleUsbConnectionv', # demarre/arrete l'USB : UI appelee + # uniquement avant usbStart/apres usbStop + '_Z11closeUsbMenuv', # uniquement sur debranchement + '_Z13POPUP_WARNINGPKcS0_', # garde _usb_ui_frozen() + '_Z17POPUP_INFORMATIONPKc', # garde _usb_ui_frozen() + '_Z18checkStorageUpdatev', # garde !usbPlugged dans perMain +} + +funcs = {} # nom -> addr +calls = collections.defaultdict(set) # caller -> {(callee, addr)} +cur = None +for line in sys.stdin: + m = re.match(r'^([0-9a-f]+) <(.+)>:', line) + if m: + cur = m.group(2); funcs[cur] = int(m.group(1), 16); continue + if cur is None: continue + m = re.search(r'\t(?:bl|b\.w|b\.n|b|beq\.w|bne\.w|bcs\.w|bcc\.w|bmi\.w|bpl\.w|bvs\.w|bvc\.w|bhi\.w|bls\.w|bge\.w|blt\.w|bgt\.w|ble\.w|cbz|cbnz)\s+([0-9a-f]+) <([^>+]+)(?:\+0x[0-9a-f]+)?>', line) + if m: + calls[cur].add((m.group(2), int(m.group(1), 16))) + +roots = [f for f in funcs + if any(re.match(p, f) for p in ROOT_PATTERNS)] + +# BFS sur les appels directs, en restant en bank 1 ; une arete vers la +# bank 2 depuis un appelant non liste = violation +violations = [] +seen = set(roots) +parent = {r: None for r in roots} +queue = list(roots) +while queue: + f = queue.pop() + if f in PRUNED: continue + for callee, addr in calls.get(f, ()): + if addr in BANK2: + chain = [] + n = f + while n is not None: + chain.append(n); n = parent.get(n) + violations.append((' -> '.join(reversed(chain)), callee)) + continue + if callee not in seen and callee in funcs: + seen.add(callee); parent[callee] = f + queue.append(callee) + +if violations: + print(f"ERREUR: {len(violations)} chemin(s) direct(s) vers la bank 2 :") + for chain, callee in sorted(set(violations))[:40]: + print(f" {chain} ==> {callee}") + sys.exit(1) +print(f"OK ({len(roots)} racines, {len(seen)} fonctions parcourues, " + f"aucun chemin direct vers la bank 2)") +PYEOF + +echo "== verification bank 1 : OK ==" diff --git a/radio/src/boards/generic_stm32/linker/definitions.ld b/radio/src/boards/generic_stm32/linker/definitions.ld index 936ba752c85..ac91cbbb64d 100644 --- a/radio/src/boards/generic_stm32/linker/definitions.ld +++ b/radio/src/boards/generic_stm32/linker/definitions.ld @@ -17,3 +17,8 @@ _heap_end = HEAP_ADDRESS; PROVIDE(_sfastcode = 0); PROVIDE(_efastcode = 0); PROVIDE(_fastcode_load = 0); + +/* Same for scripts without a .text.shared section */ +PROVIDE(_stext_shared = 0); +PROVIDE(_etext_shared = 0); +PROVIDE(_text_shared_load = 0); diff --git a/radio/src/boards/generic_stm32/linker/firmware.ld b/radio/src/boards/generic_stm32/linker/firmware.ld index af3ba72fbad..71309eea5ce 100644 --- a/radio/src/boards/generic_stm32/linker/firmware.ld +++ b/radio/src/boards/generic_stm32/linker/firmware.ld @@ -58,6 +58,34 @@ SECTIONS _fastcode_load = LOADADDR(.fastcode); + /* C++ comdat sections (templates, inline functions, operator + new/delete) from the std:: namespace are instantiated by both UI + and core objects, and the linker keeps a single copy whose home + object depends on link order. That copy must stay callable at + any time, so claim it here, before any target-specific pre-text + section can grab it through file patterns (see + pre_text_sections.ld). */ + .text.shared (READONLY) : + { + . = ALIGN(4); + _stext_shared = .; + *(.text._ZSt* .text._ZNSt* .text._ZNKSt* .text._ZN9__gnu_cxx*) + *(.text._Znwj .text._Znwm .text._Znaj .text._Znam) + *(.text._ZdlPv* .text._ZdaPv*) + *(.rodata._ZSt* .rodata._ZNSt* .rodata._ZNKSt*) + *(.rodata._ZTVSt* .rodata._ZTVNSt*) + . = ALIGN(4); + _etext_shared = .; + } > REGION_TEXT AT> REGION_TEXT_STORAGE + + _text_shared_load = LOADADDR(.text.shared); + + /* Target-specific output sections that must claim their input + sections before the .text catch-all (e.g. TX16S UI code + relegated to flash bank 2, see + stm32f429_sdram/pre_text_sections.ld) */ + INCLUDE pre_text_sections.ld + /* Main code section */ .text (READONLY) : { @@ -77,7 +105,8 @@ SECTIONS INCLUDE common_sections.ld - /* Assets in FLASH only */ + /* Assets in FLASH only (REGION_ASSETS: same as REGION_TEXT_STORAGE + on most targets, flash bank 2 on TX16S/F429) */ .flash (READONLY) : { . = ALIGN(4); @@ -86,9 +115,10 @@ SECTIONS . = ALIGN(4); _eflash = .; - } > REGION_TEXT_STORAGE + } > REGION_ASSETS .text_end_section : {} > REGION_TEXT AT > REGION_TEXT_STORAGE - _firmware_length = LOADADDR(.text_end_section) - LOADADDR(.firmware_header); + /* .flash is the highest LMA of the image on every target */ + _firmware_length = (LOADADDR(.flash) + SIZEOF(.flash)) - LOADADDR(.firmware_header); _firmware_version = _text_load; } diff --git a/radio/src/boards/generic_stm32/linker/stm32f20x/layout.ld b/radio/src/boards/generic_stm32/linker/stm32f20x/layout.ld index 8190ec13c30..ded0dca4c02 100644 --- a/radio/src/boards/generic_stm32/linker/stm32f20x/layout.ld +++ b/radio/src/boards/generic_stm32/linker/stm32f20x/layout.ld @@ -25,6 +25,7 @@ REGION_ALIAS("REGION_BOOTLOADER", FLASH); REGION_ALIAS("REGION_ISR_VECT", FLASH); REGION_ALIAS("REGION_TEXT", FLASH); REGION_ALIAS("REGION_TEXT_STORAGE", FLASH); +REGION_ALIAS("REGION_ASSETS", FLASH); REGION_ALIAS("REGION_DATA", RAM); REGION_ALIAS("REGION_BSS", RAM); diff --git a/radio/src/boards/generic_stm32/linker/stm32f20x/pre_text_sections.ld b/radio/src/boards/generic_stm32/linker/stm32f20x/pre_text_sections.ld new file mode 100644 index 00000000000..42c12af0136 --- /dev/null +++ b/radio/src/boards/generic_stm32/linker/stm32f20x/pre_text_sections.ld @@ -0,0 +1 @@ +/* No target-specific pre-text sections (see firmware.ld) */ diff --git a/radio/src/boards/generic_stm32/linker/stm32f40x/layout.ld b/radio/src/boards/generic_stm32/linker/stm32f40x/layout.ld index 94b26b49380..34f35ebadab 100644 --- a/radio/src/boards/generic_stm32/linker/stm32f40x/layout.ld +++ b/radio/src/boards/generic_stm32/linker/stm32f40x/layout.ld @@ -29,6 +29,7 @@ REGION_ALIAS("REGION_BOOTLOADER", FLASH); REGION_ALIAS("REGION_ISR_VECT", FLASH); REGION_ALIAS("REGION_TEXT", FLASH); REGION_ALIAS("REGION_TEXT_STORAGE", FLASH); +REGION_ALIAS("REGION_ASSETS", FLASH); REGION_ALIAS("REGION_DATA", CCM); REGION_ALIAS("REGION_BSS", CCM); diff --git a/radio/src/boards/generic_stm32/linker/stm32f40x/pre_text_sections.ld b/radio/src/boards/generic_stm32/linker/stm32f40x/pre_text_sections.ld new file mode 100644 index 00000000000..42c12af0136 --- /dev/null +++ b/radio/src/boards/generic_stm32/linker/stm32f40x/pre_text_sections.ld @@ -0,0 +1 @@ +/* No target-specific pre-text sections (see firmware.ld) */ diff --git a/radio/src/boards/generic_stm32/linker/stm32f413/layout.ld b/radio/src/boards/generic_stm32/linker/stm32f413/layout.ld index c660b556256..3d67f953a22 100644 --- a/radio/src/boards/generic_stm32/linker/stm32f413/layout.ld +++ b/radio/src/boards/generic_stm32/linker/stm32f413/layout.ld @@ -25,6 +25,7 @@ REGION_ALIAS("REGION_BOOTLOADER", FLASH); REGION_ALIAS("REGION_ISR_VECT", FLASH); REGION_ALIAS("REGION_TEXT", FLASH); REGION_ALIAS("REGION_TEXT_STORAGE", FLASH); +REGION_ALIAS("REGION_ASSETS", FLASH); REGION_ALIAS("REGION_DATA", RAM); REGION_ALIAS("REGION_BSS", RAM); diff --git a/radio/src/boards/generic_stm32/linker/stm32f413/pre_text_sections.ld b/radio/src/boards/generic_stm32/linker/stm32f413/pre_text_sections.ld new file mode 100644 index 00000000000..42c12af0136 --- /dev/null +++ b/radio/src/boards/generic_stm32/linker/stm32f413/pre_text_sections.ld @@ -0,0 +1 @@ +/* No target-specific pre-text sections (see firmware.ld) */ diff --git a/radio/src/boards/generic_stm32/linker/stm32f429_sdram/layout.ld b/radio/src/boards/generic_stm32/linker/stm32f429_sdram/layout.ld index 66924e688d1..fb14cef8983 100644 --- a/radio/src/boards/generic_stm32/linker/stm32f429_sdram/layout.ld +++ b/radio/src/boards/generic_stm32/linker/stm32f429_sdram/layout.ld @@ -18,19 +18,29 @@ EXTRAM_SIZE = DEFINED(__EXTRAM_SIZE__) ? __EXTRAM_SIZE__ : 8192K; /* Highest heap address */ HEAP_ADDRESS = EXTRAM_START + EXTRAM_SIZE; -/* Specify the memory areas */ +/* Specify the memory areas. + + The 2MB flash is split at the hardware bank boundary: on some + STM32F42x units (flash rev < 3), USB D+ (PA12) switching noise + corrupts reads from bank 2 (ST errata, EdgeTX#5899). Restricting + FLASH to bank 1 turns any overflow of code into a link error; + only cold UI code/rodata and assets are placed in bank 2 (see + pre_text_sections.ld) and must not be accessed while a USB + session is active. */ MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K - RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K - CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K - SDRAM(xrw) : ORIGIN = EXTRAM_START, LENGTH = EXTRAM_SIZE + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* bank 1 */ + FLASH_BANK2 (rx) : ORIGIN = 0x08100000, LENGTH = 1024K /* bank 2 */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K + CCM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K + SDRAM(xrw) : ORIGIN = EXTRAM_START, LENGTH = EXTRAM_SIZE } REGION_ALIAS("REGION_BOOTLOADER", FLASH); REGION_ALIAS("REGION_ISR_VECT", FLASH); REGION_ALIAS("REGION_TEXT", FLASH); REGION_ALIAS("REGION_TEXT_STORAGE", FLASH); +REGION_ALIAS("REGION_ASSETS", FLASH_BANK2); REGION_ALIAS("REGION_DATA", CCM); REGION_ALIAS("REGION_BSS", RAM); diff --git a/radio/src/boards/generic_stm32/linker/stm32f429_sdram/pre_text_sections.ld b/radio/src/boards/generic_stm32/linker/stm32f429_sdram/pre_text_sections.ld new file mode 100644 index 00000000000..450cd5d2884 --- /dev/null +++ b/radio/src/boards/generic_stm32/linker/stm32f429_sdram/pre_text_sections.ld @@ -0,0 +1,43 @@ +/* TX16S / STM32F429 (EdgeTX#5899): on units with flash rev < 3, + USB D+ (PA12) switching noise corrupts reads from flash bank 2 + (0x08100000 and above). All code and rodata that may be used + while a USB session is active must live in bank 1; this is + guaranteed by the FLASH region being restricted to bank 1 + (overflow = link error). + + UI code and its rodata are relegated to bank 2 here: they only + run while USB is inactive (the UI is frozen in USB modes). + Claimed before the .text catch-all so these input sections land + here; file-pattern matching requires non-LTO objects. Shared + C++ comdats are already pinned to bank 1 by .text.shared. */ +/* Bank 1 pins: symbols living in UI objects but called from non-UI + tasks (mixer pulses, telemetry/logs timers, audio), found by the + bank1->bank2 call scan (bench/check_bank1.sh). Section names + follow the mangled symbol: a signature change silently un-pins, + the scan is the gatekeeper. Claimed before .uicode. */ +.text.pinned : +{ + . = ALIGN(4); + /* tick LVGL incremente par le hook SysTick (ISR, meme pendant l'USB) */ + *(.text.lv_tick_inc) + /* helpers R9M inline (pulses, chaque cycle mixer) */ + *(.text._Z17isModuleR9MAccessh .text._Z20isModuleR9MNonAccessh) + /* mailbox popup differee (timers telemetrie/logs) */ + *(.text._Z24POPUP_WARNING_ON_UI_TASKPKcS0_) + /* popups bloquants, gardes par _usb_ui_frozen() (audio, PXX2) */ + *(.text._Z13POPUP_WARNINGPKcS0_ .text._Z17POPUP_INFORMATIONPKc) + /* fonction speciale SET_SCREEN (mixer) */ + *(.text._Z20setRequestedMainViewh) + . = ALIGN(4); +} > REGION_TEXT AT> REGION_TEXT_STORAGE + +.uicode : +{ + . = ALIGN(4); + _suicode = .; + *colorlcd*(.text .text.* .rodata .rodata.*) + *lvgl*(.text .text.* .rodata .rodata.*) + *libopenui*(.text .text.* .rodata .rodata.*) + . = ALIGN(4); + _euicode = .; +} > REGION_ASSETS diff --git a/radio/src/boards/generic_stm32/linker/stm32h750_sdram/layout.ld b/radio/src/boards/generic_stm32/linker/stm32h750_sdram/layout.ld index 1ce2dbb7456..a64bce94ee7 100644 --- a/radio/src/boards/generic_stm32/linker/stm32h750_sdram/layout.ld +++ b/radio/src/boards/generic_stm32/linker/stm32h750_sdram/layout.ld @@ -35,6 +35,7 @@ MEMORY REGION_ALIAS("REGION_BOOTLOADER", FLASH); REGION_ALIAS("REGION_TEXT_STORAGE", NORFLASH); +REGION_ALIAS("REGION_ASSETS", NORFLASH); REGION_ALIAS("REGION_TEXT", SDRAM); REGION_ALIAS("REGION_ISR_VECT", DTCMRAM); REGION_ALIAS("REGION_DATA", DTCMRAM); diff --git a/radio/src/boards/generic_stm32/linker/stm32h750_sdram/pre_text_sections.ld b/radio/src/boards/generic_stm32/linker/stm32h750_sdram/pre_text_sections.ld new file mode 100644 index 00000000000..42c12af0136 --- /dev/null +++ b/radio/src/boards/generic_stm32/linker/stm32h750_sdram/pre_text_sections.ld @@ -0,0 +1 @@ +/* No target-specific pre-text sections (see firmware.ld) */ diff --git a/radio/src/boards/generic_stm32/linker/stm32h7rs_sdram/layout.ld b/radio/src/boards/generic_stm32/linker/stm32h7rs_sdram/layout.ld index cd0582a2dea..1f17268b919 100644 --- a/radio/src/boards/generic_stm32/linker/stm32h7rs_sdram/layout.ld +++ b/radio/src/boards/generic_stm32/linker/stm32h7rs_sdram/layout.ld @@ -34,6 +34,7 @@ MEMORY REGION_ALIAS("REGION_BOOTLOADER", FLASH); REGION_ALIAS("REGION_TEXT_STORAGE", NORFLASH); +REGION_ALIAS("REGION_ASSETS", NORFLASH); REGION_ALIAS("REGION_TEXT", PSRAM); REGION_ALIAS("REGION_ISR_VECT", DTCMRAM); REGION_ALIAS("REGION_DATA", RAM); diff --git a/radio/src/boards/generic_stm32/linker/stm32h7rs_sdram/pre_text_sections.ld b/radio/src/boards/generic_stm32/linker/stm32h7rs_sdram/pre_text_sections.ld new file mode 100644 index 00000000000..42c12af0136 --- /dev/null +++ b/radio/src/boards/generic_stm32/linker/stm32h7rs_sdram/pre_text_sections.ld @@ -0,0 +1 @@ +/* No target-specific pre-text sections (see firmware.ld) */ diff --git a/radio/src/targets/common/arm/stm32/system_init.c b/radio/src/targets/common/arm/stm32/system_init.c index 0e69f7578f6..f291b0161aa 100644 --- a/radio/src/targets/common/arm/stm32/system_init.c +++ b/radio/src/targets/common/arm/stm32/system_init.c @@ -80,6 +80,15 @@ NAKED BOOTSTRAP void Reset_Handler() "bl naked_copy \n" ); + // Copy shared C++ comdat code (.text.shared); no-op on XIP + // targets where VMA == LMA + asm inline ( + "ldr r0, =_stext_shared \n" + "ldr r1, =_etext_shared \n" + "ldr r2, =_text_shared_load \n" + "bl naked_copy \n" + ); + #if defined(BOOT) && defined(REQUIRE_MPU_CONFIG) asm inline ( "bl MPU_Config \n" From c2dca83bd529bed8d2776b602348a8e3a8803665 Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Tue, 14 Jul 2026 10:34:17 +0200 Subject: [PATCH 04/10] fix(tx16s): freeze the UI while a USB session is active All UI code lives in flash bank 2 which must not be read during USB traffic (PA12 errata). Draw a static USB screen before usbStart() for every mode (mass storage included: LVGL used to keep running during MSC), then return early from perMain() until unplug. The blocking popup dialogs are also guarded so audio/telemetry callers cannot start a nested UI loop while frozen. Co-Authored-By: Claude Fable 5 --- radio/src/gui/colorlcd/libui/popups.cpp | 17 +++++ radio/src/main.cpp | 87 +++++++++++++++++-------- 2 files changed, 76 insertions(+), 28 deletions(-) diff --git a/radio/src/gui/colorlcd/libui/popups.cpp b/radio/src/gui/colorlcd/libui/popups.cpp index 5f70b2212ef..0e12c7ee79e 100644 --- a/radio/src/gui/colorlcd/libui/popups.cpp +++ b/radio/src/gui/colorlcd/libui/popups.cpp @@ -24,6 +24,7 @@ #include "dialog.h" #include "edgetx.h" #include "etx_lv_theme.h" +#include "hal/usb_driver.h" #include "hal/watchdog_driver.h" #include "lvgl/src/hal/lv_hal_tick.h" #include "mainwindow.h" @@ -40,13 +41,29 @@ static void _run_popup_dialog(const char* title, const char* msg, }); } +// While a USB session is active the UI is frozen (PA12/bank 2 +// errata, EdgeTX#5899): a nested popup dialog must never run then. +// These two functions are pinned to flash bank 1 on TX16S (see +// stm32f429_sdram/pre_text_sections.ld) so callers from non-UI +// tasks (audio, telemetry) never fetch bank 2 code. +static inline __attribute__((always_inline)) bool _usb_ui_frozen() +{ +#if defined(STM32) && !defined(SIMU) + return usbPlugged() && getSelectedUsbMode() != USB_UNSELECTED_MODE; +#else + return false; +#endif +} + void POPUP_INFORMATION(const char* message) { + if (_usb_ui_frozen()) return; _run_popup_dialog("Message", message); } void POPUP_WARNING(const char* message, const char* info) { + if (_usb_ui_frozen()) return; _run_popup_dialog("Warning", message, info); } diff --git a/radio/src/main.cpp b/radio/src/main.cpp index 0906531ad90..16754607d7e 100644 --- a/radio/src/main.cpp +++ b/radio/src/main.cpp @@ -161,6 +161,8 @@ class UsbSDConnected : public Window static UsbSDConnected* usbConnectedWindow = nullptr; #endif +void usbSessionCleanup(bool wait_sd = true); + void handleUsbConnection() { #if defined(STM32) && !defined(SIMU) @@ -191,9 +193,6 @@ void handleUsbConnection() if (getSelectedUsbMode() != USB_UNSELECTED_MODE) { if (getSelectedUsbMode() == USB_MASS_STORAGE_MODE) { edgeTxClose(false); -#if defined(COLORLCD) - usbConnectedWindow = new UsbSDConnected(); -#endif } #if defined(USB_SERIAL) else if (getSelectedUsbMode() == USB_SERIAL_MODE) { @@ -201,40 +200,62 @@ void handleUsbConnection() } #endif +#if defined(COLORLCD) + // Static USB screen for every mode, rendered now: the UI + // freezes as soon as the USB session starts (PA12/bank 2 + // errata, EdgeTX#5899) and will not draw anything anymore + usbConnectedWindow = new UsbSDConnected(); + lv_refr_now(nullptr); +#endif + usbStart(); TRACE("USB started"); } } if (usbStarted() && !usbPlugged()) { - usbStop(); - TRACE("USB stopped"); - if (getSelectedUsbMode() == USB_MASS_STORAGE_MODE) { + usbSessionCleanup(); + } +#endif // defined(STM32) && !defined(SIMU) +} + +#if defined(STM32) && !defined(SIMU) +// Tear a running USB session down (same cleanup as a physical +// unplug). wait_sd selects the blocking "no SD card" screen +// (unplug path only: forced stops must not block on it). +void usbSessionCleanup(bool wait_sd) +{ + usbStop(); + TRACE("USB stopped"); #if defined(COLORLCD) - usbConnectedWindow->deleteLater(); - usbConnectedWindow = nullptr; - // In case the SD card is removed during the session - if (!SD_CARD_PRESENT()) { - // Blocks until shutdown or SD card inserted - auto w = drawFatalErrorScreen(STR_NO_SDCARD); - MainWindow::instance()->blockUntilClose(true, []() { - return SD_CARD_PRESENT(); - }, true); - w->deleteLater(); - } - edgeTxResume(); -#else - edgeTxResume(); - pushEvent(EVT_ENTRY); + if (usbConnectedWindow) { + usbConnectedWindow->deleteLater(); + usbConnectedWindow = nullptr; + } #endif - } else if (getSelectedUsbMode() == USB_SERIAL_MODE) { - serialStop(SP_VCP); + if (getSelectedUsbMode() == USB_MASS_STORAGE_MODE) { +#if defined(COLORLCD) + // In case the SD card is removed during the session + if (wait_sd && !SD_CARD_PRESENT()) { + // Blocks until shutdown or SD card inserted + auto w = drawFatalErrorScreen(STR_NO_SDCARD); + MainWindow::instance()->blockUntilClose(true, []() { + return SD_CARD_PRESENT(); + }, true); + w->deleteLater(); } - TRACE("reset selected USB mode"); - setSelectedUsbMode(USB_UNSELECTED_MODE); +#endif + edgeTxResume(); +#if !defined(COLORLCD) + pushEvent(EVT_ENTRY); +#endif + } else if (getSelectedUsbMode() == USB_SERIAL_MODE) { + serialStop(SP_VCP); } -#endif // defined(STM32) && !defined(SIMU) + TRACE("reset selected USB mode"); + setSelectedUsbMode(USB_UNSELECTED_MODE); } +#endif // defined(STM32) && !defined(SIMU) void checkSpeakerVolume() { @@ -543,6 +564,16 @@ void perMain() checkBacklight(); +#if defined(COLORLCD) + // Freeze the whole UI while a USB session is active: on TX16S all + // UI code/rodata lives in flash bank 2, which must not be accessed + // during USB traffic (PA12 errata, EdgeTX#5899). The static USB + // screen was rendered before usbStart(). + if (usbPlugged() && getSelectedUsbMode() != USB_UNSELECTED_MODE) { + return; + } +#endif + #if defined(USE_HATS_AS_KEYS) checkHatsAsKeys(); #endif @@ -574,15 +605,15 @@ void perMain() } } - if (usbPlugged() && getSelectedUsbMode() == USB_MASS_STORAGE_MODE) { #if !defined(COLORLCD) + if (usbPlugged() && getSelectedUsbMode() == USB_MASS_STORAGE_MODE) { // disable access to menus lcdClear(); menuMainView(0); lcdRefresh(); -#endif return; } +#endif #if defined(MULTIMODULE) checkFailsafeMulti(); From cb6e21235d44d9f5ffda2f2b4834346afe63b56c Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Tue, 14 Jul 2026 10:34:17 +0200 Subject: [PATCH 05/10] fix(usb): do not refill the HID report while a transfer is in flight usbJoystickUpdate() used to rewrite the report buffer and re-arm the IN endpoint at mixer rate even when the previous transfer was still pending, corrupting the DMA buffer mid-transmit and hammering the endpoint (analysis by ulph0, EdgeTX#5899). Skip the update until the endpoint is idle; covers both the legacy and USBJ_EX paths. Co-Authored-By: Claude Fable 5 --- .../src/targets/common/arm/stm32/usb_driver.cpp | 6 ++++++ .../targets/common/arm/stm32/usbd_hid_joystick.c | 16 ++++++++++++++++ .../Class/HID/Inc/usbd_hid.h | 1 + 3 files changed, 23 insertions(+) diff --git a/radio/src/targets/common/arm/stm32/usb_driver.cpp b/radio/src/targets/common/arm/stm32/usb_driver.cpp index 291f94c3dcf..4120d86e851 100644 --- a/radio/src/targets/common/arm/stm32/usb_driver.cpp +++ b/radio/src/targets/common/arm/stm32/usb_driver.cpp @@ -260,6 +260,12 @@ void usbJoystickRestart() */ void usbJoystickUpdate() { + // Do not touch the report buffer while the previous transfer is + // still in flight (DMA reads it), and do not hammer the endpoint + // at mixer rate when the host has not polled yet (EdgeTX#5899) + if (!USBD_HID_IsReady(&hUsbDevice)) + return; + #if !defined(USBJ_EX) static uint8_t HID_Buffer[HID_IN_PACKET]; diff --git a/radio/src/targets/common/arm/stm32/usbd_hid_joystick.c b/radio/src/targets/common/arm/stm32/usbd_hid_joystick.c index 2ef49a40f61..80b427227be 100644 --- a/radio/src/targets/common/arm/stm32/usbd_hid_joystick.c +++ b/radio/src/targets/common/arm/stm32/usbd_hid_joystick.c @@ -520,6 +520,22 @@ uint8_t USBD_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t return (uint8_t)USBD_OK; } +/** + * @brief USBD_HID_IsReady + * Check whether the HID IN endpoint is idle, i.e. the + * previous report has been fully transmitted and the report + * buffer may be safely rewritten + * @param pdev: device instance + * @retval 1 if ready, 0 if busy or not initialized + */ +uint8_t USBD_HID_IsReady(USBD_HandleTypeDef *pdev) +{ + USBD_HID_HandleTypeDef *hhid = + (USBD_HID_HandleTypeDef *)pdev->pClassDataCmsit[pdev->classId]; + if (hhid == NULL) return 0; + return (hhid->state == USBD_HID_IDLE) ? 1 : 0; +} + /** * @brief USBD_HID_GetPollingInterval * return polling interval from endpoint descriptor diff --git a/radio/src/thirdparty/STM32_USB_Device_Library/Class/HID/Inc/usbd_hid.h b/radio/src/thirdparty/STM32_USB_Device_Library/Class/HID/Inc/usbd_hid.h index 8b22a8df9f3..758ec5fea05 100644 --- a/radio/src/thirdparty/STM32_USB_Device_Library/Class/HID/Inc/usbd_hid.h +++ b/radio/src/thirdparty/STM32_USB_Device_Library/Class/HID/Inc/usbd_hid.h @@ -140,6 +140,7 @@ uint8_t USBD_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t uint8_t USBD_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len); #endif /* USE_USBD_COMPOSITE */ uint32_t USBD_HID_GetPollingInterval(USBD_HandleTypeDef *pdev); +uint8_t USBD_HID_IsReady(USBD_HandleTypeDef *pdev); /** * @} From 6d637838468a6a0028de112700a34791d25df73a Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Tue, 14 Jul 2026 10:39:07 +0200 Subject: [PATCH 06/10] diag(tx16s): MPU fence trapping flash bank 2 access during USB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validation builds (-DDIAG_BANK2_FENCE=ON) arm an MPU no-access region over flash bank 2 for the whole USB session: any stray access — including through function pointers invisible to the static scan — traps deterministically instead of corrupting reads probabilistically (technique proven by ulph0's experiments, EdgeTX#5899). The MemManage handler stores 0xB2FE0000|CFSR and the faulting address in RTC backup registers BKP4R/BKP5R (survive the reset) and reboots. Also adds bench/test_invariance.sh: two deliberately shifted builds (core/UI padding, swapped UI link order) that must both pass bench/check_bank1.sh, proving the layout guarantee is structural. Co-Authored-By: Claude Fable 5 --- bench/test_invariance.sh | 77 ++++++++++++++++ .../targets/common/arm/stm32/bank2_fence.c | 90 +++++++++++++++++++ .../common/arm/stm32/f4/CMakeLists.txt | 1 + .../targets/common/arm/stm32/usb_driver.cpp | 13 +++ radio/src/targets/horus/CMakeLists.txt | 5 ++ 5 files changed, 186 insertions(+) create mode 100644 bench/test_invariance.sh create mode 100644 radio/src/targets/common/arm/stm32/bank2_fence.c diff --git a/bench/test_invariance.sh b/bench/test_invariance.sh new file mode 100644 index 00000000000..9cc6eb084f2 --- /dev/null +++ b/bench/test_invariance.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# Preuve d'invariance du layout "code en bank 1" (TX16S, EdgeTX#5899) : +# la garantie doit venir du linker script, pas d'un placement chanceux. +# +# Deux builds volontairement perturbes : +# 1. +8K de rodata et une fonction dans un fichier CORE (decale tout +# le contenu de la bank 1) +# 2. +8K de rodata et une fonction dans un fichier UI, plus +# inversion de l'ordre de link de deux sources UI (decale la +# bank 2 et change la survie des comdats) +# +# Chaque build doit passer bench/check_bank1.sh. Les perturbations ne +# sont jamais commitees (restauration git a la fin, arbre propre exige). +# +# Usage: bench/test_invariance.sh + +set -e +cd "$(dirname "$0")/.." + +if ! git diff --quiet radio/src; then + echo "ERREUR: modifications non commitees dans radio/src, abandon"; exit 1 +fi + +cleanup() { + git checkout -- radio/src/gps.cpp radio/src/gui/colorlcd/mainview/view_main.cpp \ + radio/src/gui/colorlcd/CMakeLists.txt 2>/dev/null || true +} +trap cleanup EXIT + +build_and_check() { + local label=$1 + echo "=== build perturbe: $label ===" + docker run --rm -u $(id -u):$(id -g) -v "$(pwd)":/src -w /src \ + ghcr.io/edgetx/edgetx-dev:latest bash -c " +set -e +rm -rf build-invar && mkdir build-invar && cd build-invar +cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain/arm-none-eabi.cmake \ + -DPCB=X10 -DPCBREV=TX16S -DCMAKE_BUILD_TYPE=Release .. > cmake.log 2>&1 +make firmware -j\$(nproc) > make.log 2>&1 || { tail -20 make.log; exit 1; } +" + bench/check_bank1.sh build-invar/arm-none-eabi/firmware.elf + echo "=== $label: OK ===" +} + +# --- perturbation 1: core --- +cat >> radio/src/gps.cpp <<'PAD' + +// (test d'invariance bank 1 -- jamais commite) +const volatile char _invar_pad_core[8192] = {1}; +void _invar_dummy_core() { (void)_invar_pad_core[0]; } +PAD +build_and_check "padding core (+8K bank 1)" +cleanup + +# --- perturbation 2: UI + ordre de link --- +cat >> radio/src/gui/colorlcd/mainview/view_main.cpp <<'PAD' + +// (test d'invariance bank 1 -- jamais commite) +const volatile char _invar_pad_ui[8192] = {2}; +void _invar_dummy_ui() { (void)_invar_pad_ui[0]; } +PAD +python3 - <<'PYEOF' +import re +p = 'radio/src/gui/colorlcd/CMakeLists.txt' +lines = open(p).readlines() +# inverse les deux premieres sources .cpp adjacentes trouvees +for i in range(len(lines) - 1): + if lines[i].strip().endswith('.cpp') and lines[i+1].strip().endswith('.cpp'): + lines[i], lines[i+1] = lines[i+1], lines[i] + print(f"swap: {lines[i].strip()} <-> {lines[i+1].strip()}") + break +open(p, 'w').writelines(lines) +PYEOF +build_and_check "padding UI (+8K bank 2) + ordre de link inverse" + +echo +echo "== invariance: les 2 builds perturbes passent la verification ==" diff --git a/radio/src/targets/common/arm/stm32/bank2_fence.c b/radio/src/targets/common/arm/stm32/bank2_fence.c new file mode 100644 index 00000000000..94548e5de8b --- /dev/null +++ b/radio/src/targets/common/arm/stm32/bank2_fence.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) EdgeTX + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* DIAG_BANK2_FENCE (validation builds, TX16S/STM32F429): + * + * While a USB session is active, flash bank 2 (0x08100000, 1MB) must + * never be read: USB D+ (PA12) switching noise corrupts the flash + * read path on some units (ST errata ES0166 §2.4.6, EdgeTX#5899). + * The firmware layout keeps everything hot in bank 1 and freezes the + * UI (whose code lives in bank 2) during USB. + * + * This fence arms an MPU no-access region over bank 2 for the whole + * USB session, so any stray access — including through function + * pointers that the static scan (bench/check_bank1.sh) cannot see — + * traps deterministically (MemManage) instead of corrupting reads + * probabilistically. The fault handler stores a magic and the + * faulting address in RTC backup registers (they survive the reset) + * and reboots. + * + * Post-mortem readout: BKP4R = 0xB2FE0000 | (CFSR & 0xFFFF), + * BKP5R = faulting address (MMFAR if valid, else stacked PC). + */ + +#if defined(DIAG_BANK2_FENCE) && !defined(BOOT) + +#include "stm32_hal.h" + +#define FENCE_REGION_NUMBER 7 +#define FENCE_BASE 0x08100000UL +#define FENCE_SIZE_1MB (19UL << MPU_RASR_SIZE_Pos) +#define FENCE_MAGIC 0xB2FE0000UL + +void bank2FenceEnable(void) +{ + __disable_irq(); + MPU->RNR = FENCE_REGION_NUMBER; + MPU->RBAR = FENCE_BASE; + MPU->RASR = MPU_RASR_ENABLE_Msk | FENCE_SIZE_1MB | MPU_RASR_XN_Msk; + /* AP = 0b000: no access, privileged included */ + MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk; + SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; + __DSB(); + __ISB(); + __enable_irq(); +} + +void bank2FenceDisable(void) +{ + __disable_irq(); + MPU->RNR = FENCE_REGION_NUMBER; + MPU->RASR = 0; + MPU->RBAR = 0; + /* MPU left enabled with PRIVDEFENA: background map = default */ + __DSB(); + __ISB(); + __enable_irq(); +} + +void MemManage_C(uint32_t *frame) +{ + uint32_t cfsr = SCB->CFSR; + uint32_t addr = (cfsr & SCB_CFSR_MMARVALID_Msk) ? SCB->MMFAR : frame[6]; + + /* Backup domain write access (registers survive the reset) */ + __HAL_RCC_PWR_CLK_ENABLE(); + SET_BIT(PWR->CR, PWR_CR_DBP); + RTC->BKP4R = FENCE_MAGIC | (cfsr & 0xFFFF); + RTC->BKP5R = addr; + + NVIC_SystemReset(); +} + +__attribute__((naked)) void MemManage_Handler(void) +{ + __asm volatile( + "tst lr, #4 \n" + "ite eq \n" + "mrseq r0, msp \n" + "mrsne r0, psp \n" + "b MemManage_C \n"); +} + +#endif /* DIAG_BANK2_FENCE && !BOOT */ diff --git a/radio/src/targets/common/arm/stm32/f4/CMakeLists.txt b/radio/src/targets/common/arm/stm32/f4/CMakeLists.txt index 72477d740bd..bfafcaa054d 100644 --- a/radio/src/targets/common/arm/stm32/f4/CMakeLists.txt +++ b/radio/src/targets/common/arm/stm32/f4/CMakeLists.txt @@ -85,6 +85,7 @@ add_library(cmsis INTERFACE) target_sources(cmsis INTERFACE targets/common/arm/stm32/system_init.c + targets/common/arm/stm32/bank2_fence.c targets/common/arm/stm32/cortex_m_isr.c targets/common/arm/stm32/f4/system_clock.c targets/common/arm/stm32/f4/system_stm32f4xx.c diff --git a/radio/src/targets/common/arm/stm32/usb_driver.cpp b/radio/src/targets/common/arm/stm32/usb_driver.cpp index 4120d86e851..c1d6ef31982 100644 --- a/radio/src/targets/common/arm/stm32/usb_driver.cpp +++ b/radio/src/targets/common/arm/stm32/usb_driver.cpp @@ -157,6 +157,11 @@ void usbInit() extern void usbInitLUNs(); extern USBD_HandleTypeDef hUsbDevice; extern "C" USBD_StorageTypeDef USBD_Storage_Interface_fops; +#if defined(DIAG_BANK2_FENCE) && !defined(BOOT) +extern "C" void bank2FenceEnable(void); +extern "C" void bank2FenceDisable(void); +#endif + extern USBD_CDC_ItfTypeDef USBD_Interface_fops; extern USBD_DFU_MediaTypeDef USBD_DFU_MEDIA_fops; @@ -206,11 +211,19 @@ void usbStart() if (USBD_Start(&hUsbDevice) == USBD_OK) { usbDriverStarted = true; +#if defined(DIAG_BANK2_FENCE) && !defined(BOOT) + // Validation builds: trap any flash bank 2 access for the whole + // USB session (PA12 errata, EdgeTX#5899) + bank2FenceEnable(); +#endif } } void usbStop() { +#if defined(DIAG_BANK2_FENCE) && !defined(BOOT) + bank2FenceDisable(); +#endif usbDriverStarted = false; USBD_DeInit(&hUsbDevice); } diff --git a/radio/src/targets/horus/CMakeLists.txt b/radio/src/targets/horus/CMakeLists.txt index 80c04f93a8f..8082b921179 100644 --- a/radio/src/targets/horus/CMakeLists.txt +++ b/radio/src/targets/horus/CMakeLists.txt @@ -1,4 +1,5 @@ option(DISK_CACHE "Enable SD card disk cache" ON) +option(DIAG_BANK2_FENCE "MPU fence trapping any flash bank 2 access during USB (validation builds, EdgeTX#5899)" OFF) option(UNEXPECTED_SHUTDOWN "Enable the Unexpected Shutdown screen" ON) option(IMU_LSM6DS33 "Enable I2C2 and LSM6DS33 IMU" OFF) option(PXX1 "PXX1 protocol support" ON) @@ -221,6 +222,10 @@ if(DISK_CACHE) set(SRC ${SRC} disk_cache.cpp) endif() +if(DIAG_BANK2_FENCE) + add_definitions(-DDIAG_BANK2_FENCE) +endif() + if(INTERNAL_GPS) message("-- Internal GPS enabled") add_definitions(-DINTERNAL_GPS) From 49bdaf916aa46966b83c59414c2f61e82cc847be Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Tue, 14 Jul 2026 11:19:00 +0200 Subject: [PATCH 07/10] diag(tx16s): record escalated faults and dump fence traps to SD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stock HardFault handler returns into the fault without a debugger; record fence traps escalated to HardFault (magic 0xB2FA) like MemManage ones (0xB2FE) and reboot. On the next boot with storage up, append the trap to /bank2fence.log and clear the marker — the backup registers survive resets and power cycles (RTC cell). Scan: root every FreeRTOS timer callback (the timer-task dispatch is indirect, per10ms & co were unaudited) and reject roots living in bank 2 (UI callbacks caught by a broad pattern are frozen contexts, not hot ones). Co-Authored-By: Claude Fable 5 --- bench/check_bank1.sh | 10 ++++- radio/src/edgetx.cpp | 9 ++++ .../targets/common/arm/stm32/bank2_fence.c | 42 +++++++++++++++++++ .../targets/common/arm/stm32/cortex_m_isr.c | 6 +++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/bench/check_bank1.sh b/bench/check_bank1.sh index e71aa3a3b2d..3778efe345e 100755 --- a/bench/check_bank1.sh +++ b/bench/check_bank1.sh @@ -65,6 +65,11 @@ ROOT_PATTERNS = [ r'.*mixerTask.*', r'.*audioTask.*', r'^telemetryTimerCb$', r'.*logsTimerCb.*', r'^_Z9logsWritev$', r'^_Z15telemetryWakeupv$', r'^_Z13evalFunctions.*', + # timers FreeRTOS (indirects via la tache timer, non vus par le + # graphe d'appels directs) : chaque callback est une racine + r'.*_timer_10ms_cb.*', r'.*per10msv?$', r'.*loggingTimerCb.*', + r'.*spacemouseTimerCb.*', r'.*MultiRfProtocols7timerCb.*', + r'^_ZL11_refresh_cb.*', # rgb_leds uniquement # tete de perMain : ce qui s'execute AVANT le retour anticipe du # gel USB (perMain lui-meme est tronque a l'execution) r'^_Z17checkSpeakerVolumev$', r'^_Z16initLoggingTimerv$', @@ -96,8 +101,11 @@ for line in sys.stdin: if m: calls[cur].add((m.group(2), int(m.group(1), 16))) +# une racine doit vivre en bank 1 : un symbole apparie en bank 2 est +# du code UI (gele pendant l'USB), pas un contexte chaud roots = [f for f in funcs - if any(re.match(p, f) for p in ROOT_PATTERNS)] + if any(re.match(p, f) for p in ROOT_PATTERNS) + and funcs[f] not in BANK2] # BFS sur les appels directs, en restant en bank 1 ; une arete vers la # bank 2 depuis un appelant non liste = violation diff --git a/radio/src/edgetx.cpp b/radio/src/edgetx.cpp index e856137cacc..4ec2d57ee44 100644 --- a/radio/src/edgetx.cpp +++ b/radio/src/edgetx.cpp @@ -36,6 +36,10 @@ #include "hal/watchdog_driver.h" #include "hal/abnormal_reboot.h" #include "hal/usb_driver.h" + +#if defined(DIAG_BANK2_FENCE) +extern "C" void bank2FenceDumpPostMortem(void); +#endif #include "hal/audio_driver.h" #include "hal/rgbleds.h" @@ -1503,6 +1507,11 @@ void edgeTxInit() if (!sdMounted()) sdInit(); +#if defined(DIAG_BANK2_FENCE) + // report a bank 2 fence trap from the previous session, if any + bank2FenceDumpPostMortem(); +#endif + #if !defined(COLORLCD) if (!sdMounted()) { g_eeGeneral.pwrOffSpeed = 2; diff --git a/radio/src/targets/common/arm/stm32/bank2_fence.c b/radio/src/targets/common/arm/stm32/bank2_fence.c index 94548e5de8b..9f57d9b3317 100644 --- a/radio/src/targets/common/arm/stm32/bank2_fence.c +++ b/radio/src/targets/common/arm/stm32/bank2_fence.c @@ -87,4 +87,46 @@ __attribute__((naked)) void MemManage_Handler(void) "b MemManage_C \n"); } +/* A MemManage fault raised while the core is already at or above the + * MemManage priority escalates to HardFault: record it from the + * existing HardFault handler (cortex_m_isr.c) with a distinct magic + * (0xB2FA). */ +void bank2FenceRecordHardFault(uint32_t return_address) +{ + uint32_t cfsr = SCB->CFSR; + uint32_t addr = (cfsr & SCB_CFSR_MMARVALID_Msk) ? SCB->MMFAR + : (cfsr & SCB_CFSR_BFARVALID_Msk) ? SCB->BFAR + : return_address; + __HAL_RCC_PWR_CLK_ENABLE(); + SET_BIT(PWR->CR, PWR_CR_DBP); + RTC->BKP4R = 0xB2FA0000UL | (cfsr & 0xFFFF); + RTC->BKP5R = addr; + + NVIC_SystemReset(); +} + +/* Called once at boot when storage is up: if the previous reset was + * a fence trap, append it to /bank2fence.log and clear the marker. */ +#include "ff.h" + +void bank2FenceDumpPostMortem(void) +{ + uint32_t r4 = RTC->BKP4R; + uint32_t hi = r4 & 0xFFFF0000UL; + if (hi != 0xB2FE0000UL && hi != 0xB2FA0000UL) return; + + FIL f; + if (f_open(&f, "/bank2fence.log", FA_WRITE | FA_OPEN_APPEND) == FR_OK) { + f_printf(&f, "%s cfsr=%04X addr=%08lX\n", + (hi == 0xB2FE0000UL) ? "MEMMANAGE" : "HARDFAULT", + (unsigned)(r4 & 0xFFFF), (unsigned long)RTC->BKP5R); + f_close(&f); + } + + __HAL_RCC_PWR_CLK_ENABLE(); + SET_BIT(PWR->CR, PWR_CR_DBP); + RTC->BKP4R = 0; + RTC->BKP5R = 0; +} + #endif /* DIAG_BANK2_FENCE && !BOOT */ diff --git a/radio/src/targets/common/arm/stm32/cortex_m_isr.c b/radio/src/targets/common/arm/stm32/cortex_m_isr.c index 667c2eba682..52bbf0c8cf8 100644 --- a/radio/src/targets/common/arm/stm32/cortex_m_isr.c +++ b/radio/src/targets/common/arm/stm32/cortex_m_isr.c @@ -73,6 +73,12 @@ typedef struct __attribute__((packed)) ContextStateFrame { __attribute__((optimize("O0"))) void hard_fault_handler_c(sContextStateFrame *frame) { HALT_IF_DEBUGGING(); +#if defined(DIAG_BANK2_FENCE) && !defined(BOOT) + // record escalated fence traps (bank 2 access from a high-priority + // context) and reboot instead of returning into the fault + extern void bank2FenceRecordHardFault(uint32_t return_address); + bank2FenceRecordHardFault(frame->return_address); +#endif } void HardFault_Handler(void) { From 1fb5ecdaf6ffe2144f460ecac5a1134ac75db0ec Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Tue, 14 Jul 2026 11:42:10 +0200 Subject: [PATCH 08/10] fix(tx16s): stop the USB session before any shutdown UI runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First on-radio fence trap (IACCVIOL @ cancelShutdownAnimation): pwrCheck(), polled by menusTask every 50ms even while the UI is frozen, fetched bank 2 code on every cycle of a USB session through its button-released branch — a strong candidate for the historical random crashes (EdgeTX#5899). Power long-press during USB now tears the session down first (usbSessionForceStop, same cleanup as an unplug — factored out of handleUsbConnection): with PA12 quiet, the bank 2 shutdown UI is safe again. The shutdown animation is skipped while frozen and cancelShutdownAnimation is pinned to bank 1. Deliberate trade-off: long-press during USB powers off without the confirmation dialog. Scan: root menusTask/pwrCheck and every FreeRTOS timer callback, add per-edge whitelist for runtime-guarded paths. Co-Authored-By: Claude Fable 5 --- bench/check_bank1.sh | 24 +++++++++++++++++++ .../stm32f429_sdram/pre_text_sections.ld | 2 ++ radio/src/edgetx.cpp | 21 ++++++++++++++-- radio/src/main.cpp | 9 +++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/bench/check_bank1.sh b/bench/check_bank1.sh index 3778efe345e..e32fa336af9 100755 --- a/bench/check_bank1.sh +++ b/bench/check_bank1.sh @@ -65,6 +65,9 @@ ROOT_PATTERNS = [ r'.*mixerTask.*', r'.*audioTask.*', r'^telemetryTimerCb$', r'.*logsTimerCb.*', r'^_Z9logsWritev$', r'^_Z15telemetryWakeupv$', r'^_Z13evalFunctions.*', + # menusTask tourne pendant le gel (seul perMain est tronque) et + # appelle pwrCheck a chaque cycle de 50 ms + r'.*menusTask.*', r'^pwrCheck$', # timers FreeRTOS (indirects via la tache timer, non vus par le # graphe d'appels directs) : chaque callback est une racine r'.*_timer_10ms_cb.*', r'.*per10msv?$', r'.*loggingTimerCb.*', @@ -87,6 +90,26 @@ PRUNED = { '_Z13POPUP_WARNINGPKcS0_', # garde _usb_ui_frozen() '_Z17POPUP_INFORMATIONPKc', # garde _usb_ui_frozen() '_Z18checkStorageUpdatev', # garde !usbPlugged dans perMain + '_Z17usbSessionCleanupv', # commence par usbStop() : la bank 2 + '_Z19usbSessionForceStopv', # redevient sure (PA12 au repos) + '_Z10edgeTxInitv', # boot uniquement, avant toute session USB + '_Z7perMainv', # gel : retour anticipe avant toute UI ; + # sa tete est auditee par racines dediees + '_Z11edgeTxCloseh', # extinction (toujours apres + # usbSessionForceStop) ou pre-usbStart (MSC) +} + +# Aretes tolerees individuellement : gardees a l'execution, mais dont +# l'appelant doit rester audite pour le reste de son sous-arbre +WHITELIST_EDGES = { + # pwrCheck coupe la session USB (usbSessionForceStop) avant tout + # flux d'extinction, et saute l'animation pendant le gel + ('pwrCheck', '_Z21drawShutdownAnimationmmPKc'), + ('pwrCheck', '_Z18confirmationDialogPKcS0_bRKSt8functionIFbvEE'), + # sortie de menusTask = power off, toujours apres usbSessionForceStop + ('_ZL9menusTaskv', '_Z15drawSleepBitmapv'), + # initialisation LVGL au demarrage de la tache, avant tout USB + ('_ZL9menusTaskv', '_ZN11LvglWrapper8instanceEv'), } funcs = {} # nom -> addr @@ -118,6 +141,7 @@ while queue: if f in PRUNED: continue for callee, addr in calls.get(f, ()): if addr in BANK2: + if (f, callee) in WHITELIST_EDGES: continue chain = [] n = f while n is not None: diff --git a/radio/src/boards/generic_stm32/linker/stm32f429_sdram/pre_text_sections.ld b/radio/src/boards/generic_stm32/linker/stm32f429_sdram/pre_text_sections.ld index 450cd5d2884..e9d9a100177 100644 --- a/radio/src/boards/generic_stm32/linker/stm32f429_sdram/pre_text_sections.ld +++ b/radio/src/boards/generic_stm32/linker/stm32f429_sdram/pre_text_sections.ld @@ -28,6 +28,8 @@ *(.text._Z13POPUP_WARNINGPKcS0_ .text._Z17POPUP_INFORMATIONPKc) /* fonction speciale SET_SCREEN (mixer) */ *(.text._Z20setRequestedMainViewh) + /* appelee par pwrCheck (menusTask, toutes les 50 ms, gel compris) */ + *(.text._Z23cancelShutdownAnimationv) . = ALIGN(4); } > REGION_TEXT AT> REGION_TEXT_STORAGE diff --git a/radio/src/edgetx.cpp b/radio/src/edgetx.cpp index 4ec2d57ee44..4188ecc1698 100644 --- a/radio/src/edgetx.cpp +++ b/radio/src/edgetx.cpp @@ -40,6 +40,12 @@ #if defined(DIAG_BANK2_FENCE) extern "C" void bank2FenceDumpPostMortem(void); #endif + +#if defined(STM32) && !defined(SIMU) +void usbSessionForceStop(); +#else +inline void usbSessionForceStop() {} +#endif #include "hal/audio_driver.h" #include "hal/rgbleds.h" @@ -1814,6 +1820,10 @@ uint32_t pwrCheck() } if (get_tmr10ms() - pwr_press_time > PWR_PRESS_SHUTDOWN_DELAY()) { #if defined(COLORLCD) + // End the USB session before any shutdown UI runs: the UI + // lives in flash bank 2, unreadable during USB traffic + // (PA12 errata, EdgeTX#5899). Same cleanup as an unplug. + usbSessionForceStop(); bool usbConfirmed = !usbPlugged() || getSelectedUsbMode() == USB_UNSELECTED_MODE; bool modelConnectedConfirmed = !TELEMETRY_STREAMING() || g_eeGeneral.disableRssiPoweroffAlarm; bool trainerConfirmed = !isTrainerConnected(); @@ -1923,14 +1933,21 @@ uint32_t pwrCheck() return e_power_off; } else { - drawShutdownAnimation(pwrPressedDuration(), PWR_PRESS_SHUTDOWN_DELAY(), message); +#if defined(COLORLCD) + // no drawing while the USB session is active (UI in bank 2) + if (!usbPlugged() || getSelectedUsbMode() == USB_UNSELECTED_MODE) +#endif + drawShutdownAnimation(pwrPressedDuration(), PWR_PRESS_SHUTDOWN_DELAY(), message); return e_power_press; } } } else { #if defined(COLORLCD) - cancelShutdownAnimation(); + // pinned to bank 1 on TX16S, and skipped while the USB session + // is active: menusTask polls this every 50ms (PA12 errata) + if (!usbPlugged() || getSelectedUsbMode() == USB_UNSELECTED_MODE) + cancelShutdownAnimation(); #endif pwr_check_state = PWR_CHECK_ON; pwr_press_time = 0; diff --git a/radio/src/main.cpp b/radio/src/main.cpp index 16754607d7e..5db3f6c85b5 100644 --- a/radio/src/main.cpp +++ b/radio/src/main.cpp @@ -255,6 +255,15 @@ void usbSessionCleanup(bool wait_sd) TRACE("reset selected USB mode"); setSelectedUsbMode(USB_UNSELECTED_MODE); } + +// Shutdown path (PA12 errata, EdgeTX#5899): the shutdown UI lives in +// flash bank 2 and may only run once USB traffic has stopped. +void usbSessionForceStop() +{ + if (usbStarted() && getSelectedUsbMode() != USB_UNSELECTED_MODE) { + usbSessionCleanup(false); + } +} #endif // defined(STM32) && !defined(SIMU) void checkSpeakerVolume() From 268d92e4673aed54c5b5aae306fbe90a9516d908 Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Tue, 14 Jul 2026 13:09:34 +0200 Subject: [PATCH 09/10] fix(tx16s): name the active USB mode on the frozen USB screen The same static screen serves all three USB modes; with only the mass-storage icon, a joystick session reads as the wrong mode being active (confused a tester into power-cycling the radio mid-session). Co-Authored-By: Claude Fable 5 --- radio/src/main.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/radio/src/main.cpp b/radio/src/main.cpp index 5db3f6c85b5..16df162129b 100644 --- a/radio/src/main.cpp +++ b/radio/src/main.cpp @@ -35,6 +35,7 @@ #include "etx_lv_theme.h" #include "menu.h" #include "mainwindow.h" +#include "static.h" #endif #if defined(CLI) @@ -205,6 +206,21 @@ void handleUsbConnection() // freezes as soon as the USB session starts (PA12/bank 2 // errata, EdgeTX#5899) and will not draw anything anymore usbConnectedWindow = new UsbSDConnected(); + { + // name the active mode: the same frozen screen serves all + // three modes and the icon alone reads as "mass storage" + const char* mode_str = STR_USB_MASS_STORAGE; + if (getSelectedUsbMode() == USB_JOYSTICK_MODE) + mode_str = STR_USB_JOYSTICK; +#if defined(USB_SERIAL) + else if (getSelectedUsbMode() == USB_SERIAL_MODE) + mode_str = STR_USB_SERIAL; +#endif + auto label = new StaticText( + usbConnectedWindow, {0, LCD_H - 60, LCD_W, LV_SIZE_CONTENT}, + mode_str, COLOR_THEME_PRIMARY2_INDEX, FONT(L) | CENTERED); + (void)label; + } lv_refr_now(nullptr); #endif From 30361bf7876e3c88846307738cff446fb19420b0 Mon Sep 17 00:00:00 2001 From: AdsumSOK Date: Tue, 14 Jul 2026 13:29:53 +0200 Subject: [PATCH 10/10] chore(tx16s): move bank-1 verification scripts to tools/, adapt to main The emergency-mode screen is now drawn directly from menusTask at boot (before any USB session can exist): whitelist that edge. Co-Authored-By: Claude Fable 5 --- {bench => tools}/check_bank1.sh | 4 +++- {bench => tools}/test_invariance.sh | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) rename {bench => tools}/check_bank1.sh (97%) rename {bench => tools}/test_invariance.sh (95%) diff --git a/bench/check_bank1.sh b/tools/check_bank1.sh similarity index 97% rename from bench/check_bank1.sh rename to tools/check_bank1.sh index e32fa336af9..0f5ca0c02be 100755 --- a/bench/check_bank1.sh +++ b/tools/check_bank1.sh @@ -17,7 +17,7 @@ # sont pas suivis ; la couverture est completee a l'execution par la # barriere MPU du build de validation (DIAG_BANK2_FENCE). # -# Usage: bench/check_bank1.sh [chemin/firmware.elf] +# Usage: tools/check_bank1.sh [chemin/firmware.elf] set -e ELF=${1:-"$(dirname "$0")/../build-bank1/arm-none-eabi/firmware.elf"} @@ -110,6 +110,8 @@ WHITELIST_EDGES = { ('_ZL9menusTaskv', '_Z15drawSleepBitmapv'), # initialisation LVGL au demarrage de la tache, avant tout USB ('_ZL9menusTaskv', '_ZN11LvglWrapper8instanceEv'), + # ecran d'urgence au boot (UNEXPECTED_SHUTDOWN), avant tout USB + ('_ZL9menusTaskv', '_Z20drawFatalErrorScreenPKc'), } funcs = {} # nom -> addr diff --git a/bench/test_invariance.sh b/tools/test_invariance.sh similarity index 95% rename from bench/test_invariance.sh rename to tools/test_invariance.sh index 9cc6eb084f2..848dac05e9c 100644 --- a/bench/test_invariance.sh +++ b/tools/test_invariance.sh @@ -9,7 +9,7 @@ # inversion de l'ordre de link de deux sources UI (decale la # bank 2 et change la survie des comdats) # -# Chaque build doit passer bench/check_bank1.sh. Les perturbations ne +# Chaque build doit passer tools/check_bank1.sh. Les perturbations ne # sont jamais commitees (restauration git a la fin, arbre propre exige). # # Usage: bench/test_invariance.sh @@ -38,7 +38,7 @@ cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain/arm-none-eabi.cmake \ -DPCB=X10 -DPCBREV=TX16S -DCMAKE_BUILD_TYPE=Release .. > cmake.log 2>&1 make firmware -j\$(nproc) > make.log 2>&1 || { tail -20 make.log; exit 1; } " - bench/check_bank1.sh build-invar/arm-none-eabi/firmware.elf + tools/check_bank1.sh build-invar/arm-none-eabi/firmware.elf echo "=== $label: OK ===" }