Add shared emulator services, waveform generation and STM32 USB boot ports

This commit is contained in:
2026-09-28 11:54:17 +03:00
parent 80ba17d77d
commit b7af25a434
56 changed files with 3627 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
# STM32G474xB USB boot port
SET v2 USB CDC bootloader for STM32G474 with 128 KiB Flash. Uses HSI16 for
the CPU and HSI48 with CRS USB SOF synchronization for USB; no HSE required.
Dedicated USB pins: PA11/PA12. The board must supply VDDUSB correctly.
Compile the shared `src/set_protocol.c`, `src/set_firmware.c`, `src/set_boot.c`,
this port's `boot_main.c`, `boot_flash.c`, `boot_request.c`, `boot_usb_stream.c`
and `boot_usb_port.c`. Supply G4 CMSIS/HAL/startup, ST USB Device CDC and the
board's CDC byte transport (`usb_cdc.h`, `usb_setp_stream.h`).
Copy `boot_config.template.h` as `boot_config.h`. The wrapper uses the common
`../stm32-usb-boot/boot_usb_protocol.inc`; retain that directory when importing.
For the shared emulator CDC transport, set `EMU_USB_SET_V2=1`,
`USB_CDC_IRQn=USB_LP_IRQn` and optionally `USB_CDC_PRODUCT_NAME`.
Provide `usbd_conf.h` using `stm32g4xx_hal.h`. The low-level port allocates
non-overlapping PMA buffers for EP0, CDC IN/OUT and notifications.
Link boot code below `0x0800F000`, reserve `0x0800F000..0x0800FFFF` for metadata,
and link the application at `0x08010000` with VTOR at that address.
Application capacity is 64 KiB, ending at `0x08020000` (exclusive).
Flash size must be 128 KiB and DBANK must be zero (single bank, 4 KiB pages).
BFB2 and FB_MODE bank swapping must be disabled. Unsupported geometry is
rejected before erase. On xB in dual-bank mode the upper 64 KiB starts at
`0x08040000`, leaving a hole at the application's link address. Therefore this
profile deliberately rejects dual-bank mode. Set DBANK=0 via SWD before the
initial installation, then program both bootloader and application afresh.
The port does not modify Option Bytes.
HAL doubleword programming plus an eight-byte staging buffer handles arbitrary
SET block sizes without programming ECC words twice. Only the final word is
padded with FF; CRC covers the declared image size. END checks written Flash
and vectors, commits metadata last; ACTIVATE defers reset by 500 ms for the reply.
The reset policy matches F407: valid vectors alone allow SWD-installed images.
Metadata is not required at reset, so an incomplete image with valid vectors can
also start after an interrupted update. Erase the first application page through
SWD to recover. No rollback, resume or signature authentication is implemented.
Application integration: compile `boot_request.c`, reserve `TAMP->BKP0R`, call
`g474_request_boot(now)` for an update request and `g474_reset_poll(now)` from
the main loop. Reset consumes and clears the backup-register request.
Host test `test_boot_port.c` covers the 64 KiB slot boundaries, geometry rejection, vector validation,
every block size 1..256, all final-word tails, sequential offsets and failures.
Compile it with this directory and `../../include` on the include path.
Hardware USB/Flash/power-loss tests remain required on the target board.

View File

@@ -0,0 +1,5 @@
#ifndef G474_BOOT_CONFIG_H
#define G474_BOOT_CONFIG_H
#define G474_BOOT_NODE 16U
#define G474_BOOT_MODEL "STM32G474RB-USB-BOOT"
#endif

View File

