Добавить идентификацию прошивки и чтение EEPROM Бальзам-167
This commit is contained in:
275
c/firmware-info/BUILD_IDENTITY.md
Normal file
275
c/firmware-info/BUILD_IDENTITY.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# Версия работающей прошивки и 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 сохраняют разные транспортные порты.
|
||||
Reference in New Issue
Block a user