Обеспечь единый процесс обновления для STM32F103/F407/G474 и TMS320F2812/F28335: discovery, служебные ответы, BUSY, тайм-аут сессии и подтверждение приложения. Добавь самостоятельные проекты CCS, аппаратные хранилища и Python-клиент. Совместимость: структуры конфигурации и порта расширены, поэтому потребители должны пересобраться и обнулить новые поля. Для C28x callbacks используют pcan_boot_byte_t; периодический pcan_boot_poll обслуживает discovery и настроенный тайм-аут.
260 lines
43 KiB
C
260 lines
43 KiB
C
#if defined(_WIN32) /* Windows требует VirtualAlloc для отображения Flash по настоящему адресу MCU. */
|
||
#include <windows.h> /* Предоставляет выделение виртуальной памяти по фиксированному адресу. */
|
||
#else /* POSIX использует аналогичное приватное отображение без файлов устройства. */
|
||
#define _DEFAULT_SOURCE /* Разрешает MAP_ANONYMOUS при строгом режиме C99. */
|
||
#include <sys/mman.h> /* Предоставляет mmap и munmap для модели адресного пространства STM32. */
|
||
#endif /* Завершает выбор host-платформы. */
|
||
#include <assert.h> /* Останавливает проверку на первом нарушении границ или протокольного контракта. */
|
||
#include <stdio.h> /* Выводит компактный результат каждого семейного прогона. */
|
||
#include <string.h> /* Копирует данные модели Flash и сериализованные поля manifest. */
|
||
#if defined(TEST_STM32F103) /* Один сценарий исполняется с настоящим семейным production-портом F1. */
|
||
#include "../ports/stm32f103/pcan_boot_stm32f103.h" /* Подключает API проверяемого профиля STM32F103C8. */
|
||
#define PORT_INIT pcan_boot_stm32f103_init /* Выбирает функцию настройки F103 без копирования сценариев. */
|
||
#define PORT_POLL pcan_boot_stm32f103_poll /* Выбирает основной цикл транспорта F103. */
|
||
#define PORT_VECTORS pcan_boot_stm32f103_vectors_valid /* Выбирает семейную проверку границ SRAM и Flash. */
|
||
#define APP_BASE PCAN_BOOT_F103_APP_ADDRESS /* Берёт исходный адрес KONOR из production-заголовка. */
|
||
#define APP_END PCAN_BOOT_F103_FLASH_END /* Берёт верхнюю границу штатной Flash F103C8. */
|
||
#define RAM_TOP 0x20005000UL /* Тестовый MSP указывает за последним байтом 20 КиБ SRAM F103C8. */
|
||
#define TEST_HANDLE CAN_HandleTypeDef /* F103 использует штатный bxCAN handle HAL. */
|
||
#define TEST_INSTANCE CAN1 /* F103 проверяется с выделенным CAN1. */
|
||
#elif defined(TEST_STM32G474) /* Третий прогон использует production FDCAN/Flash-порт G474. */
|
||
#include "../ports/stm32g474/pcan_boot_stm32g474.h" /* Подключает готовый reusable G474-порт, а не boot main. */
|
||
#define PORT_INIT pcan_boot_stm32g474_init /* Выбирает текущую настройку single-slot FDCAN-порта. */
|
||
#define PORT_POLL pcan_boot_stm32g474_poll /* Выбирает обработку FDCAN FIFO0 и таймеров ядра. */
|
||
#define PORT_VECTORS() pcan_boot_stm32g474_vectors_valid(CAN_BOOT_APP_SIZE) /* Сохраняет смысл структурной проверки всего профиля для общих сценариев. */
|
||
#define APP_BASE CAN_BOOT_APP_ADDRESS /* G474 приложение начинается после исходных 32 КиБ загрузчика. */
|
||
#define APP_END CAN_BOOT_FLASH_END /* Профиль G474 использует оставшиеся 480 КиБ Flash. */
|
||
#define RAM_TOP 0x20014000UL /* Векторы исходного CANdle G474 ограничены основной используемой SRAM 80 КиБ. */
|
||
#define TEST_HANDLE FDCAN_HandleTypeDef /* G474 использует современный FDCAN handle вместо bxCAN. */
|
||
#define TEST_INSTANCE FDCAN1 /* Проверяется только выделенный FDCAN1 исходной платы. */
|
||
#else /* Второй прогон компилирует те же сценарии с настоящим F407-портом. */
|
||
#include "../ports/stm32f407/pcan_boot_stm32f407.h" /* Подключает API профиля climate. */
|
||
#define PORT_INIT pcan_boot_stm32f407_init /* Выбирает функцию настройки F407. */
|
||
#define PORT_POLL pcan_boot_stm32f407_poll /* Выбирает основной цикл транспорта F407. */
|
||
#define PORT_VECTORS pcan_boot_stm32f407_vectors_valid /* Выбирает проверку векторов F407. */
|
||
#define APP_BASE PCAN_BOOT_F407_APP_ADDRESS /* Берёт начало сектора 2 из production-заголовка. */
|
||
#define APP_END PCAN_BOOT_F407_FLASH_END /* Исключает EEPROM-кольцо секторов 6 и 7. */
|
||
#define RAM_TOP 0x20020000UL /* Тестовый MSP указывает за последним байтом основной SRAM F407. */
|
||
#define TEST_HANDLE CAN_HandleTypeDef /* F407 использует штатный bxCAN handle HAL. */
|
||
#define TEST_INSTANCE CAN1 /* F407 проверяется с выделенным CAN1. */
|
||
#endif /* Завершает выбор проверяемой аппаратной реализации. */
|
||
CAN_TypeDef stm32_test_can; /* Эмулирует регистр подтверждения TX, читаемый production-кодом. */
|
||
static uint8_t *flash; /* Ссылается на host-отображение физического диапазона Flash STM32. */
|
||
static uint32_t tick, sends, resets, aborted, erases, writes; /* Считает время и аппаратные эффекты сценария. */
|
||
static bool unlocked, fail_erase, fail_write, corrupt_write, no_mailbox, pending, tx_error, fail_start; /* Управляет воспроизводимыми отказами HAL. */
|
||
static uint8_t last_tx[8], rx_bytes[8]; /* Хранит полный безопасно скопированный TX и единственный входной payload. */
|
||
static CAN_RxHeaderTypeDef rx_header; /* Содержит входной IDE/RTR/DLC для проверок транспортного адаптера. */
|
||
static CAN_FilterTypeDef filters[2]; /* Фиксирует адресный и broadcast-discovery фильтры. */
|
||
static bool have_rx; /* Моделирует наличие одного доступного кадра FIFO0. */
|
||
static bool cache_dirty; /* Фиксирует необходимость синхронизации Flash-кэша F407 после HAL program. */
|
||
#if defined(TEST_STM32G474) /* Расширяет общую модель только для FDCAN/двухбанковой Flash G474. */
|
||
FLASH_TypeDef stm32_test_flash; SYSCFG_TypeDef stm32_test_syscfg; FDCAN_GlobalTypeDef stm32_test_fdcan; uint16_t stm32_test_flash_kib; /* Предоставляет production-коду безопасные регистры геометрии и TX. */
|
||
static bool dual_bank; /* Перебирает оба штатных DBANK-режима одного физического G474VE 512 КиБ. */
|
||
uint32_t HAL_FDCAN_GetTxFifoFreeLevel(const FDCAN_HandleTypeDef *can) { (void)can; return no_mailbox ? 0U : 3U; } /* Воспроизводит ожидание свободного аппаратного TX FIFO. */
|
||
HAL_StatusTypeDef HAL_FDCAN_AddMessageToTxFifoQ(FDCAN_HandleTypeDef *can, const FDCAN_TxHeaderTypeDef *header, const uint8_t *data) /* Проверяет настоящий TX adapter G474 с иным кодированием DLC HAL. */
|
||
{ /* Все восемь доступных байтов payload обязан подготовить production-порт. */
|
||
assert(can->Instance == FDCAN1 && header->IdType == FDCAN_EXTENDED_ID && header->TxFrameType == FDCAN_DATA_FRAME && header->FDFormat == FDCAN_CLASSIC_CAN && header->DataLength <= FDCAN_DLC_BYTES_8); /* Не допускает случайный CAN FD, standard или некорректный DLC. */
|
||
(void)memcpy(last_tx, data, 8U); can->Instance->TXBTO = tx_error ? 0U : 1U; ++sends; return HAL_OK; /* Имитирует отдельный TXBTO и проверяет запрет reset при отсутствии подтверждённой передачи. */
|
||
} /* Завершает модель передачи FDCAN. */
|
||
uint32_t HAL_FDCAN_GetLatestTxFifoQRequestBuffer(const FDCAN_HandleTypeDef *can) { (void)can; return 1U; } /* Выбирает bitmask первого TX-буфера, как реальный API HAL. */
|
||
uint32_t HAL_FDCAN_IsTxBufferMessagePending(const FDCAN_HandleTypeDef *can, uint32_t buffer) { (void)can; assert(buffer == 1U); return pending; } /* Позволяет проверить тайм-аут именно поставленного TX-запроса. */
|
||
HAL_StatusTypeDef HAL_FDCAN_AbortTxRequest(FDCAN_HandleTypeDef *can, uint32_t buffer) { (void)can; assert(buffer == 1U); ++aborted; return HAL_OK; } /* Считает отменённые после тайм-аута кадры. */
|
||
HAL_StatusTypeDef HAL_FDCAN_ConfigFilter(FDCAN_HandleTypeDef *can, const FDCAN_FilterTypeDef *filter) { (void)can; assert(filter->FilterIndex < 2U); filters[filter->FilterIndex].FilterBank = filter->FilterIndex; filters[filter->FilterIndex].FilterMaskIdHigh = (filter->FilterID2 << 3U) >> 16U; return HAL_OK; } /* Сохраняет аппаратные маски адреса и discovery для общих проверок. */
|
||
HAL_StatusTypeDef HAL_FDCAN_ConfigGlobalFilter(FDCAN_HandleTypeDef *can, uint32_t standard, uint32_t extended, uint32_t remote_standard, uint32_t remote_extended) { (void)can; assert(standard == FDCAN_REJECT && extended == FDCAN_REJECT && remote_standard == FDCAN_REJECT_REMOTE && remote_extended == FDCAN_REJECT_REMOTE); return HAL_OK; } /* Не позволяет broad фильтрации обойти проверку адресов и remote-кадров. */
|
||
HAL_StatusTypeDef HAL_FDCAN_Start(FDCAN_HandleTypeDef *can) { (void)can; return fail_start ? HAL_ERROR : HAL_OK; } /* Воспроизводит отказ аппаратного запуска FDCAN. */
|
||
uint32_t HAL_FDCAN_GetRxFifoFillLevel(const FDCAN_HandleTypeDef *can, uint32_t fifo) { (void)can; assert(fifo == FDCAN_RX_FIFO0); return have_rx; } /* Сообщает наличие подготовленного общего тестового RX-кадра. */
|
||
HAL_StatusTypeDef HAL_FDCAN_GetRxMessage(FDCAN_HandleTypeDef *can, uint32_t fifo, FDCAN_RxHeaderTypeDef *header, uint8_t *data) { (void)can; (void)fifo; header->Identifier = rx_header.ExtId; header->IdType = rx_header.IDE; header->RxFrameType = rx_header.RTR; header->DataLength = rx_header.DLC << 16U; header->FDFormat = FDCAN_CLASSIC_CAN; (void)memcpy(data, rx_bytes, 8U); have_rx = false; return HAL_OK; } /* Проверяет decoder старого shifted-DLC HAL на тех же ошибочных входах, что bxCAN. */
|
||
#endif /* Завершает аппаратную модель только для G474. */
|
||
uint32_t HAL_GetTick(void) { return tick++; } /* Продвигает время даже в синхронном ожидании TX для воспроизводимого тайм-аута. */
|
||
uint32_t HAL_CAN_GetTxMailboxesFreeLevel(CAN_HandleTypeDef *can) { (void)can; return no_mailbox ? 0U : 3U; } /* Имитирует свободный либо постоянно занятый TX. */
|
||
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *can, CAN_TxHeaderTypeDef *header, uint8_t *data, uint32_t *mailbox) /* Захватывает ответ и состояние завершения его передачи. */
|
||
{ /* Реальный codec и сборка payload остаются в production-коде. */
|
||
assert(can->Instance == CAN1 && header->IDE == CAN_ID_EXT && header->RTR == CAN_RTR_DATA && header->DLC <= 8U); /* Проверяет аппаратный формат каждого ответа. */
|
||
assert(can->Instance->TSR == (CAN_TSR_RQCP0 | CAN_TSR_RQCP1 | CAN_TSR_RQCP2)); /* До передачи порт обязан очистить старые TXOK/ошибки аппаратной записью W1C. */
|
||
(void)memcpy(last_tx, data, sizeof(last_tx)); /* Восемь доступных байтов гарантирует локальный TX-буфер порта. */
|
||
*mailbox = CAN_TX_MAILBOX0; /* Выбирает первый mailbox с известным TXOK-флагом. */
|
||
can->Instance->TSR = tx_error ? 0U : CAN_TSR_TXOK0; /* Различает успешную передачу и снятый без ACK запрос. */
|
||
++sends; /* Фиксирует факт постановки кадра в передатчик. */
|
||
return HAL_OK; /* Модель успешно приняла параметры аппаратной передачи. */
|
||
} /* Завершает модель отправки кадра. */
|
||
uint32_t HAL_CAN_IsTxMessagePending(CAN_HandleTypeDef *can, uint32_t mailbox) { (void)can; (void)mailbox; return pending; } /* Удерживает TX незавершённым для проверки запрета reset. */
|
||
HAL_StatusTypeDef HAL_CAN_AbortTxRequest(CAN_HandleTypeDef *can, uint32_t mailbox) { (void)can; (void)mailbox; ++aborted; return HAL_OK; } /* Считает отменённые после тайм-аута запросы. */
|
||
HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *can, CAN_FilterTypeDef *filter) { (void)can; assert(filter->FilterBank < 2U); filters[filter->FilterBank] = *filter; return HAL_OK; } /* Проверяет владение только двумя заявленными банками. */
|
||
HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *can) { (void)can; return fail_start ? HAL_ERROR : HAL_OK; } /* Даёт воспроизводимый отказ аппаратного запуска. */
|
||
uint32_t HAL_CAN_GetRxFifoFillLevel(CAN_HandleTypeDef *can, uint32_t fifo) { (void)can; assert(fifo == CAN_RX_FIFO0); return have_rx; } /* Сообщает наличие подготовленного кадра. */
|
||
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *can, uint32_t fifo, CAN_RxHeaderTypeDef *header, uint8_t *data) { (void)can; (void)fifo; *header = rx_header; (void)memcpy(data, rx_bytes, 8U); have_rx = false; return HAL_OK; } /* Передаёт кадр через настоящий путь RX порта. */
|
||
HAL_StatusTypeDef HAL_FLASH_Unlock(void) { unlocked = true; return HAL_OK; } /* Разрешает последующие модельные erase и program. */
|
||
HAL_StatusTypeDef HAL_FLASH_Lock(void) { unlocked = false; return HAL_OK; } /* Восстанавливает аппаратную блокировку модели. */
|
||
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *erase, uint32_t *error) /* Моделирует геометрию стирания без изменения самого алгоритма порта. */
|
||
{ /* Любое пересечение загрузчика либо резервной памяти немедленно проваливает тест. */
|
||
uint32_t start, end; /* Вычисляет физический диапазон выбранной операции HAL. */
|
||
assert(unlocked); /* Запрещает erase до аппаратной разблокировки. */
|
||
#if defined(TEST_STM32F103) /* Проверяет 1-КиБ страницы среднего семейства F103. */
|
||
assert(erase->TypeErase == FLASH_TYPEERASE_PAGES && erase->NbPages != 0U); /* Исключает подмену на mass erase. */
|
||
start = erase->PageAddress; end = start + erase->NbPages * 1024U; /* Воспроизводит диапазон HAL page erase в байтах. */
|
||
#elif defined(TEST_STM32G474) /* Проверяет физические страницы обоих банков G474. */
|
||
uint32_t page_size = dual_bank ? 2048U : 4096U, bank_size = dual_bank ? 262144U : 524288U; /* Независимая геометрия по выбранному DBANK-режиму. */
|
||
assert(erase->TypeErase == FLASH_TYPEERASE_PAGES && erase->NbPages == 1U && erase->Banks >= FLASH_BANK_1 && erase->Banks <= (dual_bank ? FLASH_BANK_2 : FLASH_BANK_1)); /* Не допускает второго физического банка в single-bank конфигурации. */
|
||
start = FLASH_BASE + (erase->Banks == FLASH_BANK_2 ? bank_size : 0U) + erase->Page * page_size; end = start + page_size; /* Независимо восстанавливает физический диапазон erase из номера банка и страницы. */
|
||
#else /* Проверяет конкретные секторы STM32F407. */
|
||
static const uint32_t bounds[] = {0x08008000UL, 0x0800C000UL, 0x08010000UL, 0x08020000UL, 0x08040000UL}; /* Независимая карта физических границ секторов 2..5 из исходного climate. */
|
||
assert(erase->TypeErase == FLASH_TYPEERASE_SECTORS && erase->Sector >= 2U && erase->Sector <= 5U && erase->NbSectors == 1U && erase->VoltageRange == FLASH_VOLTAGE_RANGE_3); /* Исключает загрузчик, EEPROM-кольцо и неверную разрядность Flash. */
|
||
start = bounds[erase->Sector - 2U]; end = bounds[erase->Sector - 1U]; /* Определяет полный стираемый сектор по его физическому номеру. */
|
||
#endif /* Завершает проверку геометрии семейства. */
|
||
assert(start >= APP_BASE && end <= APP_END); /* Проверяет границы до обращения к host-памяти. */
|
||
*error = 0U; ++erases; tick += 2000U; /* Воспроизводит длительный erase, превышающий короткий тайм-аут сессии. */
|
||
if (fail_erase) return HAL_ERROR; /* Модель аппаратного отказа не должна оставить Flash разблокированной. */
|
||
(void)memset((void *)(uintptr_t)start, 0xFF, end - start); /* Стирает только разрешённые реальные байты модели. */
|
||
return HAL_OK; /* Выбранные страницы или сектор успешно очищены. */
|
||
} /* Завершает независимую модель аппаратного erase. */
|
||
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t type, uint32_t address, uint64_t value) /* Программирует только выровненную аппаратную единицу. */
|
||
{ /* Чтение результата выполняется настоящим production-кодом через тот же адрес. */
|
||
uint32_t size = type == FLASH_TYPEPROGRAM_HALFWORD ? 2U : 4U; /* Различает аппаратные единицы F103 и F407. */
|
||
#if defined(TEST_STM32G474) /* G474 программируется аппаратными doubleword, а не словами F4. */
|
||
assert(type == FLASH_TYPEPROGRAM_DOUBLEWORD); size = 8U; /* Исключает неверную разрядность записи ECC Flash G474. */
|
||
#endif /* Завершает выбор аппаратной единицы модели программирования. */
|
||
assert(unlocked && address >= APP_BASE && address <= APP_END - size && address % size == 0U); /* Не допускает пересечения Flash-профиля или неверного выравнивания. */
|
||
assert(!cache_dirty); /* Следующая запись не должна скрыть пропущенную синхронизацию предыдущего Flash readback. */
|
||
++writes; /* Считает обращения к реальному программированию HAL. */
|
||
if (fail_write) return HAL_ERROR; /* Имитирует отказ до изменения данных. */
|
||
(void)memcpy((void *)(uintptr_t)address, &value, size); /* Сохраняет именно аппаратно записанное полуслово либо слово. */
|
||
#if !defined(TEST_STM32F103) && !defined(TEST_STM32G474) /* Только F407 требует явного flush от вызывающего порта; HAL G4 выполняет его внутри Program. */
|
||
cache_dirty = true; /* Следующий readback требует семейного вызова FLASH_FlushCaches. */
|
||
#endif /* Завершает моделирование необходимости синхронизации F407. */
|
||
if (corrupt_write) *(uint8_t *)(uintptr_t)address ^= 1U; /* Вызывает readback-ошибку при формальном успехе HAL. */
|
||
return HAL_OK; /* Даёт production-коду независимо перечитать память. */
|
||
} /* Завершает модель программирования Flash. */
|
||
void FLASH_FlushCaches(void) { cache_dirty = false; } /* Обновляет модель видимости записанных слов F407 перед последующим readback. */
|
||
void NVIC_SystemReset(void) { ++resets; } /* Считает reset только после успешного ACK команды REBOOT. */
|
||
static void put32(uint8_t *out, uint32_t value) /* Сериализует поля образа независимо от host-endian. */
|
||
{ /* ProtoCAN manifest и Cortex-M векторы используют младший байт первым. */
|
||
out[0] = (uint8_t)value; out[1] = (uint8_t)(value >> 8U); out[2] = (uint8_t)(value >> 16U); out[3] = (uint8_t)(value >> 24U); /* Записывает четыре октета little-endian без невыровненного доступа. */
|
||
} /* Завершает тестовую сериализацию 32-битного поля. */
|
||
static bool command(pcan_boot_stm32_t *port, uint8_t cmd, const uint8_t *data, uint8_t dlc) /* Отправляет адресную команду в настоящую state machine. */
|
||
{ /* Сессия 9 и адрес 3:5 являются тестовыми значениями и не задают адрес рабочего устройства. */
|
||
return pcan_boot_process(&port->boot, pcan_boot_make_id(1U, 0U, 3U, 5U, PCAN_BOOT_MSG_CONTROL, (uint16_t)(0x0900U | cmd)), data, dlc); /* Формирует SessionID в старшем октете Body и команду в младшем. */
|
||
} /* Завершает подачу адресной команды. */
|
||
static void setup(pcan_boot_stm32_t *port, TEST_HANDLE *can) /* Подготавливает независимое начальное состояние перед каждым сценарием. */
|
||
{ /* Память вне приложения заполнена сторожевым значением для обнаружения повреждений. */
|
||
pcan_boot_config_t config = {0}; /* Исключает влияние полей новых версий общего API. */
|
||
tick = sends = resets = aborted = erases = writes = 0U; /* Сбрасывает наблюдаемые эффекты предыдущего сценария. */
|
||
unlocked = fail_erase = fail_write = corrupt_write = no_mailbox = pending = tx_error = fail_start = have_rx = false; /* Возвращает все аппаратные отказы в исходное состояние. */
|
||
cache_dirty = false; /* Новая сессия начинается без устаревшего cache-состояния предыдущей проверки. */
|
||
(void)memset(flash, 0xA5, 0x81000U); /* Сторожевое заполнение покрывает все 512 КиБ Flash и сторожевую страницу после физического конца. */
|
||
config.device_type = 3U; config.device_id = 5U; config.ack_window = 1U; config.session_timeout_ms = 1000U; /* Выбирает адрес и сессию короче модельного стирания. */
|
||
#if defined(TEST_STM32G474) /* Подготавливает независимую физическую геометрию G474 для каждого сценария. */
|
||
stm32_test_flash.OPTR = dual_bank ? FLASH_OPTR_DBANK : 0U; stm32_test_syscfg.MEMRMP = 0U; stm32_test_flash_kib = 512U; can->Init.ExtFiltersNbr = 2U; /* Сохраняет выбранный DBANK и выделяет два аппаратных extended-фильтра. */
|
||
#endif /* Завершает подготовку FDCAN/Flash G474. */
|
||
assert(PORT_INIT(port, can, &config)); /* Проверяет настоящую настройку single-slot, callbacks и двух фильтров. */
|
||
assert(port->boot.config.slot_count == 1U && port->boot.config.active_slot == 0U && port->boot.config.max_image_size == APP_END - APP_BASE); /* Исключает возврат старой фиктивной A/B-конфигурации. */
|
||
} /* Завершает подготовку сценария. */
|
||
static void begin(pcan_boot_stm32_t *port, const uint8_t *image, uint32_t size) /* Открывает полную сессию с двумя частями manifest и стиранием. */
|
||
{ /* Все ограничения проверяет общий production-автомат. */
|
||
uint8_t metadata[8] = {0}; /* Product/HW/FW тестового устройства равны нулю. */
|
||
assert(command(port, PCAN_BOOT_CMD_ENTER_BOOT, NULL, 0U)); /* Открывает адресную сессию 9. */
|
||
put32(metadata, size); put32(metadata + 4U, pcan_boot_crc32_update(0U, image, size)); /* Передаёт полезную длину и ожидаемый CRC без хвоста FF. */
|
||
assert(command(port, PCAN_BOOT_CMD_BEGIN_IMAGE, metadata, 8U)); /* Передаёт первую часть manifest. */
|
||
(void)memset(metadata, 0, sizeof(metadata)); /* Очищает поля для части совместимости. */
|
||
assert(command(port, PCAN_BOOT_CMD_BEGIN_COMPAT, metadata, 8U)); /* Передаёт совпадающий Product и диапазон HW. */
|
||
assert(command(port, PCAN_BOOT_CMD_ERASE, NULL, 0U)); /* Вызывает настоящую семейную процедуру стирания. */
|
||
assert(port->boot.state == PCAN_BOOT_STATE_RECEIVING && !unlocked); /* Стирание завершилось и Flash снова защищена. */
|
||
PORT_POLL(port); /* Проверяет, что двухсекундный erase не погасил секундную сессию. */
|
||
assert(port->boot.state == PCAN_BOOT_STATE_RECEIVING); /* Финальное обновление BUSY обязано продлить сессию после Flash stall. */
|
||
} /* Завершает подготовку к передаче блоков образа. */
|
||
static void upload(pcan_boot_stm32_t *port, const uint8_t *image, uint32_t size) /* Передаёт образ через общий decoder восьмибайтовых CAN-блоков. */
|
||
{ /* Последний блок всегда имеет DLC=8, а аппаратный порт получает только полезный остаток. */
|
||
uint32_t offset; /* Считает смещение полезных данных в байтах. */
|
||
for (offset = 0U; offset < size; offset += 8U) { /* Повторяет реальное блочное поведение ProtoCAN Boot. */
|
||
uint8_t block[8]; uint32_t count = size - offset; /* Выделяет полный payload и вычисляет фактический остаток. */
|
||
if (count > 8U) count = 8U; /* Не читает за границей переданного тестового образа. */
|
||
(void)memset(block, 0xFF, 8U); (void)memcpy(block, image + offset, count); /* Формирует wire-дополнение последнего кадра FF. */
|
||
assert(pcan_boot_process(&port->boot, pcan_boot_make_id(1U, 0U, 3U, 5U, PCAN_BOOT_MSG_DATA_A, (uint16_t)(offset / 8U)), block, 8U)); /* Вызывает production-запись выбранного логического слота A. */
|
||
assert(port->boot.state == PCAN_BOOT_STATE_RECEIVING && !unlocked && !cache_dirty); /* Каждый подтверждённый блок обязан оставить Flash защищённой и кэш синхронизированным. */
|
||
} /* Завершает передачу образа. */
|
||
} /* Завершает helper загрузки без обхода протокольной state machine. */
|
||
static void test_update_and_tails(TEST_HANDLE *can) /* Проверяет все возможные остатки аппаратных полуслов/слов и полный COMMIT/REBOOT. */
|
||
{ /* Размеры 16..23 покрывают все восемь вариантов последнего CAN-блока. */
|
||
uint32_t size; /* Перебирает длины полезного образа в байтах. */
|
||
for (size = 16U; size < 24U; ++size) { /* В каждом случае создаёт отдельную сессию обновления. */
|
||
pcan_boot_stm32_t port; uint8_t image[24] = {0}; /* Изолирует состояние и небольшой образ с корректными векторами. */
|
||
setup(&port, can); put32(image, RAM_TOP); put32(image + 4U, APP_BASE + 9U); /* Reset handler указывает внутрь образа, MSP лежит на верхней границе SRAM. */
|
||
begin(&port, image, size); upload(&port, image, size); /* Выполняет реальную последовательность erase и DATA. */
|
||
assert(memcmp((const void *)(uintptr_t)APP_BASE, image, size) == 0 && PORT_VECTORS()); /* Проверяет фактические байты Flash и векторы после программирования. */
|
||
assert(*(uint8_t *)(uintptr_t)(APP_BASE + size) == 0xFFU); /* За полезной длиной остаётся FF, включая дополнение последней аппаратной единицы записи. */
|
||
assert(*(uint8_t *)(uintptr_t)(APP_BASE - 1U) == 0xA5U && *(uint8_t *)(uintptr_t)APP_END == 0xA5U); /* Доказывает сохранность загрузчика и памяти сразу за приложением. */
|
||
assert(command(&port, PCAN_BOOT_CMD_REBOOT, NULL, 0U) && resets == 0U); /* Запрещает reset до VERIFY и COMMIT даже при корректных векторах. */
|
||
assert(command(&port, PCAN_BOOT_CMD_VERIFY, NULL, 0U) && port.boot.state == PCAN_BOOT_STATE_VERIFIED); /* Проверяет CRC именно прочитанной Flash. */
|
||
assert(command(&port, PCAN_BOOT_CMD_COMMIT, NULL, 0U) && port.ready); /* Разрешает активацию проверенного образа. */
|
||
pending = true; assert(!command(&port, PCAN_BOOT_CMD_REBOOT, NULL, 0U) && resets == 0U && aborted == 1U); /* Незавершённый TX ответа не должен вызвать reset. */
|
||
pending = false; tx_error = true; assert(!command(&port, PCAN_BOOT_CMD_REBOOT, NULL, 0U) && resets == 0U); /* Снятый с ошибкой mailbox также не подтверждает отправку ACK. */
|
||
tx_error = false; assert(command(&port, PCAN_BOOT_CMD_REBOOT, NULL, 0U) && resets == 1U); /* Подтверждённый TX допускает ровно один CMSIS reset. */
|
||
} /* Завершает перебор всех хвостов последнего CAN-блока. */
|
||
} /* Завершает основной end-to-end сценарий обновления. */
|
||
static void test_failures_and_transport(TEST_HANDLE *can) /* Проверяет повреждения Flash, пограничные входы и фильтрацию внешних кадров. */
|
||
{ /* Набор ошибок использует те же production-callbacks, что MCU. */
|
||
pcan_boot_stm32_t port; uint8_t image[16] = {0}, one = 0x5AU; uint32_t before; /* Подготавливает минимальный образ и однобайтовый служебный payload. */
|
||
setup(&port, can); put32(image, RAM_TOP); put32(image + 4U, APP_BASE + 9U); begin(&port, image, 16U); upload(&port, image, 16U); /* Записывает корректный образ для независимой проверки повреждения. */
|
||
*(uint8_t *)(uintptr_t)(APP_BASE + 12U) ^= 1U; /* Имитирует повреждение Flash после успешного получения потока. */
|
||
assert(command(&port, PCAN_BOOT_CMD_VERIFY, NULL, 0U) && port.boot.last_status == PCAN_BOOT_STATUS_CRC_ERROR && !port.ready); /* CRC потока остаётся верным, но readback обязан выявить ошибку. */
|
||
setup(&port, can); put32(image + 4U, APP_BASE + 33U); begin(&port, image, 16U); upload(&port, image, 16U); /* Создаёт корректный CRC, но reset handler находится за фактическим образом. */
|
||
assert(PORT_VECTORS() && command(&port, PCAN_BOOT_CMD_VERIFY, NULL, 0U) && port.boot.last_status == PCAN_BOOT_STATUS_INVALID_SIZE); /* Проверка диапазона профиля не заменяет проверку точной длины manifest. */
|
||
put32((uint8_t *)(uintptr_t)APP_BASE, RAM_TOP - 1U); assert(!PORT_VECTORS()); /* Отвергает невыровненный начальный стек внутри допустимой SRAM. */
|
||
put32((uint8_t *)(uintptr_t)APP_BASE, 0x20000000UL); assert(!PORT_VECTORS()); /* Нижняя граница SRAM не оставляет пространства для растущего вниз стека. */
|
||
put32(image + 4U, APP_BASE + 9U); /* Восстанавливает корректную точку входа для следующих независимых проверок. */
|
||
setup(&port, can); begin(&port, image, 16U); corrupt_write = true; /* Имитирует формальный успех HAL при неверном аппаратном readback. */
|
||
assert(!port.boot.port.write_slot(&port, 0U, 0U, image, 8U) && !unlocked && port.written == 0U); /* Не подтверждает ни байта повреждённой записи и блокирует Flash. */
|
||
corrupt_write = false; fail_write = true; assert(!port.boot.port.write_slot(&port, 0U, 0U, image, 8U) && !unlocked); /* При аппаратном отказе записи защита также восстанавливается. */
|
||
fail_write = false; before = writes; /* Начинает проверку входов, которые не должны дойти до HAL. */
|
||
assert(!port.boot.port.write_slot(&port, 1U, 0U, image, 8U) && !port.boot.port.write_slot(&port, 0U, 1U, image, 8U) && !port.boot.port.write_slot(&port, 0U, UINT32_MAX, image, 8U)); /* Отвергает чужой слот, невыровненный адрес и переполнение. */
|
||
assert(!port.boot.port.write_slot(&port, 0U, 0U, image, 3U) && writes == before); /* Не программирует неполное аппаратное слово в середине образа. */
|
||
assert(port.boot.port.send(&port, 1U, &one, 1U) && last_tx[0] == one && last_tx[1] == 0U && last_tx[7] == 0U); /* Короткий payload не читает семь отсутствующих октетов. */
|
||
no_mailbox = true; assert(!port.boot.port.send(&port, 1U, &one, 1U)); no_mailbox = false; /* Ожидание свободного TX ограничено тайм-аутом. */
|
||
fail_erase = true; assert(!port.boot.port.erase_slot(&port, 0U) && !unlocked); fail_erase = false; /* Отказ erase оставляет Flash заблокированной. */
|
||
port.boot.manifest.image_size = APP_END - APP_BASE + 1U; before = erases; assert(!port.boot.port.erase_slot(&port, 0U) && erases == before); /* Превышение области не вызывает ни одной аппаратной операции. */
|
||
setup(&port, can); before = sends; (void)memset(&rx_header, 0, sizeof(rx_header)); rx_header.ExtId = pcan_boot_make_id(1U, 0U, 3U, 5U, PCAN_BOOT_MSG_CONTROL, PCAN_BOOT_CMD_IDENTIFY); /* Готовит валидный адрес при неверном IDE. */
|
||
have_rx = true; PORT_POLL(&port); assert(sends == before); /* Отвергает стандартный CAN-кадр до общего decoder. */
|
||
rx_header.IDE = CAN_ID_EXT; rx_header.RTR = 2U; have_rx = true; PORT_POLL(&port); assert(sends == before); /* Отвергает remote-запрос вместо data-кадра. */
|
||
rx_header.RTR = CAN_RTR_DATA; rx_header.DLC = 9U; have_rx = true; PORT_POLL(&port); assert(sends == before); /* Отвергает DLC больше ёмкости classic CAN. */
|
||
rx_header.DLC = 0U; have_rx = true; PORT_POLL(&port); assert(sends == before + 1U); /* Валидный IDENTIFY проходит через настоящий FIFO adapter. */
|
||
rx_header.ExtId = pcan_boot_make_id(1U, 0U, 7U, 15U, PCAN_BOOT_MSG_DISCOVERY, 0U); have_rx = true; PORT_POLL(&port); tick += 300U; PORT_POLL(&port); assert(sends == before + 2U); /* Broadcast discovery выдаёт отложенный адресный ответ без открытия сессии. */
|
||
assert((filters[0].FilterMaskIdHigh & 0x8000U) == 0U && filters[1].FilterBank == 1U); /* Оба приоритета разрешены, discovery занимает отдельный банк. */
|
||
fail_start = true; assert(!PORT_INIT(&port, can, &port.boot.config)); PORT_POLL(&port); assert(port.can == NULL); /* Отказ инициализации делает последующий poll безопасным. */
|
||
} /* Завершает проверки отказов и транспортных границ. */
|
||
static void test_full_capacity(TEST_HANDLE *can) /* Проверяет округление последней страницы/сектора и запись последнего аппаратного слова. */
|
||
{ /* Полная ёмкость профиля затрагивает все разрешённые сектора, но не следующий. */
|
||
pcan_boot_stm32_t port; uint8_t bytes[8] = {0}; uint32_t size = APP_END - APP_BASE; /* Готовит граничные значения в байтах. */
|
||
setup(&port, can); port.boot.manifest.image_size = size; /* Задаёт максимальную длину без выделения большого wire-образа. */
|
||
assert(port.boot.port.erase_slot(&port, 0U) && !unlocked); /* Стирает все разрешённые страницы или секторы production-алгоритмом. */
|
||
port.written = size - 8U; assert(port.boot.port.write_slot(&port, 0U, size - 8U, bytes, 8U)); /* Проверяет последний допустимый блок точно на верхней границе. */
|
||
assert(*(uint8_t *)(uintptr_t)(APP_BASE - 1U) == 0xA5U && *(uint8_t *)(uintptr_t)APP_END == 0xA5U); /* Не допускает стирания либо программирования соседних защищённых областей. */
|
||
} /* Завершает проверку полного физического диапазона. */
|
||
int main(void) /* Запускает регрессии production-порта в изолированном процессе. */
|
||
{ /* Каждый процесс получает собственное отображение MCU Flash без доступа к устройству. */
|
||
TEST_HANDLE can = {0}; can.Instance = TEST_INSTANCE; /* Подготавливает заранее настроенный HAL handle выделенного CAN1/FDCAN1. */
|
||
#if defined(_WIN32) /* Выделяет фиксированный диапазон виртуальных адресов на Windows. */
|
||
flash = VirtualAlloc((void *)(uintptr_t)0x08000000UL, 0x81000U, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); /* Моделирует 512 КиБ Flash STM32 и одну сторожевую страницу, включая сторожевые области. */
|
||
assert(flash == (void *)(uintptr_t)0x08000000UL); /* Не выполняет тест, если адрес MCU недоступен в host-процессе. */
|
||
#else /* Создаёт эквивалентное анонимное отображение на POSIX. */
|
||
flash = mmap((void *)(uintptr_t)0x08000000UL, 0x81000U, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); /* Использует адрес как подсказку, не заменяя существующие отображения host-процесса. */
|
||
assert(flash == (void *)(uintptr_t)0x08000000UL); /* Не продолжает проверки, если адрес MCU занят или mmap завершился ошибкой. */
|
||
#endif /* Завершает выделение памяти модели. */
|
||
#if defined(TEST_STM32G474) /* Прогоняет общие сценарии в обоих исходных DBANK-режимах G474VE. */
|
||
for (unsigned mode = 0U; mode < 2U; ++mode) { /* Каждый режим независимо проверяет весь 480-КиБ слот и переход между физическими банками. */
|
||
pcan_boot_stm32_t port; uint32_t before; dual_bank = mode != 0U; /* Выбирает single-bank 4 КиБ либо dual-bank 2 КиБ страницы. */
|
||
test_update_and_tails(&can); test_failures_and_transport(&can); test_full_capacity(&can); /* Прогоняет полный flow, аппаратные ошибки, TX/RX и верхнюю границу Flash. */
|
||
setup(&port, &can); port.boot.manifest.image_size = 16U; before = erases; stm32_test_flash.OPTR |= FLASH_OPTR_BFB2; /* Подготавливает опасную перестановку boot-банков. */
|
||
assert(!port.boot.port.erase_slot(&port, 0U) && erases == before); stm32_test_flash.OPTR &= ~FLASH_OPTR_BFB2; /* Запрещённый BFB2 отвергается до HAL erase. */
|
||
stm32_test_syscfg.MEMRMP = SYSCFG_MEMRMP_FB_MODE; assert(!port.boot.port.erase_slot(&port, 0U) && erases == before); stm32_test_syscfg.MEMRMP = 0U; /* Программная перестановка банков также не должна изменить Flash. */
|
||
stm32_test_flash_kib = 256U; assert(!port.boot.port.erase_slot(&port, 0U) && erases == before); /* Профиль VE не должен стирать неизвестную меньшую физическую ёмкость. */
|
||
} /* Завершает независимую проверку обеих аппаратных геометрий. */
|
||
#else /* bxCAN-порты используют одну фиксированную аппаратную геометрию каждый. */
|
||
test_update_and_tails(&can); test_failures_and_transport(&can); test_full_capacity(&can); /* Проверяет нормальный flow, аппаратные отказы и обе границы памяти. */
|
||
#endif /* Завершает семейный набор host-сценариев. */
|
||
#if defined(_WIN32) /* Освобождает всю зарезервированную область Windows. */
|
||
assert(VirtualFree(flash, 0U, MEM_RELEASE)); /* Не оставляет виртуальную память после успешного теста. */
|
||
#else /* Освобождает POSIX-отображение. */
|
||
assert(munmap(flash, 0x81000U) == 0); /* Снимает модель Flash целиком. */
|
||
#endif /* Завершает освобождение host-ресурсов. */
|
||
puts("STM32 ProtoCAN port: OK"); return 0; /* Сообщает успешное завершение семейного прогона без аппаратных заявлений. */
|
||
} /* Завершает host-регрессию реального MCU-порта. */
|