feat(f28335-rs-boot): добавьте постоянный загрузчик по RS
Принимайте INITLOAD, LOAD и TFLASH общим автоматом с повторной проверкой SCI8/FAB1 до стирания и сохранением сектора A. Сохраняйте параметры прошивки в паспорте prebuild, выпускайте независимые образы загрузчика и приложения с manifest и SHA-256. Проверено: шесть испытаний модели, включая обрывы записи и восстановление.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# Загрузчик F28335 по RS для periph_28335
|
||||
|
||||
Постоянный загрузчик принимает `INITLOAD` (58), `LOAD` (51) и `TFLASH` (55),
|
||||
совместимые с Flash RS в терминале `periph_28335` SETGUI. Ядро не зависит от
|
||||
UART, GPIO, ОС и Qt. Оно использует существующий валидатор и writer
|
||||
`c/protocan-boot/ports/tms320f28335`, сохраняя единый формат SCI8/FAB1.
|
||||
|
||||
Слои: клиент periph_28335 → автомат RS → проверка SCI8/FAB1 → callbacks платы
|
||||
→ TI Flash API. Передаются октеты; адреса CPU считаются 16-битными словами.
|
||||
|
||||
| Файл | Назначение и зависимости |
|
||||
|---|---|
|
||||
| `include/f28335_rs_boot.h`, `src/f28335_rs_boot.c` | Переносимый автомат, зависит от protocan-boot |
|
||||
| `ports/tms320f28335/flash_api.c` | Официальный TI API v2.10, заголовки устройства, частота F28335_RS_CPU_HZ |
|
||||
| `ports/tms320f28335/boot.cmd` | Постоянный сектор A, отдельные RAMAPI, RAMFUNCS и стек |
|
||||
| `tools/build.py` | Сборка boot и выпуск проверенных Flash OUT/SCI8 с manifest/SHA-256 |
|
||||
| `tests/test_boot.py` | Настоящий автомат и writer в модели RAM/Flash без оборудования |
|
||||
|
||||
Приложение занимает `[0x300000,0x338000)`, первые 16 слов содержат FAB1.
|
||||
Сектор A и переход `0x33FFF6` принадлежат постоянному загрузчику.
|
||||
`INITLOAD` разрешает только staging `0x200000`, чётный размер 24..130560
|
||||
октетов. Ошибочные CRC, таймаут и ошибка UART отзывают подтверждение LOAD.
|
||||
Перед erase повторно проверяются RAM, CRC32, все адреса, entry и FAB1.
|
||||
Общий writer пишет FAB1 последним и проверяет физическую Flash.
|
||||
Ошибка не удаляет recovery; перезапуск разрешён после успешного ACK.
|
||||
|
||||
Контракт callbacks: `stage_write(user, offset, octet)` и `stage.read(user,
|
||||
offset, buffer, size)` обслуживают RAM относительно начала файла;
|
||||
`transmit(user, data, size)` возвращает true после физического окончания TX;
|
||||
`program(user, image)` вызывает общий Flash writer; `reboot(user)` сбрасывает
|
||||
контроллер. Все чтения/записи должны завершиться полностью либо вернуть false.
|
||||
Прерывания в постоянном загрузчике запрещены, watchdog выключен на время
|
||||
стирания. Межбайтовый таймаут аппаратный порт передаёт в `f28335_rs_abort`.
|
||||
|
||||
Быстрый старт: выделить `f28335_rs_boot_t` в RAM, заполнить пять callbacks,
|
||||
вызвать `f28335_rs_init(&boot, &port, 16)` и передавать каждый октет SCI
|
||||
через `f28335_rs_feed(&boot, octet)`. До запуска приложения вызвать
|
||||
`f28335_flash_app_validate`, после успеха перейти на возвращённый entry.
|
||||
Аппаратные таймауты, выключение силовых выходов и выбор скорости задаёт плата.
|
||||
|
||||
Подключено в `prolitarski_ups`: SCI-A/RS-422, 115200 8N1, адрес 16;
|
||||
Monitor №93=1 в остановленном состоянии запрашивает boot через watchdog.
|
||||
Первичная установка требует записи двух OUT через JTAG с сохранением
|
||||
сектора A при загрузке приложения. Все последующие RS-обновления меняют B–H.
|
||||
Повреждённое приложение оставляет устройство в RS recovery после сброса.
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef F28335_RS_BOOT_H /* Один контракт обслуживает постоянный загрузчик и его модель на ПК. */
|
||||
#define F28335_RS_BOOT_H /* Исключает повторные объявления при подключении аппаратного порта. */
|
||||
#include "../../protocan-boot/ports/tms320f28335/f28335_flash_update.h" /* Повторно использует проверку SCI8/FAB1 и запись с защитой сектора A. */
|
||||
#define F28335_RS_STAGE 0x200000UL /* Адрес слова внешней RAM совпадает с INITLOAD в periph_28335. */
|
||||
#define F28335_RS_LIMIT F28335_AB_SLOT_BYTES /* Проверяемый SCI8 ограничен 130560 октетами общего валидатора F28335. */
|
||||
typedef struct { /* Аппаратная плата владеет памятью и UART; ядро владеет только состоянием протокола. */
|
||||
bool (*stage_write)(void *, uint32_t, unsigned int); /* Записывает один октет по относительному смещению staging RAM. */
|
||||
f28335_boot_io_t stage; /* Читает октеты staging для повторной CRC32 и проверки всех адресов перед erase. */
|
||||
bool (*transmit)(void *, const unsigned int *, unsigned int); /* Передаёт полный ответ и дожидается последнего стопового бита. */
|
||||
unsigned int (*program)(void *, f28335_flash_update_t *); /* Возвращает 0 после записи и проверки, код TI либо 1004/1006 при защите или ошибке проверки; управление остаётся в секторе A. */
|
||||
void (*reboot)(void *); /* Выполняет reset только после успешного ответа о завершении записи. */
|
||||
void *user; /* Независимый контекст UART, Flash и модели испытаний. */
|
||||
} f28335_rs_port_t; /* Аппаратный порт не собирает кадры и не дублирует алгоритм проверки образа. */
|
||||
typedef struct { /* Экземпляр допускает работу без heap и без прерываний. */
|
||||
f28335_rs_port_t port; /* Сохраняет callbacks на весь сеанс обновления. */
|
||||
f28335_flash_update_t image; /* Ограниченный индекс адресных секций и страничный кеш общего валидатора. */
|
||||
uint32_t length, position, expected, received, crc32; /* Длины и позиции считаются октетами, независимо от sizeof(char) C28x. */
|
||||
unsigned int address, command, crc16, wire_crc, loaded, fault, header[16]; /* Заголовок вмещает максимальный фиксированный запрос TFLASH на 16 октетов. */
|
||||
} f28335_rs_boot_t; /* Загруженный блок никогда не исполняется как произвольный код RAM. */
|
||||
void f28335_rs_init(f28335_rs_boot_t *s, const f28335_rs_port_t *port, unsigned int address); /* Настраивает адрес 1..255 и сбрасывает разрешение программирования. */
|
||||
void f28335_rs_abort(f28335_rs_boot_t *s); /* Сбрасывает неполный кадр и подтверждение LOAD при таймауте или аппаратной ошибке SCI. */
|
||||
void f28335_rs_feed(f28335_rs_boot_t *s, unsigned int value); /* Принимает один восьмибитный октет совместимых INITLOAD/LOAD/TFLASH. */
|
||||
#endif /* Завершает публичный интерфейс общего RS-загрузчика. */
|
||||
@@ -0,0 +1,28 @@
|
||||
MEMORY /* Все адреса и размеры относятся к 16-битным словам CPU C28x. */
|
||||
{ /* Постоянный загрузчик использует только сектор A и внутреннюю RAM. */
|
||||
PAGE 0: /* Исполняемый код и адреса загрузки из Flash. */
|
||||
FLASHA : origin = 0x338000, length = 0x007F80 /* Область постоянного boot заканчивается до служебной области CSM. */
|
||||
BEGIN : origin = 0x33FFF6, length = 0x000002 /* Два слова аппаратного перехода Boot ROM в режиме Flash. */
|
||||
RAMAPI : origin = 0x008000, length = 0x001800 /* 6144 слова предназначены для копии официального TI Flash API. */
|
||||
RAMFUNCS : origin = 0x00F800, length = 0x000800 /* InitFlash выполняется из RAM, не затрагивая код самого API. */
|
||||
RESET : origin = 0x3FFFC0, length = 0x000002 /* Вектор reset находится в ROM и не программируется. */
|
||||
PAGE 1: /* Данные загрузчика не зависят от размещения будущего приложения. */
|
||||
RAMM0 : origin = 0x000058, length = 0x0003A8 /* Слова 50..57 сохранены для запроса boot через watchdog reset. */
|
||||
RAMM1 : origin = 0x000400, length = 0x000400 /* Стек на 1024 слова вмещает буфер writer и контекст runtime. */
|
||||
RAMDATA : origin = 0x00E000, length = 0x001800 /* Индекс SCI8, состояния протокола и аппаратные данные находятся в RAM. */
|
||||
} /* Внешняя RAM используется через callbacks и не содержит исполняемого кода. */
|
||||
SECTIONS /* C runtime загрузчика, в отличие от приложения, целиком сохраняется в A. */
|
||||
{ /* Размещение API указано до общей .text, чтобы извлечь все его код и константы. */
|
||||
codestart : > BEGIN, PAGE = 0 /* Штатный TI startup запрещает watchdog и вызывает _c_int00 загрузчика. */
|
||||
flash_api : { -lFlash28335_API_V210.lib(.text) -lFlash28335_API_V210.lib(.econst) } LOAD = FLASHA, RUN = RAMAPI, LOAD_START(_FlashApiLoadStart), LOAD_END(_FlashApiLoadEnd), RUN_START(_FlashApiRunStart), PAGE = 0 /* Ни одна функция TI API не исполняется из занятого банка Flash. */
|
||||
.text : > FLASHA, PAGE = 0 /* Протокол, валидатор и собственный runtime загрузчика остаются после обновления приложения. */
|
||||
.cinit : > FLASHA, PAGE = 0 /* Инициализация данных не зависит от целостности B–H. */
|
||||
.pinit : > FLASHA, PAGE = 0 /* Поддерживает стандартную секцию инициализации COFF. */
|
||||
.econst : > FLASHA, PAGE = 0 /* Таблицы и константы загрузчика также защищены от erase B–H. */
|
||||
.switch : > FLASHA, PAGE = 0 /* Таблицы переходов автомата не находятся в заменяемом приложении. */
|
||||
ramfuncs : LOAD = FLASHA, RUN = RAMFUNCS, LOAD_START(_RamfuncsLoadStart), LOAD_END(_RamfuncsLoadEnd), RUN_START(_RamfuncsRunStart), PAGE = 0 /* InitFlash переносится до изменения Flash wait states. */
|
||||
.stack : > RAMM1, PAGE = 1 /* Стек отделён от handoff и буфера внешней RAM. */
|
||||
.ebss : > RAMDATA, PAGE = 1 /* Нулевая инициализация касается только состояния boot. */
|
||||
.esysmem : > RAMDATA, PAGE = 1 /* Оставляет корректную секцию runtime без использования heap в алгоритме. */
|
||||
.reset : > RESET, PAGE = 0, TYPE = DSECT /* ROM-вектор не попадает в программируемый OUT. */
|
||||
} /* Пароли CSM, OTP и сектора приложения не создаются в образе загрузчика. */
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "DSP2833x_Device.h" /* Использует официальные описания регистров выбранной платы F28335. */
|
||||
#include "../../include/f28335_rs_boot.h" /* Получает общий writer с повторной проверкой staging перед erase. */
|
||||
#include "Flash2833x_API_Library.h" /* Официальный TI API v2.10 выполняет физические операции внутренней Flash. */
|
||||
#include <string.h> /* Копирует код и таблицы TI API в RAM до изменения общего банка Flash. */
|
||||
extern Uint16 FlashApiLoadStart, FlashApiLoadEnd, FlashApiRunStart; /* LOAD/RUN-символы создаёт linker постоянного загрузчика. */
|
||||
static FLASH_ST api_status; /* Диагностика TI хранится в RAM и доступна во время занятости Flash. */
|
||||
static unsigned int api_error; /* Код последней операции TI передаётся клиенту без подмены успехом. */
|
||||
static bool erase(void *user) /* Стирает исключительно семь секторов приложения B–H. */
|
||||
{ /* Сам API исполняется из RAM; вызывающий код продолжит работу из сохранённого сектора A. */
|
||||
(void)user; api_error = Flash_Erase(0x00FEU, &api_status); return api_error == STATUS_SUCCESS; /* В TI v2.10 бит 0 выбирает A, маска FE исключает его и сохраняет recovery; результат сохраняет диагностику API. */
|
||||
} /* Ненулевой результат запрещает программирование данных. */
|
||||
static bool program(void *user, uint32_t address, uint16_t *data, unsigned int words) /* Записывает и немедленно проверяет одну ограниченную порцию. */
|
||||
{ /* Дополнительная аппаратная проверка диапазона защищает boot независимо от wire-парсера. */
|
||||
Uint16 result; (void)user; /* Результат официального API не подменяется успешным кодом протокола. */
|
||||
if (!words || address < F28335_FLASH_APP_START || address >= F28335_FLASH_APP_STOP || words > F28335_FLASH_APP_STOP - address) return false; /* Исключает сектор A, CSM, OTP и переполнение верхней границы. */
|
||||
result = Flash_Program((Uint16 *)address, data, words, &api_status); /* Данные и рабочий буфер находятся в RAM. */
|
||||
if (!result) result = Flash_Verify((Uint16 *)address, data, words, &api_status); /* Подтверждает физическую запись до перехода к следующей порции. */
|
||||
api_error = result; return result == STATUS_SUCCESS; /* Ошибка оставляет FAB1 неопубликованным, сохраняет код TI и RS recovery. */
|
||||
} /* Все прерывания остаются запрещёнными на протяжении жизни постоянного загрузчика. */
|
||||
uint16_t F28335Rs_ReadFlash(void *user, uint32_t address) /* Читает физическое слово для общей проверки приложения перед запуском. */
|
||||
{ /* Общий валидатор ограничивает адреса диапазоном B–H. */
|
||||
(void)user; return *(volatile Uint16 *)address; /* Чтение применяется только вне активной операции Flash API. */
|
||||
} /* Стёртые промежутки проверяются вместе с загруженными секциями. */
|
||||
unsigned int F28335Rs_Program(void *user, f28335_flash_update_t *image) /* Callback работает из сектора A и возвращает полный статус, совместимый с Flash RS periph_28335. */
|
||||
{ /* Проект задаёт реальный SYSCLK в герцах через F28335_RS_CPU_HZ при компиляции. */
|
||||
f28335_flash_ops_t ops; (void)user; /* Таблица операций не содержит обращений к приложению из B–H. */
|
||||
if (CsmRegs.CSMSCR.bit.SECURE) return 0x1004U; /* Не начинает стирание защищённого CSM устройства и передаёт клиенту стандартный статус защиты. */
|
||||
memcpy(&FlashApiRunStart, &FlashApiLoadStart, &FlashApiLoadEnd - &FlashApiLoadStart); /* Все .text и .econst TI API копируются во внутреннюю RAM. */
|
||||
Flash_CPUScaleFactor = (Uint32)(1048576.0L * 200.0L * F28335_RS_CPU_HZ / 1000000000.0L); /* Формула IQ20 из TI v2.10 учитывает фактическую частоту CPU, единицы — Гц. */
|
||||
Flash_CallbackPtr = 0; ops.erase = erase; ops.program = program; ops.read = F28335Rs_ReadFlash; ops.user = 0; /* Callback во Flash во время erase/program запрещён. */
|
||||
api_error = 0U; if (f28335_flash_update_program(image, &ops)) return 0U; /* Повторная CRC staging, запись FAB1 последним и CRC физической Flash находятся в общем writer; ноль означает подтверждённый успех. */
|
||||
return api_error ? api_error : 0x1006U; /* Ошибка TI сохраняет свой код, отказ структуры/readback без ошибки API получает отдельный ненулевой статус. */
|
||||
} /* После отказа управление остаётся в постоянном загрузчике и допускает новый LOAD. */
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "../include/f28335_rs_boot.h" /* Единственная реализация автомата используется платой и host-тестами. */
|
||||
#include <string.h> /* Сбрасывает состояние без динамического выделения памяти. */
|
||||
static unsigned int crc16(unsigned int crc, unsigned int value) /* CRC16 Modbus защищает запросы и ответы существующего periph_28335. */
|
||||
{ /* Все операции явно ограничены 16 битами и подходят для 16-битного int C28x. */
|
||||
unsigned int bit; /* Каждый принятый октет содержит ровно восемь значащих битов. */
|
||||
crc ^= value & 255U; /* Старшие биты аппаратного регистра SCI не являются данными протокола. */
|
||||
for (bit = 0U; bit < 8U; ++bit) crc = (crc & 1U) ? ((crc >> 1) ^ 0xA001U) : (crc >> 1); /* Отражённый полином A001 соответствует CRC16 Modbus. */
|
||||
return crc & 65535U; /* На ПК unsigned int шире C28x, поэтому маска сохраняет одинаковый результат. */
|
||||
} /* Завершает переносимый шаг CRC. */
|
||||
static uint32_t get32(const unsigned int *p) /* Поля BIOS передаются младшим октетом вперёд, в отличие от адреса внутри SCI8. */
|
||||
{ /* Приведение перед сдвигом исключает потерю старших битов на 16-битном int. */
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); /* Возвращает адрес слова либо длину октетов по назначению поля. */
|
||||
} /* Формат запроса совпадает с periph28335_firmware.request. */
|
||||
static bool reply(f28335_rs_boot_t *s, unsigned int command, unsigned int status) /* TFLASH сообщает конкретный статус, остальные успешные команды — короткий ACK. */
|
||||
{ /* Ответ не содержит хвостового паддинга, который клиент periph_28335 считает необязательным. */
|
||||
unsigned int frame[6], crc = 0xFFFFU, i, size = command == 55U ? 4U : 2U; /* CMD_TFLASH=55 включает два октета результата; ACK INITLOAD/LOAD содержит адрес и команду. */
|
||||
frame[0] = s->address; frame[1] = command; frame[2] = status & 255U; frame[3] = status >> 8; /* Сохраняет принятый адрес и little-endian статус. */
|
||||
for (i = 0U; i < size; ++i) crc = crc16(crc, frame[i]); /* Контрольная сумма защищает все значащие поля ответа. */
|
||||
frame[size] = crc & 255U; frame[size + 1U] = crc >> 8; /* CRC16 передаётся младшим октетом вперёд. */
|
||||
return s->port.transmit(s->port.user, frame, size + 2U); /* Reset разрешается только после фактического завершения передачи. */
|
||||
} /* Ошибка UART никогда не превращается в разрешение перезапуска. */
|
||||
void f28335_rs_abort(f28335_rs_boot_t *s) /* Инвалидирует частичный кадр и прежнее подтверждение образа. */
|
||||
{ /* Порт вызывает эту функцию при межбайтовом таймауте, переполнении либо RXERROR. */
|
||||
s->position = 0UL; s->loaded = 0U; s->length = 0UL; s->image.valid = false; /* Новый TFLASH после сбоя требует полного нового INITLOAD/LOAD. */
|
||||
} /* Прерывание сеанса не изменяет действующую Flash. */
|
||||
void f28335_rs_init(f28335_rs_boot_t *s, const f28335_rs_port_t *port, unsigned int address) /* Создаёт независимый экземпляр без скрытых глобальных переменных. */
|
||||
{ /* Конфигурация платы должна передать все обязательные callbacks. */
|
||||
memset(s, 0, sizeof(*s)); s->port = *port; s->address = address; /* Обнуляет кеш, счётчики и разрешение LOAD до первого принятого кадра. */
|
||||
} /* Аппаратная инициализация SCI и GPIO остаётся в проекте платы. */
|
||||
static void execute(f28335_rs_boot_t *s) /* Выполняет только полностью принятый запрос с подтверждённой CRC16. */
|
||||
{ /* Транспортные поля никогда не разыменовываются как произвольные CPU-адреса. */
|
||||
uint32_t source, destination, length; unsigned int status = 0U; pcan_boot_manifest_t manifest; /* Manifest общего валидатора используется только для размера и транспортной CRC32. */
|
||||
if (s->command == 58U) { /* CMD_INITLOAD=58 резервирует фиксированный безопасный staging вместо произвольной записи памяти. */
|
||||
source = get32(s->header + 2); length = get32(s->header + 6); s->loaded = 0U; s->length = 0UL; /* Новая подготовка всегда отзывает подтверждение прежнего блока. */
|
||||
if (source == F28335_RS_STAGE && length >= 24UL && length <= F28335_RS_LIMIT && !(length & 1UL)) { s->length = length; reply(s, 58U, 0U); } /* Чётный SCI8 должен поместиться в предел общего проверяемого формата. */
|
||||
} else if (s->command == 51U) { /* CMD_LOAD=51 уже принял поток в staging, но до этой точки не разрешал запись Flash. */
|
||||
if (!s->fault && s->length) { s->loaded = 1U; reply(s, 51U, 0U); } /* Подтверждает блок только после полного приёма, успешной записи RAM и CRC16. */
|
||||
} else if (s->command == 55U) { /* CMD_TFLASH=55 совместим с шестибайтовым статусом periph_28335. */
|
||||
destination = get32(s->header + 2); source = get32(s->header + 6); length = get32(s->header + 10); /* Назначение, источник и размер сохраняют порядок полей существующего GUI. */
|
||||
if (destination || source || length) { /* Нулевая тройка служит безопасной пробой поддержки без стирания и запуска. */
|
||||
if (!s->loaded || destination != F28335_FLASH_APP_START || source != F28335_RS_STAGE || length != s->length) status = 0x1003U; /* Требует ровно тот блок, который подтвердил завершённый LOAD. */
|
||||
else { /* Общий валидатор повторно читает RAM и сравнивает CRC32, записанную при приёме. */
|
||||
memset(&manifest, 0, sizeof(manifest)); manifest.image_size = length; manifest.image_crc32 = s->crc32; /* Не доверяет CRC из непроверенного пользовательского manifest. */
|
||||
if (!f28335_flash_update_validate(&s->image, &s->port.stage, 0UL, &manifest)) status = 0x1002U; /* Отвергает сектор A, CSM, OTP, перекрытия, неверный FAB1 и повреждённые данные до erase. */
|
||||
else status = s->port.program(s->port.user, &s->image); /* Сохраняет конкретный код TI, защиту CSM 1004 и ошибку readback 1006; отказ оставляет RS recovery доступным. */
|
||||
} /* Завершает проверку и аппаратную запись текущего образа. */
|
||||
s->loaded = 0U; /* Повторная команда записи требует нового подтверждённого LOAD независимо от результата. */
|
||||
} /* Безопасная проба не потребляет подтверждённый блок RAM. */
|
||||
if (reply(s, 55U, status) && !status && length) s->port.reboot(s->port.user); /* Успешный reset следует только после записи, проверки физической Flash и отправки ACK. */
|
||||
} /* Остальные команды не поддерживаются и не меняют память. */
|
||||
} /* EEPROM параметров не входит ни в один callback загрузчика. */
|
||||
void f28335_rs_feed(f28335_rs_boot_t *s, unsigned int value) /* Разбирает запросы последовательно без большого приёмного массива. */
|
||||
{ /* Один вызов обрабатывает ровно один октет; время межбайтового ожидания контролирует порт. */
|
||||
uint32_t position = s->position; pcan_boot_byte_t octet = value & 255U; /* Маска исключает флаги SCI и старшие биты широкого char. */
|
||||
if (!position) { /* Новый кадр начинается только с адреса этого устройства. */
|
||||
if (octet != s->address || !s->address || s->address > 255U) return; /* Чужой адрес не получает ACK и не запускает запись RAM. */
|
||||
s->crc16 = 0xFFFFU; s->wire_crc = 0U; s->fault = 0U; /* CRC16 каждого запроса начинается со значения FFFF протокола Modbus. */
|
||||
} /* После первого октета автомат ожидает номер команды. */
|
||||
if (position == 1UL) { /* Второй октет задаёт фиксированную либо потоковую длину кадра. */
|
||||
s->command = octet; /* Сохраняет неизменный номер BIOS-команды. */
|
||||
if (octet == 58U) s->expected = 12UL; /* INITLOAD содержит адрес/команду, два u32 и CRC16: 12 октетов. */
|
||||
else if (octet == 55U) s->expected = 16UL; /* TFLASH содержит три u32 и занимает 16 октетов с CRC16. */
|
||||
else if (octet == 51U && s->length) { s->expected = s->length + 4UL; s->loaded = 0U; s->crc32 = 0UL; } /* LOAD состоит из адреса/команды, точного SCI8 и CRC16. */
|
||||
else { f28335_rs_abort(s); return; } /* Неизвестная команда не позволяет использовать оставшиеся байты как адрес CPU. */
|
||||
} /* Только распознанный заголовок разрешает приём полезных данных. */
|
||||
if (position < 2UL || s->command != 51U) { if (position < 16UL) s->header[position] = octet; } /* Фиксированные запросы сохраняются в ограниченном заголовке. */
|
||||
else if (position < s->expected - 2UL) { /* Поток LOAD записывается в RAM, пока завершающая CRC16 ещё не подтверждена. */
|
||||
if (!s->port.stage_write(s->port.user, position - 2UL, octet)) s->fault = 1U; /* Ошибка RAM запрещает подтверждение и последующий erase. */
|
||||
s->crc32 = pcan_boot_crc32_update(s->crc32, &octet, 1U); /* Независимая CRC32 позволяет обнаружить изменение staging после LOAD. */
|
||||
} /* Два завершающих октета CRC не попадают в staging образа. */
|
||||
if (position < 2UL || position < s->expected - 2UL) s->crc16 = crc16(s->crc16, octet); /* CRC16 включает адрес, команду и всю полезную нагрузку. */
|
||||
else if (position == s->expected - 2UL) s->wire_crc = octet; /* Первый октет контрольной суммы является младшим. */
|
||||
else s->wire_crc |= (unsigned int)octet << 8; /* Второй октет завершает 16-битную CRC принятого кадра. */
|
||||
s->position = position + 1UL; /* Продвигает курсор только после обработки текущего октета. */
|
||||
if (position >= 1UL && s->position == s->expected) { /* Запрос исполняется ровно один раз после получения последнего октета. */
|
||||
s->position = 0UL; /* Следующий адрес начнёт независимый кадр даже при ошибочной CRC текущего. */
|
||||
if (s->wire_crc == s->crc16) execute(s); else f28335_rs_abort(s); /* Повреждённый кадр отзывает прежний LOAD и никогда не вызывает program. */
|
||||
} /* Частичный кадр остаётся в ожидании следующего октета либо внешнего timeout. */
|
||||
} /* Автомат не использует системные часы, регистры MCU или динамическую память. */
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,88 @@
|
||||
"""Проверяет production-автомат RS и общий writer на модели сбоев без платы.""" # Не вызывает JTAG, COM и TI Flash API.
|
||||
import ctypes # Передаёт октеты настоящему скомпилированному C-автомату.
|
||||
import importlib.util # Использует общий подписыватель FAB1 для корректных тестовых образов.
|
||||
import os # Добавляет runtime GCC только в окружение тестового процесса.
|
||||
from pathlib import Path # Разрешает пути относительно установленной библиотеки templates.
|
||||
import struct # Формирует стандартные поля запросов и адресные секции SCI8.
|
||||
import subprocess # Собирает C-модель с production-исходниками GCC.
|
||||
import tempfile # Удаляет только собственные временные DLL проверки.
|
||||
import unittest # Каждый отказ выполняется независимо и получает отдельный результат.
|
||||
LIBRARY = Path(__file__).resolve().parents[1]; TEMPLATES = LIBRARY.parents[1]; PORT = TEMPLATES / "c/protocan-boot/ports/tms320f28335" # Тест использует те же валидатор и writer, которые компилируются для платы.
|
||||
spec = importlib.util.spec_from_file_location("boot_test_contract", TEMPLATES / "python/protocan/f28335_flash.py"); contract = importlib.util.module_from_spec(spec); spec.loader.exec_module(contract) # Эталон FAB1 берётся из действующей общей библиотеки.
|
||||
|
||||
def section(address, words): # Создаёт адресный блок TI без дополнительных правил обновления.
|
||||
return struct.pack("<HHH", len(words), address >> 16, address & 65535) + struct.pack("<" + "H" * len(words), *words) # Длина считается словами, каждое слово little endian, адрес — старшим словом вперёд.
|
||||
|
||||
def image(): # Минимальный правильный образ содержит разрыв для проверки CRC стёртых промежутков.
|
||||
entry = contract.APP_DATA; loaded = {entry: 0x1234, entry + 1: 0x5678, entry + 12: 0xAABB, entry + 13: 0xCCDD} # Промежуток между двумя секциями должен остаться FFFF после erase.
|
||||
return b"\xaa\x08" + bytes(16) + struct.pack("<HH", entry >> 16, entry & 65535) + section(contract.APP_START, struct.unpack("<16H", contract.descriptor(entry, loaded))) + section(entry, [0x1234, 0x5678]) + section(entry + 12, [0xAABB, 0xCCDD]) + bytes(2) # Заголовок FAB1 передаётся первым, но writer обязан опубликовать его последним.
|
||||
|
||||
class BootTests(unittest.TestCase): # Проверяет наблюдаемое поведение полного обмена, а не отдельные строки реализации.
|
||||
@classmethod # DLL компилируется один раз для всех сценариев отказа.
|
||||
def setUpClass(cls): # Изолирует бинарный результат и зависимости GCC.
|
||||
gcc = Path(os.environ.get("CC", "C:/msys64/mingw64/bin/gcc.exe")); os.environ["PATH"] = str(gcc.parent) + os.pathsep + os.environ["PATH"] # CC позволяет выбрать другой совместимый GCC без изменения теста.
|
||||
cls.temporary = tempfile.TemporaryDirectory(prefix="f28335-rs-tests-"); dll = Path(cls.temporary.name) / "model.dll" # Все артефакты испытания находятся вне прошивки пользователя.
|
||||
subprocess.run([str(gcc), "-shared", "-std=c99", "-Wall", "-Wextra", "-Werror", str(LIBRARY / "tests/model.c"), str(PORT / "f28335_flash_app.c"), str(PORT / "f28335_flash_update.c"), str(TEMPLATES / "c/protocan-boot/src/pcan_boot.c"), "-o", str(dll)], check=True) # Компилирует production parser, CRC, validator и writer без извлечения копий функций.
|
||||
cls.native = ctypes.CDLL(str(dll)); cls.native.model_crc.argtypes = [ctypes.c_char_p, ctypes.c_uint]; cls.native.model_crc.restype = ctypes.c_uint; cls.native.model_value.restype = ctypes.c_uint # ABI содержит только явно заданные целые числа и октеты.
|
||||
|
||||
@classmethod # Windows требует освободить библиотеку до удаления DLL.
|
||||
def tearDownClass(cls): # Закрывает только дескриптор, созданный этим тестом.
|
||||
if os.name == "nt": # На Windows открытая DLL удерживает файл от удаления.
|
||||
release = ctypes.WinDLL("kernel32").FreeLibrary; release.argtypes = [ctypes.c_void_p]; release.restype = ctypes.c_int; release(cls.native._handle) # Освобождает единственную тестовую DLL с полным размером указателя.
|
||||
cls.temporary.cleanup() # Временные файлы не остаются в библиотеке или проекте ИБП.
|
||||
|
||||
def setUp(self): self.native.model_reset() # Стирает модель между тестами, сохраняя независимость сценариев.
|
||||
|
||||
def frame(self, command, payload=b"", address=16): # Формирует запросы совместимого BIOS для управления production-автоматом.
|
||||
body = bytes((address, command)) + payload; return body + struct.pack("<H", self.native.model_crc(body, len(body))) # Повреждение CRC тестируется отдельно, а фиксированный probe-вектор проверяет численное значение.
|
||||
|
||||
def send(self, data): # Аппаратные границы чтений могут разделить кадр после любого октета.
|
||||
for octet in data: self.native.model_feed(octet) # Никакая модель не собирает кадр за production-автомат.
|
||||
return bytes(self.native.model_value(10 + i) for i in range(self.native.model_value(0))) # Получает фактический ACK/status, отправленный callback transmit.
|
||||
|
||||
def load(self, payload): # Выполняет стандартный INITLOAD/LOAD без TFLASH.
|
||||
self.assertEqual(len(self.send(self.frame(58, struct.pack("<II", 0x200000, len(payload))))), 4) # Подготовка RAM требует точного короткого ACK.
|
||||
self.assertEqual(len(self.send(self.frame(51, payload))), 4) # Правильный LOAD подтверждается только после полной CRC16.
|
||||
|
||||
def program(self, payload): return self.send(self.frame(55, struct.pack("<III", 0x300000, 0x200000, len(payload)))) # Адреса и порядок полей совпадают с program_request periph_28335.
|
||||
|
||||
def assert_protected(self): self.assertEqual(self.native.model_value(5), 0xA55A) # Постоянный сектор A должен сохраняться даже после отказа на любой стадии записи.
|
||||
|
||||
def test_probe_and_success(self): # Полный успешный обмен должен подтвердить физическую Flash до reset.
|
||||
probe = self.frame(55, bytes(12)); self.assertEqual(probe.hex(), "1037000000000000000000000000f066") # Вектор независимо подтверждён setprotocol.dll через protocan.periph28335.with_crc и обнаруживает изменение wire-формата.
|
||||
response = self.send(probe); self.assertEqual(response[:4], bytes((16, 55, 0, 0))); self.assertEqual(self.native.model_value(1), 0) # Probe ничего не стирает и не останавливает действующую программу.
|
||||
payload = image(); self.load(payload); response = self.program(payload); self.assertEqual(response[:4], bytes((16, 55, 0, 0))) # Успешный TFLASH сообщает нулевой статус.
|
||||
self.assertEqual(self.native.model_value(4), 1); self.assertEqual(self.native.model_value(3), 1); self.assert_protected() # Реальный валидатор принимает физическую Flash, и только затем запрошен reset.
|
||||
|
||||
def test_wrong_address_crc_and_timeout(self): # Чужие либо оборванные запросы не должны разрешать запись.
|
||||
payload = image(); self.assertEqual(self.send(self.frame(58, struct.pack("<II", 0x200000, len(payload)), address=17)), b"") # Чужой адрес не получает подтверждения INITLOAD.
|
||||
self.load(payload); self.send(self.frame(58, struct.pack("<II", 0x200000, len(payload)))[:5]); self.native.model_abort() # Таймаут отбрасывает недопринятый запрос и старое подтверждение LOAD.
|
||||
self.assertEqual(self.program(payload)[2:4], b"\x03\x10"); self.assertEqual(self.native.model_value(1), 0) # Без нового LOAD статус 1003 возникает до erase.
|
||||
self.load(payload); damaged = bytearray(self.frame(55, struct.pack("<III", 0x300000, 0x200000, len(payload)))); damaged[-1] ^= 1; self.assertEqual(self.send(damaged), b"") # Неверная CRC TFLASH не вызывает никакого ответа об успехе.
|
||||
self.assertEqual(self.native.model_value(1), 0); self.assert_protected() # Ни timeout, ни повреждение запроса не затронули Flash.
|
||||
|
||||
def test_changed_ram_and_read_failure(self): # CRC32 staging повторно проверяется после успешного LOAD.
|
||||
payload = image(); self.load(payload); self.native.model_corrupt(60); self.assertEqual(self.program(payload)[2:4], b"\x02\x10") # Изменение RAM не скрывается первоначальным успешным ACK LOAD.
|
||||
self.assertEqual(self.native.model_value(1), 0); self.load(payload); self.native.model_fault(0, 0, 1); self.assertEqual(self.program(payload)[2:4], b"\x02\x10") # Ошибка чтения staging также запрещает первый erase.
|
||||
self.assertEqual(self.native.model_value(1), 0); self.assert_protected() # Память приложения и boot остались прежними.
|
||||
|
||||
def test_forbidden_addresses_and_bad_image(self): # Проверяет все опасные границы Flash и неполную таблицу.
|
||||
for address in (0x2FFFFF, 0x338000, 0x33FFF6, 0x33FFF8, 0x380400): # Включает RAM/границу приложения, boot, CSM и OTP.
|
||||
with self.subTest(address=hex(address)): # Ошибка конкретного диапазона видна отдельно в отчёте.
|
||||
self.native.model_reset(); payload = image()[:-2] + section(address, [1, 2]) + bytes(2); self.load(payload); self.assertEqual(self.program(payload)[2:4], b"\x02\x10") # Правильная CRC16 не разрешает опасный LOAD-адрес внутри SCI8.
|
||||
self.assertEqual(self.native.model_value(1), 0); self.assert_protected() # Неверная структура отвергается раньше физического стирания.
|
||||
for payload in (image()[:-2], image() + bytes(2), image()[:-2] + section(contract.APP_DATA, [1]) + bytes(2)): # Проверяет отсутствующий терминатор, хвост и перекрытие секций.
|
||||
self.native.model_reset(); self.load(payload); self.assertEqual(self.program(payload)[2:4], b"\x02\x10"); self.assertEqual(self.native.model_value(1), 0) # Любое нарушение структуры запрещает erase.
|
||||
|
||||
def test_failure_at_each_write_and_retry(self): # Имитирует обрыв каждого этапа программирования и последующее восстановление.
|
||||
for operation in (100, 1, 2, 3): # 100 обозначает erase; три program-порции — две секции данных и последний FAB1.
|
||||
with self.subTest(operation=operation): # Все стадии проверяются независимо на свежей модели Flash.
|
||||
self.native.model_reset(); payload = image(); self.load(payload); self.native.model_fault(operation, 0, 0); self.assertEqual(self.program(payload)[2:4], b"\x16\x00") # Отказ writer передаёт статус ошибки TI вместо успешного ACK.
|
||||
self.assertEqual(self.native.model_value(4), 0); self.assertEqual(self.native.model_value(3), 0); self.assert_protected() # Частичный образ не запускается, reset не запрошен, постоянный boot сохранён.
|
||||
self.native.model_fault(0, 0, 0); self.load(payload); self.assertEqual(self.program(payload)[2:4], bytes(2)); self.assertEqual(self.native.model_value(4), 1) # Новый полный LOAD восстанавливает приложение через тот же оставшийся boot.
|
||||
|
||||
def test_failed_ack_prevents_reset(self): # Потеря передачи результата не должна перезапускать контроллер неожиданно для клиента.
|
||||
payload = image(); self.load(payload); self.native.model_fault(0, 1, 0); self.program(payload) # Запись проходит успешно, но transmit возвращает ошибку.
|
||||
self.assertEqual(self.native.model_value(4), 1); self.assertEqual(self.native.model_value(3), 0); self.assert_protected() # Физическая Flash готова, однако boot остаётся в recovery до явного последующего reset.
|
||||
|
||||
if __name__ == "__main__": unittest.main(verbosity=2) # Самостоятельный запуск выводит результаты модели без утверждений о проверке платы.
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Собирает постоянный RS boot и выпускает проверенное Flash-приложение CCS.""" # Все аппаратные пути и исходники передаются проектом платы.
|
||||
import argparse # Разбирает режим полной сборки либо postbuild уже собранного приложения.
|
||||
from datetime import datetime # Восстанавливает двенадцать слов встроенного паспорта текущего prebuild.
|
||||
import importlib.util # Подключает существующие инструменты firmware-info и FAB1 без копирования алгоритмов.
|
||||
import json # Читает проектные настройки и сохраняет отдельные manifest приложения и boot.
|
||||
from pathlib import Path # Разрешает пути независимо от текущего каталога CCS.
|
||||
import re # Разбирает стандартные относительные ссылки PARENT-N-PROJECT_LOC в Eclipse.
|
||||
import shutil # Переносит только проверенный комплект из временного каталога.
|
||||
import struct # Сопоставляет двенадцать 16-битных слов паспорта точным октетам COFF.
|
||||
import subprocess # Запускает TI compiler/OFD/hex2000 без shell и без доступа к программатору.
|
||||
import tempfile # Изолирует объекты и неполные выпуски текущей попытки.
|
||||
import xml.etree.ElementTree as ET # Читает реальные linked resources и include paths выбранной конфигурации CCS.
|
||||
TEMPLATES = Path(__file__).resolve().parents[3] # Каталог tools расположен в templates/c/f28335-rs-boot/tools.
|
||||
LIBRARY = Path(__file__).resolve().parents[1] # Общие исходники и linker boot берутся из того же checkout templates.
|
||||
FLASH = TEMPLATES / "c/protocan-boot/ports/tms320f28335" # Использует единые валидатор FAB1 и writer SCI8.
|
||||
|
||||
def module(name, path): # Загружает существующий инструмент по конкретному пути выбранной библиотеки.
|
||||
spec = importlib.util.spec_from_file_location(name, path) # Избегает случайного импорта другого установленного protocan.
|
||||
result = importlib.util.module_from_spec(spec); spec.loader.exec_module(result); return result # Ошибка общей зависимости останавливает выпуск.
|
||||
|
||||
identity = module("rs_boot_identity", TEMPLATES / "c/firmware-info/tools/identity.py") # Сохраняет общие manifest, SHA-256 и имена архивов.
|
||||
contract = module("rs_boot_contract", TEMPLATES / "python/protocan/f28335_flash.py") # Сохраняет единый формат подписания и проверки FAB1.
|
||||
|
||||
def project_paths(args): # Получает фактические C/ASM и include paths из настроек проекта.
|
||||
project = args.project.resolve(); sources = [] # Проектный каталог содержит .project/.cproject и generated отдельной конфигурации.
|
||||
for link in ET.parse(project / ".project").findall(".//linkedResources/link"): # Заголовки, OBJ и старые linker-файлы не компилируются как исходники.
|
||||
uri = link.findtext("locationURI", ""); prefix, separator, relative = uri.partition("/") # Eclipse хранит корень ссылки отдельно от относительного пути.
|
||||
match = re.fullmatch(r"PARENT-(\d+)-PROJECT_LOC", prefix) # Поддерживает переносимые ссылки любого уровня, не зашивая репозиторий платы.
|
||||
root = project.parents[int(match[1]) - 1] if match else project # PARENT-1 указывает непосредственно на родителя CCS-проекта.
|
||||
if separator and (match or prefix == "PROJECT_LOC") and Path(relative).suffix.lower() in (".c", ".asm"): sources.append((root / relative).resolve()) # Сохраняет только реальные C/ASM linked resources.
|
||||
sources.extend((project / "setgui_firmware_identity").glob("*.c")) # Подключённый ранее паспорт SETGUI остаётся частью приложения.
|
||||
config = next(item for item in ET.parse(project / ".cproject").findall(".//configuration") if item.get("name") == args.configuration) # Выбирает именно Debug либо Release, запрошенный CCS.
|
||||
option = next(item for item in config.findall(".//tool/option") if "compilerID.INCLUDE_PATH" in item.get("superClass", "")) # Сохраняет порядок поиска действующих заголовков платы.
|
||||
includes = []; macros = {"${PROJECT_ROOT}": project, "${PROJECT_LOC}": project, "${CG_TOOL_ROOT}": args.compiler.resolve(), "${BuildDirectory}": args.build_dir.resolve()} # Разрешает только ожидаемые файловые макросы CCS.
|
||||
for item in option.findall("listOptionValue"): # Каждая include-настройка применяется без shell expansion.
|
||||
value = item.get("value", "") # Пустой путь не должен означать случайный текущий каталог.
|
||||
for macro, replacement in macros.items(): value = value.replace(macro, str(replacement)) # Подставляет фактические абсолютные пути текущей конфигурации.
|
||||
if not value or "${" in value: raise ValueError("Не разрешён include path CCS: " + value) # Отсутствующую зависимость требуется исправить до компиляции.
|
||||
includes.append(Path(value)) # Сохраняет приоритет dev_sup перед дополнительными TI-заголовками.
|
||||
return list(dict.fromkeys(sources)), includes # Не компилирует повторную ссылку на один исходник дважды.
|
||||
|
||||
def compile_image(args, sources, includes, linker, output, optimize=False, api=False): # Создаёт самостоятельный COFF с собственным runtime.
|
||||
compiler = args.compiler.resolve(); support = args.support.resolve(); objects = [] # Объекты boot не смешиваются с объектами приложения.
|
||||
with tempfile.TemporaryDirectory(prefix="objects-", dir=output.parent) as directory: # Не использует старые OBJ из Debug либо другой версии исходников.
|
||||
for index, source in enumerate(sources): # Уникальный индекс исключает коллизии одинаковых имён модулей из разных каталогов.
|
||||
obj = Path(directory) / (str(index) + "_" + source.stem + ".obj") # Каждый результат принадлежит только этой попытке сборки.
|
||||
command = [str(compiler / "bin/cl2000.exe"), "-v28", "-ml", "-mt", "--float_support=fpu32", "--abi=coffabi", "--gen_func_subsections=on", "--define=F28335_RS_CPU_HZ=150000000UL"] # Архитектура и частота соответствуют F28335 платы ИБП; частота используется формулой TI Flash API.
|
||||
if source.suffix.lower() == ".c": command.append("--c99") # C99 требуется общим bool/stdint, но не передаётся как режим ассемблера.
|
||||
if optimize: command.append("-O2") # Оптимизация boot уменьшает постоянный сектор A; алгоритмы управления приложения сохраняют штатный режим без оптимизации.
|
||||
command.extend("--include_path=" + str(path) for path in includes) # Пути передаются отдельными аргументами, включая пробелы Windows.
|
||||
subprocess.run(command + ["--compile_only", "--output_file=" + str(obj), str(source)], check=True); objects.append(obj) # Ненулевой код TI останавливает сборку до линковки.
|
||||
command = [str(compiler / "bin/cl2000.exe"), "-v28", "--float_support=fpu32", "--abi=coffabi", "-z", "--rom_model", "--heap_size=0", "--stack_size=0x400", "--warn_sections", "--search_path=" + str(compiler / "lib"), "--search_path=" + str(support / "source")] # Отдельный runtime сохраняет boot работоспособным при пустом приложении.
|
||||
if api: command.append("--search_path=" + str(args.flash_api.resolve() / "lib")) # Библиотека TI берётся из локальной установки и не копируется в templates.
|
||||
command.extend(str(path) for path in objects) # Линкер получает только свежие объекты этой попытки.
|
||||
command.extend([str(linker), str(support / "source/DSP2833x_Headers_nonBIOS.cmd"), "-lrts2800_fpu32.lib", "--map_file=" + str(output.with_suffix(".map")), "--output_file=" + str(output)]) # Общие регистры и runtime согласованы с COFF/FPU32 действующего проекта.
|
||||
if not api: command.append(str(support / "source/rts2800_fpu32_fast_supplement.lib")) # Приложение сохраняет ускоренные sincos/sqrt/div/atan2 в прежней ramfuncs.
|
||||
subprocess.run(command, check=True) # Отсутствие Flash API или переполнение RAM/Flash запрещает выпуск.
|
||||
|
||||
def build_boot(args, output): # Компилирует постоянный сектор A с независимыми исходниками платы.
|
||||
support = args.support.resolve(); port = LIBRARY / "ports/tms320f28335" # Общий аппаратный порт не содержит GPIO конкретного изделия.
|
||||
sources = [args.board.resolve(), LIBRARY / "src/f28335_rs_boot.c", port / "flash_api.c", FLASH / "f28335_flash_app.c", FLASH / "f28335_flash_update.c", TEMPLATES / "c/protocan-boot/src/pcan_boot.c", support / "source/DSP2833x_GlobalVariableDefs.c", support / "source/init28335_func.c", support / "source/DSP2833x_CodeStartBranch.asm"] # UART/GPIO принадлежат только board.c; формат и writer повторно используются из templates.
|
||||
includes = [support / "include", LIBRARY / "include", args.flash_api.resolve() / "include", args.compiler.resolve() / "include"] # Все зависимости TI должны существовать до компиляции.
|
||||
compile_image(args, sources, includes, port / "boot.cmd", output, optimize=True, api=True) # Boot использует только FLASHA и выделенные RAMAPI/RAMFUNCS.
|
||||
document = ET.fromstring(subprocess.check_output([str(args.compiler / "bin/ofd2000.exe"), "-x", str(output)])) # Проверяет фактические LOAD-секции, а не только текст linker-команды.
|
||||
for section in document.findall(".//ti_coff/section"): # Учитывает также Flash LOAD копируемого в RAM кода API.
|
||||
if section.findtext("raw_data") is None or section.findtext("copy") == "true": continue # Незагружаемые секции отладки не программируются.
|
||||
start = int(section.findtext("virtual_addr", "0"), 0); size = int(section.findtext("raw_data_size", "0"), 0) // 2 # OFD хранит LOAD как virtual_addr, размер raw_data измеряет октетами.
|
||||
if not (0x338000 <= start < start + size <= 0x33FF80 or start == 0x33FFF6 and size == 2): raise ValueError("Boot OUT содержит LOAD вне постоянного сектора A") # Запрещает приложение, RAM LOAD, CSM и OTP в установочном образе boot.
|
||||
|
||||
def publish(args): # Проверяет оба независимых OUT перед переносом нового выпуска в постоянный каталог.
|
||||
project = args.project.resolve(); build = args.build_dir.resolve(); metadata_path = build / "generated/firmware_identity.json" # Каждый Debug/Release использует собственный паспорт prebuild.
|
||||
metadata = json.loads(metadata_path.read_text(encoding="utf-8")); settings = json.loads((project / "firmware_identity.json").read_text(encoding="utf-8-sig")) # Не резервирует дополнительную версию после компиляции.
|
||||
if any(metadata.get(field) != settings.get(field) for field in ("product", "target", "version", "flashing")): raise ValueError("Настройки изменились: повторите prebuild и сборку") # Не связывает старый OUT с новым продуктом, номером либо маршрутом прошивки.
|
||||
output = build / "firmware"; output.mkdir(parents=True, exist_ok=True) # Все временные и постоянные результаты принадлежат выбранной конфигурации CCS.
|
||||
with tempfile.TemporaryDirectory(prefix=".rs-release-", dir=build) as directory: # Частичная сборка никогда не заменяет предыдущий выпуск.
|
||||
stage = Path(directory); app = stage / "flash" / (args.artifact + ".out"); app.parent.mkdir() # Flash OUT приложения хранится отдельно от boot.
|
||||
shutil.copy2(build / (args.artifact + ".out"), app); contract.seal_coff(app, args.compiler / "bin/ofd2000.exe") # Заполняет только заранее выделенный FAB1 после проверки всех LOAD-адресов приложения.
|
||||
document = ET.fromstring(subprocess.check_output([str(args.compiler / "bin/ofd2000.exe"), "-x", str(app)])); entry, loaded, sections = contract.coff_sections(document) # Содержимое извлекается официальным TI OFD из реального COFF.
|
||||
built = datetime.fromisoformat(metadata["built_at_utc"]); build_id = metadata["build_id"].encode("ascii"); expected = (1, *metadata["version"], built.year, built.month << 8 | built.day, built.hour << 8 | built.minute, built.second, *(build_id[i] << 8 | build_id[i + 1] for i in range(0, 8, 2))) # Двенадцать слов повторяют публичный firmware_info_static контракт паспорта без изменения версии.
|
||||
if sum(tuple(loaded.get(address + i) for i in range(12)) == expected for address in loaded) != 1: raise ValueError("OUT не содержит единственный паспорт текущего prebuild") # Старый объект, чужая версия либо дублированный паспорт не выпускаются.
|
||||
serial = stage / "rs" / (args.artifact + "_flash_rs.bin"); serial.parent.mkdir() # RS-клиент выбирает только SCI8 приложения, исключая постоянный boot.
|
||||
subprocess.run([str(args.compiler / "bin/hex2000.exe"), str(app), "-boot", "-sci8", "-b", "-o", str(serial), "-map", str(serial.with_suffix(".bin.map"))], check=True) # Официальный конвертор сохраняет адресные секции TI SCI8.
|
||||
contract.parse_sci8(serial.read_bytes()); boot = stage / "boot" / (args.artifact + "_boot.out"); boot.parent.mkdir(); build_boot(args, boot) # Проверяет BIN независимо от OUT и создаёт отдельный проверенный установочный boot.
|
||||
shutil.copy2(build / (args.artifact + ".map"), app.with_suffix(".map")) # Карта соответствует неизменным адресам приложения до заполнения заголовка.
|
||||
for image, kind in ((app, "COFF-FLASH"), (serial, "SCI8-FLASH"), (boot, "COFF-RS-BOOT")): # Каждый тип получает собственный manifest и SHA-256 точных байтов.
|
||||
specific = dict(metadata, execution="BOOTLOADER" if image == boot else "FLASH", image_format=kind, boot_contract="f28335-universal-v1", boot_protected=True) # Явно разделяет установку постоянного boot и обновление приложения.
|
||||
if image == boot: specific["flashing"] = {"default_loader": "f28335-jtag", "profiles": {"f28335-jtag": {}}} # Постоянный загрузчик устанавливается через JTAG и никогда не выбирается для RS TFLASH.
|
||||
current = stage / "manifest-source.json"; current.write_text(json.dumps(specific, ensure_ascii=False, indent=2), encoding="utf-8"); identity.seal(image, current); identity.verify(image, image.with_suffix(image.suffix + ".manifest.json")) # Все стороны используют существующие проверенные средства firmware-info.
|
||||
(stage / "manifest-source.json").unlink(); shutil.copy2(metadata_path, stage / "firmware_identity.json") # В архив попадает один исходный паспорт текущей компиляции.
|
||||
destination = output / identity.build_folder_name(metadata); shutil.copytree(stage, destination, dirs_exist_ok=True) # Сохраняет предыдущие архивы и переносит только полностью проверенный выпуск.
|
||||
shutil.copy2(app, build / app.name) # Основной OUT CCS получает тот же FAB1, что опубликованные RS BIN и Flash OUT.
|
||||
if settings.get("copy_to") and not args.no_export: identity.export_build(destination, (project / settings["copy_to"]).resolve(), "rs/" + serial.name) # Установленный проектный export проверяет BIN в папке rs и применяется после всех локальных проверок.
|
||||
print("[firmware-build] RS boot package: " + str(destination)); return destination # Показывает точный постоянный каталог для JTAG и последующего RS-обновления.
|
||||
|
||||
def main(): # Одна точка входа используется проектной обёрткой и проверкой воспроизводимости.
|
||||
parser = argparse.ArgumentParser(description=__doc__); parser.add_argument("command", choices=("build", "postbuild")) # build компилирует приложение; postbuild использует OUT, уже собранный CCS.
|
||||
for name in ("project", "compiler", "build-dir", "support", "board", "app-linker", "flash-api"): parser.add_argument("--" + name, type=Path, required=True) # Все аппаратные исходники и установки выбирает проект-потребитель.
|
||||
parser.add_argument("--artifact", required=True); parser.add_argument("--configuration", default="Debug", choices=("Debug", "Release")); parser.add_argument("--no-export", action="store_true") # Локальная проверка может отключить дополнительное копирование в GUI.
|
||||
args = parser.parse_args(); args.compiler = args.compiler.resolve(); args.build_dir = args.build_dir.resolve(); args.build_dir.mkdir(parents=True, exist_ok=True) # Нормализует только ожидаемые пути без shell expansion.
|
||||
if Path(args.artifact).name != args.artifact or any(char in args.artifact for char in '\\/:*?"<>|'): parser.error("artifact должен быть именем OUT без расширения") # Имя файла не может выйти за выбранный каталог выпуска.
|
||||
if args.command == "build": # Ручной полный build полезен для проверки проекта вне запущенной IDE.
|
||||
sources, includes = project_paths(args); compile_image(args, sources, includes, args.app_linker.resolve(), args.build_dir / (args.artifact + ".out")) # Использует действующие linked resources без ручного списка копий исходников.
|
||||
publish(args) # Ненулевые коды компиляции, конверсии и проверки распространяются в CCS.
|
||||
|
||||
if __name__ == "__main__": main() # Импорт тестами не запускает компилятор и не изменяет рабочую версию.
|
||||
@@ -9,6 +9,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import hashlib
|
||||
import importlib.util # Загружает общий контракт JSON без импорта протокольного runtime и без зависимости от установленного пакета.
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -18,6 +19,10 @@ from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile # Резервирует номер сборки через временный файл на том же диске, как общий итератор ветки 166.
|
||||
|
||||
_flashing_spec = importlib.util.spec_from_file_location("firmware_flashing_contract", Path(__file__).resolve().parents[3] / "python/set_devices/firmware_flashing.py") # Общий контракт переносится вместе с полным checkout templates.
|
||||
_flashing = importlib.util.module_from_spec(_flashing_spec) # Изолирует проверку настроек от GUI и системных транспортов.
|
||||
_flashing_spec.loader.exec_module(_flashing) # Использует ту же проверку полей, что SETGUI при чтении manifest.
|
||||
|
||||
|
||||
def git_state(repository: Path) -> dict:
|
||||
"""Record full commits, and flag untracked sources as well as tracked edits.
|
||||
@@ -66,6 +71,7 @@ def prepare(repository: Path, templates: Path, config: Path, output: Path) -> di
|
||||
for field in ("product", "target"):
|
||||
if not isinstance(settings.get(field), str) or not settings[field].strip():
|
||||
raise ValueError(f"missing {field}")
|
||||
flashing = _flashing.validate_flashing(settings.get("flashing", {})) # Проверяет скорости и адреса до резервирования следующего номера сборки.
|
||||
if settings.get("increment_version", False): # Прямые вызовы templates из CCS, Keil и CLI используют один и тот же сохраняемый итератор.
|
||||
version = settings["version"] = next_version(version) # Вычисляет номер для JSON, заголовка и метаданных только один раз.
|
||||
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", newline="\n", dir=config.parent, prefix=".firmware-version-", delete=False) as stream: # Временный файл на том же диске позволяет атомарно заменить настройки без частичной записи.
|
||||
@@ -94,6 +100,7 @@ def prepare(repository: Path, templates: Path, config: Path, output: Path) -> di
|
||||
"version": version, "build_id": build_id, "built_at_utc": stamp.isoformat(),
|
||||
"source": source, "templates": dependency,
|
||||
"identity_contract": 1, "identity_register_count": 12}
|
||||
if "flashing" in settings: metadata["flashing"] = flashing # Переносит все CAN/RS-профили в prebuild, manifest, архив и комплект Публикатора без изменения бинарного протокола паспорта.
|
||||
constants = dict(zip(("FIRMWARE_VERSION_MAJOR", "FIRMWARE_VERSION_MINOR", "FIRMWARE_VERSION_PATCH"), version))
|
||||
constants.update(dict(zip(("FI_YEAR", "FI_MONTH", "FI_DAY", "FI_HOUR", "FI_MINUTE", "FI_SECOND"),
|
||||
(stamp.year, stamp.month, stamp.day, stamp.hour, stamp.minute, stamp.second))))
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user