@@ -0,0 +1,109 @@
#include "boot_port.h"
#include "boot_image.h"
#include "boot_writer.h"
#include "stm32g4xx_hal.h"
#include <string.h>
#define COMMIT 0x53455442UL
static g474_writer writer;
static bool geometry_valid(void)
{
__HAL_RCC_SYSCFG_CLK_ENABLE();
return g474_geometry_supported(*(const uint16_t *)FLASHSIZE_BASE,
(FLASH->OPTR & FLASH_OPTR_DBANK) != 0,
(FLASH->OPTR & FLASH_OPTR_BFB2) != 0 ||
(SYSCFG->MEMRMP & SYSCFG_MEMRMP_FB_MODE) != 0);
}
static bool erase_page(uint32_t address)
{
FLASH_EraseInitTypeDef config = {0};
uint32_t bank, page, error;
if (!g474_flash_page(address, (FLASH->OPTR & FLASH_OPTR_DBANK) != 0,
&bank, &page)) return false;
config.TypeErase = FLASH_TYPEERASE_PAGES;
config.Banks = bank == 2U ? FLASH_BANK_2 : FLASH_BANK_1;
config.Page = page; config.NbPages = 1;
return HAL_FLASHEx_Erase(&config, &error) == HAL_OK;
}
static bool invalidate(void *user)
{
bool ok; (void)user;
if (!geometry_valid() || HAL_FLASH_Unlock() != HAL_OK) return false;
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
ok = erase_page(G474_META_BASE);
(void)HAL_FLASH_Lock(); return ok;
}
static bool erase(void *user, uint32_t size)
{
uint32_t address, step;
bool ok = true; (void)user;
if (size < 8 || size > G474_APP_SIZE || !geometry_valid()) return false;
g474_writer_init(&writer, size);
if (HAL_FLASH_Unlock() != HAL_OK) return false;
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
step = (FLASH->OPTR & FLASH_OPTR_DBANK) ? 2048U : 4096U;
for (address = G474_APP_BASE; address < G474_APP_BASE + size; address += step) {
if (!erase_page(address)) { ok = false; break; }
}
(void)HAL_FLASH_Lock(); return ok;
}
static bool program_word(void *user, uint32_t offset, uint64_t word)
{
uint64_t actual; (void)user;
if ((offset & 7U) || offset > G474_APP_SIZE - 8U) return false;
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, G474_APP_BASE + offset,
word) != HAL_OK) return false;
memcpy(&actual, (const void *)(G474_APP_BASE + offset), 8);
return actual == word;
}
static bool write(void *user, uint32_t offset, const uint8_t *data, uint16_t length)
{
bool ok; (void)user;
if (HAL_FLASH_Unlock() != HAL_OK) return false;
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
ok = g474_writer_write(&writer, offset, data, length, program_word, 0);
(void)HAL_FLASH_Lock(); return ok;
}
static bool verify(void *user, uint32_t size, uint32_t crc)
{
(void)user;
return !writer.failed && !writer.used && writer.received == size &&
writer.size == size &&
g474_vectors_valid((const uint8_t *)G474_APP_BASE, size) &&
setp_crc32((const uint8_t *)G474_APP_BASE, size) == crc;
}
static bool commit(void *user, uint32_t size, uint32_t crc)
{
uint32_t record[4] = {size, crc, ~size, COMMIT};
uint64_t word; unsigned i; bool ok = true; (void)user;
if (HAL_FLASH_Unlock() != HAL_OK) return false;
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
for (i = 0; i < sizeof(record); i += 8) {
memcpy(&word, (const uint8_t *)record + i, 8);
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, G474_META_BASE + i,
word) != HAL_OK) { ok = false; break; }
}
(void)HAL_FLASH_Lock();
return ok && !memcmp((const void *)G474_META_BASE, record, sizeof(record));
}
bool g474_app_valid(void)
{
/* Same reset policy as F407: SWD images need no USB commit record. */
return geometry_valid() &&
g474_vectors_valid((const uint8_t *)G474_APP_BASE, G474_APP_SIZE);
}
static void activate(void *user) { (void)user; }
const setp_boot_ops g474_boot_ops = {invalidate, erase, write, verify, commit, activate};
__attribute__((naked,noreturn)) static void enter(uint32_t sp, uint32_t pc)
{
__asm volatile("msr msp, r0\nmovs r2, #0\nmsr control, r2\ndsb\nisb\ncpsie i\nbx r1\n");
}
void g474_jump_app(void)
{
unsigned i;
uint32_t sp = *(const uint32_t *)G474_APP_BASE;
uint32_t pc = *(const uint32_t *)(G474_APP_BASE + 4);
__disable_irq(); SysTick->CTRL = 0;
for (i = 0; i < 8; ++i) { NVIC->ICER[i] = 0xffffffff; NVIC->ICPR[i] = 0xffffffff; }
SCB->ICSR = SCB_ICSR_PENDSTCLR_Msk | SCB_ICSR_PENDSVCLR_Msk;
SCB->VTOR = G474_APP_BASE; enter(sp, pc);
}

View File

