Добавить загрузку F28335 по ProtoCAN и сервисные команды клиента

This commit is contained in:
2026-10-11 03:24:44 +03:00
parent f1a7bcd8bc
commit 3c0f6fd9e6
23 changed files with 1702 additions and 97 deletions

View File

@@ -1,6 +1,6 @@
# Сборка библиотеки protocan_boot. Состав исходников и публичные include-пути задают
# подключение к проекту потребителя. Файл также собирает и регистрирует хостовые проверки;
# запускать их следует через CTest из каталога сборки.
# Сборка библиотеки protocan_boot. Состав исходников и публичные include-пути задают
# подключение к проекту потребителя. Файл также собирает и регистрирует хостовые проверки;
# запускать их следует через CTest из каталога сборки.
cmake_minimum_required(VERSION 3.13)
project(protocan_boot C)
@@ -17,10 +17,31 @@ else()
target_compile_options(protocan_boot PRIVATE -Wall -Wextra -Wpedantic)
endif()
option(PCAN_BOOT_BUILD_TESTS "Собирать тесты ProtoCAN boot" ON)
option(PCAN_BOOT_BUILD_TESTS "Собирать тесты ProtoCAN boot" ON)
if(PCAN_BOOT_BUILD_TESTS)
enable_testing()
add_executable(test_pcan_boot tests/test_pcan_boot.c)
target_link_libraries(test_pcan_boot PRIVATE protocan_boot)
add_test(NAME pcan_boot COMMAND test_pcan_boot)
endif()
if(PCAN_BOOT_BUILD_TESTS)
add_executable(test_f28335_can_boot tests/test_f28335_can_boot.c src/pcan_boot.c ports/tms320f28335/f28335_boot_store.c)
target_compile_definitions(test_f28335_can_boot PRIVATE PCAN_BOOT_WIDE_BYTES)
target_include_directories(test_f28335_can_boot PRIVATE tests/f28335_stubs)
add_test(NAME f28335_can_boot COMMAND test_f28335_can_boot)
endif()
if(PCAN_BOOT_BUILD_TESTS)
add_executable(test_f28335_application tests/test_f28335_can_boot.c src/pcan_boot.c ports/tms320f28335/f28335_boot_store.c)
target_compile_definitions(test_f28335_application PRIVATE PCAN_BOOT_WIDE_BYTES F28335_BOOT_LOADER=0)
target_include_directories(test_f28335_application PRIVATE tests/f28335_stubs)
add_test(NAME f28335_application COMMAND test_f28335_application)
foreach(width native wide)
add_executable(test_f28335_store_${width} tests/test_f28335_boot_store.c ports/tms320f28335/f28335_boot_store.c)
if(width STREQUAL "wide")
target_compile_definitions(test_f28335_store_${width} PRIVATE PCAN_BOOT_WIDE_BYTES)
endif()
add_test(NAME f28335_store_${width} COMMAND test_f28335_store_${width})
endforeach()
endif()

View File

@@ -97,7 +97,8 @@ typedef struct {
} pcan_boot_port_t;
```
Обязательны `send`, `erase_slot`, `write_slot` и `set_pending_slot`.
Обязательны `send`, `erase_slot`, `write_slot`; в A/B также `set_pending_slot`.
В single-slot этот callback необязателен и может завершать активацию образа.
`authorize` проверяет политику до стирания (тип, версия, anti-rollback).
`verify_image` выполняет платформенную проверку подписанного контейнера после
CRC32. Если callbacks отсутствуют, соответствующие дополнительные проверки
@@ -175,5 +176,12 @@ ctest --test-dir build --output-on-failure
- включить watchdog и rollback при неподтверждённом запуске;
- согласовать фильтры CAN для `MsgType=0x9..0xD`.
Проект пока не подключён ни к одной конкретной плате; это самостоятельный
шаблон для интеграции в будущие загрузчики приборов SET.
Ядро поддерживает платформенные порты; особенности интеграции перечислены ниже.
## F28335 / большая EEPROM
[Порт M95M02](ports/tms320f28335/README.md) подключён в `tms_periph/periph_167`.
Использует отдельный Flash-загрузчик и два слота EEPROM по 130560 байт.
Реализованы readback/CRC, проверка SCI8, журнал метаданных, пробный запуск,
CONFIRM/откат, PING/discovery и сервисный паспорт. Первичная установка Flash-
загрузчика и выбор Flash boot описаны в README порта.

View File

@@ -63,6 +63,10 @@ BlockIndex = 0x0000…0xFFFF
| `0x09` | `REBOOT` | 0 | отсутствует | активная сессия |
| `0x0A` | `ABORT` | 0 | отсутствует | активная сессия |
| `0x0B` | `QUERY_PROGRESS` | 0 | отсутствует | активная сессия |
| `0x0C` | `PING` | 0 | отсутствует | любое |
| `0x0D` | `CAPABILITIES` | 0 | отсутствует | любое, при поддержке порта |
| `0x0E` | `DEVICE_NAME` | 1 | номер страницы имени | любое, при поддержке порта |
| `0x0F` | `MEMORY_INFO` | 1 | номер страницы памяти | любое, при поддержке порта |
### `BEGIN_IMAGE`
@@ -81,7 +85,8 @@ DATA[4..7] FirmwareVersion, uint32 little-endian
```
До `ERASE` прибор обязан получить обе части метаданных и проверить размер,
тип изделия, аппаратную ревизию, версию и политику anti-rollback.
тип изделия и аппаратную ревизию. Проверка версии/anti-rollback выполняется
только при наличии соответствующей политики платформы (callback authorize).
## `BOOT_STATUS`
@@ -181,3 +186,16 @@ Boot metadata должна атомарно хранить:
5. ПМ выполняет `ERASE` и передаёт `BOOT_DATA_A` либо `BOOT_DATA_B`.
6. ПМ выполняет `VERIFY`, затем `COMMIT` и `REBOOT`.
7. Новое приложение после самопроверки выполняет `CONFIRM`.
## F28335 M95M02
Реализованы отдельный Flash-загрузчик, A/B по 130560 байт, журнал, пробный запуск
и откат. Подробная раскладка памяти, сервисных ответов и первичная установка:
[README порта](../ports/tms320f28335/README.md).
Общий discovery: Route=0, DeviceType=7, DeviceID=15, MsgType=D, Body=0, DLC=0.
Он только запрашивает IDENTIFY; операции изменения всегда адресные.
ENTER_BOOT может отложить ACK до готовности отдельного загрузчика после reset.
CONFIRM с ненулевой session может обрабатываться приложением без ENTER_BOOT.
`pcan_boot_poll` обслуживает задержку discovery и тайм-аут сессии, если он задан
портом. Старым портам с timeout=0 периодический вызов нужен только для discovery.

View File

