276 lines
18 KiB
Markdown
276 lines
18 KiB
Markdown
# Версия работающей прошивки и SHA-256 файла: TMS2812, TMS28335, STM32
|
||
|
||
## 1. Какие задачи решает механизм
|
||
|
||
Механизм продолжает контракт Climate: устройство сообщает SemVer, дату/время
|
||
сборки и Git Build ID в 12 регистрах. Компьютер после сборки вычисляет SHA-256
|
||
готового файла и сохраняет паспорт `*.manifest.json` и файл `*.sha256`.
|
||
|
||
Это три разных идентификатора:
|
||
|
||
| Идентификатор | Что определяет | Где хранится |
|
||
|---|---|---|
|
||
| `major.minor.patch` | Назначенный разработчиком номер версии | В образе и паспорте |
|
||
| Git Build ID | Исходный коммит; `+` отмечает локальные изменения | В образе и паспорте |
|
||
| SHA-256 | Точные байты конкретного BIN/HEX, включая заголовки | Рядом с образом |
|
||
|
||
SHA Git-коммита и SHA-256 файла прошивки не взаимозаменяемы. Один коммит может
|
||
давать разные бинарные файлы при смене компилятора, опций, времени сборки или
|
||
зависимостей. Полные коммиты приложения и templates записываются в паспорт.
|
||
|
||
Контроллер не вычисляет SHA своей памяти. Ответ версии подтверждает, какие
|
||
метаданные содержит работающий образ; SHA файла проверяет файл на компьютере.
|
||
Для проверки фактической записи нужна отдельная штатная проверка программатора
|
||
или чтение памяти назад. SHA рядом с файлом не является цифровой подписью.
|
||
|
||
## 2. Слои и файлы
|
||
|
||
```text
|
||
firmware_identity.json проекта + Git приложения + Git templates
|
||
|
|
||
tools/identity.py prepare (до компиляции)
|
||
|
|
||
generated/firmware_identity.h + .json
|
||
|
|
||
src/firmware_info_static.c (общий C-код)
|
||
|
|
||
12 неизменяемых слов -> порт транспорта -> клиент
|
||
|
||
линковка -> преобразование BIN -> проверка размера -> identity.py seal
|
||
|
|
||
BIN + BIN.manifest.json + BIN.sha256
|
||
```
|
||
|
||
| Файл | Задача и зависимости |
|
||
|---|---|
|
||
| `include/firmware_info_static.h` | Контракт компактного порта и упаковка слов, только `stdint.h` |
|
||
| `src/firmware_info_static.c` | Константная таблица и чтение одного слова; нужен generated header |
|
||
| `include/firmware_info.h`, `src/firmware_info.c` | Существующий динамический API Climate, валидация и сериализация |
|
||
| `src/firmware_info_port.c` | Динамический адаптер конфигурации; поддерживает новый UTC stamp |
|
||
| `ports/tms320f2812`, `ports/tms320f28335`, `ports/stm32` | Примеры конфигурации для нового генератора |
|
||
| `tools/identity.py` | Git, JSON, SHA-256 файла; Python 3.10+ и Git, без pip-зависимостей |
|
||
| `tests/test_static.c` | Совпадение компактного и динамического контрактов, чтение RS/CAN, запрет записи |
|
||
| `tests/test_identity.py` | Генерация, dirty-зависимость, отсутствие Git, порча файла |
|
||
|
||
Ядро не знает HAL, CMSIS, XINTF, EEPROM, UART, CAN-регистры и адрес устройства.
|
||
`firmware_info_static_read(unsigned index)` принимает индекс **0..11**, возвращает
|
||
`uint16_t`; вне диапазона возвращает 0. Проверять диапазон запроса и запрещать
|
||
запись обязан транспорт. Нулевую версию контракта клиент показывает как
|
||
«версия неизвестна», а не подставляет версию из выбранного файла.
|
||
|
||
## 3. Конфигурация проекта и генерация
|
||
|
||
В репозитории приложения создайте `firmware_identity.json`:
|
||
|
||
```json
|
||
{
|
||
"product": "MyController",
|
||
"target": "TMS320F2812",
|
||
"version": [1, 0, 0]
|
||
}
|
||
```
|
||
|
||
`product` и `target` нужны для паспорта файла. В контракт v1 они не входят:
|
||
тип подключённого устройства по-прежнему определяет его собственный протокол.
|
||
Не используйте совпадение версии как разрешение залить образ другого изделия.
|
||
|
||
До каждой компиляции выполните (пути приведены для checkout в `lib/templates`):
|
||
|
||
```powershell
|
||
python lib/templates/c/firmware-info/tools/identity.py prepare `
|
||
--repository . --templates lib/templates `
|
||
--config firmware_identity.json --output build/generated
|
||
```
|
||
|
||
Добавьте `build/generated` и `lib/templates/c/firmware-info/include` в include
|
||
paths. Скомпилируйте **один экземпляр**
|
||
`lib/templates/c/firmware-info/src/firmware_info_static.c`. Если проект включает
|
||
этот файл через агрегирующий `.c`, отдельно добавлять его в сборку нельзя.
|
||
|
||
Генератор намеренно переписывает header при каждом запуске: зависимый объект
|
||
должен пересобраться. Отсутствие header — ошибка компиляции, а не `LOCALDEV`.
|
||
Нужна корректная обработка зависимостей сборщиком; после изменения настроек IDE
|
||
выполните полную пересборку. Не редактируйте исходники во время сборки.
|
||
|
||
Для разных конфигураций и параллельных сборок используйте разные `generated`
|
||
каталоги. Не запускайте две сборки с одним output одновременно.
|
||
|
||
Время в новом пути — **UTC**, снято перед компиляцией. Если задан
|
||
`SOURCE_DATE_EPOCH`, используется этот Unix timestamp; это помогает повторяемым
|
||
сборкам, но не делает весь toolchain автоматически воспроизводимым.
|
||
|
||
### Значения Build ID
|
||
|
||
| Значение | Смысл |
|
||
|---|---|
|
||
| `a1b2c3d4` | Приложение и templates доступны в Git и оба дерева чистые |
|
||
| `a1b2c3d+` | Изменено приложение или templates, включая неотслеживаемые файлы |
|
||
| `NOGIT000` | Не удалось определить хотя бы один из двух Git-репозиториев |
|
||
| `LOCALDEV` | Только старый динамический путь без generated header |
|
||
|
||
Полные commit SHA находятся в `source.commit` и `templates.commit` паспорта.
|
||
`+` не идентифицирует содержимое локальных правок. Точный файл определяет его
|
||
SHA-256; для воспроизводимого выпуска нужны сохранённые исходники и настройки.
|
||
Добавляйте generated/build каталоги в `.gitignore`, иначе они будут честно
|
||
делать следующую сборку dirty. Намеренно не игнорируйте новые исходные файлы.
|
||
|
||
## 4. Формат: одинаковые 12 слов на всех МК
|
||
|
||
| Индекс | Значение |
|
||
|---:|---|
|
||
| 0 | Версия контракта: 1 |
|
||
| 1 | Major, 0..255 |
|
||
| 2 | Minor, 0..255 |
|
||
| 3 | Patch, 0..999 |
|
||
| 4 | Год, 2000..2199 |
|
||
| 5 | Месяц в старшем октете, день в младшем |
|
||
| 6 | Час в старшем октете, минута в младшем |
|
||
| 7 | Секунда |
|
||
| 8 | ASCII Build ID: символы 0 и 1, первый в старшем октете |
|
||
| 9 | Символы 2 и 3 |
|
||
| 10 | Символы 4 и 5 |
|
||
| 11 | Символы 6 и 7 |
|
||
|
||
Например, `abcdef0+` даёт `0x6162, 0x6364, 0x6566, 0x302B` в словах 8..11.
|
||
Modbus передаёт каждое слово старшим октетом вперёд. Старый
|
||
`firmware_info_to_le_bytes()` передаёт слова little-endian для соответствующих
|
||
byte-протоколов; эти два представления нельзя путать.
|
||
|
||
На TMS320F2812/F28335 `CHAR_BIT == 16`, `sizeof(uint16_t) == 1`, тип `uint8_t`
|
||
отсутствует. В динамическом API используется `firmware_info_octet`, основанный
|
||
на `uint_least8_t`: на STM32 это прежние 8 бит, на C28x — контейнер 16 бит.
|
||
Размеры byte-API означают число **элементов-октетов**, не физический `sizeof`
|
||
переданного массива. На проводе всё равно 24 восьмибитных октета.
|
||
|
||
## 5. Порты и включение в транспорт
|
||
|
||
### TMS320F28335 / ПМ35
|
||
|
||
В 167 уже подключено через `Source/Internal/shared_ump.c`. Макрос
|
||
`UMP_FIRMWARE_INFO_READ` связывает общий порт UMP с
|
||
`firmware_info_static_read`. Диапазон `0x1200..0x120B` обслуживается до
|
||
обращения к регистратору. Окно параметров `0..127` и лог не расширяются.
|
||
|
||
RS: функция 03, начальный адрес `0x1200` (4608), количество 12.
|
||
Разрешены частичные чтения, не выходящие за конец окна. Функция 06 возвращает
|
||
исключение 02, не изменяя ни версию, ни настройки регистратора.
|
||
|
||
Служебный CAN ПМ35: тот же UMP-запрос функции 03 на `0x1200`, количество 12,
|
||
ненулевой token. Ответ — шесть фрагментов по два слова с тем же token.
|
||
Частичные CAN-чтения этого окна отвергаются. Используйте существующие UMP
|
||
сборщики/парсеры templates, не создавайте второй кодек в GUI.
|
||
|
||
### TMS320F2812 / ПМ67
|
||
|
||
Добавьте тот же `firmware_info_static.c` и include paths. Компактный порт не
|
||
требует FPU и не использует регистры F28335. Конфигурация-пример лежит в
|
||
`ports/tms320f2812/firmware_info_config.template.h`.
|
||
|
||
Выберите свободное окно в карте **конкретного ПМ67**. В обработчике чтения
|
||
отдавайте `firmware_info_static_read(address - base)` после проверки диапазона.
|
||
Не переносите автоматически CAN ID и UMP ПМ35: у ПМ67 отдельный протокол.
|
||
Это поставляемый переносимый порт, а не заявление, что конкретная прошивка
|
||
ПМ67 уже изменена или испытана на устройстве.
|
||
|
||
### STM32 / Modbus или SET
|
||
|
||
Компактный порт одинаков для F1/F4/G4. Для HAL он не требует ни инициализации,
|
||
ни callback. Пример адаптации чтения **после проверки диапазона транспортом**:
|
||
|
||
```c
|
||
#include "firmware_info_static.h"
|
||
|
||
/* FW_BASE выбирает проект; соответствие адресов проверяет Modbus/SET handler. */
|
||
uint16_t app_firmware_word(uint16_t address)
|
||
{
|
||
unsigned index = (unsigned)(address - FW_BASE);
|
||
return firmware_info_static_read(index);
|
||
}
|
||
```
|
||
|
||
Для STM32 UMP-периферии можно использовать уже существующий
|
||
`set-protocol/ports/stm32f407-periph` и подставить этот reader в `ump_backend`.
|
||
Для обычного Modbus используйте собственное свободное input/holding read-only
|
||
окно, сохраняя 12 слов контракта. В Climate адрес был 1960; это **не** адрес
|
||
167. Если удобнее заполнить RAM-регистры при старте, оставьте динамический API:
|
||
`firmware_info_port_describe()` и `firmware_info_to_registers()`.
|
||
|
||
## 6. SHA-256 и паспорт после сборки
|
||
|
||
Сначала завершите линковку и преобразование в файл, который реально отправляется
|
||
загрузчику. Затем проверьте формат и допустимый размер. Только после успеха:
|
||
|
||
```powershell
|
||
python lib/templates/c/firmware-info/tools/identity.py seal `
|
||
--image build/controller.bin --metadata build/generated/firmware_identity.json
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
controller.bin
|
||
controller.bin.manifest.json
|
||
controller.bin.sha256
|
||
```
|
||
|
||
Паспорт содержит product, target, version, build_id, built_at_utc, полные commit
|
||
SHA и dirty-состояния двух репозиториев, имя файла, размер и SHA-256. Передавайте
|
||
эти три файла вместе. `seal` не извлекает версию из произвольного бинарника:
|
||
metadata должен быть из **той же сборки**. Нельзя перепривязать старый BIN к
|
||
новому pre-build JSON. Проверка версии на работающей плате замыкает этот цикл.
|
||
|
||
Проверка точного файла:
|
||
|
||
```powershell
|
||
python lib/templates/c/firmware-info/tools/identity.py verify `
|
||
--image build/controller.bin --manifest build/controller.bin.manifest.json
|
||
```
|
||
|
||
При изменении даже одного байта команда завершится ненулевым кодом. SHA считается
|
||
для всего файла: для SCI8 — вместе с boot table, для HEX — для текстового HEX,
|
||
а не для нормализованного содержимого Flash. Выбирайте BIN для однозначного
|
||
сравнения с бинарным файлом программатора.
|
||
|
||
## 7. Выпуск и проверка на устройстве
|
||
|
||
1. Назначьте SemVer в конфигурации приложения.
|
||
2. Сохраните изменения templates отдельным коммитом и закрепите именно эту
|
||
зависимость в потребителе. Затем сохраните исходники приложения.
|
||
3. Убедитесь, что оба дерева чистые; соберите в отдельном output.
|
||
4. Проверьте ограничения образа, наличие паспорта и результат `verify`.
|
||
5. Загрузите именно этот файл штатным загрузчиком и перезапустите устройство.
|
||
6. Прочитайте версию с устройства заново, отключив кэш клиента.
|
||
7. Сравните все 12 слов с SemVer, UTC stamp и Build ID паспорта.
|
||
8. Сохраните BIN, паспорт, настройки компилятора и результаты проверки вместе.
|
||
|
||
Изменение SemVer не должно менять `contract_version`: последняя меняется только
|
||
при несовместимом изменении формата. При несовпадении даты/Build ID сначала
|
||
проверьте выбранный BIN, конфигурацию IDE, generated include path и полную
|
||
пересборку, затем адрес и кэш клиента.
|
||
|
||
## 8. Автоматические проверки
|
||
|
||
```powershell
|
||
cmake -S c/firmware-info -B build/firmware-info
|
||
cmake --build build/firmware-info
|
||
ctest --test-dir build/firmware-info --output-on-failure
|
||
python -m unittest discover -s c/firmware-info/tests -p test_identity.py
|
||
```
|
||
|
||
Дополнительно компилируйте core/static через TI C2000 (`-v28 -ml`) и ARM
|
||
toolchain. Host-тест не заменяет проверку C28x ABI и фактического обмена с платой.
|
||
Для 167 есть отдельный `tests/firmware_identity/port_test.c`: он проверяет
|
||
реальную привязку reader, RTU CRC, CAN prepare и сохранение окна регистратора.
|
||
|
||
## 9. Совместимость с существующими проектами
|
||
|
||
Старый `make_build_id.ps1`, конфигурации STM32F1/F4/G4 и динамический API остаются.
|
||
Новый генератор не подключается к другим проектам сам: добавьте pre/post-build
|
||
и generated include path по этой инструкции. На STM32 размеры публичных полей
|
||
и сигнатура byte-буфера остаются прежними благодаря `uint_least8_t`.
|
||
|
||
Изменения сделаны в checkout templates, который уже использует 167:
|
||
`newProject/SETGUI/third_party/templates`. Другие checkout/submodule не следует
|
||
обновлять копированием отдельных `.c`: перенесите проверенный коммит templates
|
||
и обновите ссылку зависимости. ПМ67 и ПМ35 сохраняют разные транспортные порты.
|