171 lines
8.3 KiB
Markdown
171 lines
8.3 KiB
Markdown
# ProtoCAN Boot
|
||
|
||
`protocan-boot` — переносимое C99-ядро адресной прошивки приборов по classic
|
||
CAN 2.0B и 29-битному ProtoCAN ID. Оно реализует сессию обновления, два
|
||
логических слота по 512 КиБ, последовательную запись 8-байтовых блоков,
|
||
CRC32, проверку совместимости, продолжение по номеру следующего блока и
|
||
безопасный выбор неактивного слота.
|
||
|
||
Это не готовый загрузчик конкретного STM32: внутри нет HAL, регистров Flash,
|
||
linker script, обработчиков прерываний, криптографии и перехода в приложение.
|
||
Эти операции предоставляет проект через таблицу callbacks.
|
||
|
||
## Слои
|
||
|
||
```text
|
||
приложение / CAN RX loop
|
||
|
|
||
v
|
||
pcan_boot_process() — протокол и state machine
|
||
|
|
||
v
|
||
pcan_boot_port_t — Flash, политика подписи, commit, reboot
|
||
|
|
||
v
|
||
STM32 HAL / другой MCU / host-тест
|
||
```
|
||
|
||
## Файлы
|
||
|
||
| Файл | Назначение | Зависимости |
|
||
|---|---|---|
|
||
| `include/pcan_boot.h` | публичный API, команды и структуры | C99 stdint/stdbool |
|
||
| `src/pcan_boot.c` | ProtoCAN ID, state machine и CRC32 | только публичный заголовок |
|
||
| `tests/test_pcan_boot.c` | хостовые тесты с RAM вместо Flash | стандартная библиотека C |
|
||
| `CMakeLists.txt` | сборка библиотеки и теста | CMake 3.13+ |
|
||
|
||
Модуль не зависит от `protocan-transport`: разбор пяти полей 29-битного ID
|
||
сделан переносимыми сдвигами, без непереносимых C-битовых полей.
|
||
|
||
## Карта `MsgType`
|
||
|
||
| Значение | Имя | Назначение |
|
||
|---:|---|---|
|
||
| `0x9` | `BOOT_CONTROL` | команды и метаданные |
|
||
| `0xA` | `BOOT_DATA_A` | блоки слота A |
|
||
| `0xB` | `BOOT_DATA_B` | блоки слота B |
|
||
| `0xC` | `BOOT_STATUS` | ответы и прогресс |
|
||
| `0xD` | `BOOT_DISCOVERY` | идентификация |
|
||
|
||
Для `BOOT_DATA_A/B` поле `MsgBody` — номер блока `0..65535`, а payload —
|
||
8 байт образа:
|
||
|
||
```text
|
||
offset = MsgBody * 8
|
||
512 КиБ = 512 * 1024 = 524 288 байт
|
||
524 288 / 8 = 65 536 блоков на слот
|
||
```
|
||
|
||
| `MsgBody` | Номер блока | Смещение | Байты слота |
|
||
|---:|---:|---:|---:|
|
||
| `0x0000` | 0 | `0x00000` | `0x00000..0x00007` |
|
||
| `0x0001` | 1 | `0x00008` | `0x00008..0x0000F` |
|
||
| `0xFFFF` | 65 535 | `0x7FFF8` | `0x7FFF8..0x7FFFF` |
|
||
|
||
Последний блок заканчивается на смещении `0x7FFFF`. Значение `0x80000`
|
||
равно размеру слота и является первой позицией за его границей. В коде эти
|
||
границы заданы константами `PCAN_BOOT_BLOCK_COUNT=65536` и
|
||
`PCAN_BOOT_SLOT_SIZE=524288`.
|
||
|
||
Последний кадр дополняется `0xFF`; ядро передаёт во Flash только оставшиеся
|
||
байты фактического образа и включает в CRC только их.
|
||
|
||
## Контракт порта
|
||
|
||
```c
|
||
typedef struct {
|
||
bool (*send)(void *, uint32_t can_id, const uint8_t *, uint8_t dlc);
|
||
bool (*erase_slot)(void *, uint8_t slot);
|
||
bool (*write_slot)(void *, uint8_t slot, uint32_t offset,
|
||
const uint8_t *, uint8_t length);
|
||
bool (*authorize)(void *, const pcan_boot_manifest_t *);
|
||
bool (*verify_image)(void *, uint8_t slot,
|
||
const pcan_boot_manifest_t *);
|
||
bool (*set_pending_slot)(void *, uint8_t slot,
|
||
const pcan_boot_manifest_t *);
|
||
bool (*confirm_running_slot)(void *);
|
||
void (*reboot)(void *);
|
||
} pcan_boot_port_t;
|
||
```
|
||
|
||
Обязательны `send`, `erase_slot`, `write_slot` и `set_pending_slot`.
|
||
`authorize` проверяет политику до стирания (тип, версия, anti-rollback).
|
||
`verify_image` выполняет платформенную проверку подписанного контейнера после
|
||
CRC32. Если callbacks отсутствуют, соответствующие дополнительные проверки
|
||
пропускаются; для серийного изделия их следует реализовать.
|
||
|
||
`set_pending_slot` должен атомарно сохранить boot metadata. До подтверждения
|
||
нового приложения загрузчик сохраняет возможность отката. `confirm_running_slot`
|
||
снимает pending-флаг после самопроверки приложения.
|
||
|
||
## Быстрый старт
|
||
|
||
```c
|
||
pcan_boot_t boot;
|
||
pcan_boot_config_t cfg = {
|
||
.device_type = 3, .device_id = 5, .product_type = 0x1234,
|
||
.hardware_revision = 2, .firmware_version = 0x01020000,
|
||
.active_slot = 0, .ack_window = 16
|
||
};
|
||
pcan_boot_port_t port = {
|
||
.send = can_send, .erase_slot = flash_erase_slot,
|
||
.write_slot = flash_write_slot, .authorize = image_authorize,
|
||
.verify_image = image_verify_signature,
|
||
.set_pending_slot = boot_set_pending, .reboot = system_reboot
|
||
};
|
||
|
||
pcan_boot_init(&boot, &cfg, &port, &board);
|
||
/* Из CAN RX-задачи, не из длительного Flash-прерывания: */
|
||
pcan_boot_process(&boot, rx.ExtId, rx.Data, rx.DLC);
|
||
```
|
||
|
||
## Последовательность обновления
|
||
|
||
1. `IDENTIFY` или discovery получает тип и версии прибора.
|
||
2. `ENTER_BOOT` открывает ненулевой `SessionID`.
|
||
3. `BEGIN_IMAGE` передаёт размер и CRC32.
|
||
4. `BEGIN_COMPAT` передаёт тип изделия, диапазон HW и версию FW.
|
||
5. Ядро выбирает неактивный слот и отвечает его номером.
|
||
6. `ERASE` вызывает порт стирания.
|
||
7. `BOOT_DATA_A` либо `BOOT_DATA_B` передаются строго по порядку.
|
||
8. `VERIFY` сравнивает CRC32 и вызывает проверку образа/подписи порта.
|
||
9. `COMMIT` атомарно помечает слот как pending.
|
||
10. После reboot приложение подтверждает запуск через `CONFIRM`.
|
||
|
||
Все изменяющие Flash команды адресуются конкретному `DeviceType/DeviceID`.
|
||
Ядро не принимает broadcast-запись и игнорирует кадры с чужим адресом или
|
||
`Route=FROM_DEVICE`.
|
||
|
||
## State machine
|
||
|
||
```text
|
||
IDLE -> ENTER_BOOT -> METADATA -> BEGIN_IMAGE + BEGIN_COMPAT
|
||
-> READY_TO_ERASE -> ERASE -> RECEIVING -> VERIFY -> VERIFIED
|
||
-> COMMIT -> REBOOT -> CONFIRM
|
||
```
|
||
|
||
При ошибке Flash, CRC, совместимости или подписи состояние переходит в
|
||
`FAILED`. Новая сессия начинается командой `ENTER_BOOT`; `ABORT` очищает
|
||
текущую сессию без изменения Flash.
|
||
|
||
## Сборка тестов
|
||
|
||
```sh
|
||
cmake -S . -B build
|
||
cmake --build build
|
||
ctest --test-dir build --output-on-failure
|
||
```
|
||
|
||
## Что остаётся проекту устройства
|
||
|
||
- зарезервировать bootloader, slot A/B и metadata в linker script;
|
||
- реализовать выравнивание и размеры страниц Flash;
|
||
- не выполнять длительное стирание непосредственно в CAN IRQ;
|
||
- атомарно хранить active/pending/confirmed и счётчик попыток запуска;
|
||
- проверить вектор, границы и подпись образа;
|
||
- включить watchdog и rollback при неподтверждённом запуске;
|
||
- согласовать фильтры CAN для `MsgType=0x9..0xD`.
|
||
|
||
Проект пока не подключён ни к одной конкретной плате; это самостоятельный
|
||
шаблон для интеграции в будущие загрузчики приборов SET.
|