@@ -11,6 +11,13 @@
#include <stddef.h>
#include <stdint.h>
/* C28x has 16-bit addressable chars; wire octets use the low eight bits. */
#if defined(__TMS320C28XX__) || defined(PCAN_BOOT_WIDE_BYTES)
typedef unsigned int pcan_boot_byte_t;
#else
typedef uint8_t pcan_boot_byte_t;
#endif
#ifdef __cplusplus
extern "C" {
#endif
@@ -41,7 +48,11 @@ typedef enum {
PCAN_BOOT_CMD_CONFIRM = 0x08U,
PCAN_BOOT_CMD_REBOOT = 0x09U,
PCAN_BOOT_CMD_ABORT = 0x0AU,
PCAN_BOOT_CMD_QUERY_PROGRESS = 0x0BU
PCAN_BOOT_CMD_QUERY_PROGRESS = 0x0BU,
PCAN_BOOT_CMD_PING = 0x0CU,
PCAN_BOOT_CMD_CAPABILITIES = 0x0DU,
PCAN_BOOT_CMD_DEVICE_NAME = 0x0EU,
PCAN_BOOT_CMD_MEMORY_INFO = 0x0FU
} pcan_boot_command_t;
typedef enum {
@@ -66,7 +77,8 @@ typedef enum {
PCAN_BOOT_STATE_READY_TO_ERASE,
PCAN_BOOT_STATE_RECEIVING,
PCAN_BOOT_STATE_VERIFIED,
PCAN_BOOT_STATE_FAILED
PCAN_BOOT_STATE_FAILED,
PCAN_BOOT_STATE_COMMITTED
} pcan_boot_state_t;
typedef struct {
@@ -74,36 +86,47 @@ typedef struct {
uint32_t image_crc32;
uint32_t firmware_version;
uint16_t product_type;
uint8_t hardware_revision_min;
uint8_t hardware_revision_max;
pcan_boot_byte_t hardware_revision_min;
pcan_boot_byte_t hardware_revision_max;
} pcan_boot_manifest_t;
typedef struct {
uint8_t device_type;
uint8_t device_id;
pcan_boot_byte_t device_type;
pcan_boot_byte_t device_id;
uint16_t product_type;
uint8_t hardware_revision;
pcan_boot_byte_t hardware_revision;
uint32_t firmware_version;
uint8_t active_slot;
pcan_boot_byte_t active_slot;
/** 1 for an in-place/single-slot target, 2 for A/B. Zero keeps legacy A/B. */
uint8_t slot_count;
pcan_boot_byte_t slot_count;
/** Writable image capacity. Zero uses PCAN_BOOT_SLOT_SIZE. */
uint32_t max_image_size;
uint8_t ack_window; /**< 1 — ACK каждого блока, 16 — ACK каждых 16 блоков. */
pcan_boot_byte_t ack_window; /**< 1 — ACK каждого блока, 16 — ACK каждых 16 блоков. */
uint32_t session_timeout_ms; /**< 0 disables timeout for legacy ports. */
} pcan_boot_config_t;
typedef struct {
bool (*send)(void *user, uint32_t can_id, const uint8_t *data, uint8_t dlc);
bool (*erase_slot)(void *user, uint8_t slot);
bool (*write_slot)(void *user, uint8_t slot, uint32_t offset,
const uint8_t *data, uint8_t length);
bool (*send)(void *user, uint32_t can_id, const pcan_boot_byte_t *data, pcan_boot_byte_t dlc);
bool (*erase_slot)(void *user, pcan_boot_byte_t slot);
bool (*write_slot)(void *user, pcan_boot_byte_t slot, uint32_t offset,
const pcan_boot_byte_t *data, pcan_boot_byte_t length);
bool (*authorize)(void *user, const pcan_boot_manifest_t *manifest);
bool (*verify_image)(void *user, uint8_t slot,
bool (*verify_image)(void *user, pcan_boot_byte_t slot,
const pcan_boot_manifest_t *manifest);
bool (*set_pending_slot)(void *user, uint8_t slot,
/** A/B metadata, or optional single-slot activation after verification. */
bool (*set_pending_slot)(void *user, pcan_boot_byte_t slot,
const pcan_boot_manifest_t *manifest);
bool (*confirm_running_slot)(void *user);
void (*reboot)(void *user);
/** ENTER: 0 ready, 1 deferred ACK after reset, 2 denied. NULL = legacy ready. */
unsigned int (*enter_boot)(void *user, pcan_boot_byte_t session);
bool (*can_reboot)(void *user);
/** Eight-octet service response, addressed by command + page. */
bool (*service_info)(void *user, pcan_boot_byte_t command,
pcan_boot_byte_t page, pcan_boot_byte_t *out);
/** Optional precise result, replacing legacy boolean verify_image. */
pcan_boot_status_t (*verify_status)(void *user, pcan_boot_byte_t slot,
const pcan_boot_manifest_t *manifest);
} pcan_boot_port_t;
typedef struct {
@@ -115,11 +138,13 @@ typedef struct {
uint32_t running_crc32;
uint32_t bytes_received;
uint16_t next_block;
uint8_t session_id;
uint8_t target_slot;
uint8_t last_status;
pcan_boot_byte_t session_id;
pcan_boot_byte_t target_slot;
pcan_boot_byte_t last_status;
bool have_image;
bool have_compat;
uint32_t now_ms, last_activity_ms, discovery_at;
bool discovery_pending;
} pcan_boot_t;
/** Инициализирует экземпляр. Все обязательные callbacks должны быть заданы. */
@@ -134,16 +159,19 @@ void pcan_boot_abort(pcan_boot_t *boot);
* Возвращает true, если кадр принадлежал загрузочному сервису этого прибора.
*/
bool pcan_boot_process(pcan_boot_t *boot, uint32_t can_id,
const uint8_t *data, uint8_t dlc);
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc);
/** Call regularly with a wrapping monotonic millisecond clock. */
void pcan_boot_poll(pcan_boot_t *boot, uint32_t now_ms);
/** Переносимые операции с 29-битным ProtoCAN ID. */
uint32_t pcan_boot_make_id(uint8_t priority, uint8_t route,
uint8_t device_type, uint8_t device_id,
uint8_t msg_type, uint16_t msg_body);
uint8_t pcan_boot_id_route(uint32_t can_id);
uint8_t pcan_boot_id_device_type(uint32_t can_id);
uint8_t pcan_boot_id_device_id(uint32_t can_id);
uint8_t pcan_boot_id_msg_type(uint32_t can_id);
uint32_t pcan_boot_make_id(pcan_boot_byte_t priority, pcan_boot_byte_t route,
pcan_boot_byte_t device_type, pcan_boot_byte_t device_id,
pcan_boot_byte_t msg_type, uint16_t msg_body);
pcan_boot_byte_t pcan_boot_id_route(uint32_t can_id);
pcan_boot_byte_t pcan_boot_id_device_type(uint32_t can_id);
pcan_boot_byte_t pcan_boot_id_device_id(uint32_t can_id);
pcan_boot_byte_t pcan_boot_id_msg_type(uint32_t can_id);
uint16_t pcan_boot_id_msg_body(uint32_t can_id);
#ifdef __cplusplus

View File

@@ -0,0 +1,156 @@
# F28335 + M95M02: CAN, отдельный загрузчик и A/B
Реализация состоит из Flash-загрузчика `periph_167/Bootloader`, переносимого
`f28335_boot_store.c`, аппаратного `f28335_boot_hw.c` и адаптера
`f28335_can_boot.c`. Приложение подключает их через `Source/Internal/can_boot.c`.
В сборке `EEPROM_M95M02=0` CAN Boot отключён. Для установленной M95M02 — значение 1.
## Адрес и формат
Device Type **3**, Device ID **7**, имя **F28335-M95M02**, Product **0x2833**, HW **1**.
В приложении FW берётся из `firmware_version.h`, формат `major<<24 | minor<<16 | patch`;
для версии 1.1.2 это `0x01010002`. Загрузчик сообщает версию подтверждённого образа
из журнала, при отсутствии такого образа — 0. Версия кандидата передаётся в BEGIN_COMPAT.
CAN **250000 бит/с**, Extended ID, окно **16 блоков по 8 октетов**.
ENTER_BOOT для session=1: `0x13790102`, STATUS: `0x1B7C0102`.
Образ — SCI8 BIN (`AA 08`), чётная длина от 24 до **130560 байт** включительно.
Числа в протоколе little endian. Адреса внутри SCI8: сначала старшее 16-битное
слово, затем младшее; каждое слово little endian. В C28x `char` 16-битный,
поэтому `pcan_boot_byte_t` хранит октет в младших 8 битах.
## Разметка EEPROM, байтовые адреса
| Область | Начало | Конец включительно | Размер |
|---|---:|---:|---:|
| Журнал 0 | 0x00000 | 0x000FF | 256 |
| Журнал 1 | 0x00100 | 0x001FF | 256 |
| Slot A | 0x00200 | 0x1FFFF | 130560 |
| Slot B | 0x20000 | 0x3FDFF | 130560 |
| Резерв | 0x3FE00 | 0x3FEFB | 252 |
| Параметры | 0x3FEFC | 0x3FFFD | 258 |
| Последнее слово | 0x3FFFE | 0x3FFFF | 2 |
В каждой странице журнала первые 64 октета содержат magic `FAB2`, версию 1,
sequence, active/pending/attempted, два manifest, CRC32 первых 56 октетов и
commit marker в октетах 60..63. Сначала обнуляется marker запасной страницы,
затем записывается тело с проверкой чтением, последним — marker. Предыдущая
страница остаётся действующей до полной публикации новой. Выбор sequence
учитывает переполнение u32. После ошибки публикации журнал перечитывается;
ошибка чтения блокирует последующие записи.
## Сценарий обновления и отката
1. Приложение принимает ENTER_BOOT, останавливает управление нагрузкой, выставляет 4 мА и выдерживает
секундный STOP, затем сохраняет session в NOINIT RAM 0x50..0x57. Watchdog выполняет reset.
ACK ENTER_BOOT отправляет уже Flash-загрузчик; приложение ничего не стирает.
2. BEGIN_IMAGE/BEGIN_COMPAT проверяют размер, Product, HW и сессию. Неактивный
слот выбирается автоматически; при первом образе это A. ERASE инвалидирует
только его manifest. Полного стирания EEPROM не требуется.
3. Последовательные DATA_A/B записываются с немедленным readback. VERIFY
проверяет потоковый CRC32, CRC32 EEPROM и полную таблицу SCI8.
Разрешена загрузка только в CPU word RAM `[0x8000,0xE000)`: максимум 64 секции,
без пересечений, усечений и хвостовых данных; entry должен попасть в секцию.
Запись в Flash, периферию, стек, RAM загрузчика и handoff запрещена.
4. COMMIT повторно проверяет образ и публикует pending в журнале. Подтверждённый
слот сохраняется. Повтор COMMIT безопасен. REBOOT выполняется после физического
CAN ACK ответа. Потеря ACK/тайм-аут передачи отменяет запрос reset.
5. Перед первым запуском кандидата загрузчик сохраняет attempted=1, затем
проверяет и копирует SCI8 в RAM. Следующий reset без CONFIRM сбрасывает pending
и запускает прежний active. Если прежнего образа нет — остаётся CAN recovery.
6. Новое приложение включает watchdog в начале запуска. До подтверждения
прерывания не кормят watchdog вместо основного цикла. После READY, 100 проходов
main и 5 секунд выполняется автоматический CONFIRM. Неудача сохранения
подтверждения через 10 секунд прекращает кормление watchdog, вызывая откат.
Адресная команда CONFIRM с ненулевой session также разрешена после READY.
CONFIRM в загрузчике и до первого запуска кандидата отклоняется.
PING, IDENTIFY и паспорт доступны без ENTER_BOOT. QUERY_PROGRESS/ABORT привязаны
к сессии. Сессия в RAM истекает через 30 секунд без подходящих команд/данных.
После reset передачу начинают заново; параметры и подтверждённый образ остаются.
ABORT после COMMIT не отменяет опубликованный pending. Для его пробного запуска
можно открыть новую сессию и отправить REBOOT. Pending запрещено перезаписывать.
Повторы ENTER_BOOT, метаданных, ERASE до первого блока, VERIFY и COMMIT учитывают
потерю ответа. Broadcast никогда не разрешает запись.
## Сервисные команды
Запрос: MsgType=9, Body=`session<<8 | command`. IDENTIFY/PING/паспорт не требуют сессии.
Ответ паспорта: MsgType=D, Body=`page<<8 | command`, DLC=8.
| Код | Имя | DLC запроса | Ответ |
|---:|---|---:|---|
| 01 | IDENTIFY | 0 | Product:u16, HW:u8, protocol:u8, FW:u32; Body=1 |
| 0C | PING | 0 | protocol, state, active, target, session, slot_count, last_status, 0 |
| 0D | CAPABILITIES | 0 | flags:u32, slot_capacity:u32 |
| 0E | DEVICE_NAME | 1 (page) | 8 ASCII-октетов имени; последний фрагмент с NUL |
| 0F | MEMORY_INFO | 1 (page) | Страницы ниже |
MEMORY_INFO 0: EEPROM bytes:u32, page bytes:u16, mode:u8 (0 app/1 boot), running:u8.
Страница 1: адреса A:u32, B:u32. Страница 2: active, pending, attempted, storage_healthy,
session_timeout_ms:u32. Страница 3: parameters_offset:u32, parameters_bytes:u32.
Слот FF означает отсутствие образа/запущенного приложения.
CAPABILITIES bits 0..11: ping, discovery, identify, name, memory, A/B, confirm,
rollback, separate loader, session timeout, readback, SCI8 validation.
Приложение, запущенное старым SPI ROM без handoff загрузчика, сообщает только
базовые сервисы (0x1F) и отклоняет ENTER_BOOT.
Обнаружение: MsgType=D, Body=0, DLC=0, Route=0. Адресный запрос или общий
DeviceType=7/DeviceID=15. Ответ IDENTIFY приходит с адреса прибора через
`2*(1+16*DeviceType+DeviceID)` мс. При нескольких одинаковых адресах конфликт
остаётся: каждому физическому устройству нужен уникальный адрес.
Python API: `protocan.can_boot.CanBootServices` — запросы и разбор паспорта;
`CanBootTransfer` — загрузка. Кнопка «Пинг» доступна в режиме ProtoCAN Boot
окна прошивки SETGUI. Для ENTER/VERIFY/COMMIT клиент ждёт до 15 секунд.
## Аппаратная интеграция
MBOX3 — адресные входящие Boot; MBOX4 — ответы; MBOX6 — точный broadcast discovery.
MBOX0..2 сохраняют legacy/UMP, MBOX5 остаётся heartbeat. ISR помещает сообщения
в очередь на 31 кадр. Чужие адреса, RTR и неподходящие типы отбрасываются до очереди.
При переполнении/потере mailbox сессия отменяется; повторная запись начинается заново.
SPI-драйвер загрузчика имеет ограничение ожидания байта и WIP, 24-битный адрес,
разбиение по страницам 256 байт и восстановление конфигурации SPI приложения.
Он работает только при остановленном Timer2. Обычный Seeprom_write приложения
ограничен областью параметров; старый RS EXTEND 0x44 запрещён для A/B.
Загрузчик исполняется из внутренней Flash A 0x338000..0x33FF7F, entry 0x33FFF6.
Его RAM — 0xE000..0xFFFF; стек — 0x400..0x7FF. Приложение M95M02 использует
0x8000..0xD7FF для кода, 0xD800..0xDFFF для констант/инициализации,
0xE000..0xFFFF для .ebss после перехода в C runtime. Linker резервирует
NOINIT handoff в обоих проектах. CAN не содержит операций записи внутренней Flash.
## Первичная установка
1. Собрать приложение с EEPROM_M95M02=1 и обновлённым F28335.cmd; получить SCI8 BIN.
Старые BIN/OUT автоматически не заменены. Приложение должно содержать интеграцию
READY/CONFIRM, иначе на следующем reset оно останется неподтверждённым кандидатом.
2. Собрать отдельный загрузчик TI C2000 COFF compiler:
`Bootloader/build.ps1 -CompilerRoot <путь-к-ti-cgt-c2000>`.
Результат — `Bootloader/build/F28335_CAN_AB.out` и MAP.
3. Один раз установить этот OUT через JTAG и выбрать аппаратный Flash boot
согласно схеме платы. Не загружать поверх него старый application OUT с
секцией codestart во Flash.
4. После reset пустая EEPROM или старая разметка автоматически ведут в CAN recovery.
Передать новое приложение через ProtoCAN Boot, дождаться пробного запуска и
проверить MEMORY_INFO page 2: pending=FF, active=A. Затем проверить обновление в B.
Старая SPI-ROM разметка с AA08 по адресу 0 несовместима с новым журналом.
Первое обновление заменяет её; параметры по прежнему адресу сохраняются.
Загрузчик не выполняет скрытую миграцию и не меняет boot pins программно.
## Проверки и границы проверенного
`python c/protocan-boot/tests/run_tests.py` (переменная CC — clang/gcc) проверяет
ядро, приложение/загрузчик, wide-octet режим, журнал, обрыв публикации на каждом
из 69 возможных положений, пробный запуск, CONFIRM, откат, SCI8 и границы памяти.
Python-тесты проверяют клиентские запросы, паспорт, фильтрацию ответов и лимит A/B.
Проверка синтаксиса использует настоящие DSP2833x headers. Сборка TI/CCS,
раскладка итогового MAP и физические CAN/SPI/reset на плате ещё не подтверждены.
CRC32 защищает от повреждения, криптографической подписи и anti-rollback по
версии в этом порту нет. Удалённый CONFIRM подтверждает готовность приложения,
но не заменяет проверку датчиков/силовой части на стенде.
Аппаратный формат SPI boot описан в [TI Boot ROM Reference Guide](https://e2e.ti.com/cfs-file/__key/communityserver-discussions-components-files/171/2742.Boot-ROM.pdf).
В A/B-схеме ROM выбирает Flash, а разбор EEPROM выполняет этот загрузчик.

View File

@@ -0,0 +1,181 @@
/* Shared by the application and the separate Flash bootloader. */
/* Аппаратный слой: общая RAM между reset, watchdog и SPI EEPROM.
* Один исходник компилируется и в приложение, и в отдельный Flash-загрузчик.
* В отличие от store.c этот файл требует настоящих регистров F28335. */
#include "DSP2833x_Device.h"
#include "f28335_boot_platform.h"
#include "f28335_can_boot.h"
/* Must be NOINIT in BOTH linkers, outside ROM scratch and application payload. */
#pragma DATA_SECTION(boot_handoff,".boot_shared")
/* 8 слов NOINIT по адресу 0x50, одинаково зарезервированных обоими linker-файлами.
* [0..1] magic; [2] запрошенная session; [3] запущенный слот; [4..6] служебные
* поля; [7] XOR-контроль. Это кратковременная передача контекста через watchdog
* reset, а не замена энергонезависимого журнала EEPROM. */
static volatile unsigned int boot_handoff[8];
/* Проверяем magic и XOR прежде, чем трактовать RAM как запрос входа в boot.
* После включения питания содержимое NOINIT может быть произвольным. */
static unsigned int handoff_valid(void)
{
unsigned int i,sum=0;
for(i=0;i<8;++i) sum^=boot_handoff[i];
return boot_handoff[0]==0xB283 && boot_handoff[1]==0x4D7C && sum==0xA95B;
}
/* Сначала снимаем признак действительности, затем заполняем поля и контроль.
* Первый magic записываем последним; volatile сохраняет обращения к общей RAM.
* request=0 означает обычный запуск, ненулевая session — явный ENTER_BOOT. */
static void handoff_set(unsigned int request,unsigned int running)
{
unsigned int i,sum=0;
boot_handoff[0]=0; boot_handoff[1]=0x4D7C;
boot_handoff[2]=request; boot_handoff[3]=running;
boot_handoff[4]=0xF335; boot_handoff[5]=0x0CCA; boot_handoff[6]=1;
sum=0xB283;
for(i=1;i<7;++i) sum^=boot_handoff[i];
boot_handoff[7]=sum^0xA95B;
boot_handoff[0]=0xB283; /* publish last */
}
/* Приложение узнаёт свой слот из handoff, а не предполагает, что оно active:
* во время пробного запуска выполняется pending, active ещё указывает на старый образ. */
unsigned int F28335Boot_RunningSlot(void)
{ return handoff_valid()?boot_handoff[3]:F28335_AB_NONE; }
unsigned int F28335Boot_RequestedSession(void)
{ return handoff_valid() && boot_handoff[2]<256?boot_handoff[2]:0; }
/* Одновременно публикуем выбранный слот и потребляем запрос ENTER_BOOT:
* он не должен повторяться при следующем обычном reset. */
void F28335Boot_SetRunning(unsigned int slot) { handoff_set(0,slot); }
/* Timer1 должен давать 1 мс на InterruptCount и в приложении, и в загрузчике.
* Тайм-ауты используют разность unsigned-счётчиков для переноса через wrap. */
uint32_t F28335Boot_Now(void) { return CpuTimer1.InterruptCount; }
/* Вызвать сразу после InitSysCtrl, который штатно отключает watchdog.
* Сбрасываем разрешение кормления из ISR до полной инициализации CAN-сервиса.
* Для приложения, запущенного нашим загрузчиком, включаем watchdog с делителем. */
void F28335Boot_StartupWatchdog(void)
{
F28335CanBoot_EarlyInit();
if(F28335Boot_RunningSlot()<2) {
EALLOW; SysCtrlRegs.WDKEY=0x55; SysCtrlRegs.WDKEY=0xAA;
SysCtrlRegs.WDCR=0x002F; EDIS;
}
}
/* Платформенный Quiesce останавливает управление выходами перед reset.
* Session сохраняется до включения watchdog; ненулевую session загрузчик
* использует для отложенного ACK ENTER_BOOT. В бесконечном цикле watchdog
* намеренно не обслуживается — выход из функции должен произойти через reset. */
void F28335Boot_Reset(unsigned int session)
{
F28335Boot_Quiesce();
DINT;
handoff_set(session,F28335_AB_NONE);
EALLOW; SysCtrlRegs.WDCR=0x0028; EDIS;
for(;;) { }
}
/* Main context only. Legacy Timer2 EEPROM driver must be idle/stopped. Save
* its SPI configuration: it changes the character width during transactions. */
typedef struct { unsigned int ccr,ctl,brr,tx,rx,ct,pri; } boot_spi_saved;
static void spi_cs(unsigned int high)
{ if(high) GpioDataRegs.GPASET.bit.GPIO19=1; else GpioDataRegs.GPACLEAR.bit.GPIO19=1; }
/* Доступ разрешён только из main при остановленном Timer2 старого SPI-драйвера.
* Это проверка занятости в однопоточном приложении, не универсальный mutex.
* Сохраняем режим SPI, поскольку штатный драйвер меняет разрядность передачи.
* CS сначала поднимаем, затем переключаем SPI на 8-битные транзакции. */
static bool spi_take(boot_spi_saved *s)
{
volatile unsigned int discard;
if(!CpuTimer2Regs.TCR.bit.TSS) return false;
s->ccr=SpiaRegs.SPICCR.all; s->ctl=SpiaRegs.SPICTL.all; s->brr=SpiaRegs.SPIBRR;
s->tx=SpiaRegs.SPIFFTX.all; s->rx=SpiaRegs.SPIFFRX.all;
s->ct=SpiaRegs.SPIFFCT.all; s->pri=SpiaRegs.SPIPRI.all;
EALLOW;
GpioCtrlRegs.GPAMUX2.bit.GPIO16=1; GpioCtrlRegs.GPAMUX2.bit.GPIO17=1;
GpioCtrlRegs.GPAMUX2.bit.GPIO18=1; GpioCtrlRegs.GPAMUX2.bit.GPIO19=0;
GpioDataRegs.GPASET.bit.GPIO19=1; GpioCtrlRegs.GPADIR.bit.GPIO19=1;
GpioCtrlRegs.GPAQSEL2.bit.GPIO17=3;
EDIS;
SpiaRegs.SPICCR.all=7; SpiaRegs.SPICTL.all=0x0E; /* master, phase=1, IRQ off */
SpiaRegs.SPIBRR=CLKMULT*6+1;
SpiaRegs.SPIFFTX.all=0x8000; SpiaRegs.SPIFFRX.all=0; SpiaRegs.SPIFFCT.all=0;
SpiaRegs.SPIPRI.all=0x10; SpiaRegs.SPICCR.all=0x87;
/* Снимаем оставшийся RX/INT_FLAG от прежней операции, чтобы первый обмен
* не принял старое содержимое RXBUF за ответ EEPROM. */
discard=SpiaRegs.SPIRXBUF; (void)discard;
return true;
}
/* Выход и при успехе, и при ошибке: CS не должен остаться активным.
* Настройки возвращаются при удержании SPI в reset; исходный SPISWRESET
* восстанавливается последним, после остальных регистров. */
static void spi_release(const boot_spi_saved *s)
{
volatile unsigned int discard;
spi_cs(1); discard=SpiaRegs.SPIRXBUF; (void)discard;
SpiaRegs.SPICCR.all=s->ccr&0x7FU; SpiaRegs.SPICTL.all=s->ctl;
SpiaRegs.SPIBRR=s->brr; SpiaRegs.SPIFFTX.all=s->tx; SpiaRegs.SPIFFRX.all=s->rx;
SpiaRegs.SPIFFCT.all=s->ct; SpiaRegs.SPIPRI.all=s->pri; SpiaRegs.SPICCR.all=s->ccr;
}
/* У F28335 8-битный символ для передачи выравнивается в старшие биты TXBUF.
* Полученный октет читается из младших битов RXBUF. Счётчик budget ограничивает
* ожидание даже при неработающем Timer1; это число итераций, а не миллисекунды. */
static bool spi_octet(unsigned int tx,unsigned int *rx)
{
uint32_t budget=100000UL;
SpiaRegs.SPITXBUF=(tx&255U)<<8;
while(!SpiaRegs.SPISTS.bit.INT_FLAG) if(--budget==0) return false;
*rx=SpiaRegs.SPIRXBUF&255U; return true;
}
/* Команда EEPROM и 24-битный байтовый адрес, старший октет адреса первым.
* CS удерживается вызывающей функцией на всю команду и полезные данные. */
static bool spi_prefix(unsigned int command,uint32_t address)
{
unsigned int discard;
return spi_octet(command,&discard) && spi_octet((unsigned int)(address>>16),&discard) &&
spi_octet((unsigned int)(address>>8),&discard) && spi_octet((unsigned int)address,&discard);
}
/* RDSR (05h): WIP в бите 0 означает незавершённую внутреннюю запись EEPROM.
* Ожидание ограничено и 50 мс, и числом опросов на случай остановки tick.
* После каждого чтения статуса CS снимается, в том числе при ошибке SPI. */
static bool spi_ready(void)
{
unsigned int status,discard; uint32_t started=F28335Boot_Now(),budget=20000UL;
do {
spi_cs(0);
if(!spi_octet(5,&discard) || !spi_octet(255,&status)) { spi_cs(1); return false; }
spi_cs(1);
if(!(status&1U)) return true;
} while(--budget && (uint32_t)(F28335Boot_Now()-started)<50UL);
return false;
}
/* Читать разрешено всю физическую EEPROM, включая параметры. Проверка
* через вычитание защищает от переполнения адреса. spi_release обязателен
* после успешного захвата SPI независимо от результата последующего обмена. */
bool F28335Boot_Read(void *u,uint32_t address,pcan_boot_byte_t *p,unsigned int n)
{
boot_spi_saved saved; unsigned int i,value; bool ok; (void)u;
if(address>0x40000UL || n>0x40000UL-address || !spi_take(&saved)) return false;
ok=spi_ready();
spi_cs(0); if(ok) ok=spi_prefix(3,address);
for(i=0;ok && i<n;++i) { ok=spi_octet(255,&value); p[i]=value; }
spi_release(&saved); return ok;
}
/* Нижний аппаратный барьер записи: разрешены журнал и слоты до 0x3FE00.
* Резерв, параметры и последнее слово нельзя изменить даже ошибочным вызовом
* этой функции. Защита активного слота дополнительно выполняется в store.c. */
bool F28335Boot_Write(void *u,uint32_t address,const pcan_boot_byte_t *p,unsigned int n)
{
boot_spi_saved saved; unsigned int i,chunk,discard; bool ok; (void)u;
/* This driver cannot overwrite parameters, even if a caller is defective. */
if(address>0x3FE00UL || n>0x3FE00UL-address || !spi_take(&saved)) return false;
ok=spi_ready();
while(ok && n) {
/* Нельзя пересекать страницу одной командой WRITE: EEPROM может завернуть
* адрес внутри страницы. Для каждого фрагмента заново выдаём WREN (06h),
* WRITE (02h) и ждём снятия WIP. Сравнение readback выполняет верхний слой. */
chunk=256U-(unsigned int)(address&255U); if(chunk>n) chunk=n;
spi_cs(0); ok=spi_octet(6,&discard); spi_cs(1); /* WREN */
spi_cs(0); if(ok) ok=spi_prefix(2,address);
for(i=0;ok && i<chunk;++i) ok=spi_octet(p[i],&discard);
spi_cs(1); if(ok) ok=spi_ready();
n-=chunk; p+=chunk; address+=chunk;
}
spi_release(&saved); return ok;
}

View File

@@ -0,0 +1,17 @@
#ifndef F28335_BOOT_PLATFORM_H
#define F28335_BOOT_PLATFORM_H
#include "f28335_boot_store.h"
#ifndef F28335_BOOT_LOADER
#define F28335_BOOT_LOADER 0
#endif
bool F28335Boot_Read(void *u,uint32_t address,pcan_boot_byte_t *p,unsigned int n);
bool F28335Boot_Write(void *u,uint32_t address,const pcan_boot_byte_t *p,unsigned int n);
uint32_t F28335Boot_Now(void);
void F28335Boot_Reset(unsigned int session);
unsigned int F28335Boot_RunningSlot(void);
unsigned int F28335Boot_RequestedSession(void);
void F28335Boot_SetRunning(unsigned int slot);
void F28335Boot_StartupWatchdog(void);
/* Application hook must stop outputs before reset. No EEPROM operations here. */
void F28335Boot_Quiesce(void);
#endif

View File

@@ -0,0 +1,254 @@
/* Хранилище A/B без зависимости от регистров F28335.
* Здесь находятся правила сохранения образа при сбое питания и выбора слота.
* CAN отвечает за порядок команд; этот слой повторно защищает active/pending
* и границы EEPROM, даже если вызывающий код передал неверные параметры.
* Адрес EEPROM измеряется в октетах, адрес назначения SCI8 — в словах CPU. */
#include "f28335_boot_store.h"
#include <string.h>
/* Формат журнала фиксирован: u32 little endian. Нельзя сохранять C-структуру
* через memcpy: выравнивание и sizeof на C28x отличаются от обычного ПК. */
static uint32_t bs_get32(const pcan_boot_byte_t *p)
{ return (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24); }
static void bs_put32(pcan_boot_byte_t *p, uint32_t v)
{ unsigned int i; for(i=0;i<4;++i) p[i]=(pcan_boot_byte_t)((v>>(8*i))&255U); }
/* Потоковый CRC32: первый вызов получает crc=0, следующие — предыдущий результат.
* Внутренняя инверсия позволяет считать один CRC по нескольким порциям EEPROM.
* Маска 255 исключает старшие биты 16-битного представления октета на C28x.
* Это проверка повреждения данных, а не криптографическая подпись. */
uint32_t f28335_boot_crc(uint32_t crc, const pcan_boot_byte_t *p, unsigned int n)
{ unsigned int i,b; crc=~crc; for(i=0;i<n;++i) { crc^=p[i]&255U;
for(b=0;b<8;++b) crc=(crc>>1)^((crc&1U)?0xEDB88320UL:0); } return ~crc; }
static uint32_t bs_base(unsigned int slot) { return slot?F28335_AB_SLOT_B:F28335_AB_SLOT_A; }
/* Принимаем запись только при совпадении magic, версии, marker и CRC.
* Дополнительно проверяем смысл номеров слотов: pending не может быть active.
* Наличие правильного marker без правильного CRC не разрешает запуск. */
static bool bs_record_valid(const pcan_boot_byte_t *p)
{
return bs_get32(p)==0x32424146UL && bs_get32(p+4)==1 &&
bs_get32(p+60)==0x51AFC03DUL && bs_get32(p+56)==f28335_boot_crc(0,p,56) &&
(p[12]<2 || p[12]==F28335_AB_NONE) &&
(p[13]<2 || p[13]==F28335_AB_NONE) && p[14]<=1 &&
(p[13]==F28335_AB_NONE || p[13]!=p[12]);
}
/* Карта записи, смещения в октетах:
* 0..3 magic FAB2; 4..7 версия; 8..11 sequence; 12 active; 13 pending;
* 14 attempted; 15 резерв; 16..35 manifest A; 36..55 manifest B;
* 56..59 CRC первых 56 октетов; 60..63 marker завершённой публикации.
* Manifest: size, CRC, FW (по u32), Product (u16), HW min/max, 4 октета резерва. */
static void bs_decode(f28335_boot_store_t *s, const pcan_boot_byte_t *p)
{
unsigned int i; s->sequence=bs_get32(p+8); s->active=p[12];
s->pending=p[13]; s->attempted=p[14];
for(i=0;i<2;++i) {
const pcan_boot_byte_t *q=p+16+i*20;
s->image[i].image_size=bs_get32(q); s->image[i].image_crc32=bs_get32(q+4);
s->image[i].firmware_version=bs_get32(q+8);
s->image[i].product_type=(uint16_t)(q[12]|(q[13]<<8));
s->image[i].hardware_revision_min=q[14]; s->image[i].hardware_revision_max=q[15];
}
}
/* Читаем обе копии журнала. Нераспознанная разметка допускает CAN recovery
* с active=NONE; ошибка чтения запрещает запись, поскольку реальный active неизвестен.
* Эта функция не преобразует старый SCI8 по адресу 0 в новую A/B-разметку. */
bool f28335_boot_open(f28335_boot_store_t *s, const f28335_boot_io_t *io)
{
pcan_boot_byte_t a[64],b[64]; bool va,vb;
if(!s || !io || !io->read || !io->write) return false;
memset(s,0,sizeof(*s)); s->io=*io; s->active=s->pending=F28335_AB_NONE;
if(!io->read(io->user,0,a,64) || !io->read(io->user,256,b,64)) { s->io_fault=true; return false; }
va=bs_record_valid(a); vb=bs_record_valid(b);
if(!va && !vb) return true;
/* Сравнение sequence по модулю 2^32: разность менее половины диапазона
* считается более новой записью. При равенстве выбирается страница 1.
* Соседние публикации отличаются на единицу, в том числе после переполнения. */
s->page=vb && (!va || (uint32_t)(bs_get32(b+8)-bs_get32(a+8))<0x80000000UL)?1:0;
bs_decode(s,s->page?b:a); s->valid=true; return true;
}
/* Публикация выполняется в другую страницу, текущую действующую не трогаем.
* Порядок принципиален: обнулить marker -> проверить -> записать тело ->
* проверить тело -> записать marker последним -> проверить всю запись.
* При обрыве питания загрузчик выбирает прежнюю либо полностью новую запись.
* Каждый memcmp учитывает sizeof элемента, поскольку элемент здесь — октет
* протокола, но на C28x занимает адресуемое 16-битное слово. */
static bool bs_save(f28335_boot_store_t *s)
{
pcan_boot_byte_t p[64],check[64],invalid[4]={0,0,0,0}; unsigned int i;
unsigned int page=s->valid?(s->page^1U):0;
uint32_t addr=(uint32_t)page*256;
memset(p,0,sizeof(p)); bs_put32(p,0x32424146UL); bs_put32(p+4,1);
bs_put32(p+8,s->sequence+1); p[12]=s->active; p[13]=s->pending; p[14]=s->attempted;
for(i=0;i<2;++i) {
pcan_boot_byte_t *q=p+16+i*20;
bs_put32(q,s->image[i].image_size); bs_put32(q+4,s->image[i].image_crc32);
bs_put32(q+8,s->image[i].firmware_version);
q[12]=s->image[i].product_type&255U; q[13]=(s->image[i].product_type>>8)&255U;
q[14]=s->image[i].hardware_revision_min; q[15]=s->image[i].hardware_revision_max;
}
bs_put32(p+56,f28335_boot_crc(0,p,56)); bs_put32(p+60,0x51AFC03DUL);
/* The old page is never erased. A torn write cannot publish a partial record. */
if(!s->io.write(s->io.user,addr+60,invalid,4) ||
!s->io.read(s->io.user,addr+60,check,4) || memcmp(check,invalid,sizeof(invalid)) ||
!s->io.write(s->io.user,addr,p,60) ||
!s->io.read(s->io.user,addr,check,60) || memcmp(p,check,60*sizeof(*p)) ||
!s->io.write(s->io.user,addr+60,p+60,4) ||
!s->io.read(s->io.user,addr,check,64) || memcmp(p,check,sizeof(p))) return false;
s->page=page; s->sequence++; s->valid=true; return true;
}
/* Изменения сначала готовятся в копии состояния next. Рабочее состояние
* заменяем только после публикации. Если запись/контрольное чтение сорвались,
* перечитываем журнал: marker мог успеть записаться до ошибки readback.
* После неудачи чтения io_fault блокирует дальнейшие изменения хранилища. */
static bool bs_publish(f28335_boot_store_t *s, f28335_boot_store_t *next)
{
if(bs_save(next)) { *s=*next; return true; }
/* A failed final read may still have committed: reload before any more writes. */
{ f28335_boot_io_t io=s->io; if(!f28335_boot_open(s,&io)) {
s->valid=false; s->io_fault=true; return false; } }
return false;
}
/* Логический ERASE: обнуляем manifest неактивного слота в журнале.
* Саму EEPROM стирать целиком не требуется — каждый байт будет перезаписан.
* Active защищён всегда; наличие pending запрещает начало новой записи,
* пока кандидат не подтверждён либо не снят при следующем выборе загрузки. */
bool f28335_boot_prepare(f28335_boot_store_t *s, unsigned int slot)
{
f28335_boot_store_t next=*s;
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE) return false;
memset(&next.image[slot],0,sizeof(next.image[slot]));
return bs_publish(s,&next);
}
/* Записываем не более одного CAN-блока и немедленно сравниваем прочитанное.
* Проверка n <= capacity-off выполняется после off <= capacity: это исключает
* переполнение суммы off+n и выход из слота в соседний слот или параметры. */
bool f28335_boot_write(f28335_boot_store_t *s, unsigned int slot, uint32_t off,
const pcan_boot_byte_t *p, unsigned int n)
{
pcan_boot_byte_t check[8];
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE || !n || n>8 ||
off>F28335_AB_SLOT_BYTES || n>F28335_AB_SLOT_BYTES-off) return false;
return s->io.write(s->io.user,bs_base(slot)+off,p,n) &&
s->io.read(s->io.user,bs_base(slot)+off,check,n) && !memcmp(check,p,n*sizeof(*p));
}
/* Слово SCI8 занимает два октета файла, младший октет идёт первым.
* Смещение увеличиваем только после успешного чтения; усечённый файл отклоняем. */
static bool bs_u16(f28335_boot_store_t *s,uint32_t base,uint32_t size,uint32_t *off,uint16_t *v)
{
pcan_boot_byte_t p[2]; if(*off>size || size-*off<2 ||
!s->io.read(s->io.user,base+*off,p,2)) return false;
*v=(uint16_t)(p[0]|(p[1]<<8)); *off+=2; return true;
}
/* Особенность таблицы TI: адрес u32 передаётся старшим словом вперёд,
* при этом внутри каждого слова остаётся little endian. Не заменять на get32. */
static bool bs_addr(f28335_boot_store_t *s,uint32_t base,uint32_t size,uint32_t *off,uint32_t *v)
{
uint16_t hi,lo; if(!bs_u16(s,base,size,off,&hi) || !bs_u16(s,base,size,off,&lo)) return false;
*v=((uint32_t)hi<<16)|lo; return true;
}
/* The complete table is validated before any application RAM is written.
* Loader RAM is E000..FFFF and M0/M1. Flash, peripherals and XINTF are forbidden. */
/* writer=NULL: только разбираем и проверяем таблицу, RAM приложения не меняем.
* writer задан: повторно проверяем таблицу и переносим слова в разрешённую RAM.
* Массивы starts/ends ограничены MAX_SECTIONS; они нужны для обнаружения
* пересекающихся секций, в том числе секций с разным порядком адресов. */
static bool bs_table(f28335_boot_store_t *s,unsigned int slot,uint32_t size,
f28335_boot_ram_writer writer,void *user,uint32_t *entry)
{
uint32_t starts[F28335_AB_MAX_SECTIONS],ends[F28335_AB_MAX_SECTIONS];
uint32_t off=0,addr,end,base=bs_base(slot); unsigned int sections=0,i;
uint16_t count,word; bool entry_loaded=false;
if(!bs_u16(s,base,size,&off,&word) || word!=0x08AA) return false;
/* После ключа AA 08 пропускаем 16 резервных октетов; далее расположен entry.
* Нулевой count завершает таблицу строго на конце файла, без хвостовых данных. */
off=18; if(!bs_addr(s,base,size,&off,entry)) return false;
for(;;) {
if(!bs_u16(s,base,size,&off,&count)) return false;
if(!count) return off==size && entry_loaded;
if(sections==F28335_AB_MAX_SECTIONS || !bs_addr(s,base,size,&off,&addr)) return false;
end=addr+count;
/* Разрешён только полуинтервал RAM [0x8000,0xE000) в словах CPU.
* Верхняя граница защищает RAM работающего загрузчика; нижняя — стек,
* handoff и регистры. Проверка размера payload использует уже октеты файла. */
if(addr<0x8000UL || end>0xE000UL || end<=addr ||
off>size || (uint32_t)count*2>size-off) return false;
for(i=0;i<sections;++i) if(addr<ends[i] && end>starts[i]) return false;
starts[sections]=addr; ends[sections++]=end;
if(*entry>=addr && *entry<end) entry_loaded=true;
if(writer) { for(i=0;i<count;++i) {
if(!bs_u16(s,base,size,&off,&word) || !writer(user,addr+i,word)) return false;
} } else off+=(uint32_t)count*2;
}
}
/* VERIFY проверяет содержимое самой EEPROM, а не только CRC принятого потока.
* Затем проверяется структура SCI8 и попадание entry в загружаемую секцию.
* Выходной entry необязателен: COMMIT нужна проверка, но не адрес перехода. */
bool f28335_boot_verify(f28335_boot_store_t *s,unsigned int slot,
const pcan_boot_manifest_t *m,uint32_t *entry)
{
uint32_t off,crc=0,local_entry; unsigned int n; pcan_boot_byte_t p[32];
if(slot>1 || m->image_size<24 || m->image_size>F28335_AB_SLOT_BYTES ||
(m->image_size&1) || m->product_type!=0x2833 ||
m->hardware_revision_min>1 || m->hardware_revision_max<1) return false;
for(off=0;off<m->image_size;off+=n) {
n=(m->image_size-off<32)?(unsigned int)(m->image_size-off):32;
if(!s->io.read(s->io.user,bs_base(slot)+off,p,n)) return false;
crc=f28335_boot_crc(crc,p,n);
}
if(crc!=m->image_crc32 || !bs_table(s,slot,m->image_size,0,0,&local_entry)) return false;
if(entry) *entry=local_entry;
return true;
}
/* COMMIT публикует проверенный образ как pending, не меняя active.
* Повторная проверка EEPROM непосредственно перед публикацией не позволяет
* назначить кандидатом образ, испорченный после предыдущего VERIFY. */
bool f28335_boot_commit(f28335_boot_store_t *s,unsigned int slot,const pcan_boot_manifest_t *m)
{
f28335_boot_store_t next=*s;
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE ||
!f28335_boot_verify(s,slot,m,0)) return false;
next.image[slot]=*m; next.pending=slot; next.attempted=0;
return bs_publish(s,&next);
}
/* Подтверждать можно только действительно выбранный для пробного запуска слот.
* Требование attempted исключает подтверждение ещё ни разу не запускавшегося
* кандидата. Повтор CONFIRM уже активного слота возвращает успех без записи. */
bool f28335_boot_confirm(f28335_boot_store_t *s,unsigned int running_slot)
{
f28335_boot_store_t next=*s;
if(s->io_fault || running_slot>1) return false;
if(s->pending==F28335_AB_NONE) return s->valid && s->active==running_slot;
if(s->pending!=running_slot || !s->attempted) return false;
next.active=running_slot; next.pending=F28335_AB_NONE; next.attempted=0;
return bs_publish(s,&next);
}
/* Выбор выполняется при старте загрузчика. attempted сохраняется ДО перехода
* в приложение: после зависания, watchdog reset или потери питания следующий
* старт уже знает о незавершённой пробе и возвращает прежний active.
* Успех функции с slot=NONE означает recovery, а не разрешение перехода в RAM. */
bool f28335_boot_select(f28335_boot_store_t *s,unsigned int *slot)
{
f28335_boot_store_t next=*s; *slot=F28335_AB_NONE;
if(s->io_fault) return false;
if(s->pending!=F28335_AB_NONE) {
if(!s->attempted && f28335_boot_verify(s,s->pending,&s->image[s->pending],0)) {
next.attempted=1;
if(!bs_publish(s,&next)) return false;
*slot=s->pending; return true;
}
/* Кандидат уже запускался без подтверждения либо не прошёл проверку.
* Снимаем pending отдельной публикацией; подтверждённый active остаётся прежним. */
next.pending=F28335_AB_NONE; next.attempted=0;
if(!bs_publish(s,&next)) return false;
}
if(s->active<2 && f28335_boot_verify(s,s->active,&s->image[s->active],0)) *slot=s->active;
return true;
}
/* Сначала полная проверка CRC и всех адресов без записи, затем перенос слов.
* Платформа вызывает entry только при true; частичная загрузка при ошибке I/O
* не даёт разрешения на запуск приложения. */
bool f28335_boot_load(f28335_boot_store_t *s,unsigned int slot,
f28335_boot_ram_writer writer,void *user,uint32_t *entry)
{
return slot<2 && writer && entry && f28335_boot_verify(s,slot,&s->image[slot],entry) &&
bs_table(s,slot,s->image[slot].image_size,writer,user,entry);
}

View File

@@ -0,0 +1,55 @@
#ifndef F28335_BOOT_STORE_H
#define F28335_BOOT_STORE_H
#include "../../include/pcan_boot.h"
/* All EEPROM offsets/lengths below are OCTETS, CPU destinations are WORDS. */
/* Физическая EEPROM 256 КиБ: две страницы журнала по 256 октетов,
* два слота по 130560 октетов. С 0x3FE00 начинается резерв, с 0x3FEFC —
* прежняя область параметров. Константы ниже не являются CPU word-адресами. */
#define F28335_AB_SLOT_BYTES 0x1FE00UL
#define F28335_AB_SLOT_A 0x00200UL
#define F28335_AB_SLOT_B 0x20000UL
#define F28335_AB_PARAMS 0x3FEFCUL
#define F28335_AB_NONE 0xFFU
#define F28335_AB_RECORD 64U
#define F28335_AB_MAX_SECTIONS 64U
/* Контракт I/O: address и n измеряются в октетах, независимо от sizeof(char).
* true означает, что выполнена вся операция; короткий обмен должен вернуть false.
* Callbacks вызываются синхронно из main, а user передаётся им без изменений. */
typedef struct {
bool (*read)(void *, uint32_t, pcan_boot_byte_t *, unsigned int);
bool (*write)(void *, uint32_t, const pcan_boot_byte_t *, unsigned int);
void *user;
} f28335_boot_io_t;
typedef struct {
f28335_boot_io_t io;
/* sequence — номер последней публикации; page — выбранная копия журнала.
* active — подтверждённый слот; pending — кандидат; attempted — уже была проба.
* valid показывает наличие распознанной записи, io_fault запрещает изменения
* после ошибки чтения. Отсутствующий слот обозначается NONE, а не нулём. */
uint32_t sequence;
unsigned int page, active, pending, attempted;
bool valid, io_fault;
pcan_boot_manifest_t image[2];
} f28335_boot_store_t;
uint32_t f28335_boot_crc(uint32_t crc, const pcan_boot_byte_t *p, unsigned int n);
bool f28335_boot_open(f28335_boot_store_t *s, const f28335_boot_io_t *io);
/* Blank/unrecognised EEPROM opens successfully with active=NONE. I/O errors fail. */
bool f28335_boot_prepare(f28335_boot_store_t *s, unsigned int slot);
bool f28335_boot_write(f28335_boot_store_t *s, unsigned int slot, uint32_t offset,
const pcan_boot_byte_t *data, unsigned int n);
bool f28335_boot_verify(f28335_boot_store_t *s, unsigned int slot,
const pcan_boot_manifest_t *m, uint32_t *entry);
bool f28335_boot_commit(f28335_boot_store_t *s, unsigned int slot,
const pcan_boot_manifest_t *m);
bool f28335_boot_confirm(f28335_boot_store_t *s, unsigned int running_slot);
/* Records a trial BEFORE returning it; a second unconfirmed boot rolls back. */
bool f28335_boot_select(f28335_boot_store_t *s, unsigned int *slot);
/* Запись одного проверенного слова приложения по CPU word-адресу.
* Должна вернуть false при невозможности записи; после отказа запуск запрещён. */
typedef bool (*f28335_boot_ram_writer)(void *, uint32_t, uint16_t);
bool f28335_boot_load(f28335_boot_store_t *s, unsigned int slot,
f28335_boot_ram_writer write_word, void *user, uint32_t *entry);
#endif

View File

@@ -0,0 +1,276 @@
/* Separate Flash recovery loader and application service adapter. */
#include "DSP2833x_Device.h"
#include "f28335_can_boot.h"
#if EEPROM_M95M02
#include "f28335_boot_platform.h"
#include <string.h>
/* boot хранит текущую CAN-сессию в RAM, store — прочитанное состояние журнала.
* Сброс сессии не удаляет active/pending из EEPROM. */
static pcan_boot_t boot;
static f28335_boot_store_t store;
/* Кольцевая очередь: ISR производит кадры, main потребляет. Из 32 элементов
* используется 31, чтобы head==tail однозначно означало пустую очередь.
* Долгие операции SPI запрещены внутри обработчика CAN. */
static volatile unsigned int head,tail,overflow;
static struct { uint32_t id; unsigned int dlc; pcan_boot_byte_t data[8]; } queue[32];
/* reset_requested только планирует reset; Service ждёт завершения TX.
* ready сообщает об окончании старта приложения, confirmed — о сохранённом
* подтверждении слота. healthy отражает результат открытия журнала при Init. */
static unsigned int reset_requested,reset_session,ready,healthy,confirmed;
static uint32_t sent_at,ready_at;
static unsigned int main_passes;
static int tx_busy,tx_canceling;
/* Один исходящий mailbox исключает перезапись ещё передаваемого ответа.
* MDL содержит первые четыре октета, MDH — следующие четыре, младшим вперёд.
* Запрос CANTRS ещё не означает успешную доставку: CANTA проверяется в Service.
* Аппаратный CAN ACK не является отдельным прикладным подтверждением от ПК. */
static bool transmit(void *u, uint32_t id, const pcan_boot_byte_t *d, pcan_boot_byte_t n)
{
unsigned int i; unsigned long lo=0, hi=0; (void)u;
if (tx_busy) return false;
for(i=0;i<n;++i) {
if(i<4) lo|=(unsigned long)(d[i]&255U)<<(8U*i);
else hi|=(unsigned long)(d[i]&255U)<<(8U*(i-4U));
}
EALLOW;
ECanaRegs.CANME.all &= ~16UL;
ECanaMboxes.MBOX4.MSGID.all=0x80000000UL|id;
ECanaMboxes.MBOX4.MSGCTRL.all=n;
ECanaMboxes.MBOX4.MDL.all=lo; ECanaMboxes.MBOX4.MDH.all=hi;
ECanaRegs.CANME.all |=16UL;
ECanaRegs.CANTA.all=16UL; ECanaRegs.CANAA.all=16UL;
ECanaRegs.CANTRS.all=16UL;
EDIS;
sent_at=F28335Boot_Now(); tx_busy=1; tx_canceling=0; return true;
}
/* Изменение образа разрешено только сборке Flash-загрузчика. В приложении
* эти callbacks возвращают отказ, даже если им удастся передать команду записи.
* ERASE для EEPROM означает инвалидирование manifest выбранного слота. */
static bool erase(void *u,pcan_boot_byte_t slot)
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_prepare(&store,slot); }
static bool write_block(void *u,pcan_boot_byte_t slot,uint32_t offset,const pcan_boot_byte_t *d,pcan_boot_byte_t n)
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_write(&store,slot,offset,d,n); }
/* Проверка включает CRC EEPROM и структуру SCI8. Сейчас отказ этого слоя
* кодируется CRC_ERROR как общий отказ проверки образа; причины внутри
* парсера SCI8 отдельными статусами протокола не различаются. */
static pcan_boot_status_t verify(void *u,pcan_boot_byte_t slot,const pcan_boot_manifest_t *m)
{
(void)u;
if(!F28335_BOOT_LOADER || !healthy) return PCAN_BOOT_STATUS_INVALID_STATE;
return f28335_boot_verify(&store,slot,m,0)?PCAN_BOOT_STATUS_OK:PCAN_BOOT_STATUS_CRC_ERROR;
}
static bool commit(void *u,pcan_boot_byte_t slot,const pcan_boot_manifest_t *m)
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_commit(&store,slot,m); }
/* Подтверждение принадлежит приложению после READY. Загрузчик не может
* подтвердить кандидата сам: иначе исчезнет смысл проверки пробного запуска.
* Разрешение кормления watchdog из ISR открываем только после записи журнала. */
static bool confirm(void *u)
{
unsigned int running=F28335Boot_RunningSlot(); (void)u;
if(F28335_BOOT_LOADER || !healthy || !ready || running>1) return false;
if(!f28335_boot_confirm(&store,running)) return false;
boot.config.active_slot=running; confirmed=1; return true;
}
static bool can_reboot(void *u) { (void)u; return healthy; }
static void reboot(void *u) { (void)u; reset_session=0; reset_requested=1; }
/* Контракт enter_boot: 0 — загрузчик готов и ядро может отправить ACK;
* 1 — переход отложен, ACK придёт после reset; 2 — вход запрещён.
* Приложение требует handoff нашего загрузчика и подтверждённый текущий слот,
* чтобы не начать обновление из старого SPI-ROM старта или незавершённой пробы. */
static unsigned int enter(void *u,pcan_boot_byte_t session)
{
(void)u;
if(!healthy) return 2;
if(F28335_BOOT_LOADER) return 0;
/* No destructive fallback on boards still starting through the SPI ROM. */
if(F28335Boot_RunningSlot()>1 || !confirmed) return 2;
reset_session=session; reset_requested=1; return 1;
}
static void info32(pcan_boot_byte_t *p,uint32_t v)
{ unsigned int i; for(i=0;i<4;++i) p[i]=(pcan_boot_byte_t)((v>>(i*8))&255U); }
/* Паспорт помещается в classic CAN по страницам из восьми октетов.
* NAME возвращает фрагменты строки с завершающим NUL; неизвестная страница
* отклоняется. MEMORY: 0 — ёмкость/страница/режим/running, 1 — базы A/B,
* 2 — active/pending/attempted/healthy/тайм-аут, 3 — область параметров. */
static bool info(void *u,pcan_boot_byte_t command,pcan_boot_byte_t page,pcan_boot_byte_t *out)
{
static const char name[]=F28335_CAN_BOOT_DEVICE_NAME;
unsigned int i,index; (void)u;
if(command==PCAN_BOOT_CMD_CAPABILITIES && page==0) {
/* bits: ping, discovery, identity, name, memory, A/B, confirm, rollback,
* separate loader, session timeout, readback, SCI8 validation. */
info32(out,(F28335_BOOT_LOADER || F28335Boot_RunningSlot()<2)?0xFFFUL:0x1FUL);
info32(out+4,F28335_AB_SLOT_BYTES); return true;
}
if(command==PCAN_BOOT_CMD_DEVICE_NAME) {
if((unsigned int)page*8U>=sizeof(name)) return false;
for(i=0;i<8;++i) { index=(unsigned int)page*8U+i;
out[i]=index<sizeof(name)?(pcan_boot_byte_t)name[index]:0; }
return true;
}
if(command!=PCAN_BOOT_CMD_MEMORY_INFO) return false;
if(page==0) { info32(out,0x40000UL); out[4]=0; out[5]=1;
out[6]=F28335_BOOT_LOADER?1:0; out[7]=F28335Boot_RunningSlot(); return true; }
if(page==1) { info32(out,F28335_AB_SLOT_A); info32(out+4,F28335_AB_SLOT_B); return true; }
if(page==2) { out[0]=store.active; out[1]=store.pending; out[2]=store.attempted;
out[3]=healthy?1:0; info32(out+4,boot.config.session_timeout_ms); return true; }
if(page==3) { info32(out,F28335_AB_PARAMS); info32(out+4,258); return true; }
return false;
}
/* Вызывается до разрешения прикладных IRQ. Нельзя полагаться на случайное
* содержимое RAM или оставшееся состояние confirmed от предыдущей программы. */
void F28335CanBoot_EarlyInit(void) { confirmed=0; ready=0; }
/* Общий Init для приложения и загрузчика: открываем журнал, привязываем
* callbacks, задаём идентификацию, ёмкость слота, окно 16 и тайм-аут 30 секунд.
* В загрузчике FW берётся из active manifest; приложение сообщает версию сборки. */
void F28335CanBoot_Init(void)
{
pcan_boot_config_t cfg; pcan_boot_port_t port;
f28335_boot_io_t io; unsigned int running=F28335Boot_RunningSlot();
memset(&cfg,0,sizeof(cfg)); memset(&port,0,sizeof(port));
io.read=F28335Boot_Read; io.write=F28335Boot_Write; io.user=0;
healthy=f28335_boot_open(&store,&io);
cfg.device_type=F28335_CAN_BOOT_DEVICE_TYPE; cfg.device_id=F28335_CAN_BOOT_DEVICE_ID;
cfg.product_type=F28335_CAN_BOOT_PRODUCT; cfg.hardware_revision=1;
#if F28335_BOOT_LOADER
cfg.firmware_version=store.active<2?store.image[store.active].firmware_version:0;
#else
cfg.firmware_version=F28335_CAN_BOOT_FIRMWARE_VERSION;
#endif
head=tail=overflow=reset_requested=reset_session=ready=main_passes=0; tx_busy=tx_canceling=0;
confirmed=healthy && running<2 && store.active==running && store.pending==F28335_AB_NONE;
cfg.active_slot=store.active;
cfg.slot_count=2; cfg.max_image_size=F28335_AB_SLOT_BYTES; cfg.ack_window=16;
cfg.session_timeout_ms=30000UL;
port.send=transmit; port.erase_slot=erase; port.write_slot=write_block;
port.verify_status=verify; port.set_pending_slot=commit; port.reboot=reboot;
port.confirm_running_slot=confirm; port.enter_boot=enter;
port.can_reboot=can_reboot; port.service_info=info;
if(!pcan_boot_init(&boot,&cfg,&port,0)) return;
EALLOW;
/* Меняем только MBOX3/4/6: маска 88=8+16+64. MBOX0..2 — старый обмен,
* MBOX5 — heartbeat. Приём broadcast выделен в отдельный точный mailbox,
* чтобы слишком широкая маска Boot не перехватывала сообщения приложения.
* 72=8+64 — оба RX, 16 — TX. Регистры флагов очищаются записью единицы. */
ECanaRegs.CANME.all &= ~88UL;
ECanaMboxes.MBOX3.MSGID.all=0xC0000000UL|pcan_boot_make_id(0,0,cfg.device_type,cfg.device_id,0,0);
ECanaLAMRegs.LAM3.all=0x100FFFFFUL;
ECanaMboxes.MBOX3.MSGCTRL.all=8;
/* MBOX5 belongs to the application's heartbeat. MBOX6 is discovery only. */
ECanaMboxes.MBOX6.MSGID.all=0xC0000000UL|pcan_boot_make_id(0,0,7,15,13,0);
ECanaLAMRegs.LAM6.all=0x10000000UL; /* Ignore priority only. */
ECanaMboxes.MBOX6.MSGCTRL.all=0;
ECanaRegs.CANMD.all=(ECanaRegs.CANMD.all|72UL)&~16UL;
ECanaRegs.CANRMP.all=72UL;
ECanaRegs.CANMIM.all |=72UL;
ECanaRegs.CANME.all |=88UL;
EDIS;
}
/* ISR только проверяет заголовок и копирует кадр. Чужие адреса и RTR
* отбрасываются до очереди. Глобальный адрес допускается только для discovery;
* DLC и смысл команды окончательно проверит ядро в основном цикле. */
static void receive_box(volatile struct MBOX *box,uint32_t mask)
{
unsigned int next=(head+1U)&31U, i;
unsigned long lo, hi;
uint32_t id; unsigned int type;
if(!(ECanaRegs.CANRMP.all&mask)) return;
if(box->MSGCTRL.bit.RTR) { ECanaRegs.CANRMP.all=mask; return; }
id=box->MSGID.all&0x1FFFFFFFUL;
type=pcan_boot_id_msg_type(id);
if(!(box->MSGID.all&0x80000000UL) || pcan_boot_id_route(id)!=0 ||
(type!=9 && type!=10 && type!=11 && type!=13) ||
!((pcan_boot_id_device_type(id)==F28335_CAN_BOOT_DEVICE_TYPE &&
pcan_boot_id_device_id(id)==F28335_CAN_BOOT_DEVICE_ID) ||
(type==13 && pcan_boot_id_device_type(id)==7 && pcan_boot_id_device_id(id)==15 &&
pcan_boot_id_msg_body(id)==0))) { ECanaRegs.CANRMP.all=mask; return; }
if(next==tail) overflow=1;
else {
queue[head].id=box->MSGID.all&0x1FFFFFFFUL;
queue[head].dlc=box->MSGCTRL.bit.DLC;
lo=box->MDL.all; hi=box->MDH.all;
for(i=0;i<4;++i) { queue[head].data[i]=(lo>>(8U*i))&255U;
queue[head].data[i+4]=(hi>>(8U*i))&255U; }
head=next;
}
ECanaRegs.CANRMP.all=mask;
}
void F28335CanBoot_RxISR(void)
{
receive_box(&ECanaMboxes.MBOX3,8UL);
receive_box(&ECanaMboxes.MBOX6,64UL);
}
/* Вызывать после инициализации параметров, датчиков и прикладного режима.
* От этого момента отсчитывается интервал наблюдения перед авто-CONFIRM. */
void F28335CanBoot_Ready(void)
{ ready=1; ready_at=F28335Boot_Now(); }
/* Используется таймерными ISR приложения. Во время пробы кормление watchdog
* должно доказывать выполнение main: работа одного ISR не доказывает готовность.
* Старый старт без handoff сохраняет прежнюю политику watchdog. */
unsigned int F28335CanBoot_WatchdogAllowed(void)
{ return F28335Boot_RunningSlot()>1 || confirmed; }
/* Неблокирующий диспетчер относительно ожидания CAN: вызывать в каждом проходе main.
* Сами callbacks проверки/записи EEPROM синхронные. Порядок здесь важен:
* проба приложения -> завершение TX -> reset -> poll -> ошибки RX -> один кадр. */
void F28335CanBoot_Service(void)
{
uint32_t now=F28335Boot_Now();
if(!F28335_BOOT_LOADER && ready && !confirmed && healthy && F28335Boot_RunningSlot()<2) {
/* A trial must keep executing main, not merely a timer ISR. */
/* После 100 проходов main и 5 секунд пробуем сохранить CONFIRM.
* Если до 10 секунд сохранение не удалось, main перестаёт кормить watchdog,
* что позволяет следующему старту откатиться к подтверждённому образу. */
if((uint32_t)(now-ready_at)<10000UL) ServiceDog();
if(main_passes<100) ++main_passes;
if(main_passes==100 && (uint32_t)(now-ready_at)>=5000UL) (void)confirm(0);
}
if(tx_busy) {
/* После CANTRR ждём завершения отмены или запоздавшего ACK, прежде чем
* повторно использовать mailbox. reset_requested к этому моменту уже снят,
* поэтому позднее подтверждение не должно перезапустить устройство. */
if(tx_canceling) {
if(!((ECanaRegs.CANAA.all|ECanaRegs.CANTA.all)&16UL)) return;
ECanaRegs.CANAA.all=16UL; ECanaRegs.CANTA.all=16UL;
tx_busy=tx_canceling=0;
} else if((uint32_t)(now-sent_at)>250UL) {
/* Cancel reset immediately, even if a late CAN ACK races CANTRR. */
reset_requested=0; pcan_boot_abort(&boot); tail=head;
tx_canceling=1; ECanaRegs.CANTRR.all=16UL; return;
} else if(ECanaRegs.CANTA.all&16UL) {
ECanaRegs.CANTA.all=16UL; tx_busy=0;
} else return;
}
if(reset_requested) { F28335Boot_Reset(reset_session); return; }
pcan_boot_poll(&boot,now);
if(tx_busy) return;
/* После потери кадра нельзя молча продолжать запись: сбрасываем RAM-сессию
* и накопленную очередь. Подтверждённый образ и опубликованный pending сохраняются. */
if(overflow || (ECanaRegs.CANRML.all&72UL)) {
overflow=0; ECanaRegs.CANRML.all=72UL;
pcan_boot_abort(&boot); tail=head; return;
}
if(tail!=head) {
pcan_boot_process(&boot,queue[tail].id,queue[tail].data,queue[tail].dlc);
tail=(tail+1U)&31U;
}
}
/* Используем session из NOINIT только для нового ENTER_BOOT после reset.
* Это отправка отложенного ACK готовности, а не восстановление принятых блоков
* или CRC незавершённой передачи: данные сессии через reset не сохраняются. */
void F28335CanBoot_Resume(unsigned int session)
{
if(F28335_BOOT_LOADER && session && session<256)
(void)pcan_boot_process(&boot,pcan_boot_make_id(1,0,
F28335_CAN_BOOT_DEVICE_TYPE,F28335_CAN_BOOT_DEVICE_ID,9,(session<<8)|2),0,0);
}
#else
void F28335CanBoot_EarlyInit(void) {}
void F28335CanBoot_Init(void) {}
void F28335CanBoot_RxISR(void) {}
void F28335CanBoot_Service(void) {}
void F28335CanBoot_Ready(void) {}
unsigned int F28335CanBoot_WatchdogAllowed(void) { return 1; }
void F28335CanBoot_Resume(unsigned int session) { (void)session; }
#endif

