Files
templates/c/protocan-boot/tests/test_stm32_ports.c
T
Andrey c6390ae1c9 feat(protocan-boot): Добавь порты MCU и сервисы загрузки
Обеспечь единый процесс обновления для STM32F103/F407/G474 и TMS320F2812/F28335: discovery, служебные ответы, BUSY, тайм-аут сессии и подтверждение приложения. Добавь самостоятельные проекты CCS, аппаратные хранилища и Python-клиент.

Совместимость: структуры конфигурации и порта расширены, поэтому потребители должны пересобраться и обнулить новые поля. Для C28x callbacks используют pcan_boot_byte_t; периодический pcan_boot_poll обслуживает discovery и настроенный тайм-аут.
2026-10-23 14:16:19 +03:00

260 lines
43 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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-порта. */