Files
templates/c/firmware-info/BUILD_IDENTITY.md

276 lines
18 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.
# Версия работающей прошивки и 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 сохраняют разные транспортные порты.