@@ -0,0 +1,34 @@
#ifndef G474_BOOT_IMAGE_H
#define G474_BOOT_IMAGE_H
#include "boot_port.h"
#include <string.h>
/* SRAM1+SRAM2: 96 KB; CCM: 32 KB, also aliased after SRAM2. */
static inline bool g474_vectors_valid(const uint8_t *image, uint32_t size)
{
uint32_t sp, pc;
if (size < 8 || size > G474_APP_SIZE) return false;
memcpy(&sp, image, 4); memcpy(&pc, image + 4, 4);
return !(sp & 7U) &&
((sp > 0x20000000UL && sp <= 0x20020000UL) ||
(sp > 0x10000000UL && sp <= 0x10008000UL)) &&
(pc & 1U) && (pc & ~1UL) >= G474_APP_BASE &&
(pc & ~1UL) < G474_APP_BASE + size;
}
/* A contiguous xB slot at 0x08010000 exists only with DBANK=0.
* In dual mode the upper 64 KB is at 0x08040000, not at this slot address. */
static inline bool g474_geometry_supported(uint16_t flash_kb, bool dual, bool swapped)
{
return flash_kb == 128U && !dual && !swapped;
}
static inline bool g474_flash_page(uint32_t address, bool dual,
uint32_t *bank, uint32_t *page)
{
uint32_t offset;
if (dual || address < G474_META_BASE || address >= G474_APP_BASE + G474_APP_SIZE)
return false;
offset = address - 0x08000000UL;
*bank = 1U;
*page = offset / 4096U;
return true;
}
#endif

View File

@@ -0,0 +1,39 @@
#include "stm32g4xx_hal.h"
#include "boot_port.h"
#include "usb_cdc.h"
static volatile uint32_t clock_fault;
void SysTick_Handler(void) { HAL_IncTick(); }
static void fatal(uint32_t code) { clock_fault = code; for (;;) {} }
static void usb_clock_init(void)
{
RCC_OscInitTypeDef osc = {0};
RCC_PeriphCLKInitTypeDef peripheral = {0};
RCC_CRSInitTypeDef crs = {0};
/* CPU stays on reset HSI16. USB uses HSI48 disciplined by USB SOF. */
osc.OscillatorType = RCC_OSCILLATORTYPE_HSI48;
osc.HSI48State = RCC_HSI48_ON;
if (HAL_RCC_OscConfig(&osc) != HAL_OK) fatal(2);
peripheral.PeriphClockSelection = RCC_PERIPHCLK_USB;
peripheral.UsbClockSelection = RCC_USBCLKSOURCE_HSI48;
if (HAL_RCCEx_PeriphCLKConfig(&peripheral) != HAL_OK) fatal(3);
__HAL_RCC_CRS_CLK_ENABLE();
crs.Prescaler = RCC_CRS_SYNC_DIV1;
crs.Source = RCC_CRS_SYNC_SOURCE_USB;
crs.Polarity = RCC_CRS_SYNC_POLARITY_RISING;
crs.ReloadValue = __HAL_RCC_CRS_RELOADVALUE_CALCULATE(48000000U, 1000U);
crs.ErrorLimitValue = RCC_CRS_ERRORLIMIT_DEFAULT;
crs.HSI48CalibrationValue = RCC_CRS_HSI48CALIBRATION_DEFAULT;
HAL_RCCEx_CRSConfig(&crs);
}
void boot_protocol_init(void);
void boot_protocol_poll(uint32_t now);
int main(void)
{
bool requested;
SCB->VTOR = FLASH_BASE;
requested = g474_boot_requested();
if (!requested && g474_app_valid()) g474_jump_app();
if (HAL_Init() != HAL_OK) fatal(1);
usb_clock_init(); boot_protocol_init(); usb_cdc_init();
for (;;) { uint32_t now = HAL_GetTick(); usb_cdc_poll(now); boot_protocol_poll(now); }
}

View File

@@ -0,0 +1,15 @@
#ifndef G474_BOOT_PORT_H
#define G474_BOOT_PORT_H
#include "set_boot.h"
/* STM32G474xB (128 KB), single-bank mode, normal bank mapping. */
#define G474_APP_BASE 0x08010000UL
#define G474_APP_SIZE 0x00010000UL
#define G474_META_BASE 0x0800F000UL
#define G474_BOOT_REQUEST 0x1003U
extern const setp_boot_ops g474_boot_ops;
bool g474_app_valid(void);
bool g474_boot_requested(void);
void g474_request_boot(uint32_t now);
void g474_reset_poll(uint32_t now);
void g474_jump_app(void);
#endif

