Sync betaflight to Gitea

This commit is contained in:
2026-08-03 16:37:10 +08:00
commit ad3163cf91
4712 changed files with 3301380 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
*.bin
*.elf
*.o
*.map
+266
View File
@@ -0,0 +1,266 @@
# Betaflight N6 OpenBootloader build.
#
# Architecture:
# - Skeleton sources here: main.c (boot decision), system_stm32n6xx_obl.c,
# stm32n6xx_hal_msp.c, stm32n6xx_it.c, startup, linker, flash driver.
# - The OBL DFU stack (CubeN6 Middlewares/ST/OpenBootloader + USB Device
# Library + the Application/OpenBootloader/USB_Device glue) is pulled
# in via VPATH from the submodule, NOT modified or copied — so re-
# pulling CubeN6 keeps us up to date. Currently disabled (dfu_stub.c
# stands in) until task #6 wires it up.
#
# Usage:
# make produce obl.elf and obl.bin (skeleton only)
# make signed re-sign into prebuilt/obl_signed.stm32
# make OBL_FLASH_DRIVER=mx66uw1g45g pick driver source (default)
# make clean
ROOT := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))/../../../..)
TOOLCHAIN_BIN := $(ROOT)/tools/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi/bin
PREFIX := $(if $(wildcard $(TOOLCHAIN_BIN)/arm-none-eabi-gcc),$(TOOLCHAIN_BIN)/,)arm-none-eabi-
CC := $(PREFIX)gcc
AS := $(PREFIX)gcc -x assembler-with-cpp
LD := $(PREFIX)gcc
OBJCOPY := $(PREFIX)objcopy
SIZE := $(PREFIX)size
OBL_FLASH_DRIVER ?= mx66uw1g45g
HAL_DIR := $(ROOT)/lib/modules/STM32N6/Drivers/STM32N6xx_HAL_Driver
CMSIS_ST := $(ROOT)/lib/modules/STM32N6/Drivers/CMSIS/Device/ST/STM32N6xx/Include
CMSIS := $(ROOT)/lib/modules/STM32N6/Drivers/CMSIS/Include
OBL_MW := $(ROOT)/lib/modules/STM32N6/Middlewares/ST/OpenBootloader
OBL_USBLIB := $(OBL_MW)/STM32_USB_Device_Library
OBL_PROJ := $(ROOT)/lib/modules/STM32N6/Projects/STM32N6570-DK/Applications/OpenBootloader
INCLUDES := \
-I. \
-Iflash \
-I$(ROOT)/src/platform/STM32/include \
-I$(HAL_DIR)/Inc \
-I$(CMSIS_ST) \
-I$(CMSIS) \
-I$(OBL_MW)/Core \
-I$(OBL_MW)/Util \
-I$(OBL_MW)/Modules/Mem \
-I$(OBL_MW)/Modules/USB \
-I$(OBL_MW)/Modules/USART \
-I$(OBL_USBLIB)/Core/Inc \
-I$(OBL_USBLIB)/Class/DFU/Inc \
-I$(OBL_PROJ)/Core/Inc \
-I$(OBL_PROJ)/OpenBootloader/App \
-I$(OBL_PROJ)/OpenBootloader/Target \
-I$(OBL_PROJ)/USB_Device/App \
-I$(OBL_PROJ)/USB_Device/Target
DEFINES := \
-DSTM32N657xx \
-DUSE_HAL_DRIVER \
-DHSE_VALUE=48000000
# Bring-up override: make OBL_FORCE_DFU=1 to build an OBL that always
# enters DFU regardless of nor0 contents.
ifdef OBL_FORCE_DFU
DEFINES += -DOBL_FORCE_DFU
endif
# Bring-up override: make OBL_FORCE_RECOVERY=1 to build an OBL that
# always enters Recovery mode (exposes nor0 from 0x70000000 so OBL+BF
# can be reinstalled). RAM-load only — never sign and flash this.
ifdef OBL_FORCE_RECOVERY
DEFINES += -DOBL_FORCE_RECOVERY
endif
# OBL is the only S-state code on the device, so build it with the Cortex-M
# Security Extensions enabled. -mcmse sets __ARM_FEATURE_CMSE=3, which makes
# the ST CMSIS headers select the secure-alias peripheral base addresses
# (RCC, RIFSC, GPIOx, DBGMCU, TAMP, PWR, RTC, EXTI, GPDMA1, HPDMA1, ...). All
# peripheral access in OBL therefore goes through the S aliases naturally
# via the CMSIS struct pointers instead of hand-coded addresses.
ARCH_FLAGS := -mthumb -mcpu=cortex-m55 -mfloat-abi=hard -mfpu=fpv5-d16 -mcmse
CFLAGS := \
$(ARCH_FLAGS) \
-Os -g3 \
-ffunction-sections -fdata-sections \
-Wall -Wextra \
-Wno-unused-parameter \
-std=gnu11 \
$(INCLUDES) $(DEFINES)
ASFLAGS := $(ARCH_FLAGS) -g3 $(INCLUDES) $(DEFINES)
LDSCRIPT := STM32N657XX_OBL.ld
LDFLAGS := \
$(ARCH_FLAGS) \
-T$(LDSCRIPT) \
-nostartfiles \
-specs=nano.specs -specs=nosys.specs \
-Wl,--gc-sections \
-Wl,-Map=obl.map \
-Wl,--print-memory-usage
LOCAL_C_SRCS := \
main.c \
obl_app.c \
app_openbootloader.c \
stm32n6xx_hal_msp.c \
stm32n6xx_it.c \
system_stm32n6xx_obl.c \
external_memory_interface.c \
otp_interface.c \
usbd_conf.c \
usbd_desc.c \
usbd_dfu.c \
usbd_dfu_if.c \
flash/$(OBL_FLASH_DRIVER).c
HAL_C_SRCS := \
stm32n6xx_hal.c \
stm32n6xx_hal_cortex.c \
stm32n6xx_hal_rcc.c \
stm32n6xx_hal_rcc_ex.c \
stm32n6xx_hal_pwr.c \
stm32n6xx_hal_pwr_ex.c \
stm32n6xx_hal_gpio.c \
stm32n6xx_hal_xspi.c \
stm32n6xx_hal_dma.c \
stm32n6xx_hal_dma_ex.c \
stm32n6xx_hal_iwdg.c \
stm32n6xx_hal_pcd.c \
stm32n6xx_hal_pcd_ex.c \
stm32n6xx_ll_usb.c
# OBL middleware (protocol-agnostic core, memory mgr, USB transport bindings,
# utilities). Pulled in unchanged from the CubeN6 submodule.
OBL_MW_C_SRCS := \
Core/openbl_core.c \
Util/openbl_util.c \
Modules/Mem/openbl_mem.c \
Modules/USB/openbl_usb_cmd.c
# USB Device Library (DFU class + core). Note: usbd_dfu.c is overridden
# locally (see LOCAL_C_SRCS) — the Betaflight copy patches the per-alt
# iInterface descriptor lookup so all alts use DfuInterface->pStrDesc.
OBL_USB_C_SRCS := \
Core/Src/usbd_core.c \
Core/Src/usbd_ctlreq.c \
Core/Src/usbd_ioreq.c
# Application-level glue from the reference OBL project. We provide our
# own external_memory_interface.c (above), so that submodule file is NOT
# listed here.
OBL_APP_C_SRCS := \
OpenBootloader/Target/common_interface.c \
OpenBootloader/Target/iwdg_interface.c \
OpenBootloader/Target/ram_interface.c \
OpenBootloader/Target/usb_interface.c \
USB_Device/App/usb_device.c
LOCAL_OBJS := \
main.o \
obl_app.o \
app_openbootloader.o \
stm32n6xx_hal_msp.o \
stm32n6xx_it.o \
system_stm32n6xx_obl.o \
external_memory_interface.o \
otp_interface.o \
usbd_conf.o \
usbd_desc.o \
usbd_dfu.o \
usbd_dfu_if.o \
flash/$(OBL_FLASH_DRIVER).o \
startup_stm32n657xx_obl.o
HAL_OBJS := $(HAL_C_SRCS:.c=.hal.o)
OBL_MW_OBJS := $(notdir $(OBL_MW_C_SRCS:.c=.mw.o))
OBL_USB_OBJS:= $(notdir $(OBL_USB_C_SRCS:.c=.usbd.o))
OBL_APP_OBJS:= $(notdir $(OBL_APP_C_SRCS:.c=.app.o))
OBJS := $(LOCAL_OBJS) $(HAL_OBJS) $(OBL_MW_OBJS) $(OBL_USB_OBJS) $(OBL_APP_OBJS)
ELF := obl.elf
BIN := obl.bin
SIGNING_TOOL ?= STM32_SigningTool_CLI
SIGNED_BLOB := prebuilt/obl_$(OBL_FLASH_DRIVER)_signed.stm32
# Build fails loudly if the OBL output exceeds the linker-declared RAM
# region. Matches LENGTH in STM32N657XX_OBL.ld.
OBL_SIZE_LIMIT := 65536 # 64 KiB
.PHONY: all signed size_check clean
all: $(BIN) size_check
$(BIN): $(ELF)
$(OBJCOPY) -O binary $< $@
$(SIZE) $<
$(ELF): $(OBJS) $(LDSCRIPT)
$(LD) $(LDFLAGS) $(OBJS) -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.s
$(AS) $(ASFLAGS) -c $< -o $@
flash/%.o: flash/%.c
$(CC) $(CFLAGS) -c $< -o $@
%.hal.o: $(HAL_DIR)/Src/%.c
$(CC) $(CFLAGS) -c $< -o $@
# Pattern rules for the OBL middleware / USB Device Library / application
# glue. Each rule strips the suffix used to disambiguate object files
# (.mw.o, .usbd.o, .app.o) and finds the .c by leaf-name search across
# the upstream subdirectories. notdir + foreach keeps the rule chain
# manageable without listing every nested directory in VPATH.
OBL_MW_DIRS := \
$(OBL_MW)/Core \
$(OBL_MW)/Util \
$(OBL_MW)/Modules/Mem \
$(OBL_MW)/Modules/USB \
$(OBL_MW)/Modules/USART
OBL_USB_DIRS := \
$(OBL_USBLIB)/Core/Src \
$(OBL_USBLIB)/Class/DFU/Src
OBL_APP_DIRS := \
$(OBL_PROJ)/OpenBootloader/App \
$(OBL_PROJ)/OpenBootloader/Target \
$(OBL_PROJ)/USB_Device/App \
$(OBL_PROJ)/USB_Device/Target
vpath %.c $(OBL_MW_DIRS) $(OBL_USB_DIRS) $(OBL_APP_DIRS)
%.mw.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.usbd.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.app.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Hard size cap — keeps OBL fit-for-@FSBL-DFU even as middleware grows.
size_check: $(BIN)
@bin_size=$$(stat -c %s $(BIN)); \
if [ $$bin_size -gt $(OBL_SIZE_LIMIT) ]; then \
echo "ERROR: $(BIN) is $$bin_size bytes, exceeds $(OBL_SIZE_LIMIT) byte cap (boot ROM @FSBL alt limit)"; \
exit 1; \
else \
echo "OBL size OK: $$bin_size / $(OBL_SIZE_LIMIT) bytes"; \
fi
signed: $(SIGNED_BLOB)
$(SIGNED_BLOB): $(BIN) | prebuilt
$(SIGNING_TOOL) -bin $< -nk -of 0x80000000 -t ssbl -hv 2.3 -align -o $@ -s
prebuilt:
mkdir -p $@
clean:
rm -f $(LOCAL_OBJS) $(HAL_OBJS) $(ELF) $(BIN) obl.map
+127
View File
@@ -0,0 +1,127 @@
/*
* Linker script for the Betaflight N6 OpenBootloader (OBL).
*
* Boot ROM behaviour (BOOT0=USER, BOOT1=2):
* 1. Reads signed FSBL header from XSPI 0x70000000
* 2. Verifies + copies payload into AXISRAM2 secure starting at
* 0x34180400 (the 0x400 offset matches `STM32_SigningTool_CLI -align`)
* 3. Branches to Reset_Handler at the loaded entry point
*
* The boot ROM caps @FSBL DFU staging at ~485 KiB empirically — the
* destination region from 0x34180400 to end of AXISRAM2 secure is 521 KiB
* but the boot ROM reserves ~36 KiB of scratch. The 480 KiB region below
* is the safe budget for OBL + flash driver + DFU stack + self-installer.
*/
ENTRY(Reset_Handler)
_Min_Stack_Size = 0x2000; /* 8 KiB — DFU upload buffer + USB ISR + libc */
MEMORY
{
/* Loaded region: signed FSBL slot in AXISRAM2 secure. Boot ROM lands
* the .stm32 payload here. 64 KiB caps OBL well below the boot-ROM
* @FSBL DFU staging limit and leaves the rest of AXISRAM2-secure
* free for any future secure code. */
RAM (rwx) : ORIGIN = 0x34180400, LENGTH = 64K
}
_estack = ORIGIN(RAM) + LENGTH(RAM);
_sstack = _estack - _Min_Stack_Size;
SECTIONS
{
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} > RAM
.text :
{
. = ALIGN(4);
*(.text)
*(.text.*)
*(.glue_7)
*(.glue_7t)
*(.eh_frame)
. = ALIGN(4);
} > RAM
.rodata :
{
. = ALIGN(4);
*(.rodata)
*(.rodata.*)
. = ALIGN(4);
} > RAM
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > RAM
.ARM :
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} > RAM
/* Embedded "self" payload: the OBL writes the bytes of its own loaded
* image to nor0 0x0 during recovery self-install. The payload starts
* at the FSBL header (0x34180000 in AXISRAM, which is 0x34180400 minus
* the 0x400 alignment header — but boot ROM only stages from 0x34180400
* onwards, so the "self" we replicate is the post-header content). The
* signed header has to be re-applied externally — at install time we
* read it from nor0 too via memory-mapped XSPI before erase. */
_self_load_start = ORIGIN(RAM);
_self_load_end = .; /* updated by linker after .data */
_sidata = LOADADDR(.data);
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data)
*(.data.*)
. = ALIGN(4);
_edata = .;
} > RAM
.bss :
{
. = ALIGN(4);
_sbss = .;
__bss_start__ = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
__bss_end__ = .;
/* End-of-image marker for newlib's _sbrk. We don't use malloc,
* but linking pulls _sbrk in regardless; satisfy it with a
* symbol that points past .bss so any incidental allocation
* lands in unused heap space below the stack reservation. */
PROVIDE(end = .);
PROVIDE(_end = .);
} > RAM
/* Stack reservation at the top of RAM. Linker fails the build if BSS
* overflows into the stack region. */
._stack_reservation :
{
. = ALIGN(8);
. = . + _Min_Stack_Size;
. = ALIGN(8);
} > RAM
/DISCARD/ :
{
*(.ARM.attributes)
*(.comment)
}
}
+183
View File
@@ -0,0 +1,183 @@
# Unbricking the STM32N6 (TSV + system DFU)
Use this when OBL or BF in XSPI flash is broken and the board no longer
boots into a usable state — `bl rom` from the BF CLI, OBL DFU on a
crashed BF, and SWD-load workflows are all unavailable. The boot ROM's
built-in system DFU is always reachable, but it can't write the on-board
XSPI flash on its own; CubeProgrammer drives it through a TSV that
stages two helper binaries into RAM first.
## Why the on-chip DFU can't just write XSPI
The N6 boot ROM exposes a system DFU bootloader (`0483:DF11` with the
ST descriptor "STM32 BOOTLOADER", reached via BOOT0=DEV) but the alts
it offers are RAM- and OTP-shaped:
```
@FSBL /0x34180400/01*512Kg -- staged FSBL → AXISRAM2 secure
@RSSE_FW ...
@RSSE_BLOB ...
@RSSE_PLUGIN ...
@virtual ...
```
There's no `@nor0` alt and no XSPI alt — the ROM has no XSPI driver. To
write nor0 you have to:
1. Upload a signed FSBL into RAM via the `@FSBL` alt. The ROM verifies
it, copies it to AXISRAM2, and runs it.
2. The FSBL you upload is itself a DFU loader (ST's "OpenBootloader")
that exposes `@nor0` plus an "external memory loader" alt for the
chosen flash chip's driver.
3. Upload the matching XSPI flash driver into RAM via the `@External
memory loader` alt; the OpenBootloader links it in and the rest of
the DFU session can read/write nor0.
4. Now write your application binary to nor0 at the right offset.
The TSV (Target Sequence Vector) file is just a script for
`STM32_Programmer_CLI -d <FlashLayout.tsv>` that orchestrates these
four steps in order so it looks like a single flash operation from the
host's point of view.
## When to use this
- `bl rom` from BF CLI / OBL DFU `:leave` doesn't reboot the chip into
a working state.
- BF crashes early enough that the BF↔OBL IWDG-recovery path doesn't
trigger (e.g., a fault before OBL's iwdg_start runs, or OBL itself is
corrupt).
- You want to nuke and re-flash both OBL and BF from a clean slate.
If the chip is reachable via SWD (BOOT1 in the position that bypasses
the FSBL on the dev kit, or BF still alive enough that OBL DFU comes
up), prefer the SWD-load or OBL-DFU paths — they don't require touching
hardware switches.
## What you need
- **STM32CubeProgrammer 2.18+ with the N6 patches installed.** The
Linux build prior to 2.22 has bugs around the N6 external-loader
workflow; current ST tooling works.
- **`OpenBootloader_STM32N6570-DK-trusted.stm32`** — ST's signed
OpenBootloader image. Ships in CubeN6 at
`lib/modules/STM32N6/Projects/STM32N6570-DK/Applications/OpenBootloader/Binaries/NOR_Binary/`.
- **`MX66UW1G45G_STM32N6570-DK.bin`** — XSPI flash driver matching the
on-board MX66UW1G45G. Same directory in CubeN6.
- The binaries you want in nor0:
- `obl_mx66uw1g45g_signed.stm32` — our OBL, output of
`make -C lib/main/STM32/n6_obl signed`.
- Optionally `betaflight_<version>_STM32N657_<config>.bin` — your BF
build, if you're flashing OBL + BF together. Skip when you want
only the OBL written and the BF slot zeroed (next section).
## Recovery image variants
| Variant | What lands in nor0 | When to use |
|------------------------|--------------------------------------------------------------|-------------|
| **OBL + BF combined** | OBL at `0x0..0x87A0`, zeros to `0x100000`, BF at `0x100000+` | Normal flash after both binaries are ready. |
| **OBL only, BF erased**| OBL at `0x0..0x87A0`, zeros from end-of-OBL through `0x200000` | After an OBL change that may make BF crash on first boot (e.g. when OBL now transfers to BF in NS state). The empty BF slot fails OBL's vector-table validity check, so the next boot lands straight in OBL DFU recovery — no IWDG round-trip needed. |
### Combined image construction (OBL + BF)
```bash
cp lib/main/STM32/n6_obl/prebuilt/obl_mx66uw1g45g_signed.stm32 /tmp/obl_bf_combined.bin
truncate -s 1048576 /tmp/obl_bf_combined.bin
cat obj/betaflight_*_OPENN657V1.bin >> /tmp/obl_bf_combined.bin
```
Layout to use: `lib/main/STM32/n6_fsbl/FlashLayout_OBL.tsv`.
### OBL-only image construction (BF slot erased)
```bash
install -m 0644 lib/main/STM32/n6_obl/prebuilt/obl_mx66uw1g45g_signed.stm32 \
lib/main/STM32/n6_obl/prebuilt/obl_only_combined.bin
truncate -s 2097152 lib/main/STM32/n6_obl/prebuilt/obl_only_combined.bin
```
The 2 MiB size covers both the 1 MiB OBL slot and the 1 MiB BF slot with
a single contiguous write at nor0 offset 0; everything past the OBL
payload is zero from `truncate`, so the BF slot lands erased.
Layout to use: `lib/main/STM32/n6_fsbl/FlashLayout_OBL_only.tsv`.
## TSV layout
Save as `Unbricking.tsv` next to `OpenBootloader_STM32N6570-DK-trusted.stm32`
and `MX66UW1G45G_STM32N6570-DK.bin`, then edit the two
`<...your-path...>` lines to point at your OBL and BF artefacts.
```tsv
#Opt Id Name Type IP Offset Binary
P 0x1 fsbl-openbl Binary none 0x0 OpenBootloader_STM32N6570-DK-trusted.stm32
P 0x3 fsbl-extfl Binary none 0x0 MX66UW1G45G_STM32N6570-DK.bin
P 0x2 obl-app Binary nor0 0x0 <path/to>/obl_mx66uw1g45g_signed.stm32
P 0x4 bf-app Binary nor0 0x100000 <path/to>/obj/betaflight_<version>_STM32N657_<config>.bin
```
Columns:
- `Opt`: `P` = program (write); `PE` = program + erase first.
- `Id`: ST's partition ID. The OpenBootloader recognises:
- `0x1` → @FSBL: stage a signed FSBL image to AXISRAM2.
- `0x2` → nor0 offset 0 (OBL slot, signed `.stm32` artefact).
- `0x3` → @External memory loader: stage the XSPI driver to RAM.
- `0x4` → nor0 offset 0x100000 (BF slot).
- `Name`: free-text label, shown in CubeProg output.
- `Type`: `Binary` for raw bytes; `Force not used` to skip a row.
- `IP`: target memory — `none` means "load to RAM" (used by Id 0x1
and 0x3); `nor0` is the on-board XSPI flash.
- `Offset`: byte offset inside the target memory.
- `Binary`: path (relative to the TSV) of the file to write.
## Procedure
1. Power cycle the board.
2. Slide **BOOT0** to **DEV** (toward the edge of the board on the
N6570-DK; the boot ROM samples this pin at reset and enters system
DFU when high).
3. Press the reset button. `lsusb` should now show:
```
Bus xxx Device xxx: ID 0483:df11 STMicroelectronics STM Device in DFU Mode
```
4. Run CubeProg against the TSV (substitute `FlashLayout_OBL_only.tsv`
if you want OBL written and the BF slot erased):
```bash
~/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI \
-c port=USB1 -d Unbricking.tsv
```
You'll see four phases scroll past — `fsbl-openbl` upload, then a
re-enumeration as the OpenBootloader starts, then `fsbl-extfl`
upload, then `obl-app` and `bf-app` writes to nor0.
5. Slide **BOOT0** back to **USER**.
6. Press reset. The chip boots from XSPI into our OBL → BF.
## Verifying
After the reset in step 6:
```bash
lsusb | grep 0483
# Expect:
# Bus xxx Device xxx: ID 0483:3754 STMicroelectronics STLINK-V3
# Bus xxx Device xxx: ID 0483:5740 STMicroelectronics Virtual COM Port
```
`/dev/ttyACM1` is the BF VCP. Send `#` + LF to escape MSP into the BF
CLI; `version` should report the build you just flashed.
## Footnotes
- `STM32_Programmer_CLI -el .stldr -w` with the `.stldr` form of the
XSPI driver SIGSEGVs on Linux at least up to v2.22.0; the TSV path
works around that.
- `port=USB1` selects the first USB DFU device; if you have several DFU
devices attached, add `sn=<serial>` to disambiguate. List with
`STM32_Programmer_CLI --list usb`.
- The TSV's relative-path resolution is done from the TSV's own
directory, which is why ST's two helper binaries are kept alongside
it. OBL and BF artefact paths can be absolute or relative to that
same directory.
+107
View File
@@ -0,0 +1,107 @@
/*
* Override of CubeN6's app_openbootloader.c.
*
* The submodule version registers USART + USB + IWDG transports. We only
* use USB + IWDG (FCs don't expose UART pins for bootloader purposes),
* so dropping USART trims a few KiB of code and lets us skip building
* the openbl_usart_cmd / usart_interface sources entirely.
*
* Picked up before the submodule version because -I. and the local
* source path come first.
*/
#include "main.h"
#include "openbl_core.h"
#include "openbl_usb_cmd.h"
#include "app_openbootloader.h"
#include "usb_interface.h"
#include "iwdg_interface.h"
#include "otp_interface.h"
extern OPENBL_MemoryTypeDef RAM_Descriptor;
extern OPENBL_MemoryTypeDef RAM_FlashLoader_Descriptor;
extern OPENBL_MemoryTypeDef EXTERNAL_MEMORY_Descriptor;
static OPENBL_HandleTypeDef USB_Handle;
static OPENBL_HandleTypeDef IWDG_Handle;
static OPENBL_OpsTypeDef USB_Ops =
{
OPENBL_USB_Configuration,
OPENBL_USB_DeInit,
OPENBL_USB_ProtocolDetection,
NULL,
NULL
};
static OPENBL_OpsTypeDef IWDG_Ops =
{
OPENBL_IWDG_Configuration,
NULL,
NULL,
NULL,
NULL
};
uint16_t SpecialCmdList[SPECIAL_CMD_MAX_NUMBER] =
{
SPECIAL_CMD_DEFAULT
};
uint16_t ExtendedSpecialCmdList[EXTENDED_SPECIAL_CMD_MAX_NUMBER] =
{
SPECIAL_CMD_DEFAULT
};
void OpenBootloader_Init(void)
{
USB_Handle.p_Ops = &USB_Ops;
USB_Handle.p_Cmd = NULL;
if (OPENBL_RegisterInterface(&USB_Handle) != SUCCESS) {
Error_Handler();
}
IWDG_Handle.p_Ops = &IWDG_Ops;
IWDG_Handle.p_Cmd = NULL;
if (OPENBL_RegisterInterface(&IWDG_Handle) != SUCCESS) {
Error_Handler();
}
OPENBL_Init();
if (OPENBL_MEM_RegisterMemory(&RAM_Descriptor) != SUCCESS) {
Error_Handler();
}
if (OPENBL_MEM_RegisterMemory(&RAM_FlashLoader_Descriptor) != SUCCESS) {
Error_Handler();
}
if (OPENBL_MEM_RegisterMemory(&EXTERNAL_MEMORY_Descriptor) != SUCCESS) {
Error_Handler();
}
OPENBL_OTP_Init();
}
void OpenBootloader_DeInit(void)
{
System_DeInit();
}
void OpenBootloader_ProtocolDetection(void)
{
static uint32_t interface_detected = 0U;
if (interface_detected == 0U) {
interface_detected = OPENBL_InterfaceDetection();
if (interface_detected == 1U) {
OPENBL_InterfacesDeInit();
}
}
if (interface_detected == 1U) {
OPENBL_CommandProcess();
}
}
@@ -0,0 +1,113 @@
/*
* Override of CubeN6's external_memory_interface.c.
*
* ST's stock implementation reads function pointers from a fixed RAM
* address (0x38000000) populated by a separately-staged flash loader
* binary. That two-stage pattern is what STM32CubeProgrammer's TSV does
* (slot 0x3 stages MX66UW1G45G_*-OBL.bin into RAM before slot 0x4 writes
* to nor0). We don't want that — our OBL bundles the flash driver
* statically and the EXTERNAL_MEMORY_Descriptor below dispatches direct
* to flash_iface ops.
*
* This file is picked up by the Makefile in place of the submodule's
* external_memory_interface.c; the rest of the OBL middleware
* (openbl_core, openbl_mem, openbl_usb_cmd, the USB Device Library) is
* pulled in unchanged via VPATH.
*/
#include <string.h>
#include "stm32n6xx_hal.h"
#include "openbl_mem.h"
#include "openbootloader_conf.h"
#include "flash_iface.h"
#define MEMMAP_BASE EXT_MEMORY_START_ADDRESS /* 0x70000000 */
static uint32_t ext_init(uint32_t Address)
{
(void)Address;
/* OBL_run_dfu_* in main.c calls flash_init() before entering the OBL
* loop, so the driver is already up. Return success regardless. */
return 1U;
}
static uint8_t ext_read(uint32_t Address)
{
/* OBL Reads come through here a byte at a time. flash_memmap_on()
* idempotently re-engages memory-mapped mode; reads are direct CPU
* loads against the AXI window. */
if (!flash_memmap_on()) {
return 0xFFU;
}
return *(volatile uint8_t *)Address;
}
static void ext_write(uint32_t Address, uint8_t *Data, uint32_t DataLength)
{
if (Address < MEMMAP_BASE) {
return;
}
const uint32_t offset = Address - MEMMAP_BASE;
(void)flash_program(offset, Data, DataLength);
}
static void ext_jump(uint32_t Address)
{
/* OBL never jumps to external memory — the betaflight boot decision
* lives in main.c and runs after the DFU loop ends via
* NVIC_SystemReset, not via a direct jump. Stub. */
(void)Address;
}
static void ext_mass_erase(uint32_t Address)
{
/* The OBL DFU memory-map advertises only the BF slot (or the OBL
* slot during recovery), so DFU-driven mass erase is bounded. We
* implement it as "erase the advertised slot" rather than "erase
* the whole chip" to avoid wiping the OBL by accident. */
(void)Address;
}
static void ext_sector_erase(uint32_t StartAddress, uint32_t EndAddress)
{
if (StartAddress < MEMMAP_BASE || EndAddress < StartAddress) {
return;
}
const uint32_t offset = StartAddress - MEMMAP_BASE;
const uint32_t length = EndAddress - StartAddress + 1U;
(void)flash_erase_range(offset, length);
}
static uint64_t ext_verify(uint32_t Address, uint32_t DataAddr,
uint32_t DataLength, uint32_t Missalignement)
{
(void)Missalignement;
if (Address < MEMMAP_BASE) {
return 0U;
}
const uint32_t offset = Address - MEMMAP_BASE;
/* DataAddr in this protocol points to a host-supplied buffer in RAM
* (already received by OBL into one of its scratch areas). Compare
* directly. Returns 0 on match, otherwise an OPENBL-coded error. */
if (flash_verify(offset, (const uint8_t *)DataAddr, DataLength)) {
return 0U;
}
return 1U;
}
OPENBL_MemoryTypeDef EXTERNAL_MEMORY_Descriptor =
{
EXT_MEMORY_START_ADDRESS,
EXT_MEMORY_END_ADDRESS,
EXT_MEMORY_SIZE,
EXTERNAL_MEMORY_AREA,
ext_init,
ext_read,
ext_write,
ext_jump,
ext_mass_erase,
ext_sector_erase,
ext_verify
};
+56
View File
@@ -0,0 +1,56 @@
/*
* Common XSPI flash driver interface for the Betaflight N6 OpenBootloader.
*
* Implementations live alongside this header (mx66uw1g45g.c, etc.) and are
* selected at build time via the OBL_FLASH_DRIVER make variable. The OBL
* never indirects through the 0x38000000 function pointer table that ST's
* stock OBL relies on — the driver is statically linked.
*
* Operations expected:
* init : XSPI controller + GPIO + chip command set bring-up; leave
* the chip in 1S-1S-1S 4-byte addressing in indirect mode.
* geometry : page_size_bytes, sector_size_bytes, total_size_bytes for
* DFU descriptor advertisement.
* erase : sector- or block-erase at byte-aligned offset.
* program : page-aligned write (length <= page_size).
* memmap_on/off : engage / disengage XSPI memory-mapped mode for read.
* Reads use plain CPU loads against 0x70xxxxxx after
* memmap_on.
*
* No error reporting beyond bool — recovery image is single-purpose; if
* anything fails the only sensible response is to halt and let the user
* pull power and retry via @FSBL DFU again.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct {
uint32_t page_size_bytes;
uint32_t sector_size_bytes;
uint32_t total_size_bytes;
} flash_geometry_t;
bool flash_init(void);
void flash_deinit(void);
const flash_geometry_t *flash_get_geometry(void);
bool flash_erase_sector(uint32_t offset);
bool flash_program_page(uint32_t offset, const uint8_t *data, uint32_t length);
bool flash_memmap_on(void);
bool flash_memmap_off(void);
/* Convenience: erase enough sectors to cover [offset, offset+length). */
bool flash_erase_range(uint32_t offset, uint32_t length);
/* Convenience: program a buffer larger than one page, splitting into
* page-aligned writes. Caller must have erased first. */
bool flash_program(uint32_t offset, const uint8_t *data, uint32_t length);
/* Verify by re-reading via memory-mapped mode. Returns true on match. */
bool flash_verify(uint32_t offset, const uint8_t *data, uint32_t length);
+404
View File
@@ -0,0 +1,404 @@
/*
* Macronix MX66UW1G45G — 1 Gb octal STR/DTR flash driver for the
* Betaflight N6 OpenBootloader.
*
* Targets the wiring on the STM32N6570-DK reference design and pin-
* compatible boards: XSPI2 + XSPIM_P2 + GPIOM AF11 + NCS1. Boards with
* different XSPI controllers, IO ports, or chip selects need their own
* driver source (or this one parameterised via the OBL_FLASH_DRIVER
* config knob).
*
* XSPI bring-up + 1S-1S-1S soft reset + memory-mapped engagement is
* lifted from lib/main/STM32/n6_fsbl/main.c (proven across multiple boots
* during the FSBL stub work). The new pieces here are the indirect-mode
* write primitives (write enable / sector erase / block erase / page
* program) and a memory-mapped-mode-off hook so the driver can flip
* between read-via-memmap and write-via-indirect cleanly.
*/
#include <string.h>
#include "stm32n6xx_hal.h"
#include "flash_iface.h"
/* ---------- chip command set (1S-1S-1S, post-reset state) ----------- */
#define MX66_CMD_RESET_ENABLE 0x66U
#define MX66_CMD_RESET 0x99U
#define MX66_CMD_RDSR 0x05U /* read status register */
#define MX66_SR_WIP 0x01U /* write-in-progress bit */
#define MX66_CMD_WREN 0x06U /* write enable */
#define MX66_CMD_READ_4B 0x0CU /* 4-byte FAST_READ */
#define MX66_CMD_READ_4B_DUMMY 8U
#define MX66_CMD_PP_4B 0x12U /* 4-byte page program */
#define MX66_CMD_SE_4B 0x21U /* 4-byte 4 KiB sector erase */
#define MX66_CMD_BE_4B 0xDCU /* 4-byte 64 KiB block erase */
#define MX66_PAGE_SIZE 256U
#define MX66_SECTOR_SIZE (4U * 1024U)
#define MX66_TOTAL_SIZE (128U * 1024U * 1024U) /* 1 Gb = 128 MiB */
#define MX66_RETRIES 3
#define SETTLE_DELAY_MS 600
#define MX66_TIMEOUT_PROGRAM_MS 50
#define MX66_TIMEOUT_SECTOR_MS 400
#define MX66_TIMEOUT_BLOCK_MS 2000
#define MX66_TIMEOUT_GENERIC_MS 1000
#define MX66_MEMMAP_BASE 0x70000000U
static XSPI_HandleTypeDef hxspi2;
static const flash_geometry_t geometry = {
.page_size_bytes = MX66_PAGE_SIZE,
.sector_size_bytes = MX66_SECTOR_SIZE,
.total_size_bytes = MX66_TOTAL_SIZE,
};
static bool memmap_active;
/* HAL_XSPI_MspInit (peripheral + IO manager + GPIO N clocks, GPION pins
* to AF9_XSPIM_P2) lives in stm32n6xx_hal_msp.c — board-level config
* shared with whatever flash chip happens to be wired to XSPI2. A
* manufacturer using a different XSPI controller or pin map provides
* their own MSP file under the per-config build. */
static HAL_StatusTypeDef mx66_wait_ready(uint32_t timeout_ms)
{
XSPI_RegularCmdTypeDef cmd = {0};
uint8_t status;
uint32_t tickstart = HAL_GetTick();
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
cmd.Instruction = MX66_CMD_RDSR;
cmd.DataMode = HAL_XSPI_DATA_1_LINE;
cmd.DataLength = 1;
cmd.AddressMode = HAL_XSPI_ADDRESS_NONE;
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
cmd.DummyCycles = 0;
do {
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return HAL_ERROR;
}
if (HAL_XSPI_Receive(&hxspi2, &status, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return HAL_ERROR;
}
if ((status & MX66_SR_WIP) == 0) {
return HAL_OK;
}
} while ((HAL_GetTick() - tickstart) < timeout_ms);
return HAL_TIMEOUT;
}
static HAL_StatusTypeDef mx66_simple_cmd_1s(uint8_t opcode)
{
XSPI_RegularCmdTypeDef cmd = {0};
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
cmd.Instruction = opcode;
cmd.AddressMode = HAL_XSPI_ADDRESS_NONE;
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
cmd.DataMode = HAL_XSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
return HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE);
}
static HAL_StatusTypeDef mx66_cmd_8dtr(uint8_t opcode)
{
XSPI_RegularCmdTypeDef cmd = {0};
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_8_LINES;
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_16_BITS;
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_ENABLE;
cmd.Instruction = ((uint16_t)opcode << 8) | (uint8_t)(~opcode);
cmd.AddressMode = HAL_XSPI_ADDRESS_NONE;
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
cmd.DataMode = HAL_XSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
return HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE);
}
static HAL_StatusTypeDef mx66_software_reset(void)
{
for (int i = 0; i < MX66_RETRIES; i++) {
(void)mx66_cmd_8dtr(MX66_CMD_RESET_ENABLE);
(void)mx66_cmd_8dtr(MX66_CMD_RESET);
HAL_Delay(1);
if (mx66_simple_cmd_1s(MX66_CMD_RESET_ENABLE) == HAL_OK) {
if (mx66_simple_cmd_1s(MX66_CMD_RESET) == HAL_OK) {
HAL_Delay(1);
return mx66_wait_ready(MX66_TIMEOUT_GENERIC_MS);
}
}
}
return HAL_ERROR;
}
static HAL_StatusTypeDef mx66_write_enable(void)
{
HAL_StatusTypeDef st = mx66_simple_cmd_1s(MX66_CMD_WREN);
return st;
}
static HAL_StatusTypeDef mx66_addr_cmd_1s(uint8_t opcode, uint32_t address,
uint32_t timeout_ms)
{
if (mx66_write_enable() != HAL_OK) {
return HAL_ERROR;
}
XSPI_RegularCmdTypeDef cmd = {0};
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
cmd.Instruction = opcode;
cmd.AddressMode = HAL_XSPI_ADDRESS_1_LINE;
cmd.AddressWidth = HAL_XSPI_ADDRESS_32_BITS;
cmd.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
cmd.Address = address;
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
cmd.DataMode = HAL_XSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return HAL_ERROR;
}
return mx66_wait_ready(timeout_ms);
}
static HAL_StatusTypeDef mx66_page_program(uint32_t address, const uint8_t *data,
uint32_t length)
{
if (length == 0 || length > MX66_PAGE_SIZE) {
return HAL_ERROR;
}
/* Page-program straddles must be split by the caller — chip wraps the
* write at the page boundary, which would corrupt earlier bytes in
* the same page. */
if (((address & (MX66_PAGE_SIZE - 1)) + length) > MX66_PAGE_SIZE) {
return HAL_ERROR;
}
if (mx66_write_enable() != HAL_OK) {
return HAL_ERROR;
}
XSPI_RegularCmdTypeDef cmd = {0};
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
cmd.Instruction = MX66_CMD_PP_4B;
cmd.AddressMode = HAL_XSPI_ADDRESS_1_LINE;
cmd.AddressWidth = HAL_XSPI_ADDRESS_32_BITS;
cmd.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
cmd.Address = address;
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
cmd.DataMode = HAL_XSPI_DATA_1_LINE;
cmd.DataDTRMode = HAL_XSPI_DATA_DTR_DISABLE;
cmd.DataLength = length;
cmd.DummyCycles = 0;
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return HAL_ERROR;
}
if (HAL_XSPI_Transmit(&hxspi2, (uint8_t *)data, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return HAL_ERROR;
}
return mx66_wait_ready(MX66_TIMEOUT_PROGRAM_MS);
}
static HAL_StatusTypeDef xspi2_init_controller(void)
{
XSPIM_CfgTypeDef mgr = {0};
hxspi2.Instance = XSPI2;
hxspi2.Init.FifoThresholdByte = 4;
hxspi2.Init.MemoryMode = HAL_XSPI_SINGLE_MEM;
hxspi2.Init.MemoryType = HAL_XSPI_MEMTYPE_MACRONIX;
hxspi2.Init.MemorySize = HAL_XSPI_SIZE_1GB;
hxspi2.Init.ChipSelectHighTimeCycle = 2;
hxspi2.Init.FreeRunningClock = HAL_XSPI_FREERUNCLK_DISABLE;
hxspi2.Init.ClockMode = HAL_XSPI_CLOCK_MODE_0;
hxspi2.Init.WrapSize = HAL_XSPI_WRAP_NOT_SUPPORTED;
hxspi2.Init.ClockPrescaler = 0;
hxspi2.Init.SampleShifting = HAL_XSPI_SAMPLE_SHIFT_NONE;
hxspi2.Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_DISABLE;
hxspi2.Init.ChipSelectBoundary = HAL_XSPI_BONDARYOF_NONE;
hxspi2.Init.MaxTran = 0;
hxspi2.Init.Refresh = 0;
hxspi2.Init.MemorySelect = HAL_XSPI_CSSEL_NCS1;
if (HAL_XSPI_Init(&hxspi2) != HAL_OK) {
return HAL_ERROR;
}
mgr.nCSOverride = HAL_XSPI_CSSEL_OVR_NCS1;
mgr.IOPort = HAL_XSPIM_IOPORT_2;
mgr.Req2AckTime = 1;
return HAL_XSPIM_Config(&hxspi2, &mgr, HAL_XSPI_TIMEOUT_DEFAULT_VALUE);
}
bool flash_init(void)
{
if (xspi2_init_controller() != HAL_OK) {
return false;
}
HAL_Delay(SETTLE_DELAY_MS);
if (mx66_software_reset() != HAL_OK) {
return false;
}
HAL_Delay(SETTLE_DELAY_MS);
memmap_active = false;
return true;
}
void flash_deinit(void)
{
/* Leave XSPI2 fully configured + memory-mapped so the post-reset
* boot decision can read nor0 0x0 directly without re-init churn.
* Callers wanting a true peripheral teardown should HAL_XSPI_DeInit
* themselves; OBL never does. */
}
const flash_geometry_t *flash_get_geometry(void)
{
return &geometry;
}
bool flash_memmap_on(void)
{
if (memmap_active) {
return true;
}
XSPI_RegularCmdTypeDef cmd = {0};
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
cmd.AddressMode = HAL_XSPI_ADDRESS_1_LINE;
cmd.AddressWidth = HAL_XSPI_ADDRESS_32_BITS;
cmd.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
cmd.DataMode = HAL_XSPI_DATA_1_LINE;
cmd.DataDTRMode = HAL_XSPI_DATA_DTR_DISABLE;
cmd.DummyCycles = 0;
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
cmd.OperationType = HAL_XSPI_OPTYPE_READ_CFG;
cmd.Instruction = MX66_CMD_READ_4B;
cmd.DummyCycles = MX66_CMD_READ_4B_DUMMY;
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return false;
}
cmd.DummyCycles = 0;
cmd.OperationType = HAL_XSPI_OPTYPE_WRITE_CFG;
cmd.Instruction = MX66_CMD_PP_4B; /* placeholder — never issued */
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
return false;
}
XSPI_MemoryMappedTypeDef mm = {0};
mm.TimeOutActivation = HAL_XSPI_TIMEOUT_COUNTER_DISABLE;
/* Prefetch off — speculative reads against a chip in an unexpected
* mode would stall the AXI bus indefinitely. Bounded stalls only. */
mm.NoPrefetchData = HAL_XSPI_AUTOMATIC_PREFETCH_DISABLE;
mm.NoPrefetchAXI = HAL_XSPI_AXI_PREFETCH_DISABLE;
if (HAL_XSPI_MemoryMapped(&hxspi2, &mm) != HAL_OK) {
return false;
}
memmap_active = true;
return true;
}
bool flash_memmap_off(void)
{
if (!memmap_active) {
return true;
}
/* Aborting an in-flight memory-mapped session is the documented way
* to drop the controller back to indirect command mode. */
if (HAL_XSPI_Abort(&hxspi2) != HAL_OK) {
return false;
}
memmap_active = false;
return true;
}
bool flash_erase_sector(uint32_t offset)
{
if (!flash_memmap_off()) {
return false;
}
return mx66_addr_cmd_1s(MX66_CMD_SE_4B, offset, MX66_TIMEOUT_SECTOR_MS) == HAL_OK;
}
bool flash_program_page(uint32_t offset, const uint8_t *data, uint32_t length)
{
if (!flash_memmap_off()) {
return false;
}
return mx66_page_program(offset, data, length) == HAL_OK;
}
bool flash_erase_range(uint32_t offset, uint32_t length)
{
const uint32_t mask = MX66_SECTOR_SIZE - 1;
if (offset & mask) {
return false;
}
uint32_t end = offset + length;
/* Round end up to the next sector boundary. */
end = (end + mask) & ~mask;
for (uint32_t a = offset; a < end; a += MX66_SECTOR_SIZE) {
if (!flash_erase_sector(a)) {
return false;
}
}
return true;
}
bool flash_program(uint32_t offset, const uint8_t *data, uint32_t length)
{
while (length) {
uint32_t chunk = MX66_PAGE_SIZE - (offset & (MX66_PAGE_SIZE - 1));
if (chunk > length) {
chunk = length;
}
if (!flash_program_page(offset, data, chunk)) {
return false;
}
offset += chunk;
data += chunk;
length -= chunk;
}
return true;
}
bool flash_verify(uint32_t offset, const uint8_t *data, uint32_t length)
{
if (!flash_memmap_on()) {
return false;
}
const uint8_t *flash = (const uint8_t *)(MX66_MEMMAP_BASE + offset);
return memcmp(flash, data, length) == 0;
}
+475
View File
@@ -0,0 +1,475 @@
/*
* Betaflight N6 OpenBootloader — entry, boot decision, and S→NS
* hand-off trampoline.
*
* Loaded by the boot ROM into AXISRAM2 secure at 0x34180400 from XSPI
* nor0 0x0 on cold boot, or via USB DFU @FSBL alt during recovery.
* The two cases are distinguished by checking nor0 0x0 for the signed
* FSBL header magic.
*
* Boot decision:
* - nor0 0x0 has no valid OBL header → recovery DFU (writes nor0 0x0)
* - RCC->RSR has IWDGRSTF/LCKRSTF/
* WWDGRSTF set → previous BF crashed → DFU
* - BF vector table at 0x70100000
* invalid → DFU
* - otherwise → arm IWDG, BXNS to BF in NS
*
* The DFU loop itself is supplied by the OBL middleware (CubeN6
* OpenBootloader, pulled in via Makefile VPATH and re-entered via
* obl_app.c::OBL_run_dfu_*). This file owns the boot decision, IWDG
* arming, and the BF jump trampoline. OBL is built with -mcmse so the
* CMSIS peripheral pointers (RCC, RIFSC, GPIOx, DBGMCU, RISAF*, …)
* resolve to their secure-alias bases automatically.
*/
#include <arm_cmse.h>
#include <string.h>
#include "main.h"
#include "flash_iface.h"
/* ---------- Layout constants ---------- */
#define XSPI_NOR_BASE 0x70000000U
#define BF_VECTOR_BASE 0x70100000U
#define BF_SLOT_OFFSET 0x00100000U /* 1 MiB OBL reserve before BF */
/* "STM2" little-endian — the signed FSBL header magic at offset 0. */
#define STM2_MAGIC_LE 0x324D5453U
/* IWDG window: ~10 s @ LSI 32 kHz / (256 × 1250). BF must refresh from
* its scheduler-driven watchdog task before this expires. */
#define OBL_IWDG_PRESCALER IWDG_PRESCALER_256
#define OBL_IWDG_RELOAD 1250U
#define OBL_IWDG_WINDOW 1250U /* window disabled (= reload) */
IWDG_HandleTypeDef hiwdg;
/* Forward-declared from obl_app.c. Both run the OBL middleware DFU
* loop and never return — they end via NVIC_SystemReset. */
__attribute__((noreturn)) void OBL_run_dfu_normal(void);
__attribute__((noreturn)) void OBL_run_dfu_recovery(void);
static void SystemClock_Config(void);
extern void HAL_MspInit(void);
/* ---------- Boot decision helpers ---------- */
static bool nor0_has_valid_obl(void)
{
if (!flash_memmap_on()) {
return false;
}
const uint32_t magic = *(volatile uint32_t *)(XSPI_NOR_BASE);
return magic == STM2_MAGIC_LE;
}
static bool bf_vector_table_valid(void)
{
if (!flash_memmap_on()) {
return false;
}
const uint32_t bf_sp = *(volatile uint32_t *)(BF_VECTOR_BASE);
const uint32_t bf_pc = *(volatile uint32_t *)(BF_VECTOR_BASE + 4U);
/* SP must point into AXISRAM (top nibble 0x2). */
if ((bf_sp & 0xF8000000U) != 0x20000000U) {
return false;
}
/* Reset_Handler must live in the BF XIP slot (top 12 bits == 0x701)
* with the thumb bit set. */
if ((bf_pc & 0xFFF00000U) != BF_VECTOR_BASE) {
return false;
}
if ((bf_pc & 1U) == 0U) {
return false;
}
return true;
}
/* ---------- IWDG ---------- */
static void iwdg_start(void)
{
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = OBL_IWDG_PRESCALER;
hiwdg.Init.Reload = OBL_IWDG_RELOAD;
hiwdg.Init.Window = OBL_IWDG_WINDOW;
if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
Error_Handler();
}
/* IWDG cannot be disabled until system reset. BF must call
* HAL_IWDG_Refresh() from its scheduler, or the timeout fires NRST
* and OBL routes the next boot to DFU via RSR.IWDGRSTF. */
}
/* ---------- Jump trampoline ---------- */
/* All-8-CIDs read + write enable for RISAF region CIDCFGR. Equivalent to
* RDENC0_Msk | RDENC1_Msk | ... | RDENC7_Msk | WRENC0_Msk | ... | WRENC7_Msk
* but kept as a single hex literal because the OR'd form is noisy. */
#define RISAF_CIDCFGR_ALL_CIDS_RW 0x00FF00FFUL
/* Configure one base region of a RISAF instance. `region` is 0-indexed
* (the CMSIS RISAF_TypeDef exposes the per-region structs as REG[0..14]).
* Per RM §7.5: write STARTR/ENDR/CIDCFGR before CFGR so BREN gates the
* region atomically. CIDCFGR opens RDENC[0..7] and WRENC[0..7]. */
static inline void risaf_region_config(RISAF_TypeDef *risaf, uint32_t region,
uint32_t start, uint32_t end,
bool sec)
{
risaf->REG[region].STARTR = start;
risaf->REG[region].ENDR = end;
risaf->REG[region].CIDCFGR = RISAF_CIDCFGR_ALL_CIDS_RW;
risaf->REG[region].CFGR = (sec ? RISAF_REGx_CFGR_SEC_Msk : 0UL)
| RISAF_REGx_CFGR_BREN_Msk;
}
static __attribute__((noreturn)) void jump_to_bf(void)
{
const uint32_t bf_sp = *(volatile uint32_t *)(BF_VECTOR_BASE);
const uint32_t bf_pc = *(volatile uint32_t *)(BF_VECTOR_BASE + 4U);
HAL_SuspendTick();
__disable_irq();
/* Leave XSPI memory-mapped — BF runs XIP from 0x70100000. */
SCB_DisableICache();
SCB_DisableDCache();
/* === Hand off to BF in NS state ===========================
*
* BF runs as a Non-Secure application; OBL is the only S-state
* code on the device. RISAFs must be programmed so the NS CPU
* can reach BF's data (AXISRAM1) and code (XSPI memory-mapped)
* before BXNS. Boot-ROM default leaves every RISAF region
* disabled, which per RM §3.5.7 means "Locations outside any
* enabled region belong to the secure OS" — so without explicit
* opens, NS access is silently RAZ.
*
* Order matters: RISAFs are configured AFTER OBL's last XSPI
* read (bf_sp/bf_pc above) so OBL's S-state reads still see the
* boot-ROM permissive default. Once XSPI's RISAF12 region is
* flipped to NS, OBL itself can no longer read XSPI; only BF
* (NS) can. AXISRAM2 (where OBL lives) is left at the boot-ROM
* default (secure-only) so OBL keeps executing through the BXNS.
*
* RISAF6 is deliberately not programmed — ST's
* Template_Isolation_XIP reference for the equivalent topology
* (FSBL → AppliSecure → AppliNS in XSPI) leaves it alone too. */
/* RISAF peripheral clock — boot ROM leaves AHB3ENR.RISAFEN=0,
* which makes every access to RISAF register space stall the bus. */
RCC->AHB3ENSR = RCC_AHB3ENSR_RISAFENS;
(void)RCC->AHB3ENR;
/* AXISRAM1 + AXISRAM3..6 clocks — boot ROM only clocks AXISRAM2
* (where OBL itself runs). The NS BF lives in AXISRAM1 (data+stack)
* and AXISRAM3..6 (D2_RAM); without these clocks enabled, any NS
* access to 0x24010000+ or 0x24200000+ bus-faults. Idempotent with
* the SystemInit set. */
RCC->MEMENSR = RCC_MEMENSR_AXISRAM1ENS | RCC_MEMENSR_AXISRAM2ENS
| RCC_MEMENSR_AXISRAM3ENS | RCC_MEMENSR_AXISRAM4ENS
| RCC_MEMENSR_AXISRAM5ENS | RCC_MEMENSR_AXISRAM6ENS;
(void)RCC->MEMENR;
/* Slave-side RISAFs — open the resources BF (NS) needs.
*
* Boot-ROM default: every RISAF region disabled. RM §3.5.7 says a
* memory location covered by a RISAF but outside any enabled region
* "belongs to the secure OS", i.e., NS access bus-faults. So every
* slave-RISAF-protected bank BF touches needs an explicit NS region.
*
* RISAF3 (AXISRAM2, 1 MiB): region offset 0x91000..0xFFFFF NS
* (= 0x24191000..0x241FFFFF, 444 KiB)
* — BF's data/bss/stack/.ram_code. The
* low half stays uncovered → S-only →
* OBL's RAM at 0x34180400+ is protected.
* RISAF12 (XSPI2, 256 MiB): region covers offsets 0x100000..end
* NS, leaving the OBL slot at offsets
* 0x00000..0xFFFFF uncovered. Per RM
* §3.5.7 an uncovered range defaults
* to S-only, so accidental NS deref of
* 0x70000000..0x700FFFFF bus-faults
* instead of returning OBL image bytes.
*
* Intentionally NOT programmed:
* - RISAF2 (AXISRAM1, 1 MiB): NS access to this bank silently RAZ's
* despite a correctly-programmed RISAF2 region 0 — root cause
* unknown after exhaustive diagnostic (boot-ROM RISAF2 left at
* default, RIFSC SEC bits clear, IASR clean, CIDCFGR opens CID 1
* which the M55 presents). Empirical workaround: BF moved to
* AXISRAM2 high half (above OBL's 64 KiB block) via RISAF3, which
* matches the ST template pattern of "S app in AXISRAM1, NS app
* in AXISRAM2".
* - AXISRAM3..6 (NPURAM0..3, 0x24200000..0x243BFFFF, 1792 KiB):
* no slave RISAF exists for this range; bank security is gated by
* RIFSC->RISC_SECCFGRx[5] bits 17..20, which system_stm32n6xx_obl.c
* clears in its RIFSC open loop. NS access works once enabled. */
/* RISAF3 region 0 (AXISRAM2 NS slice for BF's data/bss/stack/.ram_code).
* BF lives in AXISRAM2 above the 64 KiB OBL block — RAM origin at
* 0x24191000, length 444 KiB. RISAF3 granularity is 4 KiB so the
* region is offset 0x91000..0xFFFFF (relative to AXISRAM2 base
* 0x24100000). The lower half of AXISRAM2 (offsets 0x00000..0x90FFF,
* which includes OBL's secure RAM) stays outside any enabled region
* and defaults to S-only per RM §3.5.7 — protecting OBL from any
* stray BF NS reach into its address space.
*
* RISAF2 (AXISRAM1) and RISAF6 are intentionally not programmed —
* BF doesn't use AXISRAM1 in this layout, and RISAF6 matches the
* ST Template_Isolation_XIP reference. */
risaf_region_config(RISAF3, 0U, 0x00091000UL, 0x000FFFFFUL, false);
risaf_region_config(RISAF12, 0U, 0x00100000UL, 0x0FFFFFFFUL, false);
/* === Per-CPU TrustZone setup for the NS application ====
*
* NSACR (S-only): grant NS code access to FPU coprocessor banks
* (CP10 + CP11). Without this, BF's first FPU instruction in NS
* state takes a NOCP UsageFault.
*
* CPACR_NS: NS-bank CPACR is at reset value (FPU access disabled
* for NS). BF's __libc_init_array runs before its own systemInit
* and may emit FPU instructions (newlib helpers, struct passing).
* Enable CP10/CP11 in the NS bank now so the early NS code can run.
*
* AIRCR.BFHFNMINS: set to 1 so NS HardFault / BusFault / NMI route to
* NS handlers. Required for BF to diagnose its own faults via NS
* HardFault_Handler. Without this NS faults route to S where OBL has
* only Default_Handler (infinite loop) — chip wedges silently. */
SCB->NSACR = SCB_NSACR_CP10_Msk | SCB_NSACR_CP11_Msk;
SCB_NS->CPACR = (3UL << 20) | (3UL << 22); /* CP10/CP11 full access in NS bank */
SCB->AIRCR = (SCB->AIRCR & ~(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_BFHFNMINS_Msk))
| (0x05FAUL << SCB_AIRCR_VECTKEY_Pos)
| SCB_AIRCR_BFHFNMINS_Msk;
__DSB();
__ISB();
/* SAU (Security Attribution Unit) — REQUIRED for BXNS to NS code.
* Per RM0486 §3.5.1: "at reset, the SAU unilaterally determines
* that the entire memory is secure." Without SAU regions defining
* NS, BXNS to 0x7010xxxx resolves the destination as S → SecureFault.
*
* Per-region NS instead of ALLNS=1: with explicit regions, addresses
* outside any region stay S-default — which is what OBL needs for
* its S-state accesses at 0x34xxxxxx, 0x50xxxxxx, 0x54xxxxxx etc.
*
* Region 0: AXISRAM1 (0x24000000..0x240FFFFF) NS — BF main RAM
* Region 1: AHB/APB NS-alias peripherals (0x40000000..0x4FFFFFFF)
* Region 2: XSPI memory-mapped (0x70100000..0x7FFFFFFF) NS — skip
* the OBL slot at 0x70000000..0x700FFFFF so a stray NS
* deref of an OBL address is rejected by the SAU before
* it reaches the bus (matches the RISAF12 region split).
* Region 3: AXISRAM3..6 (0x24200000..0x243BFFFF) NS — D2_RAM /
* LCD framebuffer. AXISRAM2 NS (0x24100000..0x241FFFFF)
* is intentionally NOT covered: nothing in BF uses it,
* and leaving it outside any SAU region means a stray
* NS access bus-faults instead of reaching the bank.
*
* RBAR/RLAR are 32-byte granularity (low 5 bits ignored).
*
* Per UM3234 the boot ROM also configures SAU. Disable the unit and
* clear every region (RLAR.ENABLE=0) before programming ours,
* otherwise leftover boot-ROM regions ≥ 4 keep their classification
* and can shadow what we configure. */
{
SAU->CTRL = 0;
for (uint32_t i = 0; i < 8U; i++) {
SAU->RNR = i;
SAU->RBAR = 0;
SAU->RLAR = 0;
}
__DSB();
__ISB();
const struct { uint32_t base; uint32_t limit; } regs[] = {
/* BF RAM lives in AXISRAM2 NS above OBL's 64 KiB block.
* AXISRAM2 lower half + OBL's secure region stays outside
* any SAU region → SAU-S by default, blocking any stray
* NS deref of OBL's address space. */
{ 0x24191000UL, 0x241FFFE0UL }, /* AXISRAM2 high (BF) */
{ 0x40000000UL, 0x4FFFFFE0UL }, /* NS peripherals */
{ 0x70100000UL, 0x7FFFFFE0UL }, /* XSPI (excl. OBL) */
{ 0x24200000UL, 0x243BFFE0UL }, /* AXISRAM3..6 / D2RAM */
};
for (uint32_t i = 0; i < (uint32_t)(sizeof(regs) / sizeof(regs[0])); i++) {
SAU->RNR = i;
SAU->RBAR = regs[i].base;
SAU->RLAR = regs[i].limit | SAU_RLAR_ENABLE_Msk;
}
SAU->CTRL = SAU_CTRL_ENABLE_Msk;
__DSB();
__ISB();
}
/* NS VTOR — point BF at its vector table at the XIP slot base. */
SCB_NS->VTOR = BF_VECTOR_BASE;
__DSB();
__ISB();
/* Clear every NVIC IRQ enable + pending bit before the hand-off so
* no S-side leftover interrupt fires the instant the NS app does
* `__enable_irq()`. BF's own startup clears these again; doing it
* here protects the transition window. */
for (uint32_t i = 0; i < (uint32_t)(sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0])); i++) {
NVIC->ICER[i] = 0xFFFFFFFFUL;
NVIC->ICPR[i] = 0xFFFFFFFFUL;
}
__DSB();
__ISB();
/* Re-target every external IRQ to NS so BF can enable/handle them
* through the NS NVIC bank. Reset default is ITNS = 0 (all IRQs
* Secure-targeted), which would route every peripheral IRQ (USB,
* GPDMA, EXTI, TIMx, …) into S handlers OBL doesn't implement —
* typically a tight Default_Handler loop. With NS-targeted IRQs the
* NVIC banks the configuration registers to the NS bank so BF's
* NVIC_EnableIRQ / SetPriority / SetTargetState calls land where
* the IRQ actually fires. */
for (uint32_t i = 0; i < (uint32_t)(sizeof(NVIC->ITNS) / sizeof(NVIC->ITNS[0])); i++) {
NVIC->ITNS[i] = 0xFFFFFFFFUL;
}
/* SysTick is a system exception, not an external IRQ — NVIC->ITNS
* doesn't cover it. Its NS/S target lives in ICSR.STTNS (bit 24,
* Secure-only writable). Without this, BF's NS SysTick fires the
* Secure-side SysTick exception, which OBL doesn't implement, so the
* exception pends in S forever (NS-side ICSR shows PENDSTSET=1 +
* VECTPENDING=15 with VECTACTIVE=0) and BF's NS handler never runs —
* uwTick / sysTickUptime stay at 0 and the first HAL_Delay /
* delay() call after the BF init that doesn't poll hardware directly
* hangs forever. */
SCB->ICSR |= SCB_ICSR_STTNS_Msk;
__DSB();
__ISB();
/* CMSIS-compliant ARMv8-M S→NS hand-off.
*
* `cmse_nonsecure_call` on the function-pointer type makes the
* compiler emit the proper BXNS-style branch *and* the AAPCS-CMSE
* register-clearing prologue (zero R0-R3 / R12 / FPSCR caller-saved
* state, so no Secure data leaks into the NS app). The function-
* pointer cast also implicitly clears the LSB of the target so the
* branch always transitions S → NS (with LSB=1 BXNS stays in S and
* would SecureFault on the NS-attributed XSPI fetch).
* __TZ_set_MSP_NS installs BF's stack pointer in the NS bank. */
__TZ_set_MSP_NS(bf_sp);
/* Clear S PRIMASK / FAULTMASK before BXNS. The __disable_irq() above
* leaves PRIMASK_S=1; empirically the Cortex-M55 keeps NS exceptions
* (SysTick, PendSV, peripheral IRQs targeted NS via ITNS) pending in
* the NS-bank ICSR but never delivers them while PRIMASK_S is set —
* even though Armv8-M masks are nominally banked per Security state.
* Re-enable S-side IRQs so NS exception delivery works post-BXNS. */
__enable_irq();
__set_FAULTMASK(0);
__set_BASEPRI(0);
__DSB();
__ISB();
typedef void __attribute__((cmse_nonsecure_call)) (*ns_reset_fn)(void);
const ns_reset_fn bf_reset = (ns_reset_fn)(bf_pc & ~1UL);
bf_reset();
while (1) {
__NOP();
}
}
/* ---------- Entry ---------- */
int main(void)
{
/* RCC->RSR must be sampled before HAL_Init / SystemClock_Config touch
* RCC — HAL_RCC_DeInit (called from System_DeInit during
* SystemClock_Config) writes RMVF and erases the IWDGRSTF / LCKRSTF /
* WWDGRSTF flags the boot decision below depends on. Snapshot the
* value here and clear so subsequent boots see a stable state. */
const uint32_t saved_rsr = RCC->RSR;
RCC->RSR = RCC_RSR_RMVF;
(void)RCC->RSR;
HAL_Init();
SystemClock_Config();
if (!flash_init()) {
Error_Handler();
}
if (!flash_memmap_on()) {
Error_Handler();
}
#ifdef OBL_FORCE_RECOVERY
/* Bring-up only: SWD-loaded OBL used to refresh nor0 0x0. Skip the
* magic check so we always enter Recovery, exposing nor0 from 0x0
* for OBL+BF reinstall. The committed signed OBL has this flag
* undefined. */
(void)nor0_has_valid_obl;
OBL_run_dfu_recovery();
/* unreachable */
#endif
if (!nor0_has_valid_obl()) {
OBL_run_dfu_recovery();
/* unreachable */
}
/* Reset-cause boot decision. RCC->RSR is the only on-chip primitive
* that survives any reset short of POR/BOR — its flags are cleared
* only by RMVF (handled above) or POR, and persist across NRST /
* SRST / SYSRESETREQ. IWDGRSTF / LCKRSTF / WWDGRSTF set means the
* previous boot ended with a crash (watchdog timeout, CPU lockup,
* or window WD); route to DFU instead of re-launching the broken
* BF in a loop. */
const uint32_t crash_flags = RCC_RSR_IWDGRSTF | RCC_RSR_LCKRSTF | RCC_RSR_WWDGRSTF;
const bool was_crash_reset = (saved_rsr & crash_flags) != 0U;
#ifdef OBL_FORCE_DFU
/* Bring-up override: SWD-load OBL with this flag set when we want
* DFU mode regardless of nor0 contents — used to flash a fresh BF
* via dfu-util on top of an existing valid (but stale) BF. */
OBL_run_dfu_normal();
/* unreachable */
#endif
if (was_crash_reset || !bf_vector_table_valid()) {
OBL_run_dfu_normal();
/* unreachable */
}
iwdg_start();
jump_to_bf();
}
/* ---------- Plumbing ---------- */
void Error_Handler(void)
{
__disable_irq();
while (1) {
}
}
/* Tear the clock tree back to reset defaults before SystemClock_Config_Impl
* runs OscConfig. Boot ROM's FSBL-load path leaves PLL1 active and the
* CPU sourced from it, which makes HAL_RCC_OscConfig fail. Also resolves
* the OpenBootloader_DeInit reference in CubeN6's app_openbootloader.c. */
void System_DeInit(void)
{
HAL_RCC_DeInit();
}
extern void SystemClock_Config_Impl(void);
static void SystemClock_Config(void)
{
SystemClock_Config_Impl();
/* Re-assert DBGMCU.CR after the clock-tree reconfig — the brief
* debug-clock outage during the CPU-clock switch can drop the
* bits. Same value as in SystemInit. */
DBGMCU->CR = DBGMCU_CR_DBGCLKEN
| DBGMCU_CR_DBG_SLEEP
| DBGMCU_CR_DBG_STOP
| DBGMCU_CR_DBG_STANDBY;
(void)DBGMCU->CR;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "stm32n6xx_hal.h"
void Error_Handler(void);
+87
View File
@@ -0,0 +1,87 @@
/*
* OBL DFU loop entry points called from main.c's boot decision.
*
* Both run the same OBL middleware loop (OpenBootloader_Init +
* OpenBootloader_ProtocolDetection in a busy loop). They differ in how
* the DFU memory map is configured before the loop starts:
* - normal mode protects the OBL slot (advertise @External_Flash from
* nor0 0x100000 onwards — BF slot only);
* - recovery mode exposes nor0 from offset 0 so the host can rewrite
* OBL itself.
*
* The mode is communicated to USB_DFU_If_* in usbd_dfu_if.c via the
* obl_recovery_mode flag below — checked when DFU initialises its memory
* descriptor string.
*
* Once a host completes a DFU download, our patched usbd_dfu.c sets
* dfu_leave_pending; the loop below detaches USB and triggers a system
* reset so the freshly-flashed BF runs without manual NRST.
*/
#include "main.h"
#include "app_openbootloader.h"
#include "usbd_core.h"
#include "flash_iface.h"
bool obl_recovery_mode;
extern void obl_dfu_apply_mode(bool recovery);
extern volatile bool dfu_leave_pending;
extern volatile uint32_t dfu_last_write_tick;
extern USBD_HandleTypeDef hUsbDeviceFS;
/* Time for the host's final GET_STATUS to return dfuMANIFEST-WAIT-RESET
* before the device disappears off the bus. dfu-util prints a clean
* "File downloaded successfully" once the manifest poll loop completes
* within ~250 ms; 400 ms is generous. */
#define DFU_LEAVE_DRAIN_MS 400U
/* Auto-reboot after this many ms of write inactivity once at least one
* data block has landed. dfu-util without --reset finishes the download
* and exits without triggering manifest, so we infer "host is done" from
* a stretch of silence. 1500 ms is comfortably above the gap between
* dfu-util's per-block transfers (~ms) and below any reasonable interactive
* wait. */
#define DFU_IDLE_REBOOT_MS 1500U
static __attribute__((noreturn)) void run_obl_loop(bool recovery)
{
obl_recovery_mode = recovery;
/* Patch the DFU descriptor string before USB enumeration starts so
* the host sees the right memory geometry on first GET_DESCRIPTOR. */
obl_dfu_apply_mode(recovery);
OpenBootloader_Init();
/* IWDG is NOT started in DFU mode — host upload can take tens of
* seconds and the BF watchdog window (~30 s) is too tight for that.
* The watchdog is armed only when handing off to BF (iwdg_start in
* main.c). */
while (1) {
OpenBootloader_ProtocolDetection();
const bool leave_request = dfu_leave_pending;
const uint32_t last_tick = dfu_last_write_tick;
const bool idle_complete = (last_tick != 0U)
&& ((HAL_GetTick() - last_tick) >= DFU_IDLE_REBOOT_MS);
if (leave_request || idle_complete) {
HAL_Delay(DFU_LEAVE_DRAIN_MS);
(void)USBD_Stop(&hUsbDeviceFS);
(void)USBD_DeInit(&hUsbDeviceFS);
HAL_Delay(50U);
NVIC_SystemReset();
}
}
}
__attribute__((noreturn)) void OBL_run_dfu_normal(void)
{
run_obl_loop(false);
}
__attribute__((noreturn)) void OBL_run_dfu_recovery(void)
{
run_obl_loop(true);
}
@@ -0,0 +1,70 @@
/*
* Override of CubeN6's openbootloader_conf.h.
*
* The submodule version uses _S secure aliases (SRAM2_AXI_BASE_S etc.)
* which are only defined when the build sets CPU_IN_SECURE_STATE +
* -mcmse — see feedback_n6_peripheral_alias_cmse.md. We don't (matches
* the FSBL stub and main BF build), so all peripheral / SRAM macros
* resolve to NS variants. RIFSC is configured for OPEN-lifecycle silicon
* to permit NS access regardless.
*
* Picked up before the submodule version because -I. comes first in
* INCLUDES.
*/
#ifndef OPENBOOTLOADER_CONF_H
#define OPENBOOTLOADER_CONF_H
#ifdef __cplusplus
extern "C" {
#endif
#include "platform.h"
#define DEVICE_ID_MSB 0x04U
#define DEVICE_ID_LSB 0x86U
/* RAM scratch — OBL middleware uses these for its receive/scratch
* buffers. Targets SRAM2 AXI (NS alias) per ST's reference. */
#define RAM_MEM_START_ADDRESS (SRAM2_AXI_BASE_NS + 0xB9000U)
#define RAM_MEM_SIZE (SRAM2_AXI_SIZE - 0xB9000U)
#define RAM_MEM_END_ADDRESS (RAM_MEM_START_ADDRESS + RAM_MEM_SIZE - 1U)
#define FLASH_LOADER_START_ADDRESS SRAM1_AHB_BASE_NS
#define FLASH_LOADER_SIZE (SRAM1_AHB_SIZE + SRAM2_AHB_SIZE)
#define FLASH_LOADER_END_ADDRESS (FLASH_LOADER_START_ADDRESS + FLASH_LOADER_SIZE - 1U)
#define FLASHLAYOUT_ADDRESS (RAM_MEM_START_ADDRESS + RAM_MEM_SIZE - 1024U)
#define RAM_WRITE_ADDRESS RAM_MEM_START_ADDRESS
#define FLASH_LOADER_WRITE_ADDRESS FLASH_LOADER_START_ADDRESS
#define EXT_MEMORY_START_ADDRESS 0x70000000U
#define EXT_MEMORY_END_ADDRESS 0x78000000U
#define EXT_MEMORY_SIZE 0x08000000U
#define EXT_MEMORY_SECTOR_SIZE 0x10000U
#define OPENBL_DEFAULT_MEM 0xFFFFFFFFU
#define UNDEF_ADDRESS 0xFFFFFFFFU
#define RDP_LEVEL_0 0xEEEEEEEEU
#define RDP_LEVEL_1 0xEEEEEEEEU
#define AREA_ERROR 0x0U
#define RAM_AREA 0x1U
#define OTP_AREA 0x2U
#define EXTERNAL_MEMORY_AREA 0x3U
#define FLASH_MASS_ERASE 0xFFFFU
/* Only USB transport in our build (no USART/I2C/SPI/CAN). The
* INTERFACES_SUPPORTED count gates how many OPENBL_RegisterInterface
* slots OBL pre-allocates. We register USB + IWDG = 2 in
* app_openbootloader.c. */
#define INTERFACES_SUPPORTED 2U
#ifdef __cplusplus
}
#endif
#endif /* OPENBOOTLOADER_CONF_H */
+40
View File
@@ -0,0 +1,40 @@
/*
* OTP interface stub.
*
* ST's CubeN6 reference OBL ships an OTP fuse-access path via the BSEC
* peripheral HAL. We don't enable BSEC HAL (no security/lifecycle work
* needed for our recovery + BF-loader use case), so the upstream
* otp_interface.c won't compile in our build. This stub provides the
* Init/DeInit symbols app_openbootloader.c calls and stubs Read/Write so
* any host attempt at OTP commands no-ops cleanly rather than wedging
* the device. If a manufacturer needs OTP commissioning later, replace
* this file with the real implementation behind a per-config knob.
*/
#include <string.h>
#include "main.h"
#include "otp_interface.h"
void OPENBL_OTP_Init(void)
{
}
void OPENBL_OTP_DeInit(void)
{
}
Otp_Partition_t OPENBL_OTP_Read(void)
{
Otp_Partition_t result;
memset(&result, 0, sizeof(result));
result.Version = OPENBL_OTP_VERSION;
result.GlobalState = BSEC_SEC_OTP_INVALID;
return result;
}
int OPENBL_OTP_Write(Otp_Partition_t Otp)
{
(void)Otp;
return OTP_ERROR;
}
@@ -0,0 +1,946 @@
/**
******************************************************************************
* @file startup_stm32n657xx.s
* @author GPM Application Team
* @brief STM32N657XX device vector table for GCC toolchain.
* This startup file should be used for the FSBL (after bootROM execution).
* This module performs:
* - Set the initial SP
* - Set the initial PC == Reset_Handler,
* - Set the vector table entries with the exceptions ISR address
* - Branches to main in the C library (which eventually
* calls main()).
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
.syntax unified
.arch armv8.1-m.main
.fpu softvfp
.thumb
.global g_pfnVectors
.global Default_Handler
/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss
/**
* @brief This is the code that gets called when the processor first
* starts execution following a reset event. Only the absolutely
* necessary set is performed, after which the application
* supplied main() routine is called.
* @param None
* @retval : None
*/
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
ldr r0, =_sstack
msr MSPLIM, r0
ldr r0, =_estack
mov sp, r0 /* set stack pointer */
/* .data copy + .bss zero must run BEFORE SystemInit. SystemInit and
* HAL_Init touch globals (uwTick, SystemCoreClock); doing them with
* uninitialised RAM corrupts state in ways that only show up later.
* See feedback_h5_startup_data_init_order.md. */
/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
movs r3, #0
b LoopCopyDataInit
CopyDataInit:
ldr r4, [r2, r3]
str r4, [r0, r3]
adds r3, r3, #4
LoopCopyDataInit:
adds r4, r0, r3
cmp r4, r1
bcc CopyDataInit
/* Zero fill the bss segment. */
ldr r2, =_sbss
ldr r4, =_ebss
movs r3, #0
b LoopFillZerobss
FillZerobss:
str r3, [r2]
adds r2, r2, #4
LoopFillZerobss:
cmp r2, r4
bcc FillZerobss
/* Call the clock system initialization function.*/
bl SystemInit
/* Call the application's entry point.*/
bl main
LoopForever:
b LoopForever
.size Reset_Handler, .-Reset_Handler
/**
* @brief This is the code that gets called when the processor receives an
* unexpected interrupt. This simply enters an infinite loop, preserving
* the system state for examination by a debugger.
*
* @param None
* @retval : None
*/
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The STM32N657XX vector table. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word SecureFault_Handler
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
.word PVD_PVM_IRQHandler /* PVDOUT through the EXTI line */
.word 0 /* Reserved */
.word DTS_IRQHandler /* Thermal sensor interruption */
.word RCC_IRQHandler /* RCC global interrupt */
.word LOCKUP_IRQHandler /* LOCKUP - no overstack in Cortex-M55 */
.word CACHE_ECC_IRQHandler /* Cache ECC error */
.word TCM_ECC_IRQHandler /* TCM ECC error */
.word BCK_ECC_IRQHandler /* Backup RAM interrupts (SEC and DED) */
.word FPU_IRQHandler /* FPU safety flag */
.word 0 /* Reserved */
.word RTC_S_IRQHandler /* RTC secure interrupt */
.word TAMP_IRQHandler /* TAMP secure and non-secure synchronous interrupt line */
.word RIFSC_TAMPER_IRQHandler /* RIF can generate an interrupt when a laser attack is detected */
.word IAC_IRQHandler /* IAC global interrupt */
.word RCC_S_IRQHandler /* RCC global secure interrupt */
.word 0 /* Reserved */
.word RTC_IRQHandler /* RTC interrupt */
.word 0 /* Reserved */
.word IWDG_IRQHandler /* Independent watchdog interrupt */
.word WWDG_IRQHandler /* Window watchdog interrupt */
.word EXTI0_IRQHandler /* EXTI Line 0 interrupt through the EXTI line */
.word EXTI1_IRQHandler /* EXTI Line 1 interrupt through the EXTI line */
.word EXTI2_IRQHandler /* EXTI Line 2 interrupt through the EXTI line */
.word EXTI3_IRQHandler /* EXTI Line 3 interrupt through the EXTI line */
.word EXTI4_IRQHandler /* EXTI Line 4 interrupt through the EXTI line */
.word EXTI5_IRQHandler /* EXTI Line 5 interrupt through the EXTI line */
.word EXTI6_IRQHandler /* EXTI Line 6 interrupt through the EXTI line */
.word EXTI7_IRQHandler /* EXTI Line 7 interrupt through the EXTI line */
.word EXTI8_IRQHandler /* EXTI Line 8 interrupt through the EXTI line */
.word EXTI9_IRQHandler /* EXTI Line 9 interrupt */
.word EXTI10_IRQHandler /* EXTI Line 10 interrupt */
.word EXTI11_IRQHandler /* EXTI Line 11 interrupt */
.word EXTI12_IRQHandler /* EXTI Line 12 interrupt */
.word EXTI13_IRQHandler /* EXTI Line 13 interrupt */
.word EXTI14_IRQHandler /* EXTI Line 14 interrupt */
.word EXTI15_IRQHandler /* EXTI Line 15 interrupt */
.word SAES_IRQHandler /* SAES global interrupt */
.word CRYP_IRQHandler /* CRYP global interrupt */
.word PKA_IRQHandler /* PKA global interrupt */
.word HASH_IRQHandler /* HASH global interrupt */
.word RNG_IRQHandler /* RNG global interrupt */
.word 0 /* Reserved */
.word MCE1_IRQHandler /* MCE1 global interrupt */
.word MCE2_IRQHandler /* MCE2 global interrupt */
.word MCE3_IRQHandler /* MCE3 global interrupt */
.word MCE4_IRQHandler /* MCE4 global interrupt */
.word ADC1_2_IRQHandler /* ADC1/ADC2 global interrupt */
.word CSI_IRQHandler /* CSI global interrupt */
.word DCMIPP_IRQHandler /* DCMIPP global interrupt */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word PAHB_ERR_IRQHandler /* Write posting errors on Cortex-M55 PAHB interface */
.word NPU0_IRQHandler /* NPU mst_ints[0] line */
.word NPU1_IRQHandler /* NPU mst_ints[1] line */
.word NPU2_IRQHandler /* NPU mst_ints[2] line */
.word NPU3_IRQHandler /* NPU mst_ints[3] line */
.word CACHEAXI_IRQHandler /* ATON interrupt cache */
.word LTDC_LO_IRQHandler /* LCD low-layer global interrupt */
.word LTDC_LO_ERR_IRQHandler /* LCD low-layer error interrupt */
.word DMA2D_IRQHandler /* DMA2D global interrupt */
.word JPEG_IRQHandler /* JPEG global interrupt */
.word VENC_IRQHandler /* VENC global interrupt */
.word GFXMMU_IRQHandler /* GFXMMU global interrupt */
.word GFXTIM_IRQHandler /* GFXTIM global interrupt */
.word GPU2D_IRQHandler /* GPU2D global interrupt */
.word GPU2D_ER_IRQHandler /* GPU2D global interrupt */
.word ICACHE_IRQHandler /* GPU cache interrupt */
.word HPDMA1_Channel0_IRQHandler /* HPDMA1 Channel 0 interrupt */
.word HPDMA1_Channel1_IRQHandler /* HPDMA1 Channel 1 interrupt */
.word HPDMA1_Channel2_IRQHandler /* HPDMA1 Channel 2 interrupt */
.word HPDMA1_Channel3_IRQHandler /* HPDMA1 Channel 3 interrupt */
.word HPDMA1_Channel4_IRQHandler /* HPDMA1 Channel 4 interrupt */
.word HPDMA1_Channel5_IRQHandler /* HPDMA1 Channel 5 interrupt */
.word HPDMA1_Channel6_IRQHandler /* HPDMA1 Channel 6 interrupt */
.word HPDMA1_Channel7_IRQHandler /* HPDMA1 Channel 7 interrupt */
.word HPDMA1_Channel8_IRQHandler /* HPDMA1 Channel 8 interrupt */
.word HPDMA1_Channel9_IRQHandler /* HPDMA1 Channel 9 interrupt */
.word HPDMA1_Channel10_IRQHandler /* HPDMA1 Channel 10 interrupt */
.word HPDMA1_Channel11_IRQHandler /* HPDMA1 Channel 11 interrupt */
.word HPDMA1_Channel12_IRQHandler /* HPDMA1 Channel 12 interrupt */
.word HPDMA1_Channel13_IRQHandler /* HPDMA1 Channel 13 interrupt */
.word HPDMA1_Channel14_IRQHandler /* HPDMA1 Channel 14 interrupt */
.word HPDMA1_Channel15_IRQHandler /* HPDMA1 Channel 15 interrupt */
.word GPDMA1_Channel0_IRQHandler /* GPDMA1 channel 0 interrupt */
.word GPDMA1_Channel1_IRQHandler /* GPDMA1 channel 1 interrupt */
.word GPDMA1_Channel2_IRQHandler /* GPDMA1 channel 2 interrupt */
.word GPDMA1_Channel3_IRQHandler /* GPDMA1 channel 3 interrupt */
.word GPDMA1_Channel4_IRQHandler /* GPDMA1 channel 4 interrupt */
.word GPDMA1_Channel5_IRQHandler /* GPDMA1 channel 5 interrupt */
.word GPDMA1_Channel6_IRQHandler /* GPDMA1 channel 6 interrupt */
.word GPDMA1_Channel7_IRQHandler /* GPDMA1 channel 7 interrupt */
.word GPDMA1_Channel8_IRQHandler /* GPDMA1 channel 8 interrupt */
.word GPDMA1_Channel9_IRQHandler /* GPDMA1 channel 9 interrupt */
.word GPDMA1_Channel10_IRQHandler /* GPDMA1 channel 10 interrupt */
.word GPDMA1_Channel11_IRQHandler /* GPDMA1 channel 11 interrupt */
.word GPDMA1_Channel12_IRQHandler /* GPDMA1 channel 12 interrupt */
.word GPDMA1_Channel13_IRQHandler /* GPDMA1 channel 13 interrupt */
.word GPDMA1_Channel14_IRQHandler /* GPDMA1 channel 14 interrupt */
.word GPDMA1_Channel15_IRQHandler /* GPDMA1 channel 15 interrupt */
.word I2C1_EV_IRQHandler /* I2C1 event interrupt */
.word I2C1_ER_IRQHandler /* I2C1 error interrupt */
.word I2C2_EV_IRQHandler /* I2C2 event interrupt */
.word I2C2_ER_IRQHandler /* I2C2 error interrupt */
.word I2C3_EV_IRQHandler /* I2C3 event interrupt */
.word I2C3_ER_IRQHandler /* I2C3 error interrupt */
.word I2C4_EV_IRQHandler /* I2C4 event interrupt */
.word I2C4_ER_IRQHandler /* I2C4 error interrupt */
.word I3C1_EV_IRQHandler /* I3C1 event interrupt */
.word I3C1_ER_IRQHandler /* I3C1 error interrupt */
.word I3C2_EV_IRQHandler /* I3C2 event interrupt */
.word I3C2_ER_IRQHandler /* I3C2 error interrupt */
.word TIM1_BRK_IRQHandler /* TIM1 Break interrupt */
.word TIM1_UP_IRQHandler /* TIM1 Update interrupt */
.word TIM1_TRG_CCU_IRQHandler /* TIM1 Trigger and Commutation interrupts */
.word TIM1_CC_IRQHandler /* TIM1 Capture Compare interrupt */
.word TIM2_IRQHandler /* TIM2 global interrupt */
.word TIM3_IRQHandler /* TIM3 global interrupt */
.word TIM4_IRQHandler /* TIM4 global interrupt */
.word TIM5_IRQHandler /* TIM5 global interrupt */
.word TIM6_IRQHandler /* TIM6 Global interrupt */
.word TIM7_IRQHandler /* TIM7 Global interrupt */
.word TIM8_BRK_IRQHandler /* TIM8 Break interrupt */
.word TIM8_UP_IRQHandler /* TIM8 Update interrupt */
.word TIM8_TRG_CCU_IRQHandler /* TIM8 Trigger and Commutation interrupts */
.word TIM8_CC_IRQHandler /* TIM8 Capture Compare interrupt */
.word TIM9_IRQHandler /* TIM9 Global interrupt */
.word TIM10_IRQHandler /* TIM10 Global interrupt */
.word TIM11_IRQHandler /* TIM11 Global interrupt */
.word TIM12_IRQHandler /* TIM12 Global interrupt */
.word TIM13_IRQHandler /* TIM13 Global interrupt */
.word TIM14_IRQHandler /* TIM14 Global interrupt */
.word TIM15_IRQHandler /* TIM15 global interrupt */
.word TIM16_IRQHandler /* TIM16 global interrupt */
.word TIM17_IRQHandler /* TIM17 global interrupt */
.word TIM18_IRQHandler /* TIM18 Global interrupt */
.word LPTIM1_IRQHandler /* LPTIM1 global interrupt */
.word LPTIM2_IRQHandler /* LPTIM2 global interrupt */
.word LPTIM3_IRQHandler /* LPTIM3 global interrupt */
.word LPTIM4_IRQHandler /* LPTIM4 global interrupt */
.word LPTIM5_IRQHandler /* LPTIM5 global interrupt */
.word ADF1_FLT0_IRQHandler /* ADF1 filter 0 global interrupt */
.word MDF1_FLT0_IRQHandler /* MDF global Interrupt for Filter0 */
.word MDF1_FLT1_IRQHandler /* MDF global Interrupt for Filter1 */
.word MDF1_FLT2_IRQHandler /* MDF global Interrupt for Filter2 */
.word MDF1_FLT3_IRQHandler /* MDF global Interrupt for Filter3 */
.word MDF1_FLT4_IRQHandler /* MDF global Interrupt for Filter4 */
.word MDF1_FLT5_IRQHandler /* MDF global Interrupt for Filter5 */
.word SAI1_A_IRQHandler /* SAI1 global interrupt A */
.word SAI1_B_IRQHandler /* SAI1 global interrupt B */
.word SAI2_A_IRQHandler /* SAI2 global interrupt A */
.word SAI2_B_IRQHandler /* SAI2 global interrupt B */
.word SPDIFRX_IRQHandler /* SPDIFRX global interrupt */
.word SPI1_IRQHandler /* SPI1 global interrupt A */
.word SPI2_IRQHandler /* SPI2 global interrupt A */
.word SPI3_IRQHandler /* SPI3 global interrupt A */
.word SPI4_IRQHandler /* SPI4 global interrupt A */
.word SPI5_IRQHandler /* SPI5 global interrupt A */
.word SPI6_IRQHandler /* SPI6 global interrupt A */
.word USART1_IRQHandler /* USART1 Global interrupt */
.word USART2_IRQHandler /* USART2 Global interrupt */
.word USART3_IRQHandler /* USART3 Global interrupt */
.word UART4_IRQHandler /* UART4 Global interrupt */
.word UART5_IRQHandler /* UART5 Global interrupt */
.word USART6_IRQHandler /* USART6 Global interrupt */
.word UART7_IRQHandler /* UART7 Global interrupt */
.word UART8_IRQHandler /* UART8 Global interrupt */
.word UART9_IRQHandler /* UART9 Global interrupt */
.word USART10_IRQHandler /* USART10 Global interrupt */
.word LPUART1_IRQHandler /* LPUART1 global interrupt */
.word XSPI1_IRQHandler /* XSPI1 global interrupt */
.word XSPI2_IRQHandler /* XSPI2 global interrupt */
.word XSPI3_IRQHandler /* XSPI3 global interrupt */
.word FMC_IRQHandler /* FMC global interrupt */
.word SDMMC1_IRQHandler /* SDMMC1 global interrupt */
.word SDMMC2_IRQHandler /* SDMMC2 global interrupt */
.word UCPD1_IRQHandler /* UCPD global interrupt */
.word USB1_OTG_HS_IRQHandler /* USB OTG1 HS global interrupt */
.word USB2_OTG_HS_IRQHandler /* USB OTG2 HS global interrupt */
.word ETH1_IRQHandler /* Ethernet global interrupt */
.word FDCAN1_IT0_IRQHandler /* FDCAN1 interrupt 0 */
.word FDCAN1_IT1_IRQHandler /* FDCAN1 interrupt 1 */
.word FDCAN2_IT0_IRQHandler /* FDCAN2 interrupt 0 */
.word FDCAN2_IT1_IRQHandler /* FDCAN2 interrupt 1 */
.word FDCAN3_IT0_IRQHandler /* FDCAN3 interrupt 0 */
.word FDCAN3_IT1_IRQHandler /* FDCAN3 interrupt 1 */
.word FDCAN_CU_IRQHandler /* Clock calibration unit interrupt line(FDCAN1 only) */
.word MDIOS_IRQHandler /* MDIOS global Interrupt */
.word DCMI_PSSI_IRQHandler /* DCMI/PSSI global interrupt */
.word WAKEUP_PIN_IRQHandler /* Wake-up pin interrupts */
.word CTI_INT0_IRQHandler /* Debug monitor (Cortex-M55 related) */
.word CTI_INT1_IRQHandler /* Debug monitor (Cortex-M55 related) */
.word 0 /* Reserved */
.word LTDC_UP_IRQHandler /* LCD up-layer global interrupt */
.word LTDC_UP_ERR_IRQHandler /* LCD up-layer error interrupt */
/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/
.weak NMI_Handler
.thumb_set NMI_Handler,Default_Handler
.weak HardFault_Handler
.thumb_set HardFault_Handler,Default_Handler
.weak MemManage_Handler
.thumb_set MemManage_Handler,Default_Handler
.weak BusFault_Handler
.thumb_set BusFault_Handler,Default_Handler
.weak UsageFault_Handler
.thumb_set UsageFault_Handler,Default_Handler
.weak SecureFault_Handler
.thumb_set SecureFault_Handler,Default_Handler
.weak SVC_Handler
.thumb_set SVC_Handler,Default_Handler
.weak DebugMon_Handler
.thumb_set DebugMon_Handler,Default_Handler
.weak PendSV_Handler
.thumb_set PendSV_Handler,Default_Handler
.weak SysTick_Handler
.thumb_set SysTick_Handler,Default_Handler
.weak PVD_PVM_IRQHandler
.thumb_set PVD_PVM_IRQHandler,Default_Handler
.weak DTS_IRQHandler
.thumb_set DTS_IRQHandler,Default_Handler
.weak RCC_IRQHandler
.thumb_set RCC_IRQHandler,Default_Handler
.weak LOCKUP_IRQHandler
.thumb_set LOCKUP_IRQHandler,Default_Handler
.weak CACHE_ECC_IRQHandler
.thumb_set CACHE_ECC_IRQHandler,Default_Handler
.weak TCM_ECC_IRQHandler
.thumb_set TCM_ECC_IRQHandler,Default_Handler
.weak BCK_ECC_IRQHandler
.thumb_set BCK_ECC_IRQHandler,Default_Handler
.weak FPU_IRQHandler
.thumb_set FPU_IRQHandler,Default_Handler
.weak RTC_S_IRQHandler
.thumb_set RTC_S_IRQHandler,Default_Handler
.weak TAMP_IRQHandler
.thumb_set TAMP_IRQHandler,Default_Handler
.weak RIFSC_TAMPER_IRQHandler
.thumb_set RIFSC_TAMPER_IRQHandler,Default_Handler
.weak IAC_IRQHandler
.thumb_set IAC_IRQHandler,Default_Handler
.weak RCC_S_IRQHandler
.thumb_set RCC_S_IRQHandler,Default_Handler
.weak RTC_IRQHandler
.thumb_set RTC_IRQHandler,Default_Handler
.weak IWDG_IRQHandler
.thumb_set IWDG_IRQHandler,Default_Handler
.weak WWDG_IRQHandler
.thumb_set WWDG_IRQHandler,Default_Handler
.weak EXTI0_IRQHandler
.thumb_set EXTI0_IRQHandler,Default_Handler
.weak EXTI1_IRQHandler
.thumb_set EXTI1_IRQHandler,Default_Handler
.weak EXTI2_IRQHandler
.thumb_set EXTI2_IRQHandler,Default_Handler
.weak EXTI3_IRQHandler
.thumb_set EXTI3_IRQHandler,Default_Handler
.weak EXTI4_IRQHandler
.thumb_set EXTI4_IRQHandler,Default_Handler
.weak EXTI5_IRQHandler
.thumb_set EXTI5_IRQHandler,Default_Handler
.weak EXTI6_IRQHandler
.thumb_set EXTI6_IRQHandler,Default_Handler
.weak EXTI7_IRQHandler
.thumb_set EXTI7_IRQHandler,Default_Handler
.weak EXTI8_IRQHandler
.thumb_set EXTI8_IRQHandler,Default_Handler
.weak EXTI9_IRQHandler
.thumb_set EXTI9_IRQHandler,Default_Handler
.weak EXTI10_IRQHandler
.thumb_set EXTI10_IRQHandler,Default_Handler
.weak EXTI11_IRQHandler
.thumb_set EXTI11_IRQHandler,Default_Handler
.weak EXTI12_IRQHandler
.thumb_set EXTI12_IRQHandler,Default_Handler
.weak EXTI13_IRQHandler
.thumb_set EXTI13_IRQHandler,Default_Handler
.weak EXTI14_IRQHandler
.thumb_set EXTI14_IRQHandler,Default_Handler
.weak EXTI15_IRQHandler
.thumb_set EXTI15_IRQHandler,Default_Handler
.weak SAES_IRQHandler
.thumb_set SAES_IRQHandler,Default_Handler
.weak CRYP_IRQHandler
.thumb_set CRYP_IRQHandler,Default_Handler
.weak PKA_IRQHandler
.thumb_set PKA_IRQHandler,Default_Handler
.weak HASH_IRQHandler
.thumb_set HASH_IRQHandler,Default_Handler
.weak RNG_IRQHandler
.thumb_set RNG_IRQHandler,Default_Handler
.weak MCE1_IRQHandler
.thumb_set MCE1_IRQHandler,Default_Handler
.weak MCE2_IRQHandler
.thumb_set MCE2_IRQHandler,Default_Handler
.weak MCE3_IRQHandler
.thumb_set MCE3_IRQHandler,Default_Handler
.weak MCE4_IRQHandler
.thumb_set MCE4_IRQHandler,Default_Handler
.weak ADC1_2_IRQHandler
.thumb_set ADC1_2_IRQHandler,Default_Handler
.weak CSI_IRQHandler
.thumb_set CSI_IRQHandler,Default_Handler
.weak DCMIPP_IRQHandler
.thumb_set DCMIPP_IRQHandler,Default_Handler
.weak PAHB_ERR_IRQHandler
.thumb_set PAHB_ERR_IRQHandler,Default_Handler
.weak NPU0_IRQHandler
.thumb_set NPU0_IRQHandler,Default_Handler
.weak NPU1_IRQHandler
.thumb_set NPU1_IRQHandler,Default_Handler
.weak NPU2_IRQHandler
.thumb_set NPU2_IRQHandler,Default_Handler
.weak NPU3_IRQHandler
.thumb_set NPU3_IRQHandler,Default_Handler
.weak CACHEAXI_IRQHandler
.thumb_set CACHEAXI_IRQHandler,Default_Handler
.weak LTDC_LO_IRQHandler
.thumb_set LTDC_LO_IRQHandler,Default_Handler
.weak LTDC_LO_ERR_IRQHandler
.thumb_set LTDC_LO_ERR_IRQHandler,Default_Handler
.weak DMA2D_IRQHandler
.thumb_set DMA2D_IRQHandler,Default_Handler
.weak JPEG_IRQHandler
.thumb_set JPEG_IRQHandler,Default_Handler
.weak VENC_IRQHandler
.thumb_set VENC_IRQHandler,Default_Handler
.weak GFXMMU_IRQHandler
.thumb_set GFXMMU_IRQHandler,Default_Handler
.weak GFXTIM_IRQHandler
.thumb_set GFXTIM_IRQHandler,Default_Handler
.weak GPU2D_IRQHandler
.thumb_set GPU2D_IRQHandler,Default_Handler
.weak GPU2D_ER_IRQHandler
.thumb_set GPU2D_ER_IRQHandler,Default_Handler
.weak ICACHE_IRQHandler
.thumb_set ICACHE_IRQHandler,Default_Handler
.weak HPDMA1_Channel0_IRQHandler
.thumb_set HPDMA1_Channel0_IRQHandler,Default_Handler
.weak HPDMA1_Channel1_IRQHandler
.thumb_set HPDMA1_Channel1_IRQHandler,Default_Handler
.weak HPDMA1_Channel2_IRQHandler
.thumb_set HPDMA1_Channel2_IRQHandler,Default_Handler
.weak HPDMA1_Channel3_IRQHandler
.thumb_set HPDMA1_Channel3_IRQHandler,Default_Handler
.weak HPDMA1_Channel4_IRQHandler
.thumb_set HPDMA1_Channel4_IRQHandler,Default_Handler
.weak HPDMA1_Channel5_IRQHandler
.thumb_set HPDMA1_Channel5_IRQHandler,Default_Handler
.weak HPDMA1_Channel6_IRQHandler
.thumb_set HPDMA1_Channel6_IRQHandler,Default_Handler
.weak HPDMA1_Channel7_IRQHandler
.thumb_set HPDMA1_Channel7_IRQHandler,Default_Handler
.weak HPDMA1_Channel8_IRQHandler
.thumb_set HPDMA1_Channel8_IRQHandler,Default_Handler
.weak HPDMA1_Channel9_IRQHandler
.thumb_set HPDMA1_Channel9_IRQHandler,Default_Handler
.weak HPDMA1_Channel10_IRQHandler
.thumb_set HPDMA1_Channel10_IRQHandler,Default_Handler
.weak HPDMA1_Channel11_IRQHandler
.thumb_set HPDMA1_Channel11_IRQHandler,Default_Handler
.weak HPDMA1_Channel12_IRQHandler
.thumb_set HPDMA1_Channel12_IRQHandler,Default_Handler
.weak HPDMA1_Channel13_IRQHandler
.thumb_set HPDMA1_Channel13_IRQHandler,Default_Handler
.weak HPDMA1_Channel14_IRQHandler
.thumb_set HPDMA1_Channel14_IRQHandler,Default_Handler
.weak HPDMA1_Channel15_IRQHandler
.thumb_set HPDMA1_Channel15_IRQHandler,Default_Handler
.weak GPDMA1_Channel0_IRQHandler
.thumb_set GPDMA1_Channel0_IRQHandler,Default_Handler
.weak GPDMA1_Channel1_IRQHandler
.thumb_set GPDMA1_Channel1_IRQHandler,Default_Handler
.weak GPDMA1_Channel2_IRQHandler
.thumb_set GPDMA1_Channel2_IRQHandler,Default_Handler
.weak GPDMA1_Channel3_IRQHandler
.thumb_set GPDMA1_Channel3_IRQHandler,Default_Handler
.weak GPDMA1_Channel4_IRQHandler
.thumb_set GPDMA1_Channel4_IRQHandler,Default_Handler
.weak GPDMA1_Channel5_IRQHandler
.thumb_set GPDMA1_Channel5_IRQHandler,Default_Handler
.weak GPDMA1_Channel6_IRQHandler
.thumb_set GPDMA1_Channel6_IRQHandler,Default_Handler
.weak GPDMA1_Channel7_IRQHandler
.thumb_set GPDMA1_Channel7_IRQHandler,Default_Handler
.weak GPDMA1_Channel8_IRQHandler
.thumb_set GPDMA1_Channel8_IRQHandler,Default_Handler
.weak GPDMA1_Channel9_IRQHandler
.thumb_set GPDMA1_Channel9_IRQHandler,Default_Handler
.weak GPDMA1_Channel10_IRQHandler
.thumb_set GPDMA1_Channel10_IRQHandler,Default_Handler
.weak GPDMA1_Channel11_IRQHandler
.thumb_set GPDMA1_Channel11_IRQHandler,Default_Handler
.weak GPDMA1_Channel12_IRQHandler
.thumb_set GPDMA1_Channel12_IRQHandler,Default_Handler
.weak GPDMA1_Channel13_IRQHandler
.thumb_set GPDMA1_Channel13_IRQHandler,Default_Handler
.weak GPDMA1_Channel14_IRQHandler
.thumb_set GPDMA1_Channel14_IRQHandler,Default_Handler
.weak GPDMA1_Channel15_IRQHandler
.thumb_set GPDMA1_Channel15_IRQHandler,Default_Handler
.weak I2C1_EV_IRQHandler
.thumb_set I2C1_EV_IRQHandler,Default_Handler
.weak I2C1_ER_IRQHandler
.thumb_set I2C1_ER_IRQHandler,Default_Handler
.weak I2C2_EV_IRQHandler
.thumb_set I2C2_EV_IRQHandler,Default_Handler
.weak I2C2_ER_IRQHandler
.thumb_set I2C2_ER_IRQHandler,Default_Handler
.weak I2C3_EV_IRQHandler
.thumb_set I2C3_EV_IRQHandler,Default_Handler
.weak I2C3_ER_IRQHandler
.thumb_set I2C3_ER_IRQHandler,Default_Handler
.weak I2C4_EV_IRQHandler
.thumb_set I2C4_EV_IRQHandler,Default_Handler
.weak I2C4_ER_IRQHandler
.thumb_set I2C4_ER_IRQHandler,Default_Handler
.weak I3C1_EV_IRQHandler
.thumb_set I3C1_EV_IRQHandler,Default_Handler
.weak I3C1_ER_IRQHandler
.thumb_set I3C1_ER_IRQHandler,Default_Handler
.weak I3C2_EV_IRQHandler
.thumb_set I3C2_EV_IRQHandler,Default_Handler
.weak I3C2_ER_IRQHandler
.thumb_set I3C2_ER_IRQHandler,Default_Handler
.weak TIM1_BRK_IRQHandler
.thumb_set TIM1_BRK_IRQHandler,Default_Handler
.weak TIM1_UP_IRQHandler
.thumb_set TIM1_UP_IRQHandler,Default_Handler
.weak TIM1_TRG_CCU_IRQHandler
.thumb_set TIM1_TRG_CCU_IRQHandler,Default_Handler
.weak TIM1_CC_IRQHandler
.thumb_set TIM1_CC_IRQHandler,Default_Handler
.weak TIM2_IRQHandler
.thumb_set TIM2_IRQHandler,Default_Handler
.weak TIM3_IRQHandler
.thumb_set TIM3_IRQHandler,Default_Handler
.weak TIM4_IRQHandler
.thumb_set TIM4_IRQHandler,Default_Handler
.weak TIM5_IRQHandler
.thumb_set TIM5_IRQHandler,Default_Handler
.weak TIM6_IRQHandler
.thumb_set TIM6_IRQHandler,Default_Handler
.weak TIM7_IRQHandler
.thumb_set TIM7_IRQHandler,Default_Handler
.weak TIM8_BRK_IRQHandler
.thumb_set TIM8_BRK_IRQHandler,Default_Handler
.weak TIM8_UP_IRQHandler
.thumb_set TIM8_UP_IRQHandler,Default_Handler
.weak TIM8_TRG_CCU_IRQHandler
.thumb_set TIM8_TRG_CCU_IRQHandler,Default_Handler
.weak TIM8_CC_IRQHandler
.thumb_set TIM8_CC_IRQHandler,Default_Handler
.weak TIM9_IRQHandler
.thumb_set TIM9_IRQHandler,Default_Handler
.weak TIM10_IRQHandler
.thumb_set TIM10_IRQHandler,Default_Handler
.weak TIM11_IRQHandler
.thumb_set TIM11_IRQHandler,Default_Handler
.weak TIM12_IRQHandler
.thumb_set TIM12_IRQHandler,Default_Handler
.weak TIM13_IRQHandler
.thumb_set TIM13_IRQHandler,Default_Handler
.weak TIM14_IRQHandler
.thumb_set TIM14_IRQHandler,Default_Handler
.weak TIM15_IRQHandler
.thumb_set TIM15_IRQHandler,Default_Handler
.weak TIM16_IRQHandler
.thumb_set TIM16_IRQHandler,Default_Handler
.weak TIM17_IRQHandler
.thumb_set TIM17_IRQHandler,Default_Handler
.weak TIM18_IRQHandler
.thumb_set TIM18_IRQHandler,Default_Handler
.weak LPTIM1_IRQHandler
.thumb_set LPTIM1_IRQHandler,Default_Handler
.weak LPTIM2_IRQHandler
.thumb_set LPTIM2_IRQHandler,Default_Handler
.weak LPTIM3_IRQHandler
.thumb_set LPTIM3_IRQHandler,Default_Handler
.weak LPTIM4_IRQHandler
.thumb_set LPTIM4_IRQHandler,Default_Handler
.weak LPTIM5_IRQHandler
.thumb_set LPTIM5_IRQHandler,Default_Handler
.weak ADF1_FLT0_IRQHandler
.thumb_set ADF1_FLT0_IRQHandler,Default_Handler
.weak MDF1_FLT0_IRQHandler
.thumb_set MDF1_FLT0_IRQHandler,Default_Handler
.weak MDF1_FLT1_IRQHandler
.thumb_set MDF1_FLT1_IRQHandler,Default_Handler
.weak MDF1_FLT2_IRQHandler
.thumb_set MDF1_FLT2_IRQHandler,Default_Handler
.weak MDF1_FLT3_IRQHandler
.thumb_set MDF1_FLT3_IRQHandler,Default_Handler
.weak MDF1_FLT4_IRQHandler
.thumb_set MDF1_FLT4_IRQHandler,Default_Handler
.weak MDF1_FLT5_IRQHandler
.thumb_set MDF1_FLT5_IRQHandler,Default_Handler
.weak SAI1_A_IRQHandler
.thumb_set SAI1_A_IRQHandler,Default_Handler
.weak SAI1_B_IRQHandler
.thumb_set SAI1_B_IRQHandler,Default_Handler
.weak SAI2_A_IRQHandler
.thumb_set SAI2_A_IRQHandler,Default_Handler
.weak SAI2_B_IRQHandler
.thumb_set SAI2_B_IRQHandler,Default_Handler
.weak SPDIFRX_IRQHandler
.thumb_set SPDIFRX_IRQHandler,Default_Handler
.weak SPI1_IRQHandler
.thumb_set SPI1_IRQHandler,Default_Handler
.weak SPI2_IRQHandler
.thumb_set SPI2_IRQHandler,Default_Handler
.weak SPI3_IRQHandler
.thumb_set SPI3_IRQHandler,Default_Handler
.weak SPI4_IRQHandler
.thumb_set SPI4_IRQHandler,Default_Handler
.weak SPI5_IRQHandler
.thumb_set SPI5_IRQHandler,Default_Handler
.weak SPI6_IRQHandler
.thumb_set SPI6_IRQHandler,Default_Handler
.weak USART1_IRQHandler
.thumb_set USART1_IRQHandler,Default_Handler
.weak USART2_IRQHandler
.thumb_set USART2_IRQHandler,Default_Handler
.weak USART3_IRQHandler
.thumb_set USART3_IRQHandler,Default_Handler
.weak UART4_IRQHandler
.thumb_set UART4_IRQHandler,Default_Handler
.weak UART5_IRQHandler
.thumb_set UART5_IRQHandler,Default_Handler
.weak USART6_IRQHandler
.thumb_set USART6_IRQHandler,Default_Handler
.weak UART7_IRQHandler
.thumb_set UART7_IRQHandler,Default_Handler
.weak UART8_IRQHandler
.thumb_set UART8_IRQHandler,Default_Handler
.weak UART9_IRQHandler
.thumb_set UART9_IRQHandler,Default_Handler
.weak USART10_IRQHandler
.thumb_set USART10_IRQHandler,Default_Handler
.weak LPUART1_IRQHandler
.thumb_set LPUART1_IRQHandler,Default_Handler
.weak XSPI1_IRQHandler
.thumb_set XSPI1_IRQHandler,Default_Handler
.weak XSPI2_IRQHandler
.thumb_set XSPI2_IRQHandler,Default_Handler
.weak XSPI3_IRQHandler
.thumb_set XSPI3_IRQHandler,Default_Handler
.weak FMC_IRQHandler
.thumb_set FMC_IRQHandler,Default_Handler
.weak SDMMC1_IRQHandler
.thumb_set SDMMC1_IRQHandler,Default_Handler
.weak SDMMC2_IRQHandler
.thumb_set SDMMC2_IRQHandler,Default_Handler
.weak UCPD1_IRQHandler
.thumb_set UCPD1_IRQHandler,Default_Handler
.weak USB1_OTG_HS_IRQHandler
.thumb_set USB1_OTG_HS_IRQHandler,Default_Handler
.weak USB2_OTG_HS_IRQHandler
.thumb_set USB2_OTG_HS_IRQHandler,Default_Handler
.weak ETH1_IRQHandler
.thumb_set ETH1_IRQHandler,Default_Handler
.weak FDCAN1_IT0_IRQHandler
.thumb_set FDCAN1_IT0_IRQHandler,Default_Handler
.weak FDCAN1_IT1_IRQHandler
.thumb_set FDCAN1_IT1_IRQHandler,Default_Handler
.weak FDCAN2_IT0_IRQHandler
.thumb_set FDCAN2_IT0_IRQHandler,Default_Handler
.weak FDCAN2_IT1_IRQHandler
.thumb_set FDCAN2_IT1_IRQHandler,Default_Handler
.weak FDCAN3_IT0_IRQHandler
.thumb_set FDCAN3_IT0_IRQHandler,Default_Handler
.weak FDCAN3_IT1_IRQHandler
.thumb_set FDCAN3_IT1_IRQHandler,Default_Handler
.weak FDCAN_CU_IRQHandler
.thumb_set FDCAN_CU_IRQHandler,Default_Handler
.weak MDIOS_IRQHandler
.thumb_set MDIOS_IRQHandler,Default_Handler
.weak DCMI_PSSI_IRQHandler
.thumb_set DCMI_PSSI_IRQHandler,Default_Handler
.weak WAKEUP_PIN_IRQHandler
.thumb_set WAKEUP_PIN_IRQHandler,Default_Handler
.weak CTI_INT0_IRQHandler
.thumb_set CTI_INT0_IRQHandler,Default_Handler
.weak CTI_INT1_IRQHandler
.thumb_set CTI_INT1_IRQHandler,Default_Handler
.weak LTDC_UP_IRQHandler
.thumb_set LTDC_UP_IRQHandler,Default_Handler
.weak LTDC_UP_ERR_IRQHandler
.thumb_set LTDC_UP_ERR_IRQHandler,Default_Handler
.weak SystemInit
/************************ (C) COPYRIGHT STMicroelectonics *****END OF FILE****/
@@ -0,0 +1,85 @@
/*
* HAL configuration for the Betaflight N6 OpenBootloader. Mirrors the FSBL
* stub's trimmed config and adds IWDG (boot watchdog armed before BF jump)
* and PCD (USB device for the DFU loop).
*/
#ifndef STM32N6xx_HAL_CONF_H
#define STM32N6xx_HAL_CONF_H
#ifdef __cplusplus
extern "C" {
#endif
#define HAL_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED
#define HAL_RCC_MODULE_ENABLED
#define HAL_PWR_MODULE_ENABLED
#define HAL_GPIO_MODULE_ENABLED
#define HAL_DMA_MODULE_ENABLED
#define HAL_XSPI_MODULE_ENABLED
#define HAL_IWDG_MODULE_ENABLED
#define HAL_PCD_MODULE_ENABLED
#if !defined(HSE_VALUE)
#define HSE_VALUE 48000000UL
#endif
#if !defined(HSE_STARTUP_TIMEOUT)
#define HSE_STARTUP_TIMEOUT 100UL
#endif
#if !defined(LSE_VALUE)
#define LSE_VALUE 32768UL
#endif
#if !defined(LSE_STARTUP_TIMEOUT)
#define LSE_STARTUP_TIMEOUT 5000UL
#endif
#if !defined(MSI_VALUE)
#define MSI_VALUE 4000000UL
#endif
#if !defined(HSI_VALUE)
#define HSI_VALUE 64000000UL
#endif
#if !defined(LSI_VALUE)
#define LSI_VALUE 32000UL
#endif
#define VDD_VALUE 3300UL
#define TICK_INT_PRIORITY 15U
#define USE_RTOS 0U
#define USE_HAL_XSPI_REGISTER_CALLBACKS 0U
#define USE_HAL_PWR_REGISTER_CALLBACKS 0U
#define USE_SPI_CRC 0U
#ifdef HAL_RCC_MODULE_ENABLED
#include "stm32n6xx_hal_rcc.h"
#endif
#ifdef HAL_GPIO_MODULE_ENABLED
#include "stm32n6xx_hal_gpio.h"
#endif
#ifdef HAL_CORTEX_MODULE_ENABLED
#include "stm32n6xx_hal_cortex.h"
#endif
#ifdef HAL_PWR_MODULE_ENABLED
#include "stm32n6xx_hal_pwr.h"
#endif
#ifdef HAL_DMA_MODULE_ENABLED
#include "stm32n6xx_hal_dma.h"
#endif
#ifdef HAL_XSPI_MODULE_ENABLED
#include "stm32n6xx_hal_xspi.h"
#endif
#ifdef HAL_IWDG_MODULE_ENABLED
#include "stm32n6xx_hal_iwdg.h"
#endif
#ifdef HAL_PCD_MODULE_ENABLED
#include "stm32n6xx_hal_pcd.h"
#endif
#define assert_param(expr) ((void)0U)
#ifdef __cplusplus
}
#endif
#endif /* STM32N6xx_HAL_CONF_H */
+45
View File
@@ -0,0 +1,45 @@
/*
* Minimal HAL MSP for the FSBL stub. Only XSPI2's MSP is non-trivial: it
* enables the peripheral + XSPIM + GPION clocks and configures the eleven
* GPIO N pins for XSPIM_P2. The boot ROM already picked a working XSPI2
* clock source; we don't reconfigure it. 1S-1S-1S READ at the boot-ROM rate
* is sufficient for XIP and works regardless of HSLV fuse state, so no BSEC
* read is needed.
*/
#include "main.h"
void HAL_MspInit(void)
{
HAL_PWREx_EnableVddIO3();
}
void HAL_XSPI_MspInit(XSPI_HandleTypeDef *hxspi)
{
if (hxspi->Instance != XSPI2) {
return;
}
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* PWR clock is left on by the boot ROM. __HAL_RCC_PWR_CLK_ENABLE
* lives behind CPU_IN_SECURE_STATE in stm32n6xx_hal_rcc.h, which pulls
* in HAL_MPU_*_NS variants that require -mcmse. */
HAL_PWREx_EnableVddIO3();
HAL_PWREx_ConfigVddIORange(PWR_VDDIO3, PWR_VDDIO_RANGE_1V8);
__HAL_RCC_XSPIM_CLK_ENABLE();
__HAL_RCC_XSPI2_CLK_ENABLE();
__HAL_RCC_GPION_CLK_ENABLE();
/* PN0 -> DQS0, PN1 -> NCS1, PN2..PN5 -> IO0..IO3, PN6 -> CLK,
* PN8..PN11 -> IO4..IO7. AF9 = XSPIM_P2 on every pin in this bank. */
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3
| GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_8
| GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_XSPIM_P2;
HAL_GPIO_Init(GPION, &GPIO_InitStruct);
}
+13
View File
@@ -0,0 +1,13 @@
/*
* Interrupt handlers for the FSBL stub. Only SysTick is wired up — HAL_Init
* starts a 1 ms tick that HAL_XSPI_Command and friends use for timeout
* accounting. Everything else stays defaulted to the spin-loops in the
* startup file (the FSBL doesn't enable any interrupts beyond SysTick).
*/
#include "main.h"
void SysTick_Handler(void)
{
HAL_IncTick();
}
@@ -0,0 +1,430 @@
/*
* CMSIS system file for the Betaflight N6 OpenBootloader.
*
* Built with -mcmse so __ARM_FEATURE_CMSE=3 and the CMSIS headers resolve
* every peripheral pointer (RCC, RIFSC, GPIOx, DBGMCU, TAMP, PWR, RTC,
* EXTI, GPDMA1, HPDMA1, RNG, SYSCFG, ...) to its secure-alias base. OBL
* is the only S-state code on the device, so all of its peripheral
* accesses must go through the secure aliases.
*
* SystemInit opens every relevant slave + master so the NS application
* can reach RAM, XSPI memory-mapped flash and AHB/APB peripherals after
* the BXNS hand-off in jump_to_bf().
*
* SystemClock_Config_Impl is called from main() to lift the CPU from
* the boot-ROM hand-off clock (HSI/4 ≈ 16 MHz) to 400 MHz so USB OTG_HS
* gets a clean SOF stream when the OBL DFU branch runs.
*/
#include "stm32n6xx.h"
#include <math.h>
#if !defined(HSE_VALUE)
#define HSE_VALUE 48000000UL
#endif
#if !defined(HSI_VALUE)
#define HSI_VALUE 64000000UL
#endif
#if !defined(MSI_VALUE)
#define MSI_VALUE 4000000UL
#endif
#if !defined(EXTERNAL_I2S_CLOCK_VALUE)
#define EXTERNAL_I2S_CLOCK_VALUE 12288000UL
#endif
uint32_t SystemCoreClock = HSI_VALUE;
extern void *g_pfnVectors;
#define INTVECT_START ((uint32_t)&g_pfnVectors)
void SystemInit(void)
{
SCB->VTOR = INTVECT_START;
/* Caches stay off throughout OBL: the boot decision reads XSPI flash
* and we want fresh fetches; BF re-enables caches in its own startup. */
SCB_DisableICache();
SCB_InvalidateICache();
SCB_DisableDCache();
SCB_InvalidateDCache();
/* Debug subsystem clock. */
RCC->MISCENSR = RCC_MISCENSR_DBGENS;
(void)RCC->MISCENR;
/* APB3 bus clock — DBGMCU lives on APB3 and accesses there read 0 /
* silently drop until the bus is ungated. */
RCC->BUSENSR = RCC_BUSENSR_APB3ENS;
(void)RCC->BUSENR;
/* RIFSC: every peripheral slave NS+Unpriv, debug-AP master CID 0. */
for (uint32_t i = 0; i < 6U; i++) {
RIFSC->RISC_SECCFGRx[i] = 0;
RIFSC->RISC_PRIVCFGRx[i] = 0;
}
RIFSC->RIMC_CR &= ~0x7UL;
(void)RIFSC->RIMC_CR;
/* DBGMCU.CR: hold the M55 debuggable across run / sleep / stop /
* standby so SWD attach to running user code works on OPEN-lifecycle
* silicon. */
DBGMCU->CR = DBGMCU_CR_DBGCLKEN
| DBGMCU_CR_DBG_SLEEP
| DBGMCU_CR_DBG_STOP
| DBGMCU_CR_DBG_STANDBY;
(void)DBGMCU->CR;
/* TAMP is RIF-aware: its security/priv gating lives in its own
* SECCFGR/PRIVCFGR (not RIFSC). Opening to NS+Unpriv exposes all
* 32 BKPxR for read/write from NS world. */
TAMP->SECCFGR = 0;
TAMP->PRIVCFGR = 0;
(void)TAMP->SECCFGR;
/* GPIO SECCFGR + PRIVCFGR per port — every pin NS + Unpriv. Without
* these the NS application's pinmux for SPI / I2C / UART / etc. is
* silently rejected even with RIFSC opened. The bank-enable on
* AHB4ENSR has to happen first: writes to a GPIO bank with its
* clock gated off silently drop on the N6, so the SECCFGR loop
* below would no-op for any port the boot ROM hadn't already
* ungated. */
RCC->AHB4ENSR = RCC_AHB4ENR_GPIOAEN_Msk | RCC_AHB4ENR_GPIOBEN_Msk
| RCC_AHB4ENR_GPIOCEN_Msk | RCC_AHB4ENR_GPIODEN_Msk
| RCC_AHB4ENR_GPIOEEN_Msk | RCC_AHB4ENR_GPIOFEN_Msk
| RCC_AHB4ENR_GPIOGEN_Msk | RCC_AHB4ENR_GPIOHEN_Msk
| RCC_AHB4ENR_GPIONEN_Msk | RCC_AHB4ENR_GPIOOEN_Msk
| RCC_AHB4ENR_GPIOPEN_Msk | RCC_AHB4ENR_GPIOQEN_Msk;
(void)RCC->AHB4ENR;
static GPIO_TypeDef * const gpio_banks[] = {
GPIOA, GPIOB, GPIOC, GPIOD,
GPIOE, GPIOF, GPIOG, GPIOH,
GPION, GPIOO, GPIOP, GPIOQ,
};
for (unsigned i = 0; i < sizeof(gpio_banks) / sizeof(gpio_banks[0]); i++) {
gpio_banks[i]->SECCFGR = 0;
gpio_banks[i]->PRIVCFGR = 0;
}
/* GPDMA1 + HPDMA1: clock-enable first (boot ROM hands them off
* ungated) then open per-controller SECCFGR + PRIVCFGR. SECCFGR is
* a bit-per-channel register at controller +0x00; writing 0 routes
* every channel under NS attribution. */
RCC->AHB1ENSR = RCC_AHB1ENSR_GPDMA1ENS;
RCC->AHB5ENSR = RCC_AHB5ENSR_HPDMA1ENS;
GPDMA1->SECCFGR = 0;
GPDMA1->PRIVCFGR = 0;
HPDMA1->SECCFGR = 0;
HPDMA1->PRIVCFGR = 0;
/* Per-peripheral SECCFGR + PRIVCFGR — peripherals carrying their
* own security config above RIFSC. Clearing each = NS + Unpriv. */
RCC->SECCFGR0 = 0;
RCC->SECCFGR1 = 0;
RCC->SECCFGR2 = 0;
RCC->SECCFGR3 = 0;
RCC->SECCFGR4 = 0;
PWR->SECCFGR = 0;
PWR->PRIVCFGR = 0;
EXTI->SECCFGR1 = 0;
EXTI->PRIVCFGR1 = 0;
EXTI->SECCFGR2 = 0;
EXTI->PRIVCFGR2 = 0;
EXTI->SECCFGR3 = 0;
EXTI->PRIVCFGR3 = 0;
RTC->SECCFGR = 0;
/* RIMC master attributes — every bus master (CPU, DMAs, USB, ETH, …)
* presents as NS + Unpriv + CID 0 by default. */
for (unsigned i = 0; i < 13U; i++) {
RIFSC->RIMC_ATTRx[i] = 0;
}
/* RNG reset + clock-disable. The boot ROM may have left it ticking. */
RCC->AHB3RSTSR = RCC_AHB3RSTSR_RNGRSTS;
RCC->AHB3RSTCR = RCC_AHB3RSTCR_RNGRSTC;
RCC->AHB3ENCR = RCC_AHB3ENCR_RNGENC;
/* SYSCFG clock + VDDIOx supply rails (errata ES0620). */
RCC->APB4ENSR2 = RCC_APB4ENSR2_SYSCFGENS;
(void)RCC->APB4ENR2;
SYSCFG->INITSVTORCR = SCB->VTOR;
PWR->SVMCR1 |= PWR_SVMCR1_VDDIO4SV;
PWR->SVMCR2 |= PWR_SVMCR2_VDDIO5SV;
PWR->SVMCR3 |= PWR_SVMCR3_VDDIO2SV | PWR_SVMCR3_VDDIO3SV;
SYSCFG->VDDIO2CCCR = 0x00000287UL;
SYSCFG->VDDIO3CCCR = 0x00000287UL;
SYSCFG->VDDIO4CCCR = 0x00000287UL;
SYSCFG->VDDIO5CCCR = 0x00000287UL;
SYSCFG->VDDCCCR = 0x00000287UL;
/* VDDADC clamp + VREF buffer. */
PWR->SVMCR3 |= PWR_SVMCR3_ASV;
PWR->SVMCR3 |= PWR_SVMCR3_AVMEN;
(void)PWR->SVMCR3;
RCC->APB4ENR1 |= RCC_APB4ENR1_VREFBUFEN;
/* Pulse APB4ENR2 bit 4 — required by ST's reference bring-up to
* lower power; no documented register name. */
RCC->APB4ENR2 |= 0x00000010UL;
(void)RCC->APB4ENR2;
RCC->APB4ENR2 &= ~0x00000010UL;
/* LSI on. IWDG is clocked from LSI exclusively; without this the
* watchdog hardware never ticks and a wedged BF never resets back
* into OBL recovery. Boot ROM doesn't reliably leave LSI on. */
RCC->CSR = RCC_CSR_LSIONS;
while ((RCC->SR & RCC_SR_LSIRDY) == 0U) {
;
}
/* Reset XSPI2 + XSPIM so we start from a known state regardless of how
* boot ROM left them after loading us. */
RCC->AHB5RSTSR = RCC_AHB5RSTSR_XSPIMRSTS | RCC_AHB5RSTSR_XSPI2RSTS;
RCC->AHB5RSTCR = RCC_AHB5RSTCR_XSPIMRSTC | RCC_AHB5RSTCR_XSPI2RSTC;
/* TIM2 reset + clock-disable. */
RCC->APB1RSTSR1 = RCC_APB1RSTSR1_TIM2RSTS;
RCC->APB1RSTCR1 = RCC_APB1RSTCR1_TIM2RSTC;
RCC->APB1ENCR1 = RCC_APB1ENCR1_TIM2ENC;
/* Enable AXISRAM1..6 clocks. Boot ROM only clocks AXISRAM2; the rest
* must be brought up here or the first store from a consumer of
* those banks busfaults silently. */
RCC->MEMENSR = RCC_MEMENSR_AXISRAM1ENS | RCC_MEMENSR_AXISRAM2ENS
| RCC_MEMENSR_AXISRAM3ENS | RCC_MEMENSR_AXISRAM4ENS
| RCC_MEMENSR_AXISRAM5ENS | RCC_MEMENSR_AXISRAM6ENS;
(void)RCC->MEMENR;
(void)SYSCFG->INITSVTORCR;
RCC->APB4ENCR2 = RCC_APB4ENCR2_SYSCFGENC;
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 20U) | (3UL << 22U)); /* CP10/CP11 full S access */
#endif
/* NSACR — bits 10/11 grant NS access to CP10/CP11 (FPU + MVE).
* 0x0FFF is the broadest "NS can touch every implemented CP" set;
* the harmless bits 0..7 hand future-proof access. NSACR is
* Secure-only-writable so it has to be set here, before the BXNS
* hand-off. (AIRCR.BFHFNMINS is deferred to jump_to_bf — flipping it
* here routes any S-side fault during OBL's own remaining execution
* to an NS state that doesn't yet have a vector table or stack,
* which deterministically wedges the chip into LOCKUP.) */
SCB->NSACR = 0x00000FFFUL;
}
void SystemCoreClockUpdate(void)
{
uint32_t sysclk = 0;
uint32_t pllm = 0;
uint32_t plln = 0;
uint32_t pllfracn = 0;
uint32_t pllp1 = 0;
uint32_t pllp2 = 0;
uint32_t pllcfgr;
uint32_t pllsource = 0;
uint32_t pllbypass = 0;
uint32_t ic_divider;
float_t pllvco;
switch (RCC->CFGR1 & RCC_CFGR1_CPUSWS) {
case 0:
sysclk = HSI_VALUE >> ((RCC->HSICFGR & RCC_HSICFGR_HSIDIV) >> RCC_HSICFGR_HSIDIV_Pos);
break;
case RCC_CFGR1_CPUSWS_0:
sysclk = (READ_BIT(RCC->MSICFGR, RCC_MSICFGR_MSIFREQSEL) == 0UL) ? MSI_VALUE : 16000000UL;
break;
case RCC_CFGR1_CPUSWS_1:
sysclk = HSE_VALUE;
break;
case (RCC_CFGR1_CPUSWS_1 | RCC_CFGR1_CPUSWS_0):
switch (READ_BIT(RCC->IC1CFGR, RCC_IC1CFGR_IC1SEL)) {
case 0:
pllcfgr = READ_REG(RCC->PLL1CFGR1);
pllsource = pllcfgr & RCC_PLL1CFGR1_PLL1SEL;
pllbypass = pllcfgr & RCC_PLL1CFGR1_PLL1BYP;
if (pllbypass == 0U) {
pllm = (pllcfgr & RCC_PLL1CFGR1_PLL1DIVM) >> RCC_PLL1CFGR1_PLL1DIVM_Pos;
plln = (pllcfgr & RCC_PLL1CFGR1_PLL1DIVN) >> RCC_PLL1CFGR1_PLL1DIVN_Pos;
pllfracn = READ_BIT(RCC->PLL1CFGR2, RCC_PLL1CFGR2_PLL1DIVNFRAC) >> RCC_PLL1CFGR2_PLL1DIVNFRAC_Pos;
pllcfgr = READ_REG(RCC->PLL1CFGR3);
pllp1 = (pllcfgr & RCC_PLL1CFGR3_PLL1PDIV1) >> RCC_PLL1CFGR3_PLL1PDIV1_Pos;
pllp2 = (pllcfgr & RCC_PLL1CFGR3_PLL1PDIV2) >> RCC_PLL1CFGR3_PLL1PDIV2_Pos;
}
break;
case RCC_IC1CFGR_IC1SEL_0:
pllcfgr = READ_REG(RCC->PLL2CFGR1);
pllsource = pllcfgr & RCC_PLL2CFGR1_PLL2SEL;
pllbypass = pllcfgr & RCC_PLL2CFGR1_PLL2BYP;
if (pllbypass == 0U) {
pllm = (pllcfgr & RCC_PLL2CFGR1_PLL2DIVM) >> RCC_PLL2CFGR1_PLL2DIVM_Pos;
plln = (pllcfgr & RCC_PLL2CFGR1_PLL2DIVN) >> RCC_PLL2CFGR1_PLL2DIVN_Pos;
pllfracn = READ_BIT(RCC->PLL2CFGR2, RCC_PLL2CFGR2_PLL2DIVNFRAC) >> RCC_PLL2CFGR2_PLL2DIVNFRAC_Pos;
pllcfgr = READ_REG(RCC->PLL2CFGR3);
pllp1 = (pllcfgr & RCC_PLL2CFGR3_PLL2PDIV1) >> RCC_PLL2CFGR3_PLL2PDIV1_Pos;
pllp2 = (pllcfgr & RCC_PLL2CFGR3_PLL2PDIV2) >> RCC_PLL2CFGR3_PLL2PDIV2_Pos;
}
break;
case RCC_IC1CFGR_IC1SEL_1:
pllcfgr = READ_REG(RCC->PLL3CFGR1);
pllsource = pllcfgr & RCC_PLL3CFGR1_PLL3SEL;
pllbypass = pllcfgr & RCC_PLL3CFGR1_PLL3BYP;
if (pllbypass == 0U) {
pllm = (pllcfgr & RCC_PLL3CFGR1_PLL3DIVM) >> RCC_PLL3CFGR1_PLL3DIVM_Pos;
plln = (pllcfgr & RCC_PLL3CFGR1_PLL3DIVN) >> RCC_PLL3CFGR1_PLL3DIVN_Pos;
pllfracn = READ_BIT(RCC->PLL3CFGR2, RCC_PLL3CFGR2_PLL3DIVNFRAC) >> RCC_PLL3CFGR2_PLL3DIVNFRAC_Pos;
pllcfgr = READ_REG(RCC->PLL3CFGR3);
pllp1 = (pllcfgr & RCC_PLL3CFGR3_PLL3PDIV1) >> RCC_PLL3CFGR3_PLL3PDIV1_Pos;
pllp2 = (pllcfgr & RCC_PLL3CFGR3_PLL3PDIV2) >> RCC_PLL3CFGR3_PLL3PDIV2_Pos;
}
break;
default:
pllcfgr = READ_REG(RCC->PLL4CFGR1);
pllsource = pllcfgr & RCC_PLL4CFGR1_PLL4SEL;
pllbypass = pllcfgr & RCC_PLL4CFGR1_PLL4BYP;
if (pllbypass == 0U) {
pllm = (pllcfgr & RCC_PLL4CFGR1_PLL4DIVM) >> RCC_PLL4CFGR1_PLL4DIVM_Pos;
plln = (pllcfgr & RCC_PLL4CFGR1_PLL4DIVN) >> RCC_PLL4CFGR1_PLL4DIVN_Pos;
pllfracn = READ_BIT(RCC->PLL4CFGR2, RCC_PLL4CFGR2_PLL4DIVNFRAC) >> RCC_PLL4CFGR2_PLL4DIVNFRAC_Pos;
pllcfgr = READ_REG(RCC->PLL4CFGR3);
pllp1 = (pllcfgr & RCC_PLL4CFGR3_PLL4PDIV1) >> RCC_PLL4CFGR3_PLL4PDIV1_Pos;
pllp2 = (pllcfgr & RCC_PLL4CFGR3_PLL4PDIV2) >> RCC_PLL4CFGR3_PLL4PDIV2_Pos;
}
break;
}
switch (pllsource) {
case 0:
sysclk = HSI_VALUE >> ((RCC->HSICFGR & RCC_HSICFGR_HSIDIV) >> RCC_HSICFGR_HSIDIV_Pos);
break;
case RCC_PLL1CFGR1_PLL1SEL_0:
sysclk = (READ_BIT(RCC->MSICFGR, RCC_MSICFGR_MSIFREQSEL) == 0UL) ? MSI_VALUE : 16000000UL;
break;
case RCC_PLL1CFGR1_PLL1SEL_1:
sysclk = HSE_VALUE;
break;
case (RCC_PLL1CFGR1_PLL1SEL_1 | RCC_PLL1CFGR1_PLL1SEL_0):
sysclk = EXTERNAL_I2S_CLOCK_VALUE;
break;
default:
break;
}
if (pllbypass == 0U) {
pllvco = ((float_t)sysclk * ((float_t)plln + ((float_t)pllfracn / (float_t)0x1000000UL))) / (float_t)pllm;
sysclk = (uint32_t)((float_t)(pllvco / (((float_t)pllp1) * ((float_t)pllp2))));
}
ic_divider = (READ_BIT(RCC->IC1CFGR, RCC_IC1CFGR_IC1INT) >> RCC_IC1CFGR_IC1INT_Pos) + 1UL;
sysclk = sysclk / ic_divider;
break;
default:
break;
}
SystemCoreClock = sysclk;
}
/*
* SystemClock_Config_Impl — bring the CPU up to a USB-friendly speed.
*
* Lifted from CubeN6's OBL reference (Projects/STM32N6570-DK/.../OBL/
* Core/Src/main.c::SystemClock_Config). Targets:
* PLL1 VCO = HSE 48 MHz / PLLM 3 * PLLN 50 = 800 MHz
* PLL1 output = VCO / (PLLP1 1 * PLLP2 1) = 800 MHz
* CPU = IC1 /2 = 400 MHz
* SYSCLK = IC2/IC6/IC11 /2 = 400 MHz (AXI / NPU / AXISRAM3..6)
* HCLK = SYSCLK /2 = 200 MHz
* PCLK1..5 = HCLK / 1 = 200 MHz
* XSPI2 kclk = IC3 = PLL1 / 24 = 33 MHz (sufficient for indirect-mode
* program / erase; XIP reads happen via the same kclk
* once memory-mapped mode is on)
* USB1_OTG_HS = HSE_DIRECT / 2 (handled in usbd_conf.c::HAL_PCD_MspInit)
* USBPHYC_CR.FSEL = 0b010 (24 MHz) — set in HAL_PCD_MspInit
*/
void SystemClock_Config_Impl(void)
{
RCC_OscInitTypeDef osc = {0};
RCC_ClkInitTypeDef clk = {0};
RCC_PeriphCLKInitTypeDef pclk = {0};
/* Boot ROM may hand off with PLL1 already running (it cranks PLL1
* for fast XSPI staging on the FSBL-load path). HAL_RCC_OscConfig
* refuses to reconfigure PLL1 while it's the active source, so
* we tear the clock tree back to defaults first. Caches must be
* disabled across the teardown — boot ROM leaves stale lines, and
* an instruction-prefetch refill during the CPU-clock switch from
* PLL1 to HSI bus-faults and escalates to LOCKUP. */
SCB_DisableICache();
SCB_DisableDCache();
System_DeInit();
osc.OscillatorType = RCC_OSCILLATORTYPE_HSI | RCC_OSCILLATORTYPE_HSE;
osc.HSIState = RCC_HSI_ON;
osc.HSIDiv = RCC_HSI_DIV1;
osc.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
osc.HSEState = RCC_HSE_ON;
osc.PLL1.PLLState = RCC_PLL_ON;
osc.PLL1.PLLSource = RCC_PLLSOURCE_HSE;
osc.PLL1.PLLM = 3;
osc.PLL1.PLLN = 50;
osc.PLL1.PLLP1 = 1;
osc.PLL1.PLLP2 = 1;
osc.PLL1.PLLFractional = 0;
osc.PLL2.PLLState = RCC_PLL_OFF;
osc.PLL3.PLLState = RCC_PLL_OFF;
osc.PLL4.PLLState = RCC_PLL_OFF;
if (HAL_RCC_OscConfig(&osc) != HAL_OK) {
while (1) {}
}
clk.ClockType = RCC_CLOCKTYPE_CPUCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1
| RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_PCLK4
| RCC_CLOCKTYPE_PCLK5;
clk.CPUCLKSource = RCC_CPUCLKSOURCE_IC1;
clk.SYSCLKSource = RCC_SYSCLKSOURCE_IC2_IC6_IC11;
clk.IC1Selection.ClockSelection = RCC_ICCLKSOURCE_PLL1;
clk.IC1Selection.ClockDivider = 2;
clk.IC11Selection.ClockSelection = RCC_ICCLKSOURCE_PLL1;
clk.IC11Selection.ClockDivider = 2;
clk.IC2Selection.ClockSelection = RCC_ICCLKSOURCE_PLL1;
clk.IC2Selection.ClockDivider = 2;
clk.IC6Selection.ClockSelection = RCC_ICCLKSOURCE_PLL1;
clk.IC6Selection.ClockDivider = 2;
clk.AHBCLKDivider = RCC_HCLK_DIV2;
clk.APB1CLKDivider = RCC_APB1_DIV1;
clk.APB2CLKDivider = RCC_APB2_DIV1;
clk.APB4CLKDivider = RCC_APB4_DIV1;
clk.APB5CLKDivider = RCC_APB5_DIV1;
if (HAL_RCC_ClockConfig(&clk) != HAL_OK) {
while (1) {}
}
/* XSPI2 kernel clock from IC3 = PLL1 / 24 = 33 MHz. Slow vs the
* 133 MHz the chip can handle, but reliable for command-mode
* programming and we don't need XIP throughput in OBL. */
pclk.PeriphClockSelection = RCC_PERIPHCLK_XSPI2;
pclk.Xspi2ClockSelection = RCC_XSPI2CLKSOURCE_IC3;
pclk.ICSelection[RCC_IC3].ClockSelection = RCC_ICCLKSOURCE_PLL1;
pclk.ICSelection[RCC_IC3].ClockDivider = 24;
if (HAL_RCCEx_PeriphCLKConfig(&pclk) != HAL_OK) {
while (1) {}
}
__HAL_RCC_XSPI2_CLK_ENABLE();
SystemCoreClockUpdate();
}
+289
View File
@@ -0,0 +1,289 @@
/*
* Override of CubeN6's usbd_conf.c.
*
* The submodule version uses __HAL_RCC_PWR_CLK_ENABLE / GPIOA_CLK_ENABLE
* macros that live behind CPU_IN_SECURE_STATE in stm32n6xx_hal_rcc.h
* (which pull in -mcmse-only HAL_MPU_*_NS variants). Our build doesn't
* define CPU_IN_SECURE_STATE — see feedback_n6_peripheral_alias_cmse.md.
* This file replaces those macro calls with direct RCC->AHB4ENSR writes
* (matching the convention in main.c::tamp_clk_enable and the FSBL
* stub's system_stm32n6xx_fsbl.c).
*
* Everything else is a verbatim copy of the upstream version — the
* PCD↔USBD callback bridges, USBD_LL_* shims around HAL_PCD_*, USB
* device init with embedded HS-PHY in FS speed.
*/
#include "main.h"
#include "usbd_dfu.h"
PCD_HandleTypeDef hpcd_USB_HS;
/* ---------------- IRQ ---------------- */
void USB1_OTG_HS_IRQHandler(void)
{
HAL_PCD_IRQHandler(&hpcd_USB_HS);
}
/* ---------------- PCD MSP ---------------- */
void HAL_PCD_MspInit(PCD_HandleTypeDef *hpcd)
{
if (hpcd->Instance != USB1_OTG_HS) {
return;
}
/* PWR clock — direct register write avoids the CMSE-gated HAL macro. */
SET_BIT(RCC->AHB4ENSR, RCC_AHB4ENSR_PWRENS);
(void)RCC->AHB4ENR;
/* VDD33USB independent USB voltage monitor. The PWREx_ helpers are
* NS-safe (no CMSE-gated macros internally). */
HAL_PWREx_EnableVddUSBVMEN();
while (__HAL_PWR_GET_FLAG(PWR_FLAG_USB33RDY) == 0U) {
;
}
HAL_PWREx_EnableVddUSB();
/* USB1 OTG HS kernel clock from HSE_DIRECT (48 MHz on N6570-DK). */
RCC_PeriphCLKInitTypeDef pclk = {0};
pclk.PeriphClockSelection = RCC_PERIPHCLK_USBOTGHS1;
pclk.UsbOtgHs1ClockSelection = RCC_USBOTGHS1CLKSOURCE_HSE_DIRECT;
if (HAL_RCCEx_PeriphCLKConfig(&pclk) != HAL_OK) {
Error_Handler();
}
/* USB OTG HS PHY1 reference clock from HSE_DIRECT. */
pclk.PeriphClockSelection = RCC_PERIPHCLK_USBPHY1;
pclk.UsbPhy1ClockSelection = RCC_USBPHY1CLKSOURCE_HSE_DIRECT;
if (HAL_RCCEx_PeriphCLKConfig(&pclk) != HAL_OK) {
Error_Handler();
}
/* GPIOA clock — direct register write (DP/DM are PA11/PA12). */
SET_BIT(RCC->AHB4ENSR, RCC_AHB4ENSR_GPIOAENS);
(void)RCC->AHB4ENR;
LL_AHB5_GRP1_ForceReset(RCC_AHB5RSTR_OTG1PHYCTLRST);
__HAL_RCC_USB1_OTG_HS_FORCE_RESET();
__HAL_RCC_USB1_OTG_HS_PHY_FORCE_RESET();
LL_RCC_HSE_SelectHSEDiv2AsDiv2Clock();
LL_AHB5_GRP1_ReleaseReset(RCC_AHB5RSTR_OTG1PHYCTLRST);
__HAL_RCC_USB1_OTG_HS_CLK_ENABLE();
/* USBPHYC FSEL = 0b010 (24 MHz reference). HAL never writes this and
* the default 0b001 silently drops every SETUP packet — see
* feedback_n6_usbphyc_fsel.md. */
USB1_HS_PHYC->USBPHYC_CR &= ~(0x7U << 0x4U);
USB1_HS_PHYC->USBPHYC_CR |= (0x2U << 0x4U);
__HAL_RCC_USB1_OTG_HS_PHY_RELEASE_RESET();
HAL_Delay(1);
__HAL_RCC_USB1_OTG_HS_RELEASE_RESET();
__HAL_RCC_USB1_OTG_HS_PHY_CLK_ENABLE();
HAL_NVIC_SetPriority(USB1_OTG_HS_IRQn, 6, 0);
HAL_NVIC_EnableIRQ(USB1_OTG_HS_IRQn);
}
void HAL_PCD_MspDeInit(PCD_HandleTypeDef *hpcd)
{
if (hpcd->Instance != USB1_OTG_HS) {
return;
}
HAL_PWREx_DisableVddUSBVMEN();
HAL_PWREx_DisableVddUSB();
__HAL_RCC_USB1_OTG_HS_CLK_DISABLE();
__HAL_RCC_USB1_OTG_HS_PHY_CLK_DISABLE();
HAL_NVIC_DisableIRQ(USB1_OTG_HS_IRQn);
}
/* ---------------- PCD → USBD callbacks ---------------- */
void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *hpcd)
{
USBD_LL_SetupStage(hpcd->pData, (uint8_t *)hpcd->Setup);
}
void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
USBD_LL_DataOutStage(hpcd->pData, epnum, hpcd->OUT_ep[epnum].xfer_buff);
}
void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
USBD_LL_DataInStage(hpcd->pData, epnum, hpcd->IN_ep[epnum].xfer_buff);
}
void HAL_PCD_SOFCallback(PCD_HandleTypeDef *hpcd)
{
USBD_LL_SOF(hpcd->pData);
}
void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd)
{
USBD_SpeedTypeDef speed = USBD_SPEED_FULL;
switch (hpcd->Init.speed) {
case PCD_SPEED_HIGH: speed = USBD_SPEED_HIGH; break;
case PCD_SPEED_FULL: speed = USBD_SPEED_FULL; break;
default: speed = USBD_SPEED_FULL; break;
}
USBD_LL_Reset(hpcd->pData);
USBD_LL_SetSpeed(hpcd->pData, speed);
}
void HAL_PCD_SuspendCallback(PCD_HandleTypeDef *hpcd)
{
USBD_LL_Suspend(hpcd->pData);
}
void HAL_PCD_ISOOUTIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
USBD_LL_IsoOUTIncomplete(hpcd->pData, epnum);
}
void HAL_PCD_ISOINIncompleteCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
USBD_LL_IsoINIncomplete(hpcd->pData, epnum);
}
void HAL_PCD_ConnectCallback(PCD_HandleTypeDef *hpcd)
{
USBD_LL_DevConnected(hpcd->pData);
}
void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd)
{
USBD_LL_DevDisconnected(hpcd->pData);
}
/* ---------------- USBD → PCD shims ---------------- */
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
hpcd_USB_HS.Instance = USB1_OTG_HS;
hpcd_USB_HS.Init.dev_endpoints = 3U;
hpcd_USB_HS.Init.speed = PCD_SPEED_HIGH;
hpcd_USB_HS.Init.dma_enable = DISABLE;
hpcd_USB_HS.Init.phy_itface = USB_OTG_HS_EMBEDDED_PHY;
hpcd_USB_HS.Init.Sof_enable = DISABLE;
hpcd_USB_HS.Init.low_power_enable = DISABLE;
hpcd_USB_HS.Init.lpm_enable = DISABLE;
hpcd_USB_HS.Init.vbus_sensing_enable = DISABLE;
hpcd_USB_HS.Init.use_dedicated_ep1 = DISABLE;
hpcd_USB_HS.Init.use_external_vbus = DISABLE;
hpcd_USB_HS.pData = pdev;
pdev->pData = &hpcd_USB_HS;
HAL_PCD_Init(&hpcd_USB_HS);
HAL_PCDEx_SetRxFiFo(&hpcd_USB_HS, 0xA0U);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_HS, 0, 0xA0U);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_DeInit(USBD_HandleTypeDef *pdev)
{
HAL_PCD_DeInit(pdev->pData);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_Start(USBD_HandleTypeDef *pdev)
{
HAL_PCD_Start(pdev->pData);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_Stop(USBD_HandleTypeDef *pdev)
{
HAL_PCD_Stop(pdev->pData);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_OpenEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr,
uint8_t ep_type, uint16_t ep_mps)
{
HAL_PCD_EP_Open(pdev->pData, ep_addr, ep_mps, ep_type);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_CloseEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
HAL_PCD_EP_Close(pdev->pData, ep_addr);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_FlushEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
HAL_PCD_EP_Flush(pdev->pData, ep_addr);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_StallEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
HAL_PCD_EP_SetStall(pdev->pData, ep_addr);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_ClearStallEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
HAL_PCD_EP_ClrStall(pdev->pData, ep_addr);
return USBD_OK;
}
uint8_t USBD_LL_IsStallEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
PCD_HandleTypeDef *hpcd = pdev->pData;
if ((ep_addr & 0x80U) == 0x80U) {
return hpcd->IN_ep[ep_addr & 0x7FU].is_stall;
}
return hpcd->OUT_ep[ep_addr & 0x7FU].is_stall;
}
USBD_StatusTypeDef USBD_LL_SetUSBAddress(USBD_HandleTypeDef *pdev, uint8_t dev_addr)
{
HAL_PCD_SetAddress(pdev->pData, dev_addr);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_Transmit(USBD_HandleTypeDef *pdev, uint8_t ep_addr,
uint8_t *pbuf, uint32_t size)
{
HAL_PCD_EP_Transmit(pdev->pData, ep_addr, pbuf, size);
return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_PrepareReceive(USBD_HandleTypeDef *pdev, uint8_t ep_addr,
uint8_t *pbuf, uint32_t size)
{
HAL_PCD_EP_Receive(pdev->pData, ep_addr, pbuf, size);
return USBD_OK;
}
uint32_t USBD_LL_GetRxDataSize(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
return HAL_PCD_EP_GetRxCount(pdev->pData, ep_addr);
}
void *USBD_static_malloc(uint32_t size)
{
(void)size;
static uint32_t mem[(sizeof(USBD_DFU_HandleTypeDef) / 4U) + 1U];
return mem;
}
void USBD_static_free(void *p)
{
(void)p;
}
void USBD_LL_Delay(uint32_t Delay)
{
HAL_Delay(Delay);
}
+79
View File
@@ -0,0 +1,79 @@
/*
* Override of CubeN6's usbd_conf.h.
*
* The stock OBL exposes 5 DFU alts (Flashlayout / FSBL-EXT / FSBL-APP /
* <user app> / OTP) wired to the OBL middleware's phase machinery
* (PHASE_FLASHLAYOUT / PHASE_3 / PHASE_4 / PHASE_CMD / PHASE_OTP). The
* Betaflight OBL only needs a single memory descriptor — the BF slot at
* 0x70100000 (or nor0 0x0 in recovery mode). Collapsing to one alt does
* three things:
*
* 1. The host shows our @Betaflight memory descriptor as THE name on
* enumeration instead of "@Flashlayout /0x00/1*1Ke".
* 2. dfu-util / STM32CubeProgrammer aim writes at alt 0 by default,
* no `-a 3` needed.
* 3. We can bypass the OBL middleware's alt→phase logic and the
* OPENBL_USB_Download/EraseMemory phase-table machinery — those
* paths are wired for ST's signed-FSBL flow and silently no-op for
* a plain XIP user app at PHASE_CMD.
*
* Pulled in via the Makefile's `-I.` include path which precedes the
* CubeN6 example's USB_Device/Target/.
*/
#ifndef USBD_CONF_H
#define USBD_CONF_H
#include "main.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define USBD_CLASS_USER_STRING_DESC 0U
#define USBD_MAX_NUM_INTERFACES 1U
#define USBD_MAX_NUM_CONFIGURATION 1U
#define USBD_MAX_STR_DESC_SIZ 130U
#define USBD_SUPPORT_USER_STRING_DESC 1U
#define USBD_SELF_POWERED 1U
#define USBD_DEBUG_LEVEL 0U
/* Single DFU alt — @Betaflight (BF flash slot, read/erase/write). */
#define USBD_DFU_MAX_ITF_NUM 1U
#define USBD_DFU_XFER_SIZE 1024U
#define USBD_DFU_APP_DEFAULT_ADD 0x70100000U
#define USBD_DFU_MAX_NB_OF_SECTORS 256U
#define DEVICE_FS 0U
#define USBD_malloc (void *)USBD_static_malloc
#define USBD_free USBD_static_free
#define USBD_memset memset
#define USBD_memcpy memcpy
#define USBD_Delay HAL_Delay
#if (USBD_DEBUG_LEVEL > 0U)
#define USBD_UsrLog(...) printf(__VA_ARGS__); printf("\n");
#else
#define USBD_UsrLog(...)
#endif
#if (USBD_DEBUG_LEVEL > 1U)
#define USBD_ErrLog(...) printf("ERROR: "); printf(__VA_ARGS__); printf("\n");
#else
#define USBD_ErrLog(...)
#endif
#if (USBD_DEBUG_LEVEL > 2U)
#define USBD_DbgLog(...) printf("DEBUG : "); printf(__VA_ARGS__); printf("\n");
#else
#define USBD_DbgLog(...)
#endif
extern PCD_HandleTypeDef hpcd_USB_HS;
void *USBD_static_malloc(uint32_t size);
void USBD_static_free(void *p);
#endif /* USBD_CONF_H */
+146
View File
@@ -0,0 +1,146 @@
/*
* Override of CubeN6's usbd_desc.c — Betaflight-branded USB descriptors.
*
* VID/PID kept as ST DfuSe defaults (0x0483/0xDF11) so dfu-util,
* STM32CubeProgrammer, and BF Configurator's existing DFU plumbing all
* work unchanged. Strings are Betaflight-branded so users see "Betaflight
* N6 OBL" on enumerate.
*/
#include <stdio.h>
#include "usbd_core.h"
#include "usbd_desc.h"
#include "usbd_conf.h"
/* DfuSe protocol default VID/PID. Standard tooling targets this pair. */
#define USBD_VID 0x0483
#define USBD_PID 0xDF11
#define USBD_LANGID_STRING 1033
#define USBD_MANUFACTURER_STRING "Betaflight"
#define USBD_PRODUCT_STRING "Betaflight N6 DFU"
#define USBD_CONFIGURATION_STRING "BF N6 DFU Config"
#define USBD_INTERFACE_STRING "BF N6 DFU Interface"
uint8_t USBD_StrDesc[USBD_MAX_STR_DESC_SIZ];
static void Get_SerialNum(void);
uint8_t *USBD_DFU_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *USBD_DFU_LangIDStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *USBD_DFU_ManufacturerStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *USBD_DFU_ProductStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *USBD_DFU_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *USBD_DFU_ConfigStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *USBD_DFU_InterfaceStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
USBD_DescriptorsTypeDef DFU_Desc =
{
USBD_DFU_DeviceDescriptor,
USBD_DFU_LangIDStrDescriptor,
USBD_DFU_ManufacturerStrDescriptor,
USBD_DFU_ProductStrDescriptor,
USBD_DFU_SerialStrDescriptor,
USBD_DFU_ConfigStrDescriptor,
USBD_DFU_InterfaceStrDescriptor
};
__ALIGN_BEGIN uint8_t USBD_DFU_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
0x12,
USB_DESC_TYPE_DEVICE,
0x10, 0x01, /* USB 1.10 — DfuSe convention */
0x00, 0x00, 0x00,
USB_MAX_EP0_SIZE,
LOBYTE(USBD_VID), HIBYTE(USBD_VID),
LOBYTE(USBD_PID), HIBYTE(USBD_PID),
0x00, 0x02, /* bcdDevice = 2.00 */
USBD_IDX_MFC_STR,
USBD_IDX_PRODUCT_STR,
USBD_IDX_SERIAL_STR,
USBD_MAX_NUM_CONFIGURATION
};
__ALIGN_BEGIN uint8_t USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC] __ALIGN_END =
{
USB_LEN_LANGID_STR_DESC,
USB_DESC_TYPE_STRING,
LOBYTE(USBD_LANGID_STRING),
HIBYTE(USBD_LANGID_STRING)
};
__ALIGN_BEGIN uint8_t USBD_StringSerial[USB_SIZ_STRING_SERIAL] __ALIGN_END =
{
USB_SIZ_STRING_SERIAL,
USB_DESC_TYPE_STRING,
};
uint8_t *USBD_DFU_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
(void)speed;
*length = sizeof(USBD_DFU_DeviceDesc);
return USBD_DFU_DeviceDesc;
}
uint8_t *USBD_DFU_LangIDStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
(void)speed;
*length = sizeof(USBD_LangIDDesc);
return USBD_LangIDDesc;
}
uint8_t *USBD_DFU_ProductStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
(void)speed;
USBD_GetString((uint8_t *)USBD_PRODUCT_STRING, USBD_StrDesc, length);
return USBD_StrDesc;
}
uint8_t *USBD_DFU_ManufacturerStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
(void)speed;
USBD_GetString((uint8_t *)USBD_MANUFACTURER_STRING, USBD_StrDesc, length);
return USBD_StrDesc;
}
uint8_t *USBD_DFU_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
(void)speed;
*length = USB_SIZ_STRING_SERIAL;
Get_SerialNum();
return (uint8_t *)USBD_StringSerial;
}
uint8_t *USBD_DFU_ConfigStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
(void)speed;
USBD_GetString((uint8_t *)USBD_CONFIGURATION_STRING, USBD_StrDesc, length);
return USBD_StrDesc;
}
uint8_t *USBD_DFU_InterfaceStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
(void)speed;
USBD_GetString((uint8_t *)USBD_INTERFACE_STRING, USBD_StrDesc, length);
return USBD_StrDesc;
}
static void Get_SerialNum(void)
{
/* Read the device unique ID from the boot ROM-published location.
* DEVICE_ID1/2/3 are CMSIS macros pointing into system memory. */
unsigned long s0 = *(uint32_t *)DEVICE_ID1;
unsigned long s1 = *(uint32_t *)DEVICE_ID2;
unsigned long s2 = *(uint32_t *)DEVICE_ID3;
char serial_string[SIZ_STRING_SERIAL + 2U];
snprintf(serial_string, sizeof(serial_string), "%08lX%08lX%08lX", s0, s1, s2);
uint8_t idx = 2U;
const char *p = serial_string;
while (*p != '\0' && idx < (USB_SIZ_STRING_SERIAL - 1U)) {
USBD_StringSerial[idx++] = *p++;
USBD_StringSerial[idx++] = 0x00U;
}
}
File diff suppressed because it is too large Load Diff
+238
View File
@@ -0,0 +1,238 @@
/*
* Override of CubeN6's usbd_dfu_if.c — Betaflight DFU memory descriptor.
*
* The DfuSe descriptor string advertises a memory layout to the host
* (start address, sector count, sector size, type code). We expose two
* different layouts depending on which mode obl_app.c entered:
*
* Normal mode (obl_recovery_mode == false):
* @Betaflight /0x70100000/0100*64Kg
* ↑ starts at the BF slot, covers 16 MiB. The OBL slot
* (0x70000000..0x700FFFFF) is NOT exposed, so a host upload
* can't accidentally trash a working OBL.
*
* Recovery mode (obl_recovery_mode == true, nor0 0x0 had no valid
* signed-FSBL header):
* @Betaflight /0x70000000/0800*64Kg
* ↑ starts at nor0 0x0, covers 128 MiB. Used for the bricked-FC
* recovery flow where the host needs to write OBL itself + BF.
*
* 64 KiB sector size matches the MX66UW1G45G 4-byte block-erase opcode
* (0xDC) and minimises DFU round-trip overhead vs the chip's 4 KiB
* sector erase. Type 'g' = read+erase+write.
*
* The pStrDesc pointer in USBD_DFU_Media_fops is updated before
* OpenBootloader_Init() runs (see obl_app.c::run_obl_loop), so by the
* time the host enumerates the DFU descriptor reflects the active mode.
*/
#include <stdbool.h>
#include <string.h>
#include "usbd_dfu_if.h"
#include "openbl_usb_cmd.h"
#include "usb_interface.h"
#include "openbl_mem.h"
#include "common_interface.h"
#include "openbootloader_conf.h"
#include "flash_iface.h"
#define MEDIA_DESC_STR_NORMAL "@Betaflight /0x70100000/0100*064Kg"
#define MEDIA_DESC_STR_RECOVERY "@Betaflight /0x70000000/0800*064Kg"
/* MX66UW1G45G program/erase windows — used by the host's DFU GET_STATUS
* to bound the polling timeout it announces in the device-state machine.
* Generous; real hardware completes faster but the host doesn't care
* about under-reporting. */
#define MEDIA_ERASE_TIME (uint16_t)400U /* ms per 64 KiB block */
#define MEDIA_PROGRAM_TIME (uint16_t)5U /* ms per page */
extern bool obl_recovery_mode;
extern USBD_HandleTypeDef hUsbDeviceFS;
static uint16_t USB_DFU_If_Init(void);
static uint16_t USB_DFU_If_Erase(uint32_t Add);
static uint16_t USB_DFU_If_Write(uint8_t *pSrc, uint32_t alt, uint32_t Len, uint32_t BlockNumber);
static uint8_t *USB_DFU_If_Read(uint32_t alt, uint8_t *pDest, uint32_t Len, uint32_t BlockNumber);
static uint16_t USB_DFU_If_DeInit(void);
static uint16_t USB_DFU_If_GetStatus(uint32_t Add, uint8_t Cmd, uint8_t *buffer);
/* USBD_DFU_Media_fops::pStrDesc is patched at runtime by
* obl_dfu_apply_mode() (called from obl_app.c) before
* OpenBootloader_Init enables USB enumeration. The default points at
* the recovery string so a stray enumeration before mode-set fails safe
* (recovery is the more permissive of the two — better than the host
* trying to write the OBL slot through a normal-mode descriptor that
* doesn't cover it). */
__ALIGN_BEGIN USBD_DFU_MediaTypeDef USBD_DFU_Media_fops __ALIGN_END =
{
(uint8_t *)MEDIA_DESC_STR_RECOVERY,
USB_DFU_If_Init,
USB_DFU_If_DeInit,
USB_DFU_If_Erase,
USB_DFU_If_Write,
USB_DFU_If_Read,
USB_DFU_If_GetStatus
};
void obl_dfu_apply_mode(bool recovery)
{
USBD_DFU_Media_fops.pStrDesc = (uint8_t *)(recovery
? MEDIA_DESC_STR_RECOVERY
: MEDIA_DESC_STR_NORMAL);
}
static uint16_t USB_DFU_If_Init(void)
{
return 0;
}
static uint16_t USB_DFU_If_DeInit(void)
{
return 0;
}
static uint16_t USB_DFU_If_Erase(uint32_t Add)
{
if (OPENBL_MEM_GetAddressArea((uint32_t)Add) == AREA_ERROR) {
return OPENBL_USB_SendAddressNack(&hUsbDeviceFS);
}
/* CubeN6's OPENBL_MEM_Erase is a stub that always returns ERROR
* (Modules/Mem/openbl_mem.c). Bypass the middleware and erase a
* single descriptor sector directly via the flash driver. The DFU
* descriptor advertises EXT_MEMORY_SECTOR_SIZE-byte sectors so the
* host issues exactly one Erase per sector before writing into it. */
if (Add < EXT_MEMORY_START_ADDRESS) {
return 1U;
}
const uint32_t offset = Add - EXT_MEMORY_START_ADDRESS;
if (!flash_erase_range(offset, EXT_MEMORY_SECTOR_SIZE)) {
return 1U;
}
return 0U;
}
/* DfuSe address pointer captured from SET_ADDRESS_POINTER. Defaults
* mirror the descriptor's advertised start so a data block before any
* SET_ADDR still lands at the slot's base. */
static uint32_t dfuse_addr_ptr_normal = 0x70100000U;
static uint32_t dfuse_addr_ptr_recovery = 0x70000000U;
/* Auto-reboot watchdog for DFU downloads. dfu-util without --reset does
* not send the protocol's DFU_DETACH / manifest-leave trigger, so
* DFU_Leave (which sets dfu_leave_pending) never runs and the chip sits
* in DFU forever. Stamp the tick each time we accept a real data block;
* run_obl_loop in obl_app.c watches for idleness after the first write
* and triggers NVIC_SystemReset on its own. 0 means "no download seen
* yet"; obl_app.c only consults the value once it's non-zero. */
volatile uint32_t dfu_last_write_tick;
static uint32_t dfuse_get_base(void)
{
return obl_recovery_mode ? dfuse_addr_ptr_recovery : dfuse_addr_ptr_normal;
}
static uint16_t USB_DFU_If_Write(uint8_t *pSrc, uint32_t alt, uint32_t Len, uint32_t BlockNumber)
{
(void)alt;
/* DfuSe extension commands (bcdDFUVersion=0x011A) arrive as
* BlockNumber=0, wLength==5, command byte at pSrc[0]:
* 0x21 SET_ADDRESS_POINTER pSrc[1..4] = LE target address
* 0x41 ERASE_PAGE pSrc[1..4] = LE sector address
* 0x91 READ_UNPROTECT (ignored)
* 0x92 ERASE_ALL (ignored — host issues per-sector)
*
* The vendored usbd_dfu.c routes every DOWNLOAD here without
* decoding DfuSe, so this is the only point where these sub-
* commands get dispatched. */
if (BlockNumber == 0U && Len >= 5U) {
const uint8_t cmd = pSrc[0];
const uint32_t addr = (uint32_t)pSrc[1]
| ((uint32_t)pSrc[2] << 8)
| ((uint32_t)pSrc[3] << 16)
| ((uint32_t)pSrc[4] << 24);
switch (cmd) {
case 0x21U:
if (obl_recovery_mode) {
dfuse_addr_ptr_recovery = addr;
} else {
dfuse_addr_ptr_normal = addr;
}
return 0U;
case 0x41U: {
if (addr < EXT_MEMORY_START_ADDRESS) {
return 1U;
}
const uint32_t offset = addr - EXT_MEMORY_START_ADDRESS;
if (!flash_erase_range(offset, EXT_MEMORY_SECTOR_SIZE)) {
return 1U;
}
return 0U;
}
default:
return 0U;
}
}
if (BlockNumber < 2U) {
return 0U;
}
/* Block 2+ data writes — addressed relative to the most recent
* SET_ADDRESS_POINTER (or the descriptor default if none arrived). */
const uint32_t addr = dfuse_get_base() + (BlockNumber - 2U) * USBD_DFU_XFER_SIZE;
if (addr < EXT_MEMORY_START_ADDRESS) {
return 1U;
}
const uint32_t offset = addr - EXT_MEMORY_START_ADDRESS;
if (!flash_program(offset, pSrc, Len)) {
return 1U;
}
/* Refresh the auto-reboot timestamp; run_obl_loop reboots if no
* further writes arrive within DFU_IDLE_REBOOT_MS. */
dfu_last_write_tick = HAL_GetTick();
if (dfu_last_write_tick == 0U) {
dfu_last_write_tick = 1U;
}
return 0U;
}
static uint8_t *USB_DFU_If_Read(uint32_t alt, uint8_t *pDest, uint32_t Len, uint32_t BlockNumber)
{
(void)alt;
/* alt 0: memcpy from XSPI memory-mapped flash, anchored at the
* most recent DfuSe SET_ADDRESS_POINTER. flash_memmap_on() is
* idempotent and brings the controller back from indirect mode
* if a prior Write() flipped it off. */
if (BlockNumber < 2U) {
memset(pDest, 0, Len);
return pDest;
}
if (!flash_memmap_on()) {
memset(pDest, 0, Len);
return pDest;
}
const uint32_t addr = dfuse_get_base() + (BlockNumber - 2U) * USBD_DFU_XFER_SIZE;
memcpy(pDest, (const uint8_t *)addr, Len);
return pDest;
}
static uint16_t USB_DFU_If_GetStatus(uint32_t Add, uint8_t Cmd, uint8_t *pBuffer)
{
(void)Add;
switch (Cmd) {
case DFU_MEDIA_PROGRAM:
pBuffer[1] = (uint8_t)(MEDIA_PROGRAM_TIME & 0xFFU);
pBuffer[2] = (uint8_t)((MEDIA_PROGRAM_TIME >> 8U) & 0xFFU);
pBuffer[3] = 0U;
break;
case DFU_MEDIA_ERASE:
default:
pBuffer[1] = (uint8_t)(MEDIA_ERASE_TIME & 0xFFU);
pBuffer[2] = (uint8_t)((MEDIA_ERASE_TIME >> 8U) & 0xFFU);
pBuffer[3] = 0U;
break;
}
return USBD_OK;
}