View File

@@ -0,0 +1,28 @@
#ifndef F28335_CAN_BOOT_H
#define F28335_CAN_BOOT_H
#include "../../include/pcan_boot.h"
/* Приложение задаёт версию из firmware_version.h до подключения этого заголовка.
* Формат поля FW: major[31:24], minor[23:16], patch[15:0]. Ноль — запасное
* значение при отсутствии версии сборки, а не прошитая здесь версия устройства. */
#ifndef F28335_CAN_BOOT_FIRMWARE_VERSION
#define F28335_CAN_BOOT_FIRMWARE_VERSION 0UL /* Board supplies the build identity. */
#endif
/* Адрес ProtoCAN состоит из Device Type (3 бита) и Device ID (4 бита).
* ProductType — отдельный код совместимости прошивки; это не CAN ID и не RS-адрес. */
#define F28335_CAN_BOOT_DEVICE_TYPE 3U
#define F28335_CAN_BOOT_DEVICE_ID 7U
#define F28335_CAN_BOOT_PRODUCT 0x2833U
#define F28335_CAN_BOOT_DEVICE_NAME "F28335-M95M02"
/* Порядок интеграции приложения:
* StartupWatchdog/EarlyInit сразу после InitSysCtrl; Init после настройки
* таймера и EEPROM; Ready после старта прикладных подсистем; Service в main.
* RxISR вызывается из CAN ISR, WatchdogAllowed проверяется перед кормлением
* watchdog таймерным ISR. Resume применяется только отдельным загрузчиком. */
void F28335CanBoot_Init(void);
void F28335CanBoot_EarlyInit(void);
void F28335CanBoot_RxISR(void);
void F28335CanBoot_Service(void);
void F28335CanBoot_Ready(void);
void F28335CanBoot_Resume(unsigned int session);
unsigned int F28335CanBoot_WatchdogAllowed(void);
#endif