View File

@@ -0,0 +1,26 @@
#include "boot_port.h"
#include "stm32g4xx_hal.h"
#define REQUEST_MAGIC 0x42555342UL
static uint32_t reset_at;
static bool reset_pending;
static void backup_access(void)
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_RTCAPB_CLK_ENABLE();
}
bool g474_boot_requested(void)
{
bool requested;
backup_access(); requested = TAMP->BKP0R == REQUEST_MAGIC;
TAMP->BKP0R = 0; return requested;
}
void g474_request_boot(uint32_t now)
{
backup_access(); TAMP->BKP0R = REQUEST_MAGIC;
reset_at = now + 500; reset_pending = true;
}
void g474_reset_poll(uint32_t now)
{
if (reset_pending && (int32_t)(now - reset_at) >= 0) NVIC_SystemReset();
}

View File

@@ -0,0 +1,94 @@
/* STM32G474 USB FS device, dedicated PA11/PA12 pins, single-buffer PMA. */
#include "usbd_core.h"
#include "usbd_cdc.h"
static PCD_HandleTypeDef pcd;
/* Единственный статический объект CDC вместо heap. Контракт предполагает один экземпляр класса. */
static USBD_CDC_HandleTypeDef class_memory;
/* Проверяет размер запроса и возвращает статический объект либо NULL. Не является универсальным malloc. */
void *BoardUsb_Alloc(uint32_t size) { return size<=sizeof(class_memory)?&class_memory:NULL; }
/* Освобождение не требуется: объект существует до перезапуска MCU. */
void BoardUsb_Free(void *ptr) { (void)ptr; }
/* Согласует enum HAL и USB: OK -> OK, BUSY -> BUSY, остальные результаты -> FAIL. */
static USBD_StatusTypeDef status(HAL_StatusTypeDef s) {
return s==HAL_OK?USBD_OK:s==HAL_BUSY?USBD_BUSY:USBD_FAIL;
}
/* Единственная точка входа USB ISR; события декодирует HAL_PCD_IRQHandler. */
void USB_LP_IRQHandler(void) { HAL_PCD_IRQHandler(&pcd); }
/* Передаёт 8-байтовый SETUP control-запрос стандартному обработчику USB Device. */
void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_SetupStage(h->pData,(uint8_t *)h->Setup); }
/* OUT: хост отправил данные устройству; передаём номер endpoint и его принятый буфер. */
void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *h,uint8_t ep) { (void)USBD_LL_DataOutStage(h->pData,ep,h->OUT_ep[ep].xfer_buff); }
/* IN: завершена передача данных от устройства к хосту; уведомляем USB Device. */
void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *h,uint8_t ep) { (void)USBD_LL_DataInStage(h->pData,ep,h->IN_ep[ep].xfer_buff); }
/* Передаёт событие начала USB-кадра; в текущей конфигурации SOF-прерывания отключены. */
void HAL_PCD_SOFCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_SOF(h->pData); }
/* При reset шины фиксируем Full Speed и сбрасываем состояние USB Device. */
void HAL_PCD_ResetCallback(PCD_HandleTypeDef *h) {
(void)USBD_LL_SetSpeed(h->pData,USBD_SPEED_FULL); (void)USBD_LL_Reset(h->pData);
}
/* Сообщает middleware о приостановке шины; аппаратный low-power режим здесь не включается. */
void HAL_PCD_SuspendCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_Suspend(h->pData); }
/* Сообщает middleware о возобновлении обмена после suspend. */
void HAL_PCD_ResumeCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_Resume(h->pData); }
/* Уведомление верхнего уровня о подключении контроллера. */
void HAL_PCD_ConnectCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_DevConnected(h->pData); }
/* Уведомление верхнего уровня об отключении контроллера. */
void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *h) { (void)USBD_LL_DevDisconnected(h->pData); }
/* Переадресует незавершённую изохронную IN-передачу; обычный CDC использует bulk endpoints. */
void HAL_PCD_ISOINIncompleteCallback(PCD_HandleTypeDef *h,uint8_t ep) { (void)USBD_LL_IsoINIncomplete(h->pData,ep); }
/* Переадресует незавершённую изохронную OUT-передачу для полноты порта PCD. */
void HAL_PCD_ISOOUTIncompleteCallback(PCD_HandleTypeDef *h,uint8_t ep) { (void)USBD_LL_IsoOUTIncomplete(h->pData,ep); }
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *d) {
/* Dedicated USB pins: no GPIO alternate-function selection on G474. */
__HAL_RCC_USB_CLK_ENABLE();
pcd.Instance = USB; pcd.Init.dev_endpoints = 8;
pcd.Init.speed = PCD_SPEED_FULL; pcd.Init.phy_itface = PCD_PHY_EMBEDDED;
pcd.Init.Sof_enable = DISABLE; pcd.Init.low_power_enable = DISABLE;
pcd.Init.lpm_enable = DISABLE; pcd.Init.battery_charging_enable = DISABLE;
pcd.pData = d; d->pData = &pcd;
if (HAL_PCD_Init(&pcd) != HAL_OK) return USBD_FAIL;
/* BTABLE: 0x00..0x3f; EP0 RX/TX 64 bytes each; CDC data RX/TX 64;
* CDC notification 16. Byte offsets are disjoint within the 1 KB PMA. */
if (HAL_PCDEx_PMAConfig(&pcd, 0x00, PCD_SNG_BUF, 0x40) != HAL_OK ||
HAL_PCDEx_PMAConfig(&pcd, 0x80, PCD_SNG_BUF, 0x80) != HAL_OK ||
HAL_PCDEx_PMAConfig(&pcd, CDC_OUT_EP, PCD_SNG_BUF, 0xc0) != HAL_OK ||
HAL_PCDEx_PMAConfig(&pcd, CDC_IN_EP, PCD_SNG_BUF, 0x100) != HAL_OK ||
HAL_PCDEx_PMAConfig(&pcd, CDC_CMD_EP, PCD_SNG_BUF, 0x140) != HAL_OK)
return USBD_FAIL;
HAL_NVIC_SetPriority(USB_LP_IRQn, 7, 0); HAL_NVIC_EnableIRQ(USB_LP_IRQn);
return USBD_OK;
}
/* Запрещает USB IRQ до деинициализации контроллера, чтобы callbacks не работали с разобранным состоянием. */
USBD_StatusTypeDef USBD_LL_DeInit(USBD_HandleTypeDef *d) {
HAL_NVIC_DisableIRQ(USB_LP_IRQn); return status(HAL_PCD_DeInit(d->pData));
}
/* Включает USB device через HAL после завершения настройки класса и endpoints. */
USBD_StatusTypeDef USBD_LL_Start(USBD_HandleTypeDef *d) { return status(HAL_PCD_Start(d->pData)); }
/* Останавливает контроллер USB device; память состояния остаётся статической. */
USBD_StatusTypeDef USBD_LL_Stop(USBD_HandleTypeDef *d) { return status(HAL_PCD_Stop(d->pData)); }
/* Открывает endpoint: ep содержит номер и направление (бит 7), type — тип, mps — максимальный пакет в байтах. */
USBD_StatusTypeDef USBD_LL_OpenEP(USBD_HandleTypeDef *d,uint8_t ep,uint8_t type,uint16_t mps) { return status(HAL_PCD_EP_Open(d->pData,ep,mps,type)); }
/* Закрывает endpoint по USB-адресу ep, включая бит направления. */
USBD_StatusTypeDef USBD_LL_CloseEP(USBD_HandleTypeDef *d,uint8_t ep) { return status(HAL_PCD_EP_Close(d->pData,ep)); }
/* Очищает FIFO указанного endpoint через HAL. */
USBD_StatusTypeDef USBD_LL_FlushEP(USBD_HandleTypeDef *d,uint8_t ep) { return status(HAL_PCD_EP_Flush(d->pData,ep)); }
/* Устанавливает STALL: endpoint отклоняет неподдерживаемую операцию хоста. */
USBD_StatusTypeDef USBD_LL_StallEP(USBD_HandleTypeDef *d,uint8_t ep) { return status(HAL_PCD_EP_SetStall(d->pData,ep)); }
/* Снимает STALL по запросу верхнего уровня USB Device. */
USBD_StatusTypeDef USBD_LL_ClearStallEP(USBD_HandleTypeDef *d,uint8_t ep) { return status(HAL_PCD_EP_ClrStall(d->pData,ep)); }
/* Применяет адрес устройства, назначенный хостом при перечислении USB. */
USBD_StatusTypeDef USBD_LL_SetUSBAddress(USBD_HandleTypeDef *d,uint8_t address) { return status(HAL_PCD_SetAddress(d->pData,address)); }
/* Запускает IN-передачу size байтов из buf. Буфер должен жить до callback завершения; копии здесь нет. */
USBD_StatusTypeDef USBD_LL_Transmit(USBD_HandleTypeDef *d,uint8_t ep,uint8_t *buf,uint32_t size) { return status(HAL_PCD_EP_Transmit(d->pData,ep,buf,size)); }
/* Взводит OUT endpoint для приёма в buf ёмкостью size байтов; завершение придёт через callback PCD. */
USBD_StatusTypeDef USBD_LL_PrepareReceive(USBD_HandleTypeDef *d,uint8_t ep,uint8_t *buf,uint32_t size) { return status(HAL_PCD_EP_Receive(d->pData,ep,buf,size)); }
/* Читает состояние endpoint из HAL handle: bit7 выбирает IN, младшие 7 бит — номер. */
uint8_t USBD_LL_IsStallEP(USBD_HandleTypeDef *d,uint8_t ep) {
PCD_HandleTypeDef *h=d->pData; return ep&0x80?h->IN_ep[ep&0x7F].is_stall:h->OUT_ep[ep].is_stall;
}
/* Возвращает фактическое число принятых байтов последней OUT-передачи. */
uint32_t USBD_LL_GetRxDataSize(USBD_HandleTypeDef *d,uint8_t ep) { return HAL_PCD_EP_GetRxCount(d->pData,ep); }
/* Блокирующая задержка ST middleware в миллисекундах HAL; требует работающего SysTick. */
void USBD_LL_Delay(uint32_t ms) { HAL_Delay(ms); }

