Files
templates/c/protocan-boot/docs/BOOTLOADER.md

184 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ProtoCAN Boot Protocol
Статус: **Draft**
Версия протокола: **1.0**
Совместимость: **classic CAN 2.0B, Extended ID, DLC 0…8**
Реализация: [`templates/c/protocan-boot`](../../../templates/c/protocan-boot)
## Назначение
Сервис обновляет адресованный прибор по CAN и поддерживает два логических
слота A/B. Активный слот не стирается: новый образ записывается в неактивный,
проверяется и атомарно назначается кандидатом на запуск.
## Карта сообщений
| `MsgType` | Имя | `MsgBody` | Payload |
|---:|---|---|---|
| `0x9` | `BOOT_CONTROL` | `SessionID[15:8] \| Command[7:0]` | параметры команды |
| `0xA` | `BOOT_DATA_A` | `BlockIndex[15:0]` | 8 байт слота A |
| `0xB` | `BOOT_DATA_B` | `BlockIndex[15:0]` | 8 байт слота B |
| `0xC` | `BOOT_STATUS` | `SessionID[15:8] \| Command[7:0]` | статус и прогресс |
| `0xD` | `BOOT_DISCOVERY` | подтип ответа | идентификация |
Все команды записи адресуются конкретному `DeviceType/DeviceID` и имеют
`Route=0`. Ответы сохраняют адрес прибора и имеют `Route=1`.
## Адресация образа
`MsgBody` кадра данных — номер 8-байтового блока:
```c
offset = (uint32_t)BlockIndex * 8U;
address = SLOT_X_BASE + offset;
```
```text
512 КиБ = 524 288 байт
524 288 / 8 = 65 536 блоков
BlockIndex = 0x0000…0xFFFF
```
| `BlockIndex` | Смещение | Диапазон байтов |
|---:|---:|---:|
| `0x0000` | `0x00000` | `0x00000…0x00007` |
| `0x0001` | `0x00008` | `0x00008…0x0000F` |
| `0xFFFF` | `0x7FFF8` | `0x7FFF8…0x7FFFF` |
`0x80000` является первой позицией за границей слота. Последний кадр
дополняется `0xFF`, но CRC32 вычисляется только по `ImageSize` байтам.
## Команды `BOOT_CONTROL`
| Код | Команда | DLC | Payload | Допустимое состояние |
|---:|---|---:|---|---|
| `0x01` | `IDENTIFY` | 0 | отсутствует | любое |
| `0x02` | `ENTER_BOOT` | 0 | отсутствует | любое; `SessionID != 0` |
| `0x03` | `BEGIN_IMAGE` | 8 | размер и CRC32 | metadata |
| `0x04` | `BEGIN_COMPAT` | 8 | совместимость и версия | metadata |
| `0x05` | `ERASE` | 0 | отсутствует | ready-to-erase |
| `0x06` | `VERIFY` | 0 | отсутствует | образ получен |
| `0x07` | `COMMIT` | 0 | отсутствует | verified |
| `0x08` | `CONFIRM` | 0 | отсутствует | запущенное приложение |
| `0x09` | `REBOOT` | 0 | отсутствует | активная сессия |
| `0x0A` | `ABORT` | 0 | отсутствует | активная сессия |
| `0x0B` | `QUERY_PROGRESS` | 0 | отсутствует | активная сессия |
### `BEGIN_IMAGE`
```text
DATA[0..3] ImageSize, uint32 little-endian
DATA[4..7] ImageCRC32, uint32 little-endian
```
### `BEGIN_COMPAT`
```text
DATA[0..1] ProductType, uint16 little-endian
DATA[2] HardwareRevisionMin
DATA[3] HardwareRevisionMax
DATA[4..7] FirmwareVersion, uint32 little-endian
```
До `ERASE` прибор обязан получить обе части метаданных и проверить размер,
тип изделия, аппаратную ревизию, версию и политику anti-rollback.
## `BOOT_STATUS`
```text
MsgBody[15..8] SessionID
MsgBody[7..0] команда, на которую дан ответ
DATA[0] Status
DATA[1] TargetSlot: 0=A, 1=B, 0xFF=не выбран
DATA[2..3] NextBlock, uint16 little-endian
DATA[4..7] RunningCRC32, uint32 little-endian
```
| Код | Статус | Повтор допустим |
|---:|---|---|
| `0x00` | `OK` | — |
| `0x01` | `BUSY` | да, после задержки |
| `0x02` | `INVALID_COMMAND` | после исправления |
| `0x03` | `WRONG_DEVICE` | нет для этого образа |
| `0x04` | `WRONG_HARDWARE` | нет для этого образа |
| `0x05` | `INVALID_SIZE` | нет для этого образа |
| `0x06` | `CRC_ERROR` | новая передача |
| `0x07` | `FLASH_ERROR` | зависит от платформы |
| `0x08` | `SEQUENCE_ERROR` | да, с `NextBlock` |
| `0x09` | `SIGNATURE_ERROR` | нет |
| `0x0A` | `SESSION_ERROR` | открыть новую сессию |
| `0x0B` | `VOLTAGE_ERROR` | да после нормализации питания |
| `0x0C` | `INVALID_STATE` | выполнить правильный переход |
## State machine
```text
IDLE
└─ ENTER_BOOT ─> METADATA
├─ BEGIN_IMAGE
└─ BEGIN_COMPAT
v
READY_TO_ERASE
│ ERASE
v
RECEIVING
│ VERIFY
v
VERIFIED
│ COMMIT
v
PENDING + REBOOT
│ CONFIRM
v
CONFIRMED
```
Ошибка Flash, CRC, совместимости или подписи переводит сессию в `FAILED`.
Новая `ENTER_BOOT` создаёт чистую сессию. `ABORT` прекращает текущую передачу,
не активируя частично записанный слот.
## Надёжность и повторы
- Блоки передаются строго по возрастанию `BlockIndex`.
- Дубликат или пропуск возвращает `SEQUENCE_ERROR` и ожидаемый `NextBlock`.
- Базовый режим подтверждает каждый блок.
- Рабочий режим может подтверждать окно из 16 блоков.
- После потери связи `QUERY_PROGRESS` возвращает следующий ожидаемый блок,
пока состояние загрузчика сохранено.
- Для продолжения после перезагрузки порт должен сохранять session metadata
и восстановить её при инициализации; ядро версии 1.0 само это не делает.
## Безопасность и A/B-обновление
```text
active=A -> target=B -> verify -> pending=B
active=B -> target=A -> verify -> pending=A
```
CRC32 защищает только от случайного повреждения. Серийный загрузчик должен
дополнительно проверить подпись контейнера, границы вектора, совместимость и
anti-rollback. Bootloader не обновляется командами `BOOT_DATA_A/B`.
Boot metadata должна атомарно хранить:
- активный слот;
- pending-слот;
- подтверждение запуска;
- число неудачных попыток;
- версию и CRC32 образа.
Если приложение не выполняет `CONFIRM` за установленное число запусков,
загрузчик возвращается к предыдущему подтверждённому слоту.
## Эталонный сценарий
1. ПМ адресно отправляет `IDENTIFY`.
2. ПМ открывает ненулевой `SessionID` командой `ENTER_BOOT`.
3. ПМ отправляет `BEGIN_IMAGE` и `BEGIN_COMPAT`.
4. Прибор сообщает выбранный неактивный слот.
5. ПМ выполняет `ERASE` и передаёт `BOOT_DATA_A` либо `BOOT_DATA_B`.
6. ПМ выполняет `VERIFY`, затем `COMMIT` и `REBOOT`.
7. Новое приложение после самопроверки выполняет `CONFIRM`.