View File

@@ -1,21 +1,16 @@
/*
* Автомат адресной загрузки прошивки по ProtoCAN с сессией, контрольной суммой и A/B-слотами.
* Приём блоков, проверка образа и активация разделены на этапы; операции Flash и метаданные
* слотов предоставляет платформа.
*/
#include "pcan_boot.h"
/* ProtoCAN update state machine; platform owns storage and reset. */
#include "../include/pcan_boot.h"
#include <string.h>
#define PCAN_BOOT_ID_MASK 0x1FFFFFFFUL
static uint16_t get_u16(const uint8_t *p)
static uint16_t get_u16(const pcan_boot_byte_t *p)
{
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static uint32_t get_u32(const uint8_t *p)
static uint32_t get_u32(const pcan_boot_byte_t *p)
{
return (uint32_t)p[0]
| ((uint32_t)p[1] << 8)
@@ -23,26 +18,26 @@ static uint32_t get_u32(const uint8_t *p)
| ((uint32_t)p[3] << 24);
}
static void put_u16(uint8_t *p, uint16_t value)
static void put_u16(pcan_boot_byte_t *p, uint16_t value)
{
p[0] = (uint8_t)value;
p[1] = (uint8_t)(value >> 8);
p[0] = (pcan_boot_byte_t)(value & 0xFFU);
p[1] = (pcan_boot_byte_t)((value >> 8) & 0xFFU);
}
static void put_u32(uint8_t *p, uint32_t value)
static void put_u32(pcan_boot_byte_t *p, uint32_t value)
{
p[0] = (uint8_t)value;
p[1] = (uint8_t)(value >> 8);
p[2] = (uint8_t)(value >> 16);
p[3] = (uint8_t)(value >> 24);
p[0] = (pcan_boot_byte_t)(value & 0xFFU);
p[1] = (pcan_boot_byte_t)((value >> 8) & 0xFFU);
p[2] = (pcan_boot_byte_t)((value >> 16) & 0xFFU);
p[3] = (pcan_boot_byte_t)(value >> 24);
}
static uint32_t crc32_update(uint32_t crc, const uint8_t *data, size_t length)
static uint32_t crc32_update(uint32_t crc, const pcan_boot_byte_t *data, size_t length)
{
size_t i;
crc = ~crc;
for (i = 0U; i < length; ++i) {
uint8_t bit;
pcan_boot_byte_t bit;
crc ^= data[i];
for (bit = 0U; bit < 8U; ++bit) {
crc = (crc >> 1) ^ ((crc & 1U) != 0U ? 0xEDB88320UL : 0U);
@@ -51,9 +46,9 @@ static uint32_t crc32_update(uint32_t crc, const uint8_t *data, size_t length)
return ~crc;
}
uint32_t pcan_boot_make_id(uint8_t priority, uint8_t route,
uint8_t device_type, uint8_t device_id,
uint8_t msg_type, uint16_t msg_body)
uint32_t pcan_boot_make_id(pcan_boot_byte_t priority, pcan_boot_byte_t route,
pcan_boot_byte_t device_type, pcan_boot_byte_t device_id,
pcan_boot_byte_t msg_type, uint16_t msg_body)
{
return ((((uint32_t)priority & 1U) << 28)
| (((uint32_t)route & 1U) << 27)
@@ -63,14 +58,14 @@ uint32_t pcan_boot_make_id(uint8_t priority, uint8_t route,
| msg_body) & PCAN_BOOT_ID_MASK;
}
uint8_t pcan_boot_id_route(uint32_t can_id) { return (uint8_t)((can_id >> 27) & 1U); }
uint8_t pcan_boot_id_device_type(uint32_t can_id) { return (uint8_t)((can_id >> 24) & 7U); }
uint8_t pcan_boot_id_device_id(uint32_t can_id) { return (uint8_t)((can_id >> 20) & 15U); }
uint8_t pcan_boot_id_msg_type(uint32_t can_id) { return (uint8_t)((can_id >> 16) & 15U); }
pcan_boot_byte_t pcan_boot_id_route(uint32_t can_id) { return (pcan_boot_byte_t)((can_id >> 27) & 1U); }
pcan_boot_byte_t pcan_boot_id_device_type(uint32_t can_id) { return (pcan_boot_byte_t)((can_id >> 24) & 7U); }
pcan_boot_byte_t pcan_boot_id_device_id(uint32_t can_id) { return (pcan_boot_byte_t)((can_id >> 20) & 15U); }
pcan_boot_byte_t pcan_boot_id_msg_type(uint32_t can_id) { return (pcan_boot_byte_t)((can_id >> 16) & 15U); }
uint16_t pcan_boot_id_msg_body(uint32_t can_id) { return (uint16_t)can_id; }
static bool send_frame(pcan_boot_t *boot, uint8_t msg_type, uint16_t body,
const uint8_t *data, uint8_t dlc)
static bool send_frame(pcan_boot_t *boot, pcan_boot_byte_t msg_type, uint16_t body,
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc)
{
uint32_t id = pcan_boot_make_id(1U, PCAN_BOOT_ROUTE_FROM_DEVICE,
boot->config.device_type,
@@ -79,9 +74,12 @@ static bool send_frame(pcan_boot_t *boot, uint8_t msg_type, uint16_t body,
return boot->port.send(boot->port_user, id, data, dlc);
}
static bool send_status(pcan_boot_t *boot, uint8_t command, uint8_t status)
/* STATUS всегда содержит состояние передачи: status, target_slot, next_block, CRC32.
* CRC здесь относится к уже принятому потоку, а не заменяет проверку EEPROM.
* Номер следующего блока помогает клиенту продолжить обмен после потери ACK. */
static bool send_status(pcan_boot_t *boot, pcan_boot_byte_t command, pcan_boot_byte_t status)
{
uint8_t data[8];
pcan_boot_byte_t data[8];
uint16_t body = (uint16_t)(((uint16_t)boot->session_id << 8) | command);
data[0] = status;
data[1] = boot->target_slot;
@@ -91,9 +89,11 @@ static bool send_status(pcan_boot_t *boot, uint8_t command, uint8_t status)
return send_frame(boot, PCAN_BOOT_MSG_STATUS, body, data, 8U);
}
/* IDENTIFY имеет отдельный тип сообщения DISCOVERY и Body=1. Он доступен
* без открытия сессии, поэтому обнаружение устройства не должно запускать обновление. */
static bool send_identity(pcan_boot_t *boot)
{
uint8_t data[8];
pcan_boot_byte_t data[8];
put_u16(&data[0], boot->config.product_type);
data[2] = boot->config.hardware_revision;
data[3] = PCAN_BOOT_VERSION;
@@ -101,6 +101,9 @@ static bool send_identity(pcan_boot_t *boot)
return send_frame(boot, PCAN_BOOT_MSG_DISCOVERY, 1U, data, 8U);
}
/* Отменяется только состояние протокола в RAM: manifest, счётчики и session.
* Функция не стирает EEPROM и не снимает ранее опубликованный pending.
* Ответственность за постоянное состояние слотов остаётся у платформы. */
void pcan_boot_abort(pcan_boot_t *boot)
{
if (boot == NULL) {
@@ -118,19 +121,24 @@ void pcan_boot_abort(pcan_boot_t *boot)
boot->have_compat = false;
}
/* Конфигурация копируется в экземпляр; указатели callbacks остаются привязаны
* к port_user. Значения slot_count=0 и max_image_size=0 сохраняют исторические
* настройки по умолчанию. Для пустого A/B-хранилища допустим active_slot=NONE. */
bool pcan_boot_init(pcan_boot_t *boot, const pcan_boot_config_t *config,
const pcan_boot_port_t *port, void *port_user)
{
uint8_t slot_count;
pcan_boot_byte_t slot_count;
if ((boot == NULL) || (config == NULL) || (port == NULL)
|| (config->device_type > 7U) || (config->device_id > 15U)
|| (config->active_slot > 1U)
|| ((config->active_slot > 1U) && (config->active_slot != PCAN_BOOT_SLOT_NONE))
|| (port->send == NULL) || (port->erase_slot == NULL)
|| (port->write_slot == NULL)) {
return false;
}
slot_count = config->slot_count == 0U ? 2U : config->slot_count;
if ((slot_count > 2U) || (config->active_slot >= slot_count)
if ((slot_count > 2U) || ((config->active_slot >= slot_count) &&
(config->active_slot != PCAN_BOOT_SLOT_NONE || slot_count != 2U))
|| config->max_image_size > PCAN_BOOT_SLOT_SIZE
|| ((slot_count == 2U) && (port->set_pending_slot == NULL))) {
return false;
}
@@ -149,7 +157,10 @@ bool pcan_boot_init(pcan_boot_t *boot, const pcan_boot_config_t *config,
return true;
}
static bool metadata_complete(pcan_boot_t *boot, uint8_t command)
/* BEGIN_IMAGE и BEGIN_COMPAT приходят отдельными кадрами. До получения обоих
* не разрешаем ERASE. Проверяем размер, Product, HW и необязательную политику
* authorize. В A/B выбираем противоположный active слот; для первого образа — A. */
static bool metadata_complete(pcan_boot_t *boot, pcan_boot_byte_t command)
{
if (!boot->have_image || !boot->have_compat) {
boot->state = PCAN_BOOT_STATE_METADATA;
@@ -160,8 +171,11 @@ static bool metadata_complete(pcan_boot_t *boot, uint8_t command)
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_SIZE);
}
if ((boot->manifest.product_type != boot->config.product_type)
|| (boot->config.hardware_revision < boot->manifest.hardware_revision_min)
if (boot->manifest.product_type != boot->config.product_type) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_WRONG_DEVICE);
}
if ((boot->config.hardware_revision < boot->manifest.hardware_revision_min)
|| (boot->config.hardware_revision > boot->manifest.hardware_revision_max)) {
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_WRONG_HARDWARE);
@@ -171,19 +185,51 @@ static bool metadata_complete(pcan_boot_t *boot, uint8_t command)
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_SIGNATURE_ERROR);
}
boot->target_slot = boot->config.slot_count == 1U
boot->target_slot = boot->config.active_slot == PCAN_BOOT_SLOT_NONE ? 0U :
boot->config.slot_count == 1U
? boot->config.active_slot
: (uint8_t)(boot->config.active_slot ^ 1U);
: (pcan_boot_byte_t)(boot->config.active_slot ^ 1U);
boot->state = PCAN_BOOT_STATE_READY_TO_ERASE;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
}
/* Body состоит из session в старшем октете и command в младшем.
* Сначала проверяем DLC, чтобы обработчики не читали отсутствующие данные.
* Идентификация и паспорт не открывают сессию; CONFIRM обрабатывается отдельно,
* поскольку приложение не должно входить в загрузчик ради подтверждения себя.
* Остальные команды изменяют RAM-автомат только в допустимой сессии/состоянии. */
static bool process_control(pcan_boot_t *boot, uint16_t body,
const uint8_t *data, uint8_t dlc)
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc)
{
uint8_t session = (uint8_t)(body >> 8);
uint8_t command = (uint8_t)body;
pcan_boot_byte_t session = (pcan_boot_byte_t)(body >> 8);
pcan_boot_byte_t command = (pcan_boot_byte_t)(body & 0xFFU);
if (dlc != ((command == PCAN_BOOT_CMD_BEGIN_IMAGE || command == PCAN_BOOT_CMD_BEGIN_COMPAT) ? 8U :
(command == PCAN_BOOT_CMD_DEVICE_NAME || command == PCAN_BOOT_CMD_MEMORY_INFO) ? 1U : 0U)) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_SIZE);
}
if (command == PCAN_BOOT_CMD_PING) {
pcan_boot_byte_t reply[8] = {0};
reply[0] = PCAN_BOOT_VERSION; reply[1] = boot->state;
reply[2] = boot->config.active_slot; reply[3] = boot->target_slot;
reply[4] = boot->session_id; reply[5] = boot->config.slot_count;
reply[6] = boot->last_status;
return send_frame(boot, PCAN_BOOT_MSG_DISCOVERY, command, reply, 8);
}
if (command >= PCAN_BOOT_CMD_CAPABILITIES && command <= PCAN_BOOT_CMD_MEMORY_INFO) {
pcan_boot_byte_t reply[8] = {0};
pcan_boot_byte_t page = dlc ? data[0] : 0;
if (!boot->port.service_info || !boot->port.service_info(boot->port_user,command,page,reply))
return send_status(boot,command,PCAN_BOOT_STATUS_INVALID_COMMAND);
return send_frame(boot,PCAN_BOOT_MSG_DISCOVERY,(uint16_t)((page<<8)|command),reply,8);
}
if (command == PCAN_BOOT_CMD_CONFIRM) {
pcan_boot_byte_t saved=boot->session_id; bool sent;
boot->session_id=session;
sent=send_status(boot,command,session && boot->port.confirm_running_slot &&
boot->port.confirm_running_slot(boot->port_user) ? PCAN_BOOT_STATUS_OK : PCAN_BOOT_STATUS_INVALID_STATE);
boot->session_id=saved; return sent;
}
if (command == PCAN_BOOT_CMD_IDENTIFY) {
return send_identity(boot);
}
@@ -191,8 +237,18 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
if (session == 0U) {
return send_status(boot, command, PCAN_BOOT_STATUS_SESSION_ERROR);
}
if (boot->state != PCAN_BOOT_STATE_IDLE && boot->session_id != session)
return send_status(boot,command,PCAN_BOOT_STATUS_BUSY);
if (boot->port.enter_boot) {
unsigned int result=boot->port.enter_boot(boot->port_user,session);
if(result==1) return true;
if(result!=0) return send_status(boot,command,PCAN_BOOT_STATUS_INVALID_STATE);
}
if (boot->session_id==session && boot->state!=PCAN_BOOT_STATE_IDLE)
return send_status(boot,command,PCAN_BOOT_STATUS_OK);
pcan_boot_abort(boot);
boot->session_id = session;
boot->last_activity_ms=boot->now_ms;
boot->state = PCAN_BOOT_STATE_METADATA;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
}
@@ -200,8 +256,11 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
return send_status(boot, command, PCAN_BOOT_STATUS_SESSION_ERROR);
}
boot->last_activity_ms=boot->now_ms;
switch (command) {
case PCAN_BOOT_CMD_BEGIN_IMAGE:
if(boot->state==PCAN_BOOT_STATE_READY_TO_ERASE && boot->manifest.image_size==get_u32(data) &&
boot->manifest.image_crc32==get_u32(data+4)) return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if ((dlc != 8U) || (boot->state != PCAN_BOOT_STATE_METADATA)) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
}
@@ -211,6 +270,9 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
return metadata_complete(boot, command);
case PCAN_BOOT_CMD_BEGIN_COMPAT:
if(boot->state==PCAN_BOOT_STATE_READY_TO_ERASE && boot->manifest.product_type==get_u16(data) &&
boot->manifest.hardware_revision_min==data[2] && boot->manifest.hardware_revision_max==data[3] &&
boot->manifest.firmware_version==get_u32(data+4)) return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if ((dlc != 8U) || (boot->state != PCAN_BOOT_STATE_METADATA)) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
}
@@ -222,6 +284,8 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
return metadata_complete(boot, command);
case PCAN_BOOT_CMD_ERASE:
if(boot->state==PCAN_BOOT_STATE_RECEIVING && boot->bytes_received==0)
return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if (boot->state != PCAN_BOOT_STATE_READY_TO_ERASE) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
}
@@ -236,6 +300,8 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
case PCAN_BOOT_CMD_VERIFY:
if(boot->state==PCAN_BOOT_STATE_VERIFIED || boot->state==PCAN_BOOT_STATE_COMMITTED)
return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if ((boot->state != PCAN_BOOT_STATE_RECEIVING)
|| (boot->bytes_received != boot->manifest.image_size)) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
@@ -244,7 +310,13 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
boot->state = PCAN_BOOT_STATE_FAILED;
return send_status(boot, command, PCAN_BOOT_STATUS_CRC_ERROR);
}
if ((boot->port.verify_image != NULL)
if (boot->port.verify_status) {
pcan_boot_status_t status=boot->port.verify_status(boot->port_user,boot->target_slot,&boot->manifest);
if(status!=PCAN_BOOT_STATUS_OK) {
boot->state=PCAN_BOOT_STATE_FAILED; return send_status(boot,command,status);
}
}
if (!boot->port.verify_status && (boot->port.verify_image != NULL)
&& !boot->port.verify_image(boot->port_user, boot->target_slot,
&boot->manifest)) {
boot->state = PCAN_BOOT_STATE_FAILED;
@@ -254,21 +326,16 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
case PCAN_BOOT_CMD_COMMIT:
if(boot->state==PCAN_BOOT_STATE_COMMITTED) return send_status(boot,command,PCAN_BOOT_STATUS_OK);
if (boot->state != PCAN_BOOT_STATE_VERIFIED) {
return send_status(boot, command, PCAN_BOOT_STATUS_INVALID_STATE);
}
if ((boot->config.slot_count == 2U)
if ((boot->port.set_pending_slot != NULL)
&& !boot->port.set_pending_slot(boot->port_user, boot->target_slot,
&boot->manifest)) {
return send_status(boot, command, PCAN_BOOT_STATUS_FLASH_ERROR);
}
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
case PCAN_BOOT_CMD_CONFIRM:
if ((boot->port.confirm_running_slot == NULL)
|| !boot->port.confirm_running_slot(boot->port_user)) {
return send_status(boot, command, PCAN_BOOT_STATUS_FLASH_ERROR);
}
boot->state=PCAN_BOOT_STATE_COMMITTED;
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
case PCAN_BOOT_CMD_QUERY_PROGRESS:
@@ -280,7 +347,9 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
return send_status(boot, command, PCAN_BOOT_STATUS_OK);
case PCAN_BOOT_CMD_REBOOT:
(void)send_status(boot, command, PCAN_BOOT_STATUS_OK);
if(!boot->port.reboot || (boot->port.can_reboot && !boot->port.can_reboot(boot->port_user)))
return send_status(boot,command,PCAN_BOOT_STATUS_INVALID_STATE);
if(!send_status(boot,command,PCAN_BOOT_STATUS_OK)) return false;
if (boot->port.reboot != NULL) {
boot->port.reboot(boot->port_user);
}
@@ -291,11 +360,16 @@ static bool process_control(pcan_boot_t *boot, uint16_t body,
}
}
static bool process_data(pcan_boot_t *boot, uint8_t msg_type, uint16_t block,
const uint8_t *data, uint8_t dlc)
/* DATA не содержит session в Body: все 16 бит заняты номером блока. Поэтому
* кадры данных принимаются только в состоянии RECEIVING и для выбранного A/B.
* При неверном номере ничего не записываем и возвращаем ожидаемый next_block.
* Последний кадр дополняется клиентом до 8 октетов, но запись и CRC учитывают
* только оставшиеся октеты image_size. ACK выдаётся на границе окна или в конце. */
static bool process_data(pcan_boot_t *boot, pcan_boot_byte_t msg_type, uint16_t block,
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc)
{
uint8_t expected_type;
uint8_t write_length;
pcan_boot_byte_t expected_type;
pcan_boot_byte_t write_length;
uint32_t remaining;
if (boot->state != PCAN_BOOT_STATE_RECEIVING) {
@@ -309,8 +383,9 @@ static bool process_data(pcan_boot_t *boot, uint8_t msg_type, uint16_t block,
if ((block != boot->next_block) || (dlc != PCAN_BOOT_BLOCK_SIZE)) {
return send_status(boot, 0U, PCAN_BOOT_STATUS_SEQUENCE_ERROR);
}
boot->last_activity_ms=boot->now_ms;
remaining = boot->manifest.image_size - boot->bytes_received;
write_length = remaining < PCAN_BOOT_BLOCK_SIZE ? (uint8_t)remaining
write_length = remaining < PCAN_BOOT_BLOCK_SIZE ? (pcan_boot_byte_t)remaining
: PCAN_BOOT_BLOCK_SIZE;
if ((write_length == 0U)
|| !boot->port.write_slot(boot->port_user, boot->target_slot,
@@ -329,10 +404,14 @@ static bool process_data(pcan_boot_t *boot, uint8_t msg_type, uint16_t block,
return true;
}
/* Общая точка входа для одного кадра после аппаратной фильтрации.
* Проверки адреса повторяются программно, чтобы широкая маска CAN не разрешила
* обновление чужого устройства. Broadcast допускается только для DISCOVERY
* с Body=0/DLC=0; ответ откладывается до poll для разведения устройств по времени. */
bool pcan_boot_process(pcan_boot_t *boot, uint32_t can_id,
const uint8_t *data, uint8_t dlc)
const pcan_boot_byte_t *data, pcan_boot_byte_t dlc)
{
uint8_t msg_type;
pcan_boot_byte_t msg_type;
if ((boot == NULL) || (dlc > 8U) || ((dlc != 0U) && (data == NULL))) {
return false;
}
@@ -340,6 +419,17 @@ bool pcan_boot_process(pcan_boot_t *boot, uint32_t can_id,
if ((msg_type < PCAN_BOOT_MSG_CONTROL) || (msg_type > PCAN_BOOT_MSG_DISCOVERY)) {
return false;
}
if(msg_type==PCAN_BOOT_MSG_DISCOVERY && pcan_boot_id_route(can_id)==0 &&
pcan_boot_id_msg_body(can_id)==0 && dlc==0 &&
((pcan_boot_id_device_type(can_id)==7 && pcan_boot_id_device_id(can_id)==15) ||
(pcan_boot_id_device_type(can_id)==boot->config.device_type &&
pcan_boot_id_device_id(can_id)==boot->config.device_id))) {
if(!boot->discovery_pending) {
boot->discovery_at=boot->now_ms+2U*(1U+16U*boot->config.device_type+boot->config.device_id);
boot->discovery_pending=true;
}
return true;
}
if ((pcan_boot_id_route(can_id) != PCAN_BOOT_ROUTE_FROM_PM)
|| (pcan_boot_id_device_type(can_id) != boot->config.device_type)
|| (pcan_boot_id_device_id(can_id) != boot->config.device_id)) {
@@ -353,3 +443,18 @@ bool pcan_boot_process(pcan_boot_t *boot, uint32_t can_id,
}
return false;
}
/* Обслуживание времени не требует входящего кадра. Регулярно вызывать из main
* с монотонным счётчиком миллисекунд: иначе не истечёт сессия и не выйдет discovery.
* При занятом TX discovery остаётся pending до успешного send_identity.
* Вычитание unsigned учитывает переход счётчика через 0xFFFFFFFF. */
void pcan_boot_poll(pcan_boot_t *boot,uint32_t now_ms)
{
if(!boot) return;
boot->now_ms=now_ms;
if(boot->config.session_timeout_ms && boot->state!=PCAN_BOOT_STATE_IDLE &&
(uint32_t)(now_ms-boot->last_activity_ms)>=boot->config.session_timeout_ms)
pcan_boot_abort(boot);
if(boot->discovery_pending && (uint32_t)(now_ms-boot->discovery_at)<0x80000000UL && send_identity(boot))
boot->discovery_pending=false;
}

View File

@@ -0,0 +1,13 @@
#ifndef TEST_DSP_H
#define TEST_DSP_H
#include <stdint.h>
#define EALLOW
#define EDIS
struct test_reg { uint32_t all; struct { unsigned DLC, RTR; } bit; };
struct MBOX { struct test_reg MSGID, MSGCTRL, MDL, MDH; };
static struct { struct MBOX MBOX3, MBOX4, MBOX6; } ECanaMboxes;
static struct { struct test_reg CANME,CANMD,CANRMP,CANRML,CANMIM,CANTA,CANAA,CANTRS,CANTRR; } ECanaRegs;
static struct { struct test_reg LAM3,LAM6; } ECanaLAMRegs;
static struct { unsigned long InterruptCount; } CpuTimer1;
static struct { unsigned WDCR; } SysCtrlRegs;
#endif

View File

@@ -0,0 +1,7 @@
#include <stdint.h>
#ifndef EEPROM_M95M02
#define EEPROM_M95M02 1
#endif
#define BALSAM167_EEPROM_FIRMWARE_BYTES 261884UL
void Seeprom_write(unsigned long word, unsigned int *data, unsigned long bytes);
void Seeprom_read(unsigned long word, unsigned int *data, unsigned long bytes);

View File

@@ -0,0 +1,27 @@
"""Host regression suite. CC may point to clang/gcc; this does not replace a TI build."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
root = Path(__file__).resolve().parents[1]
cc = os.environ.get('CC') or shutil.which('clang') or shutil.which('gcc')
if not cc:
raise SystemExit('Set CC to a C99 host compiler')
store = 'ports/tms320f28335/f28335_boot_store.c'
cases = [
('core', [], ['src/pcan_boot.c', 'tests/test_pcan_boot.c']),
('store_native', [], [store, 'tests/test_f28335_boot_store.c']),
('store_wide', ['-DPCAN_BOOT_WIDE_BYTES'], [store, 'tests/test_f28335_boot_store.c']),
('loader', ['-DPCAN_BOOT_WIDE_BYTES'], [store, 'src/pcan_boot.c', 'tests/test_f28335_can_boot.c']),
('application', ['-DPCAN_BOOT_WIDE_BYTES', '-DF28335_BOOT_LOADER=0'],
[store, 'src/pcan_boot.c', 'tests/test_f28335_can_boot.c']),
]
with tempfile.TemporaryDirectory(prefix='f28335-ab-') as tmp:
for name, defines, sources in cases:
exe = Path(tmp) / (name + '.exe')
subprocess.run([cc, '-std=c99', '-Wall', '-Wextra', '-Werror', '-Iinclude',
'-Itests/f28335_stubs', *defines, *sources, '-o', str(exe)], cwd=root, check=True)
sample = os.environ.get('F28335_SAMPLE_IMAGE') if name == 'store_wide' else None
subprocess.run([str(exe), *([sample] if sample else [])], check=True, timeout=30)

View File

@@ -0,0 +1,118 @@
#define _CRT_SECURE_NO_WARNINGS
#include "../ports/tms320f28335/f28335_boot_store.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
static unsigned char eeprom[0x40000],backup[0x40000];
static long budget=-1;
static int read_fail;
static unsigned written_words;
static bool read_mem(void *u,uint32_t addr,pcan_boot_byte_t *p,unsigned int n)
{ unsigned i; (void)u; assert(addr+n<=sizeof(eeprom)); if(read_fail) return false;
for(i=0;i<n;++i) p[i]=eeprom[addr+i]; return true; }
static bool write_mem(void *u,uint32_t addr,const pcan_boot_byte_t *p,unsigned int n)
{ unsigned i; (void)u; assert(addr+n<=0x3FE00);
for(i=0;i<n;++i) { if(budget==0) return false; if(budget>0) --budget; eeprom[addr+i]=(unsigned char)p[i]; }
return true; }
static f28335_boot_io_t io={read_mem,write_mem,0};
static pcan_boot_byte_t image[40];
static pcan_boot_manifest_t manifest;
static void make_image(void)
{
memset(image,0,sizeof(image)); image[0]=0xAA; image[1]=8;
image[20]=0; image[21]=0x80; /* entry=8000, high-word first */
image[22]=5; image[26]=0; image[27]=0x80;
image[28]=0x34; image[29]=0x12;
memset(&manifest,0,sizeof(manifest)); manifest.product_type=0x2833;
manifest.hardware_revision_min=manifest.hardware_revision_max=1;
manifest.image_size=40; manifest.image_crc32=f28335_boot_crc(0,image,40);
manifest.firmware_version=0x01010002;
}
static void image_to(f28335_boot_store_t *s,unsigned slot)
{ unsigned i; assert(f28335_boot_prepare(s,slot));
for(i=0;i<40;i+=8) assert(f28335_boot_write(s,slot,i,image+i,8)); }
static bool ram(void *u,uint32_t address,uint16_t word)
{ (void)u; assert(address==0x8000+written_words); if(!written_words) assert(word==0x1234);
++written_words; return true; }
static void reopen(f28335_boot_store_t *s) { assert(f28335_boot_open(s,&io)); }
int main(int argc,char **argv)
{
f28335_boot_store_t s; unsigned slot; uint32_t entry; long cut;
memset(eeprom,0xA5,sizeof(eeprom)); make_image(); reopen(&s);
assert(s.active==255 && !s.valid); assert(f28335_boot_select(&s,&slot) && slot==255);
image_to(&s,0); assert(f28335_boot_verify(&s,0,&manifest,&entry) && entry==0x8000);
assert(f28335_boot_commit(&s,0,&manifest));
assert(!f28335_boot_confirm(&s,0)); /* Cannot confirm an image not yet started. */
reopen(&s); assert(f28335_boot_select(&s,&slot) && slot==0 && s.attempted);
assert(f28335_boot_load(&s,slot,ram,0,&entry) && written_words==5);
assert(!f28335_boot_confirm(&s,1)); assert(f28335_boot_confirm(&s,0));
assert(f28335_boot_confirm(&s,0)); /* idempotent */
assert(!f28335_boot_prepare(&s,0));
assert(!f28335_boot_write(&s,0,0,image,8));
assert(!f28335_boot_write(&s,1,F28335_AB_SLOT_BYTES,image,8));
image_to(&s,1); memcpy(backup,eeprom,sizeof(eeprom));
/* Every torn write position in journal publication keeps confirmed A. */
for(cut=0;cut<=68;++cut) {
memcpy(eeprom,backup,sizeof(eeprom)); reopen(&s); budget=cut;
(void)f28335_boot_commit(&s,1,&manifest); budget=-1; reopen(&s);
assert(s.active==0); assert(f28335_boot_verify(&s,0,&s.image[0],0));
assert(s.pending==255 || s.pending==1);
}
memcpy(eeprom,backup,sizeof(eeprom)); reopen(&s); assert(f28335_boot_commit(&s,1,&manifest));
memcpy(backup,eeprom,sizeof(eeprom));
/* Torn trial bookkeeping must never select an unrecorded trial. */
for(cut=0;cut<=68;++cut) {
memcpy(eeprom,backup,sizeof(eeprom)); reopen(&s); budget=cut;
if(f28335_boot_select(&s,&slot)) { assert(slot==1); assert(s.attempted); }
budget=-1; reopen(&s); assert(s.active==0);
}
memcpy(eeprom,backup,sizeof(eeprom)); reopen(&s);
assert(f28335_boot_select(&s,&slot) && slot==1);
reopen(&s); assert(f28335_boot_select(&s,&slot) && slot==0 && s.pending==255);
image_to(&s,1); assert(f28335_boot_commit(&s,1,&manifest));
assert(f28335_boot_select(&s,&slot) && slot==1);
memcpy(backup,eeprom,sizeof(eeprom));
for(cut=0;cut<=68;++cut) {
memcpy(eeprom,backup,sizeof(eeprom)); reopen(&s); budget=cut;
(void)f28335_boot_confirm(&s,1); budget=-1; reopen(&s);
assert(s.active==0 || s.active==1);
assert(f28335_boot_select(&s,&slot) && (slot==0 || slot==1));
}
memcpy(eeprom,backup,sizeof(eeprom)); reopen(&s); assert(f28335_boot_confirm(&s,1));
/* CRC correct but malicious SCI8 destination and entry are rejected. */
image_to(&s,0); image[26]=0; image[27]=0xE0;
manifest.image_crc32=f28335_boot_crc(0,image,40);
image_to(&s,0); assert(!f28335_boot_verify(&s,0,&manifest,0));
image[27]=0x80; image[21]=0x90; manifest.image_crc32=f28335_boot_crc(0,image,40);
image_to(&s,0); assert(!f28335_boot_verify(&s,0,&manifest,0));
make_image(); image[22]=1; image[30]=1; image[35]=0x80;
manifest.image_crc32=f28335_boot_crc(0,image,40);
image_to(&s,0); assert(!f28335_boot_verify(&s,0,&manifest,0)); /* overlapping sections */
make_image(); image_to(&s,0); manifest.image_size=38;
manifest.image_crc32=f28335_boot_crc(0,image,38);
assert(!f28335_boot_verify(&s,0,&manifest,0)); /* missing terminator */
make_image(); image[22]=4; manifest.image_crc32=f28335_boot_crc(0,image,40);
image_to(&s,0); assert(!f28335_boot_verify(&s,0,&manifest,0)); /* trailing octets */
make_image(); image_to(&s,0); eeprom[F28335_AB_SLOT_A+29]^=1;
assert(!f28335_boot_verify(&s,0,&manifest,0));
/* Failed EEPROM reads fail closed, including subsequent writes. */
read_fail=1; assert(!f28335_boot_open(&s,&io));
assert(!f28335_boot_prepare(&s,0)); assert(!f28335_boot_write(&s,0,0,image,8)); read_fail=0;
for(entry=0x3FE00;entry<0x40000;++entry) assert(eeprom[entry]==0xA5);
if(argc>1) {
FILE *file=fopen(argv[1],"rb"); uint32_t offset,crc=0;
pcan_boot_byte_t bytes[32]; unsigned n;
assert(file); make_image();
manifest.image_size=(uint32_t)fread(eeprom+F28335_AB_SLOT_A,1,F28335_AB_SLOT_BYTES,file);
assert(!ferror(file) && fgetc(file)==EOF); fclose(file); reopen(&s);
for(offset=0;offset<manifest.image_size;offset+=n) {
n=manifest.image_size-offset<32?(unsigned)(manifest.image_size-offset):32;
assert(read_mem(0,F28335_AB_SLOT_A+offset,bytes,n)); crc=f28335_boot_crc(crc,bytes,n);
}
manifest.image_crc32=crc;
assert(f28335_boot_verify(&s,0,&manifest,&entry));
puts("Production SCI8 binary: CRC, destinations and entry accepted");
}
puts("F28335 A/B: journal power cuts, trial, rollback, confirm, bounds, SCI8: OK");
return 0;
}

View File

@@ -0,0 +1,94 @@
/* Production CAN adapter, fake hardware, wide C28x wire octets. */
#define EEPROM_M95M02 1
#ifndef F28335_BOOT_LOADER
#define F28335_BOOT_LOADER 1
#endif
#define F28335_CAN_BOOT_FIRMWARE_VERSION 0x12345678UL
#include <assert.h>
#include <stdio.h>
#include <string.h>
void ServiceDog(void);
#include "../ports/tms320f28335/f28335_can_boot.c"
static unsigned char memory[0x40000];
static unsigned int running=255,resets,reset_arg,dog_feeds;
static int fail_write;
void ServiceDog(void) { ++dog_feeds; }
bool F28335Boot_Read(void *u,uint32_t a,pcan_boot_byte_t *p,unsigned int n)
{ unsigned i; (void)u; assert(a+n<=sizeof(memory)); for(i=0;i<n;++i)p[i]=memory[a+i]; return true; }
bool F28335Boot_Write(void *u,uint32_t a,const pcan_boot_byte_t *p,unsigned int n)
{ unsigned i; (void)u; assert(a+n<=0x3FE00); if(fail_write)return false;
for(i=0;i<n;++i)memory[a+i]=(unsigned char)p[i]; return true; }
uint32_t F28335Boot_Now(void) { return CpuTimer1.InterruptCount; }
unsigned int F28335Boot_RunningSlot(void) { return running; }
void F28335Boot_Reset(unsigned int session) { ++resets;reset_arg=session;reset_requested=0; }
static void command(unsigned cmd,const pcan_boot_byte_t *d,unsigned n)
{ tx_busy=0; assert(pcan_boot_process(&boot,pcan_boot_make_id(1,0,3,7,9,0x100|cmd),d,n)); }
static void put32(pcan_boot_byte_t *p,uint32_t v)
{ unsigned i;for(i=0;i<4;++i)p[i]=(v>>(i*8))&255; }
int main(void)
{
pcan_boot_byte_t image[40]={0xAA,8},meta[8],compat[8]={0x33,0x28,1,1,2,0,1,1};
unsigned i,slot; pcan_boot_manifest_t m;
memset(memory,0xA5,sizeof(memory)); image[21]=0x80; image[22]=5; image[27]=0x80;
F28335CanBoot_Init(); assert(boot.config.max_image_size==130560 && boot.config.slot_count==2);
if(!F28335_BOOT_LOADER) {
/* SPI-ROM application without Flash-loader handoff cannot erase anything. */
command(2,0,0); assert(boot.last_status==PCAN_BOOT_STATUS_INVALID_STATE && !reset_requested);
memset(&m,0,sizeof(m));m.image_size=40;m.image_crc32=f28335_boot_crc(0,image,40);
m.product_type=0x2833;m.hardware_revision_min=m.hardware_revision_max=1;
assert(f28335_boot_prepare(&store,0));
for(i=0;i<40;i+=8)assert(f28335_boot_write(&store,0,i,image+i,8));
assert(f28335_boot_commit(&store,0,&m)); assert(f28335_boot_select(&store,&slot));
running=0; F28335CanBoot_Init();
assert(!F28335CanBoot_WatchdogAllowed());
command(8,0,0);assert(boot.last_status==PCAN_BOOT_STATUS_INVALID_STATE);
F28335CanBoot_Ready();
for(i=0;i<100;++i) F28335CanBoot_Service();
assert(!confirmed && store.pending==0);
CpuTimer1.InterruptCount=5000; F28335CanBoot_Service();
assert(confirmed && store.active==0 && dog_feeds);
command(2,0,0); assert(reset_requested && reset_session==1 && boot.state==PCAN_BOOT_STATE_IDLE);
F28335CanBoot_Service();assert(resets==1 && reset_arg==1);
command(1,0,0);assert(ECanaMboxes.MBOX4.MDH.all==0x12345678UL);
} else {
command(2,0,0); put32(meta,40);put32(meta+4,f28335_boot_crc(0,image,40));
command(3,meta,8);command(4,compat,8);assert(boot.target_slot==0);
command(5,0,0);
for(i=0;i<5;++i) { tx_busy=0; assert(pcan_boot_process(&boot,
pcan_boot_make_id(1,0,3,7,10,i),image+i*8,8)); }
command(6,0,0);assert(boot.state==PCAN_BOOT_STATE_VERIFIED);
command(7,0,0);assert(store.pending==0 && boot.state==PCAN_BOOT_STATE_COMMITTED);
command(7,0,0);assert(boot.last_status==0);
command(8,0,0);assert(boot.last_status==PCAN_BOOT_STATUS_INVALID_STATE);
command(9,0,0);assert(reset_requested);tx_busy=0;
F28335CanBoot_Service();assert(resets==1 && reset_arg==0);
command(10,0,0);command(2,0,0);command(3,meta,8);command(4,compat,8);
command(5,0,0);assert(boot.last_status==PCAN_BOOT_STATUS_FLASH_ERROR); /* Pending is protected. */
}
command(12,0,0); assert((ECanaMboxes.MBOX4.MSGID.all&65535)==12);
command(13,0,0); assert(ECanaMboxes.MBOX4.MDH.all==130560);
meta[0]=0;command(14,meta,1); assert(ECanaMboxes.MBOX4.MDL.all==0x33383246UL); /* F283 */
meta[0]=3;command(15,meta,1);assert(ECanaMboxes.MBOX4.MDL.all==0x3FEFCUL);
assert(!pcan_boot_process(&boot,pcan_boot_make_id(1,0,3,6,9,0x102),0,0));
tx_busy=0;
assert(pcan_boot_process(&boot,pcan_boot_make_id(1,0,7,15,13,0),0,0));
assert(boot.discovery_pending); pcan_boot_poll(&boot,boot.discovery_at);
assert(!boot.discovery_pending && (ECanaMboxes.MBOX4.MSGID.all&65535)==1);
assert(!pcan_boot_process(&boot,pcan_boot_make_id(1,0,7,15,9,0x102),0,0));
head=tail=0; ECanaRegs.CANRMP.all=8;
ECanaMboxes.MBOX3.MSGID.all=0x80000000UL|pcan_boot_make_id(1,0,3,7,9,12);
ECanaMboxes.MBOX3.MSGCTRL.bit.DLC=0;
F28335CanBoot_RxISR(); assert(head==1 && tail==0);
tx_busy=0; F28335CanBoot_Service(); assert(tail==1);
ECanaMboxes.MBOX3.MSGCTRL.bit.RTR=1; F28335CanBoot_RxISR(); assert(head==1);
ECanaMboxes.MBOX3.MSGCTRL.bit.RTR=0;
overflow=1;tx_busy=0;F28335CanBoot_Service();assert(boot.state==PCAN_BOOT_STATE_IDLE);
/* An ACK arriving after cancellation must never execute a queued reset. */
tx_busy=1;tx_canceling=0;reset_requested=1;sent_at=0;
CpuTimer1.InterruptCount=300;ECanaRegs.CANTA.all=0;ECanaRegs.CANAA.all=0;
F28335CanBoot_Service();assert(!reset_requested && tx_canceling);
ECanaRegs.CANTA.all=16; F28335CanBoot_Service();assert(!tx_busy && resets==1);
for(i=0x3FE00;i<0x40000;++i)assert(memory[i]==0xA5);
puts(F28335_BOOT_LOADER?"F28335 loader CAN services: OK":"F28335 application handoff/confirmation: OK");
return 0;
}

View File

@@ -264,7 +264,7 @@ static void test_rejects_wrong_session_and_compatibility(void)
data[3] = 3U;
put_u32(&data[4], 1U);
assert(command(&boot, 7U, PCAN_BOOT_CMD_BEGIN_COMPAT, data, 8U));
assert(fake.last_data[0] == PCAN_BOOT_STATUS_WRONG_HARDWARE);
assert(fake.last_data[0] == PCAN_BOOT_STATUS_WRONG_DEVICE);
assert(boot.state == PCAN_BOOT_STATE_FAILED);
}
@@ -306,8 +306,41 @@ static void test_single_slot_update(void)
assert(memcmp(fake.flash[0], image, sizeof(image)) == 0);
}
static void test_services_timeout_and_retries(void)
{
pcan_boot_t boot; fake_t fake;
uint8_t image[8]={0},compat[8]={0}; unsigned count;
setup(&boot,&fake); boot.config.session_timeout_ms=30000;
pcan_boot_poll(&boot,0xFFFFFF00UL);
assert(command(&boot,7,PCAN_BOOT_CMD_ENTER_BOOT,NULL,0));
assert(command(&boot,8,PCAN_BOOT_CMD_ENTER_BOOT,NULL,0));
assert(boot.session_id==7 && fake.last_data[0]==PCAN_BOOT_STATUS_BUSY);
assert(command(&boot,7,PCAN_BOOT_CMD_ERASE,image,1));
assert(fake.last_data[0]==PCAN_BOOT_STATUS_INVALID_SIZE);
pcan_boot_poll(&boot,29743); assert(boot.state!=PCAN_BOOT_STATE_IDLE);
pcan_boot_poll(&boot,29744); assert(boot.state==PCAN_BOOT_STATE_IDLE);
assert(command(&boot,0,PCAN_BOOT_CMD_PING,NULL,0));
assert(pcan_boot_id_msg_body(fake.last_id)==12);
count=fake.sends;
assert(pcan_boot_process(&boot,pcan_boot_make_id(1,0,7,15,13,0),NULL,0));
assert(fake.sends==count); pcan_boot_poll(&boot,boot.discovery_at);
assert(fake.sends==count+1 && pcan_boot_id_msg_body(fake.last_id)==1);
assert(!pcan_boot_process(&boot,pcan_boot_make_id(1,0,7,15,9,0x702),NULL,0));
begin_update(&boot,image,8);
put_u16(compat,boot.manifest.product_type);
compat[2]=boot.manifest.hardware_revision_min; compat[3]=boot.manifest.hardware_revision_max;
put_u32(compat+4,boot.manifest.firmware_version);
assert(command(&boot,7,4,compat,8)); assert(fake.last_data[0]==0);
assert(command(&boot,7,5,NULL,0)); count=fake.erases;
assert(command(&boot,7,5,NULL,0)); assert(fake.erases==count && fake.last_data[0]==0);
assert(pcan_boot_process(&boot,request_id(PCAN_BOOT_MSG_DATA_B,0),image,8));
assert(command(&boot,7,6,NULL,0)); assert(command(&boot,7,6,NULL,0)); assert(fake.last_data[0]==0);
assert(command(&boot,7,7,NULL,0)); assert(command(&boot,7,7,NULL,0)); assert(fake.last_data[0]==0);
}
int main(void)
{
test_services_timeout_and_retries();
test_id_layout();
test_crc32_reference();
test_complete_update();

View File

@@ -7,6 +7,7 @@ Device Name — отображаемое имя адреса, а не допол
| DeviceType | DeviceID | Device Name | Назначение |
|---|---|---|---|
| `0x0` | существующие | Верхний уровень | Существующие адреса исходной таблицы |
| `0x3` | `0x7` | **F28335-M95M02** | Загрузка прошивки по ProtoCAN в EEPROM M95M02 платы TMS320F28335; ProductType `0x2833` |
| `0x6` | `0xE` | **Altera Logic** | Онлайн-анализатор 16 цифровых каналов |
| `0x7` | `0xD` | configurator | Существующий конфигуратор |
| `0x7` | `0xF` | KONOR / SETTINGS | Существующий профиль SETCAN и привязка датчиков |

View File

@@ -597,11 +597,11 @@ CRC32 обнаруживает случайное повреждение, но
<td class="x14">&nbsp;</td>
<td colspan="8" class="x96">64 битовых адресса</td>
</tr>
<tr style="height:17px">
<tr style="height:72px">
<td class="x26">&nbsp;</td>
<td class="x27">&nbsp;</td>
<td colspan="3" class="x131">0x3</td>
<td colspan="4" class="x136">0x0 - 0xF устройств</td>
<td colspan="4" class="x136">0x0 - 0xF устройств<br>0x7: F28335-M95M02<br>ProductType: 0x2833</td>
<td colspan="12" class="x138">0x3. Установка времени</td>
<td colspan="4" class="x140">&nbsp;</td>
<td class="x14">&nbsp;</td>

View File

@@ -37,6 +37,8 @@ class CanBootTransfer:
def __init__(self, image, target: CanBootTarget):
if not 0 < len(image.data) <= 512 * 1024:
raise ValueError("ProtoCAN Boot: размер образа 1…512 КиБ")
if target.product_type == 0x2833 and len(image.data) > 0x1FE00:
raise ValueError("F28335 M95M02 A/B: образ не должен превышать 130560 байт")
if not 0 <= image.version <= 0xFFFFFFFF:
raise ValueError("Версия образа вне диапазона u32")
self.image, self.target = image, target
@@ -46,6 +48,10 @@ class CanBootTransfer:
self.finished = False
self._window_end = 0
@property
def response_timeout_ms(self):
return 15000 if self.stage in ("enter", "verify", "commit") else 1500
@property
def percent(self):
return min(100, self.next_block * 8 * 100 // len(self.image.data))
@@ -128,4 +134,93 @@ class CanBootTransfer:
self.stage = "reboot"
return self._control(9), "Перезапуск прибора"
self.stage, self.finished = "finished", True
if self.target.product_type == 0x2833:
return [], "Образ принят; после перезапуска приложение подтвердит пробный запуск"
return [], "Прошивка ProtoCAN Boot завершена"
class CanBootServices:
"""Read-only passport queries and explicit session service commands.
Replies use message D, body=(page<<8)|command. IDENTIFY/discovery reply body=1.
All multibyte values are little endian; version is major:8/minor:8/patch:16.
"""
def __init__(self, target: CanBootTarget):
self.target = target
def request(self, command: int, page: int = 0):
if command not in (1, 8, 11, 12, 13, 14, 15):
raise ValueError("Unsupported service command")
if not 0 <= page <= 255 or (page and command not in (14, 15)):
raise ValueError("Invalid service page")
t = self.target
payload = bytes([page]) if command in (14, 15) else b""
return build_frame(ProtoCanId.build(1, 0, t.device_type, t.device, 9,
t.session_id << 8 | command), payload, to_can=True)
@staticmethod
def discover():
return build_frame(ProtoCanId.build(1, 0, 7, 15, 13, 0), b"", to_can=True)
def decode(self, frame, *, discovery=False):
p = ProtoCanId.parse(frame.can_id)
if (frame.to_can or not frame.ide or frame.rtr or frame.is_error or p.pm != 1
or p.msg_type not in (12, 13) or len(frame.data) != 8):
return None
if discovery:
if p.msg_type != 13 or p.body != 1:
return None
elif (p.device_type, p.device) != (self.target.device_type, self.target.device):
return None
if p.msg_type == 12:
if p.body >> 8 != self.target.session_id:
return None
status, slot, next_block, crc32 = struct.unpack("<BBHI", frame.data)
return {"kind": "status", "device_type": p.device_type, "device": p.device,
"command": p.body & 255, "status": status, "target_slot": slot,
"next_block": next_block, "crc32": crc32}
command, page = p.body & 255, p.body >> 8
d = frame.data
result = {"kind": "passport", "device_type": p.device_type, "device": p.device, "command": command, "page": page}
if command == 1 and page == 0:
product, hardware, protocol, version = struct.unpack("<HBBI", d)
result.update(product_type=product, hardware_revision=hardware, protocol_version=protocol,
firmware_version=version, version=(version >> 24, (version >> 16) & 255, version & 65535))
elif command == 12 and page == 0:
result.update(protocol_version=d[0], state=d[1], active_slot=d[2], target_slot=d[3],
session_id=d[4], slot_count=d[5], last_status=d[6])
elif command == 13 and page == 0:
flags, capacity = struct.unpack("<II", d)
result.update(capabilities=flags, slot_capacity=capacity)
elif command == 14:
result["name_fragment"] = d.split(b"\0", 1)[0].decode("ascii", errors="replace")
elif command == 15:
if page == 0:
size, page_size, mode, running = struct.unpack("<IHBB", d)
result.update(memory_bytes=size, page_bytes=page_size, bootloader=bool(mode), running_slot=running)
elif page == 1:
result["slot_offsets"] = struct.unpack("<II", d)
elif page == 2:
active, pending, attempted, healthy, timeout = struct.unpack("<BBBBI", d)
result.update(active_slot=active, pending_slot=pending, trial_attempted=bool(attempted),
storage_healthy=bool(healthy), session_timeout_ms=timeout)
elif page == 3:
result["parameters_offset"], result["parameters_bytes"] = struct.unpack("<II", d)
else:
return None
else:
return None
return result
class CanBootPingExchange:
"""PING adapter with the same request/feed shape as the SETProtocol client."""
def __init__(self, target: CanBootTarget):
self.services = CanBootServices(target)
def request(self):
return [self.services.request(12)]
def feed(self, frame):
reply = self.services.decode(frame)
return reply if reply and reply["kind"] == "passport" and reply["command"] == 12 else None

View File

@@ -0,0 +1,70 @@
from types import SimpleNamespace
import struct
import pytest
from protocan.can_boot import CanBootServices, CanBootTarget, CanBootTransfer, CanBootPingExchange
from protocan.protocan import ProtoCanId
from protocan.transport import build_frame
def response(command, data, device=7):
return build_frame(ProtoCanId.build(1, 1, 3, device, 13, command), data, to_can=False)
def test_passport_and_discovery_address_filters():
service = CanBootServices(CanBootTarget(device_type=3, device=7, product_type=0x2833))
identify = response(1, struct.pack('<HBBI', 0x2833, 1, 1, 0x01010002))
assert service.decode(identify)['version'] == (1, 1, 2)
foreign = response(1, identify.data, device=8)
assert service.decode(foreign) is None
assert service.decode(foreign, discovery=True)['device'] == 8
capabilities = service.decode(response(13, struct.pack('<II', 0xFFF, 130560)))
assert capabilities['slot_capacity'] == 130560
assert capabilities['capabilities'] & (1 << 8)
assert service.decode(response(14, b'F28335-M'))['name_fragment'] == 'F28335-M'
assert service.decode(response(0x10E, b'95M02\0\0\0'))['name_fragment'] == '95M02'
memory = service.decode(response(15, struct.pack('<IHBB', 262144, 256, 1, 255)))
assert memory['bootloader'] and memory['memory_bytes'] == 262144
assert service.decode(response(0x10F, struct.pack('<II', 512, 131072)))['slot_offsets'] == (512, 131072)
pending = service.decode(response(0x20F, struct.pack('<BBBBI', 0, 1, 1, 1, 30000)))
assert pending['trial_attempted'] and pending['pending_slot'] == 1
assert service.decode(response(0x30F, struct.pack('<II', 0x3FEFC, 258)))['parameters_bytes'] == 258
assert service.decode(response(0x40F, bytes(8))) is None
discover = ProtoCanId.parse(service.discover().can_id)
assert (discover.device_type, discover.device, discover.msg_type, discover.body) == (7, 15, 13, 0)
def test_service_requests_and_transfer_limits():
target = CanBootTarget(device_type=3, device=7, product_type=0x2833, session_id=15)
service = CanBootServices(target)
request = service.request(15, 2)
assert request.data == b'\x02' and ProtoCanId.parse(request.can_id).body == 0xF0F
assert ProtoCanId.parse(service.request(8).can_id).body == 0xF08
with pytest.raises(ValueError):
service.request(5)
with pytest.raises(ValueError):
service.request(1, 3)
with pytest.raises(ValueError):
CanBootTransfer(SimpleNamespace(data=bytes(130561), version=1), target)
transfer = CanBootTransfer(SimpleNamespace(data=bytes(40), version=1), target)
assert transfer.response_timeout_ms == 15000
transfer.stage = 'data'
assert transfer.response_timeout_ms == 1500
def test_ping_exchange_only_accepts_addressed_ping_reply():
ping = CanBootPingExchange(CanBootTarget(device_type=3, device=7))
assert ProtoCanId.parse(ping.request()[0].can_id).body & 255 == 12
assert ping.feed(response(1, bytes(8))) is None
assert ping.feed(response(12, bytes([1, 0, 0, 255, 0, 2, 0, 0]), device=8)) is None
assert ping.feed(response(12, bytes([1, 0, 0, 255, 0, 2, 0, 0])))['slot_count'] == 2
def test_confirm_and_progress_status_correlate_session():
service = CanBootServices(CanBootTarget(device_type=3, device=7, session_id=5))
frame = build_frame(ProtoCanId.build(1, 1, 3, 7, 12, 0x508),
struct.pack('<BBHI', 0, 1, 123, 0x12345678), to_can=False)
reply = service.decode(frame)
assert reply['status'] == 0 and reply['command'] == 8 and reply['next_block'] == 123
assert service.decode(frame, discovery=True) is None
assert CanBootServices(CanBootTarget(device_type=3, device=7, session_id=6)).decode(frame) is None