View File

@@ -0,0 +1,10 @@
#include "stm32g4xx_hal.h"
#include "boot_port.h"
#include "boot_config.h"
#define BOOT_OPS g474_boot_ops
#define BOOT_APP_BASE G474_APP_BASE
#define BOOT_APP_SIZE G474_APP_SIZE
#define BOOT_NODE G474_BOOT_NODE
#define BOOT_MODEL G474_BOOT_MODEL
#define BOOT_RESET() NVIC_SystemReset()
#include "../stm32-usb-boot/boot_usb_protocol.inc"

View File

@@ -0,0 +1,37 @@
#ifndef G474_BOOT_WRITER_H
#define G474_BOOT_WRITER_H
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
/* ECC Flash must be programmed once per 64-bit word. Keep partial words in RAM
* across SET packets; pad only the final word, without changing the image CRC. */
typedef bool (*g474_program_word)(void *user, uint32_t offset, uint64_t word);
typedef struct {
uint32_t size, received;
uint8_t pending[8];
uint8_t used;
bool failed;
} g474_writer;
static inline void g474_writer_init(g474_writer *w, uint32_t size)
{
memset(w, 0, sizeof(*w)); w->size = size;
memset(w->pending, 0xff, sizeof(w->pending));
}
static inline bool g474_writer_write(g474_writer *w, uint32_t offset,
const uint8_t *data, uint16_t length, g474_program_word program, void *user)
{
if (w->failed || !length || offset != w->received ||
offset > w->size || length > w->size - offset) return false;
while (length--) {
w->pending[w->used++] = *data++; ++w->received;
if (w->used == 8 || w->received == w->size) {
uint64_t word;
uint32_t start = w->received - w->used;
memcpy(&word, w->pending, sizeof(word));
if (!program(user, start, word)) { w->failed = true; return false; }
w->used = 0; memset(w->pending, 0xff, sizeof(w->pending));
}
}
return true;
}
#endif

