Files
2026-08-03 16:37:10 +08:00

239 lines
8.6 KiB
C

/*
* 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;
}