From 3c0f6fd9e6275f375732e7f4b2c1adb42a838942 Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Sun, 11 Oct 2026 03:24:44 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D1=83=20F28?= =?UTF-8?q?335=20=D0=BF=D0=BE=20ProtoCAN=20=D0=B8=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B8=D1=81=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=B0?= =?UTF-8?q?=D0=BD=D0=B4=D1=8B=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- c/protocan-boot/CMakeLists.txt | 29 +- c/protocan-boot/README.md | 14 +- c/protocan-boot/docs/BOOTLOADER.md | 20 +- c/protocan-boot/include/pcan_boot.h | 82 ++++-- c/protocan-boot/ports/tms320f28335/README.md | 156 ++++++++++ .../ports/tms320f28335/f28335_boot_hw.c | 181 ++++++++++++ .../ports/tms320f28335/f28335_boot_platform.h | 17 ++ .../ports/tms320f28335/f28335_boot_store.c | 254 ++++++++++++++++ .../ports/tms320f28335/f28335_boot_store.h | 55 ++++ .../ports/tms320f28335/f28335_can_boot.c | 276 ++++++++++++++++++ .../ports/tms320f28335/f28335_can_boot.h | 28 ++ c/protocan-boot/src/pcan_boot.c | 223 ++++++++++---- .../tests/f28335_stubs/DSP2833x_Device.h | 13 + c/protocan-boot/tests/f28335_stubs/spise2p.h | 7 + c/protocan-boot/tests/run_tests.py | 27 ++ .../tests/test_f28335_boot_store.c | 118 ++++++++ c/protocan-boot/tests/test_f28335_can_boot.c | 94 ++++++ c/protocan-boot/tests/test_pcan_boot.c | 35 ++- doc/setcan/DEVICE_REGISTRY.md | 1 + doc/setcan/Протокол CAN и ОАП.md | 4 +- .../Протокол CAN и ОАП.xlsx | Bin 103216 -> 87602 bytes python/protocan/can_boot.py | 95 ++++++ python/tests/test_can_boot_services.py | 70 +++++ 23 files changed, 1702 insertions(+), 97 deletions(-) create mode 100644 c/protocan-boot/ports/tms320f28335/README.md create mode 100644 c/protocan-boot/ports/tms320f28335/f28335_boot_hw.c create mode 100644 c/protocan-boot/ports/tms320f28335/f28335_boot_platform.h create mode 100644 c/protocan-boot/ports/tms320f28335/f28335_boot_store.c create mode 100644 c/protocan-boot/ports/tms320f28335/f28335_boot_store.h create mode 100644 c/protocan-boot/ports/tms320f28335/f28335_can_boot.c create mode 100644 c/protocan-boot/ports/tms320f28335/f28335_can_boot.h create mode 100644 c/protocan-boot/tests/f28335_stubs/DSP2833x_Device.h create mode 100644 c/protocan-boot/tests/f28335_stubs/spise2p.h create mode 100644 c/protocan-boot/tests/run_tests.py create mode 100644 c/protocan-boot/tests/test_f28335_boot_store.c create mode 100644 c/protocan-boot/tests/test_f28335_can_boot.c create mode 100644 python/tests/test_can_boot_services.py diff --git a/c/protocan-boot/CMakeLists.txt b/c/protocan-boot/CMakeLists.txt index 47d6c8e..2548300 100644 --- a/c/protocan-boot/CMakeLists.txt +++ b/c/protocan-boot/CMakeLists.txt @@ -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() diff --git a/c/protocan-boot/README.md b/c/protocan-boot/README.md index 174756f..b1bb5c7 100644 --- a/c/protocan-boot/README.md +++ b/c/protocan-boot/README.md @@ -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 порта. diff --git a/c/protocan-boot/docs/BOOTLOADER.md b/c/protocan-boot/docs/BOOTLOADER.md index f4ccf78..b180cdb 100644 --- a/c/protocan-boot/docs/BOOTLOADER.md +++ b/c/protocan-boot/docs/BOOTLOADER.md @@ -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. diff --git a/c/protocan-boot/include/pcan_boot.h b/c/protocan-boot/include/pcan_boot.h index 7118b51..cf9f7a9 100644 --- a/c/protocan-boot/include/pcan_boot.h +++ b/c/protocan-boot/include/pcan_boot.h @@ -11,6 +11,13 @@ #include #include +/* 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 diff --git a/c/protocan-boot/ports/tms320f28335/README.md b/c/protocan-boot/ports/tms320f28335/README.md new file mode 100644 index 0000000..3d45984 --- /dev/null +++ b/c/protocan-boot/ports/tms320f28335/README.md @@ -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 выполняет этот загрузчик. diff --git a/c/protocan-boot/ports/tms320f28335/f28335_boot_hw.c b/c/protocan-boot/ports/tms320f28335/f28335_boot_hw.c new file mode 100644 index 0000000..e3f7aa1 --- /dev/null +++ b/c/protocan-boot/ports/tms320f28335/f28335_boot_hw.c @@ -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 && i0x3FE00UL || 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 + +/* Формат журнала фиксирован: 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>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;istarts[i]) return false; + starts[sections]=addr; ends[sections++]=end; + if(*entry>=addr && *entry1 || 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;offimage_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); +} diff --git a/c/protocan-boot/ports/tms320f28335/f28335_boot_store.h b/c/protocan-boot/ports/tms320f28335/f28335_boot_store.h new file mode 100644 index 0000000..f5065a9 --- /dev/null +++ b/c/protocan-boot/ports/tms320f28335/f28335_boot_store.h @@ -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 diff --git a/c/protocan-boot/ports/tms320f28335/f28335_can_boot.c b/c/protocan-boot/ports/tms320f28335/f28335_can_boot.c new file mode 100644 index 0000000..5cbd7a4 --- /dev/null +++ b/c/protocan-boot/ports/tms320f28335/f28335_can_boot.c @@ -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 +/* 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;i1) 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]=indexMSGCTRL.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 diff --git a/c/protocan-boot/ports/tms320f28335/f28335_can_boot.h b/c/protocan-boot/ports/tms320f28335/f28335_can_boot.h new file mode 100644 index 0000000..17b44db --- /dev/null +++ b/c/protocan-boot/ports/tms320f28335/f28335_can_boot.h @@ -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 diff --git a/c/protocan-boot/src/pcan_boot.c b/c/protocan-boot/src/pcan_boot.c index 7b92239..2748d03 100644 --- a/c/protocan-boot/src/pcan_boot.c +++ b/c/protocan-boot/src/pcan_boot.c @@ -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 #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; +} diff --git a/c/protocan-boot/tests/f28335_stubs/DSP2833x_Device.h b/c/protocan-boot/tests/f28335_stubs/DSP2833x_Device.h new file mode 100644 index 0000000..00842d5 --- /dev/null +++ b/c/protocan-boot/tests/f28335_stubs/DSP2833x_Device.h @@ -0,0 +1,13 @@ +#ifndef TEST_DSP_H +#define TEST_DSP_H +#include +#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 diff --git a/c/protocan-boot/tests/f28335_stubs/spise2p.h b/c/protocan-boot/tests/f28335_stubs/spise2p.h new file mode 100644 index 0000000..acb88b5 --- /dev/null +++ b/c/protocan-boot/tests/f28335_stubs/spise2p.h @@ -0,0 +1,7 @@ +#include +#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); diff --git a/c/protocan-boot/tests/run_tests.py b/c/protocan-boot/tests/run_tests.py new file mode 100644 index 0000000..5180dea --- /dev/null +++ b/c/protocan-boot/tests/run_tests.py @@ -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) diff --git a/c/protocan-boot/tests/test_f28335_boot_store.c b/c/protocan-boot/tests/test_f28335_boot_store.c new file mode 100644 index 0000000..7c0fe34 --- /dev/null +++ b/c/protocan-boot/tests/test_f28335_boot_store.c @@ -0,0 +1,118 @@ +#define _CRT_SECURE_NO_WARNINGS +#include "../ports/tms320f28335/f28335_boot_store.h" +#include +#include +#include +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;i0) --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 +#include +#include +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>(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; +} diff --git a/c/protocan-boot/tests/test_pcan_boot.c b/c/protocan-boot/tests/test_pcan_boot.c index d871412..259dcb9 100644 --- a/c/protocan-boot/tests/test_pcan_boot.c +++ b/c/protocan-boot/tests/test_pcan_boot.c @@ -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(); diff --git a/doc/setcan/DEVICE_REGISTRY.md b/doc/setcan/DEVICE_REGISTRY.md index f25a2cd..62df0ec 100644 --- a/doc/setcan/DEVICE_REGISTRY.md +++ b/doc/setcan/DEVICE_REGISTRY.md @@ -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 и привязка датчиков | diff --git a/doc/setcan/Протокол CAN и ОАП.md b/doc/setcan/Протокол CAN и ОАП.md index da42337..171ec3b 100644 --- a/doc/setcan/Протокол CAN и ОАП.md +++ b/doc/setcan/Протокол CAN и ОАП.md @@ -597,11 +597,11 @@ CRC32 обнаруживает случайное повреждение, но   64 битовых адресса - +     0x3 -0x0 - 0xF устройств +0x0 - 0xF устройств
0x7: F28335-M95M02
ProductType: 0x2833 0x3. Установка времени     diff --git a/doc/setcan/Протокол CAN и ОАП.xlsx b/doc/setcan/Протокол CAN и ОАП.xlsx index 7ddba5f08aabed041d3dd2b145e341a7c40310b6..0ce7a429c13acf0d072a187c2202ab926d75cee2 100644 GIT binary patch literal 87602 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 z�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 diff --git a/python/protocan/can_boot.py b/python/protocan/can_boot.py index 8b80446..46e5a4b 100644 --- a/python/protocan/can_boot.py +++ b/python/protocan/can_boot.py @@ -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("> 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("> 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("