View File

@@ -0,0 +1,97 @@
#include "boot_image.h"
#include "boot_writer.h"
#include <assert.h>
#include <stdio.h>
static uint8_t flash[1024];
static bool touched[128];
static bool fail_write;
static bool program(void *user, uint32_t offset, uint64_t word)
{
(void)user;
assert(!(offset & 7U)); assert(offset <= sizeof(flash) - 8);
assert(!touched[offset / 8]);
if (fail_write) return false;
touched[offset / 8] = true;
memcpy(flash + offset, &word, 8); return true;
}
static void test_writer(void)
{
uint8_t image[521];
uint32_t block, size, i;
for (i = 0; i < sizeof(image); ++i) image[i] = (uint8_t)(i * 19U);
/* Every block size, all possible final-word tails, including all-FF words. */
memset(image + 128, 0xff, 32);
for (block = 1; block <= 256; ++block) {
for (size = 512; size <= sizeof(image); ++size) {
g474_writer w;
memset(flash, 0xff, sizeof(flash)); memset(touched, 0, sizeof(touched));
g474_writer_init(&w, size);
for (i = 0; i < size;) {
uint16_t n = (uint16_t)(size - i < block ? size - i : block);
assert(g474_writer_write(&w, i, image + i, n, program, 0)); i += n;
}
assert(!w.failed && !w.used && w.received == size);
assert(!memcmp(flash, image, size));
for (i = size; i < sizeof(flash); ++i) assert(flash[i] == 0xff);
assert(!g474_writer_write(&w, size, image, 1, program, 0));
}
}
{
g474_writer w;
g474_writer_init(&w, 9);
assert(!g474_writer_write(&w, 1, image, 1, program, 0));
assert(!g474_writer_write(&w, 0, image, 10, program, 0));
assert(!g474_writer_write(&w, 0, image, 0, program, 0));
assert(w.received == 0);
memset(touched, 0, sizeof(touched)); fail_write = true;
assert(!g474_writer_write(&w, 0, image, 8, program, 0));
assert(w.failed);
fail_write = false;
assert(!g474_writer_write(&w, w.received, image, 1, program, 0));
g474_writer_init(&w, 9);
assert(g474_writer_write(&w, 0, image, 9, program, 0));
}
}
static void check_page(uint32_t address, bool dual, uint32_t bank, uint32_t page)
{
uint32_t b, p;
assert(g474_flash_page(address, dual, &b, &p));
assert(b == bank && p == page);
}
int main(void)
{
uint32_t vectors[2] = {0x20018000UL, G474_APP_BASE + 9};
uint32_t b, p;
const uint8_t *image = (const uint8_t *)vectors;
assert(g474_geometry_supported(128, false, false));
assert(!g474_geometry_supported(128, true, false));
assert(!g474_geometry_supported(128, false, true));
assert(!g474_geometry_supported(512, false, false));
assert(!g474_geometry_supported(256, false, false));
assert(!g474_geometry_supported(0xffff, false, false));
check_page(G474_META_BASE, false, 1, 15);
check_page(G474_APP_BASE, false, 1, 16);
check_page(0x0801ffffUL, false, 1, 31);
assert(!g474_flash_page(G474_META_BASE - 1, false, &b, &p));
assert(!g474_flash_page(0x08020000UL, false, &b, &p));
assert(!g474_flash_page(0x08040000UL, false, &b, &p));
assert(!g474_flash_page(G474_META_BASE, true, &b, &p));
assert(!g474_flash_page(G474_APP_BASE, true, &b, &p));
assert(g474_vectors_valid(image, G474_APP_SIZE));
vectors[0] = 0x10008000UL; assert(g474_vectors_valid(image, 16));
vectors[0] = 0x20020000UL; assert(g474_vectors_valid(image, 16));
vectors[0] = 0x20020008UL; assert(!g474_vectors_valid(image, 16));
vectors[0] = 0x10008008UL; assert(!g474_vectors_valid(image, 16));
vectors[0] = 0x20000000UL; assert(!g474_vectors_valid(image, 16));
vectors[0] = 0x20017fffUL; assert(!g474_vectors_valid(image, 16));
vectors[0] = 0x20018000UL;
vectors[1] = G474_APP_BASE + 8; assert(!g474_vectors_valid(image, 16));
vectors[1] = G474_APP_BASE - 1; assert(!g474_vectors_valid(image, 16));
vectors[1] = G474_APP_BASE + 17; assert(!g474_vectors_valid(image, 16));
vectors[1] = G474_APP_BASE + 9;
assert(!g474_vectors_valid(image, 7));
assert(!g474_vectors_valid(image, G474_APP_SIZE + 1));
test_writer();
puts("G474: bank/page boundaries, vectors, all block sizes and ECC word tails passed");
return 0;
}