import ctypes # Вызывает настоящие MCU-валидатор и writer на изолированной модели Flash, не используя оборудование. import importlib.util # Подключает контракт FAB1 из выбранной зависимости templates без изменения окружения GUI. import os # Передаёт процессу GCC путь к его runtime DLL в Windows. from pathlib import Path # Все файлы испытания находятся в собственном временном каталоге. import struct # Формирует SCI8 с известными адресами и проверяемыми повреждениями. import subprocess # Компилирует production-функции host-компилятором без обращения к JTAG. import tempfile # Автоматически очищает только временные исходники и DLL текущего испытания. ROOT = Path(__file__).resolve().parents[2] # Корень содержит production flash_rs.c и исходники host-модели. TEMPLATES = ROOT.parent / 'newProject/SETGUI/third_party/templates' # Использует ту же реализацию FAB1, что компилируемая прошивка. PORT = TEMPLATES / 'c/protocan-boot/ports/tms320f28335' # Включает общий C-валидатор запуска постоянного загрузчика. GCC = Path('C:/msys64/mingw64/bin/gcc.exe') # Установленный GCC предоставляет независимый от TI host-компилятор. os.environ['PATH'] = str(GCC.parent) + os.pathsep + os.environ['PATH'] # Добавляет runtime только в среду этого тестового процесса. spec = importlib.util.spec_from_file_location('flash_contract', TEMPLATES / 'python/protocan/f28335_flash.py') # Загружает общий Python-подписыватель описания. contract = importlib.util.module_from_spec(spec) # Не подменяет установленный пакет protocan у пользователя. spec.loader.exec_module(contract) # Ошибка контракта останавливает проверку вместо использования старого образа. def block(address, words): # Формирует стандартный адресный блок TI SCI8 в октетах. return struct.pack('> 16, address & 65535) + struct.pack('<' + 'H' * len(words), *words) # Размер и адрес выражены словами C28x; каждое слово little endian. entry = contract.APP_DATA # Точка входа расположена после фиксированного описания приложения в секторе H. loaded = {entry: 0x1234, entry + 1: 0x5678, entry + 12: 0xAABB, entry + 13: 0xCCDD} # Разрыв между секциями проверяет включение стёртых слов 0xFFFF в CRC. signed = contract.descriptor(entry, loaded) # Python формирует тот же FAB1, который C-загрузчик проверяет перед запуском. header = b'\xaa\x08' + bytes(16) + struct.pack('> 16, entry & 65535) # Сохраняет штатный SCI8 header и адрес безопасного startup. valid = header + block(contract.APP_START, struct.unpack('<16H', signed)) + block(entry, [0x1234, 0x5678]) + block(entry + 12, [0xAABB, 0xCCDD]) + bytes(2) # Три блока отдельно проверяют отложенную публикацию описания после данных. source = (ROOT / 'Source/Internal/flash_rs.c').read_text(encoding='utf-8') # Читает текущий production writer со всеми защитами адресов. helpers = source[source.index('#pragma CODE_SECTION(byte_at'):source.index('#pragma CODE_SECTION(reply_status')] # Декодирование RS и callback виртуального Flash остаются производственными. validator = source[source.index('#pragma CODE_SECTION(validate_image'):source.index('#pragma CODE_SECTION(program_image')] # Извлекает настоящую проверку до первого erase. writer = source[source.index('#pragma CODE_SECTION(program_image'):source.index('void FlashRs_Command')] # Извлекает настоящий порядок erase, program, verify и последней публикации FAB1. with tempfile.TemporaryDirectory(prefix='protected-f28335-') as directory: # Никакие промежуточные файлы не заменяют рабочую прошивку. directory = Path(directory) # Разрешает все пути относительно одного изолированного запуска. (directory / 'production.inc').write_text(helpers + validator + writer, encoding='utf-8') # Русские комментарии production-функций сохраняются в UTF-8. library = directory / 'writer.dll' # Динамическая библиотека содержит только модель аппаратуры и настоящий MCU-код. subprocess.run([str(GCC), '-shared', '-std=c99', '-Wno-unknown-pragmas', '-Wno-int-to-pointer-cast', '-I' + str(PORT), '-I' + str(directory), str(ROOT / 'tests/protected_boot/rs_program_model.c'), '-o', str(library)], check=True) # Не вызывает TI, программатор или аппаратные драйверы. native = ctypes.CDLL(str(library)) # Загружает только созданную библиотеку этой проверки. native.check_image.argtypes = [ctypes.POINTER(ctypes.c_uint16), ctypes.c_uint32] # Каждая физическая октета передаётся отдельным словом модели. native.check_image.restype = ctypes.c_uint16 # Сохраняет коды предварительной проверки MCU. native.check_program.argtypes = [ctypes.POINTER(ctypes.c_uint16), ctypes.c_uint32, ctypes.c_uint] # Последний параметр задаёт прерываемую стадию записи. native.check_program.restype = ctypes.c_uint16 # Ненулевой результат API не превращается в успех модели. for payload, expected in [(valid, 0), (valid[:-2], 0x1002), (valid + bytes(2), 0x1002), (valid[:-4] + b'\0\0' + bytes(2), 0x1002)]: # Проверяет полный файл, обрезание, хвост и нарушение CRC данных. octets = (ctypes.c_uint16 * len(payload))(*payload) # Исключает зависимость от host-char при вызове производственного C-кода. assert native.check_image(octets, len(payload)) == expected # Python-подпись и MCU-проверка должны согласовываться на реальном контракте. for address in (0x2FFFFF, 0x338000, 0x33FFF6, 0x33FFF8, 0x340000, 0x380400): # Проверяет сектор A, аппаратный boot, CSM, OTP и обе внешние границы. payload = valid[:-2] + block(address, [1, 2]) + bytes(2) # Добавляет запрещённую секцию к иначе полностью правильному приложению. octets = (ctypes.c_uint16 * len(payload))(*payload) # Передаёт исходные октеты в неизменённый MCU-валидатор. assert native.check_image(octets, len(payload)) == 0x1002 # Ни один запрещённый адрес не допускает erase. octets = (ctypes.c_uint16 * len(valid))(*valid) # Одни и те же правильные данные проходят все стадии испытания writer. for fault, expected in ((0, 0), (1, 22), (2, 22), (3, 22)): # Обрыв моделируется на каждой порции двух секций данных и на публикации FAB1. assert native.check_program(octets, len(valid), fault) == expected # Boot сохраняется побитно, а частичная Flash не проходит проверку запуска. unload = ctypes.WinDLL('kernel32').FreeLibrary # Освобождает DLL перед удалением её временного каталога в Windows. unload.argtypes, unload.restype = [ctypes.c_void_p], ctypes.c_int # Передаёт полный размер дескриптора библиотеки. assert unload(native._handle) # Закрывает только библиотеку текущего испытания. print('PASS: production FAB1 CRC, protected sector A, RS erase/program/verify and power-loss stages') # Подтверждает проверки модели, не выдавая их за физическое испытание платы.