Добавить неизменяемое окно firmware-info v1 0x1200 из 12 слов для FC03 и служебного CAN, запретить запись. Сохранить текущую технологическую карту и логгер v4 с записью 12 слов. Подключить общий генератор версии 1.0.0 для Balsam-166-PM35, prebuild CCS и локальный выпуск BIN с manifest и SHA-256. Проверять соответствие встроенных слов манифесту до публикации; старый OUT без паспорта отклонять. Проверка: реальные обработчики RS/CAN и CRC под GCC O3, прежний тест логгера, анализатор установленного SetGUI, полная сборка TI C2000 22.6.0.LTS. BIN 30248 байт при границе параметров 32508 байт. Аппаратная проверка требует установки нового образа и перезапуска платы.
68 lines
12 KiB
Python
68 lines
12 KiB
Python
"""Выпускает BIN 166 с проверяемым паспортом и SHA-256 по той же схеме, что 167.""" # Генерация версии выполняется отдельно до компиляции.
|
||
import argparse # Разбирает пути текущей конфигурации CCS и компилятора TI.
|
||
from datetime import datetime # Преобразует время UTC паспорта в те же шесть компонентов, что firmware-info v1.
|
||
import json # Читает именно паспорт завершившейся компиляции.
|
||
from pathlib import Path # Привязывает настройки и результаты к текущему проекту.
|
||
import shutil # Находит компилятор и копирует только полностью проверенный выпуск.
|
||
import struct # Проверяет 16-битные слова загрузочного SCI8 без исполнения кода прошивки.
|
||
import sys # Подключает общий postbuild вместо копии конвертера из 167.
|
||
import tempfile # Не публикует результаты конверсии, пока встроенный паспорт не прошёл проверку.
|
||
ROOT = Path(__file__).resolve().parents[1] # Корень 166 содержит существующие Source и Bin.
|
||
PROJECT = Path(__file__).resolve().parent # Конфигурации Debug/Release и заголовок версии находятся рядом со сценарием.
|
||
TEMPLATES = ROOT.parent / "newProject/templates" # Использует ту же общую библиотеку firmware-info, что проект 167.
|
||
def verify_embedded(image, metadata): # Не позволяет выдать новый манифест за паспорт старого OUT без встроенной версии.
|
||
data = image.read_bytes() # Проверяет точные октеты, которые будут отправлены в плату.
|
||
if data[:2] != b"\xaa\x08" or not 24 <= len(data) <= 32508: # SCI8 имеет ключ 0x08AA; граница параметров 166 равна (0x3FFF - 129) * 2 = 32508 байт.
|
||
raise ValueError("SCI8 is invalid or overlaps the 166 parameter area at byte 32508") # Не допускает повреждения существующих параметров EEPROM.
|
||
stamp = datetime.fromisoformat(metadata["built_at_utc"]) # Использует исходное время prebuild без подмены временем конверсии BIN.
|
||
build_id = metadata["build_id"] # Ровно восемь ASCII-символов совпадают с четырьмя регистрами firmware-info v1.
|
||
words = [1, *metadata["version"], stamp.year, stamp.month * 256 + stamp.day, stamp.hour * 256 + stamp.minute, stamp.second] # Восстанавливает первые восемь регистров независимо от C-упаковщика.
|
||
words += [ord(build_id[i]) * 256 + ord(build_id[i + 1]) for i in range(0, 8, 2)] # Восстанавливает четыре слова Build ID в сетевом порядке символов.
|
||
expected = struct.pack("<12H", *words) # Загрузочная таблица SCI8 хранит 16-битные слова C28x младшим октетом вперёд.
|
||
offset, found = 22, 0 # Первые 22 октета заняты ключом, настройками и аппаратной точкой входа.
|
||
while offset + 2 <= len(data): # Каждый шаг потребляет заголовок либо завершает конечный файл; ожидания аппаратуры нет.
|
||
count, = struct.unpack_from("<H", data, offset) # Размер очередной секции задан в 16-битных словах C28x.
|
||
offset += 2 # Переходит к адресу секции либо концу таблицы при нулевом размере.
|
||
if not count: break # Нулевая секция завершает SCI8 и не содержит адреса назначения.
|
||
end = offset + 4 + count * 2 # Четыре октета адреса не входят в проверяемую полезную нагрузку секции.
|
||
if end > len(data): raise ValueError("Truncated SCI8 section") # Останавливает выпуск при выходе секции за фактический размер файла.
|
||
payload = data[offset + 4:end] # Рассматривает загружаемые слова, а не случайное совпадение в заголовке.
|
||
found += sum(payload[i:i + 24] == expected for i in range(0, len(payload) - 23, 2)) # Паспорт должен присутствовать один раз на границе слова C28x.
|
||
offset = end # Переходит к следующей непересекающейся секции таблицы.
|
||
if offset != len(data) or count or found != 1: # Запрещает хвосты, отсутствующий терминатор и манифест от другой компиляции.
|
||
raise ValueError(f"SCI8 does not contain exactly one matching prebuild identity: {found}") # Требует полноценную пересборку вместо фиктивного подписания старого BIN.
|
||
def find_compiler(): # Сохраняет возможность ручного запуска build_bin.bat без жёсткой привязки к одной версии CCS.
|
||
executable = shutil.which("cl2000") or shutil.which("cl2000.exe") # Предпочитает компилятор среды текущего проекта, а не старый hex2000 рядом с BIN.
|
||
if executable: return Path(executable).resolve().parents[1] # Возвращает корень toolchain с каталогом bin/hex2000.exe.
|
||
installed = sorted(Path("C:/ti").glob("ccs*/ccs/tools/compiler/ti-cgt-c2000_*")) # Для установленного CCS использует только штатные каталоги TI.
|
||
installed = [path for path in installed if (path / "bin/hex2000.exe").is_file()] # Не принимает неполную установку за пригодный конвертер.
|
||
if not installed: raise FileNotFoundError("Pass --compiler with the C2000 toolchain root") # Явно останавливает ручной запуск без инструментов.
|
||
return installed[-1] # Выбирает последнюю найденную установленную цепочку только при отсутствии заданной CCS версии.
|
||
def build(compiler, build_dir, artifact): # Выполняет общий postbuild и локальную публикацию после проверки встроенной версии.
|
||
sys.path.insert(0, str(TEMPLATES / "c/firmware-info/tools")) # Открывает общие identity.py и c2000_postbuild.py из той же зависимости, что 167.
|
||
from c2000_postbuild import postbuild # Не копирует формат манифеста, архивирование и вычисление SHA-256.
|
||
metadata_path = build_dir / "generated/firmware_identity.json" # Отсутствующий prebuild должен остановить выпуск вместо использования произвольной версии.
|
||
metadata = json.loads(metadata_path.read_text(encoding="utf-8")) # Читает JSON, по которому компилятор получил firmware_identity.h.
|
||
if metadata.get("product") != "Balsam-166-PM35" or metadata.get("target") != "TMS320F28335": raise ValueError("Run the 166 prebuild before publishing this image") # Старый generated от ветки 167 не должен выдавать её product за паспорт 166.
|
||
output = build_dir / "firmware" # Сохраняет прежнюю для 167 структуру локального каталога выпусков.
|
||
with tempfile.TemporaryDirectory(prefix=".identity-release-", dir=build_dir) as directory: # Непроверенные BIN и manifest существуют только во временном каталоге текущего вызова.
|
||
stage = Path(directory); staged_image = postbuild(compiler, build_dir, artifact, metadata_path, stage) # Общий код создаёт HEX, BIN, SHA-256 и архив, пока не доступные как готовый выпуск.
|
||
verify_embedded(staged_image, metadata) # Не переносит в firmware или Bin результаты от старого OUT либо другой компиляции.
|
||
output.mkdir(parents=True, exist_ok=True) # Создаёт каталог публикации только после успешной проверки.
|
||
for item in stage.iterdir(): # Сохраняет только результаты этого вызова, не сканируя старые выпуски.
|
||
if item.is_dir(): shutil.copytree(item, output / item.name, dirs_exist_ok=True) # Публикует полный архив проверенной сборки.
|
||
else: shutil.copy2(item, output / item.name) # Переносит плоские HEX/BIN и контрольные файлы без подмены других исходников.
|
||
image = output / staged_image.name # После удаления временного каталога возвращает постоянный путь проверенного BIN.
|
||
for file in (image, image.with_suffix(".bin.manifest.json"), image.with_suffix(".bin.sha256"), image.parent / (image.stem.rsplit("_", 1)[0] + ".hex")): # Сохраняет рядом проверенный BIN, его контрольные файлы и прежний Intel HEX для CCS.
|
||
shutil.copy2(file, ROOT / "Bin" / file.name) # Основной каталог получает только результат успешной текущей сборки.
|
||
shutil.copy2(image.parent / (image.stem.rsplit("_", 1)[0] + ".hex.map"), ROOT / "Bin/UKSSTMS320F28335.map") # Согласовывает прежнюю основную карту конверсии с новым HEX и OUT.
|
||
print(ROOT / "Bin" / image.name) # Показывает конкретный BIN, который нужно выбрать в SetGUI вместе с лежащим рядом манифестом.
|
||
return image # Позволяет тестам проверить результат без поиска случайного старого файла.
|
||
if __name__ == "__main__": # Импорт проверки SCI8 не выполняет сборку или публикацию файлов.
|
||
parser = argparse.ArgumentParser(description=__doc__) # Описывает контракт запуска из CCS и build_bin.bat.
|
||
parser.add_argument("--compiler", type=Path) # CCS передаёт CG_TOOL_ROOT; ручной вызов может использовать обнаружение установленного компилятора.
|
||
parser.add_argument("--build-dir", type=Path, default=PROJECT / "Debug") # Изолирует паспорт и архив выбранной конфигурации.
|
||
parser.add_argument("--artifact", default=str(ROOT / "Bin/UKSSTMS320F28335")) # Общий postbuild ожидает полный путь OUT без расширения.
|
||
args = parser.parse_args() # Проверяет параметры до выполнения конверсии.
|
||
build((args.compiler or find_compiler()).resolve(), args.build_dir.resolve(), args.artifact) # Публикует только образ, соответствующий своему встроенному паспорту.
|