Files
templates/c/protocan-boot/ports/stm32-common/boot_native.inc
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

128 lines
22 KiB
C++

static __attribute__((naked, noreturn)) void native_enter(uint32_t sp __attribute__((unused)), uint32_t pc __attribute__((unused))) /* Меняет стек без последующего обращения C-кода к старому кадру функции. */
{ /* AAPCS передаёт новый MSP в r0, Thumb entry в r1; последовательность совместима с GCC и Arm Compiler 6. */
__asm volatile("msr msp, r0\n" "movs r2, #0\n" "msr control, r2\n" "dsb\n" "isb\n" "cpsie i\n" "bx r1\n"); /* Возвращает привилегированный режим MSP, синхронизирует конвейер и передаёт управление Thumb-вектору. */
} /* Завершает переход без эпилога C после замены MSP. */
void SysTick_Handler(void) { ++ticks_ms; } /* Сохраняет миллисекундные часы только в самостоятельной сборке загрузчика. */
static bool native_vectors_valid(uint32_t size) /* Проверяет начальный стек и reset handler в пределах фактического образа. */
{ /* До чтения Flash отвергает некорректный размер. */
const uint32_t *vectors = (const uint32_t *)(uintptr_t)NATIVE_APP_BASE; /* Таблица векторов лежит в начале зарезервированной области приложения. */
uint32_t sp, pc; /* Хранит начальные значения Cortex-M без невыровненного доступа. */
if (size < 8U || size > NATIVE_APP_SIZE) return false; /* Для двух векторов нужно минимум восемь байт; верхняя граница профиля исключена. */
sp = vectors[0]; pc = vectors[1]; /* Читает MSP и Thumb-точку входа только после проверки геометрии. */
return sp > 0x20000000UL && sp <= NATIVE_RAM_END && (sp & 7U) == 0U && (pc & 1U) != 0U && (pc & ~1UL) >= NATIVE_APP_BASE && (pc & ~1UL) < NATIVE_APP_BASE + size; /* Требует восемь байт выравнивания стека и точку входа внутри выбранной длины приложения. */
} /* Структурная проверка не заменяет постоянный manifest после отключения питания. */
static void native_jump(uint32_t address) /* Отключает загрузочную периферию перед запуском приложения либо ROM. */
{ /* Вызывающий код предварительно проверяет векторы либо использует фиксированный заводской ROM. */
uint32_t index; /* Перебирает все банки NVIC Cortex-M3/M4 с запасом до восьми банков. */
const uint32_t *vectors = (const uint32_t *)(uintptr_t)address; /* Сохраняет адрес новой таблицы до отключения прерываний. */
uint32_t sp = vectors[0], pc = vectors[1]; /* Считывает аргументы перехода, пока C-стек ещё принадлежит загрузчику. */
__disable_irq(); SysTick->CTRL = 0U; /* Не позволяет SysTick загрузчика вызвать обработчик нового приложения в промежуточном состоянии. */
native_peripheral_stop(); /* Семейный порт отключает CAN и используемый UART, не трогая резервную память. */
for (index = 0U; index < 8U; ++index) { NVIC->ICER[index] = 0xFFFFFFFFUL; NVIC->ICPR[index] = 0xFFFFFFFFUL; } /* Сбрасывает enable/pending всех внешних IRQ записью W1C по CMSIS. */
SCB->ICSR = SCB_ICSR_PENDSTCLR_Msk | SCB_ICSR_PENDSVCLR_Msk; /* Убирает отложенные SysTick и PendSV до смены VTOR. */
SCB->VTOR = address; /* Согласует векторизацию с linker нового образа. */
native_enter(sp, pc); /* Передаёт управление без возврата и без обращения к старому C-стеку. */
} /* Завершает общую процедуру handoff. */
static bool native_image_valid(uint32_t size, uint32_t crc) /* Проверяет реальную Flash независимо от CRC принятого CAN/UART-потока. */
{ /* Одно правило проверки используется обоими транспортами KONOR. */
return native_vectors_valid(size) && pcan_boot_crc32_update(0U, (const uint8_t *)(uintptr_t)NATIVE_APP_BASE, size) == crc; /* Проверяет точную полезную длину, исключая заполнение FF последней аппаратной единицы. */
} /* Завершает проверку векторов и readback CRC. */
static bool native_write_bytes(uint32_t image_size, uint32_t offset, const uint8_t *data, uint16_t length) /* Записывает CAN-блок или UART-часть с одной проверкой физической геометрии. */
{ /* Неполная аппаратная единица разрешена только в самом конце образа. */
uint32_t done = 0U; bool ok = true; /* Считает полезные байты и сохраняет результат до восстановления блокировки Flash. */
if (image_size < 8U || image_size > NATIVE_APP_SIZE || data == NULL || length == 0U || offset > image_size || length > image_size - offset || offset % NATIVE_PROGRAM_UNIT != 0U) return false; /* Проверяет диапазоны вычитанием, исключая переполнение и неправильное выравнивание. */
if (length % NATIVE_PROGRAM_UNIT != 0U && offset + length != image_size) return false; /* Не допускает повторного программирования уже дополненного слова в середине потока. */
if (!native_unlock()) return false; /* Не изменяет заблокированную или занятую Flash. */
while (done < length) { /* Делит полезные октеты на аппаратные полуслова F1 или слова F4. */
uint32_t word = 0xFFFFFFFFUL, count = length - done; /* По умолчанию хвост аппаратной записи остаётся стёртым FF. */
if (count > NATIVE_PROGRAM_UNIT) count = NATIVE_PROGRAM_UNIT; /* Не копирует больше одной аппаратной единицы за итерацию. */
(void)memcpy(&word, data + done, count); /* Не читает за фактическим хвостом последнего блока. */
if (!native_program(NATIVE_APP_BASE + offset + done, word)) { ok = false; break; } /* Проверяет аппаратные флаги и фактический readback каждого записанного слова. */
done += count; /* Продвигается только на число полезных байтов без дополнения. */
} /* Завершает программирование текущей части образа. */
native_lock(); /* Восстанавливает защиту Flash как после успеха, так и после обработанной ошибки. */
return ok; /* Передаёт отказ общему CAN либо UART-автомату. */
} /* Завершает общую безопасную запись Flash. */
static bool native_send(void *user, uint32_t id, const pcan_boot_byte_t *data, pcan_boot_byte_t length) /* Передаёт служебный CAN-ответ с ожиданием реального TXOK до reset. */
{ /* Использует только mailbox 0 выделенного загрузочного CAN1. */
uint32_t start = ticks_ms, low = 0U, high = 0U; uint8_t index; bool sent; /* Сохраняет начало 100-мс тайм-аута и регистровые слова payload. */
(void)user; /* Самостоятельный загрузчик содержит один статический экземпляр CAN. */
if (length > 8U || id > 0x1FFFFFFFUL || (length != 0U && data == NULL)) return false; /* Не допускает чтение за DLC или передачу идентификатора шире 29 бит. */
while ((CAN1->TSR & CAN_TSR_TME0) == 0U) if ((uint32_t)(ticks_ms - start) >= 100U) return false; /* Ограничивает ожидание свободного mailbox 100 миллисекундами с учётом переполнения часов. */
for (index = 0U; index < length; ++index) { if (index < 4U) low |= (uint32_t)data[index] << (index * 8U); else high |= (uint32_t)data[index] << ((index - 4U) * 8U); } /* Читает только фактические октеты DLC, оставляя остальные позиции нулевыми. */
CAN1->TSR = CAN_TSR_RQCP0; /* Сбрасывает прежние TXOK/ошибки mailbox 0 записью W1C. */
CAN1->sTxMailBox[0].TDTR = length; CAN1->sTxMailBox[0].TDLR = low; CAN1->sTxMailBox[0].TDHR = high; /* Загружает длину и два безопасно сформированных слова данных. */
CAN1->sTxMailBox[0].TIR = (id << 3U) | CAN_TI0R_IDE | CAN_TI0R_TXRQ; /* Запускает расширенный data-кадр ProtoCAN без remote-флага. */
while ((CAN1->TSR & CAN_TSR_RQCP0) == 0U) if ((uint32_t)(ticks_ms - start) >= 100U) { CAN1->TSR = CAN_TSR_ABRQ0; return false; } /* Прерывает неподтверждённую передачу и запрещает reset после тайм-аута. */
sent = (CAN1->TSR & CAN_TSR_TXOK0) != 0U; CAN1->TSR = CAN_TSR_RQCP0; /* Проверяет ACK шины до очистки аппаратного результата. */
return sent; /* Ядро перезапустит MCU только после успешно отправленного ответа. */
} /* Завершает синхронную передачу ответа CAN. */
static bool native_erase_slot(void *user, pcan_boot_byte_t slot) /* Подключает проверенное стирание профиля к текущему ProtoCAN API. */
{ /* Аппаратная реализация семейства никогда не затрагивает загрузчик или EEPROM-кольцо. */
bool ok; (void)user; /* Хранит результат до финального обновления времени сессии. */
if (slot != 0U || boot.manifest.image_size < 8U || boot.manifest.image_size > NATIVE_APP_SIZE) return false; /* Разрешает только логический слот A и полный набор векторов. */
(void)pcan_boot_report_busy(&boot, PCAN_BOOT_CMD_ERASE, ticks_ms); /* Предупреждает клиента перед синхронным Flash stall. */
ok = native_erase(boot.manifest.image_size); /* Стирает только реально необходимые страницы или секторы. */
if (ok) (void)pcan_boot_report_busy(&boot, PCAN_BOOT_CMD_ERASE, ticks_ms); /* Продлевает сессию после завершения длительной аппаратной операции. */
return ok; /* Сообщает результат стирания общему автомату. */
} /* Завершает callback стирания CAN. */
static bool native_write_slot(void *user, pcan_boot_byte_t slot, uint32_t offset, const pcan_boot_byte_t *data, pcan_boot_byte_t length) /* Подключает единый Flash writer к последовательным блокам CAN. */
{ /* Последовательность номеров блоков и максимум DLC уже проверены общим автоматом. */
(void)user; /* Единственный образ управляется статическим экземпляром загрузчика. */
return slot == 0U && offset == boot.bytes_received && native_write_bytes(boot.manifest.image_size, offset, data, length); /* Не допускает произвольное смещение даже при прямом вызове callback. */
} /* Завершает callback записи CAN. */
static pcan_boot_status_t native_verify(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *manifest) /* Различает повреждение реальной Flash и неверную таблицу векторов. */
{ /* Операция не доверяет одному CRC входящего потока. */
(void)user; /* Использует фиксированную область выбранного семейного профиля. */
if (slot != 0U || manifest == NULL || !native_vectors_valid(manifest->image_size)) return PCAN_BOOT_STATUS_INVALID_SIZE; /* Отвергает неверный слот, геометрию и точку входа до чтения всего образа. */
return native_image_valid(manifest->image_size, manifest->image_crc32) ? PCAN_BOOT_STATUS_OK : PCAN_BOOT_STATUS_CRC_ERROR; /* Перечитывает Flash общим CRC32 ProtoCAN. */
} /* Завершает независимую проверку принятого образа. */
static bool native_commit(void *user, pcan_boot_byte_t slot, const pcan_boot_manifest_t *manifest) { return native_verify(user, slot, manifest) == PCAN_BOOT_STATUS_OK; } /* COMMIT повторно проверяет single-slot образ без имитации постоянных метаданных. */
static bool native_can_reboot(void *user) { (void)user; return boot.state == PCAN_BOOT_STATE_COMMITTED; } /* Протокольный reset разрешён только после VERIFY и COMMIT. */
static void native_reboot(void *user) { (void)user; NVIC_SystemReset(); } /* Вызывается ядром только после успешного TXOK ответа REBOOT. */
static bool native_init(void) /* Инициализирует текущий API ядра без позиционных структур старой версии. */
{ /* Идентичность устройства задаёт профиль либо конфигурация проекта. */
pcan_boot_config_t config = {0}; pcan_boot_port_t callbacks = {0}; /* Обнуляет необязательные поля и callbacks расширяемого интерфейса. */
config.device_type = PCAN_BOOT_DEVICE_TYPE; config.device_id = PCAN_BOOT_DEVICE_ID; config.product_type = PCAN_BOOT_PRODUCT_TYPE; /* Сохраняет адрес и Product исходного проекта. */
config.hardware_revision = PCAN_BOOT_HW_REVISION; config.firmware_version = PCAN_BOOT_FW_VERSION; /* Сохраняет совместимость manifest по HW/FW. */
config.active_slot = 0U; config.slot_count = 1U; config.max_image_size = NATIVE_APP_SIZE; /* Явно использует single-slot A вместо прежней фиктивной A/B-конфигурации. */
config.ack_window = PCAN_BOOT_ACK_WINDOW; config.session_timeout_ms = 30000UL; /* Сохраняет исходное окно ACK и прерывает неактивную RAM-сессию через 30 секунд. */
callbacks.send = native_send; callbacks.erase_slot = native_erase_slot; callbacks.write_slot = native_write_slot; /* Подключает аппаратный транспорт и защищённую область Flash. */
callbacks.verify_status = native_verify; callbacks.set_pending_slot = native_commit; callbacks.can_reboot = native_can_reboot; callbacks.reboot = native_reboot; /* Подключает readback, COMMIT и согласованную политику reset. */
return pcan_boot_init(&boot, &config, &callbacks, NULL); /* Проверяет конфигурацию и создаёт единственный экземпляр общего автомата. */
} /* Завершает настройку ProtoCAN независимо от включения аппаратной шины. */
static bool native_can_init(void) /* Настраивает выделенный CAN1 и два фильтра для адресных запросов и discovery. */
{ /* Тактирование GPIO и CAN включает семейная функция; UART во время Flash/CAN init не изменяется. */
static const uint8_t shifts[8] = {0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U}; /* RCC кодирует APB1: значения 0..3 без деления, 4..7 дают делители 2,4,8,16. */
uint32_t clock = SystemCoreClock >> shifts[(RCC->CFGR >> NATIVE_PPRE1_SHIFT) & 7U]; /* Получает действительную частоту APB1 в Гц, без удвоения timer clocks. */
uint32_t tq, btr = 0U, started, index; bool found = false; /* Сохраняет bit timing, тайм-аут init и индекс фильтра. */
if (clock % PCAN_BOOT_CAN_BITRATE != 0U) return false; /* Не округляет скорость шины при неподходящем clock tree. */
for (tq = 18U; tq >= 8U; --tq) { /* Сохраняет диапазон 8..18 time quanta из исходного KONOR. */
uint32_t divisor = clock / PCAN_BOOT_CAN_BITRATE, prescaler = divisor / tq, ts2 = tq / 5U; /* Выбирает sample point около 80% при допустимом целочисленном делителе. */
if (divisor % tq == 0U && prescaler >= 1U && prescaler <= 1024U) { btr = ((ts2 - 1U) << 20U) | ((tq - 2U - ts2) << 16U) | (prescaler - 1U); found = true; break; } /* Кодирует BRP/TS1/TS2 с аппаратным смещением минус один и SJW=1 tq. */
} /* Завершает поиск точной скорости для штатного тактирования F103/F407. */
if (!found) return false; /* Не включает шину с неизвестными битовыми интервалами. */
native_gpio_init(); CAN1->MCR = CAN_MCR_INRQ | CAN_MCR_ABOM; started = ticks_ms; /* Включает семейные GPIO и запрашивает init с автоматическим восстановлением bus-off. */
while ((CAN1->MSR & CAN_MSR_INAK) == 0U) if ((uint32_t)(ticks_ms - started) >= 100U) return false; /* Ограничивает вход в CAN init ста миллисекундами. */
CAN1->BTR = btr; CAN1->FMR |= CAN_FMR_FINIT; CAN1->FA1R = 0U; CAN1->FS1R |= 3U; CAN1->FM1R &= ~3UL; CAN1->FFA1R &= ~3UL; /* Настраивает только два 32-битных mask-фильтра в FIFO0 и отключает чужие банки загрузчика. */
for (index = 0U; index < 2U; ++index) { /* Банк 0 принимает собственный адрес, банк 1 — только broadcast discovery. */
uint32_t id = index == 0U ? pcan_boot_make_id(0U, 0U, PCAN_BOOT_DEVICE_TYPE, PCAN_BOOT_DEVICE_ID, 0U, 0U) : pcan_boot_make_id(0U, 0U, 7U, 15U, PCAN_BOOT_MSG_DISCOVERY, 0U); /* Не маскирует приоритет, сохраняя оба штатных уровня ProtoCAN. */
uint32_t mask = index == 0U ? 0x0FF00000UL : 0x0FFFFFFFUL; /* Адресный фильтр оставляет MsgType/Body открытыми; discovery сравнивает их точно. */
CAN1->sFilterRegister[index].FR1 = (id << 3U) | CAN_RI0R_IDE; CAN1->sFilterRegister[index].FR2 = (mask << 3U) | CAN_RI0R_IDE | CAN_RI0R_RTR; /* Исключает standard и remote кадры на аппаратном уровне. */
} /* Завершает настройку обоих фильтров. */
CAN1->FA1R |= 3U; CAN1->FMR &= ~CAN_FMR_FINIT; CAN1->MCR &= ~CAN_MCR_INRQ; started = ticks_ms; /* Активирует фильтры и переводит CAN в рабочий режим. */
while ((CAN1->MSR & CAN_MSR_INAK) != 0U) if ((uint32_t)(ticks_ms - started) >= 100U) return false; /* Отсутствие доступной шины не должно навсегда блокировать обычный запуск приложения. */
return true; /* Рабочий CAN готов принимать адресные команды и discovery. */
} /* Завершает общую аппаратную настройку bxCAN. */
static void native_can_poll(void) /* Обрабатывает не больше трёх кадров — аппаратной глубины FIFO bxCAN. */
{ /* Длительные операции Flash выполняются только из основного цикла. */
uint32_t count; pcan_boot_poll(&boot, ticks_ms); /* Обновляет тайм-аут сессии и отложенное discovery независимо от входящих команд. */
for (count = 0U; count < 3U && (CAN1->RF0R & CAN_RF0R_FMP0) != 0U; ++count) { /* Ограничивает время одного вызова на нагруженной шине. */
uint32_t rir = CAN1->sFIFOMailBox[0].RIR, low = CAN1->sFIFOMailBox[0].RDLR, high = CAN1->sFIFOMailBox[0].RDHR; /* Сохраняет поля до освобождения аппаратного FIFO. */
uint8_t dlc = (uint8_t)(CAN1->sFIFOMailBox[0].RDTR & 15U), data[8], index; /* Читает входящий DLC отдельно от фиксированного буфера classic CAN. */
for (index = 0U; index < 8U; ++index) data[index] = (uint8_t)(index < 4U ? low >> (index * 8U) : high >> ((index - 4U) * 8U)); /* Извлекает октеты без невыровненного чтения аппаратных регистров. */
CAN1->RF0R |= CAN_RF0R_RFOM0; /* Освобождает обработанный элемент FIFO0 до потенциально долгого erase. */
if ((rir & CAN_RI0R_IDE) != 0U && (rir & CAN_RI0R_RTR) == 0U && dlc <= 8U) { pcan_boot_poll(&boot, ticks_ms); (void)pcan_boot_process(&boot, (rir >> 3U) & 0x1FFFFFFFUL, data, dlc); } /* Повторно проверяет формат внешнего кадра и вызывает общий автомат. */
} /* Завершает ограниченный пакет входящих кадров. */
} /* Завершает обслуживание транспорта и часов ядра. */