feat(protocan-boot): добавь прошивку приборов по CAN
This commit is contained in:
158
c/protocan-boot/README.md
Normal file
158
c/protocan-boot/README.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# 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
|
||||
65536 * 8 = 512 КиБ на слот
|
||||
```
|
||||
|
||||
Последний кадр дополняется `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.
|
||||
Reference in New Issue
Block a user