Files
templates/c/protocan-boot/ports/tms320f28335/f28335_boot_store.c

255 lines
18 KiB
C
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.
/* Хранилище A/B без зависимости от регистров F28335.
* Здесь находятся правила сохранения образа при сбое питания и выбора слота.
* CAN отвечает за порядок команд; этот слой повторно защищает active/pending
* и границы EEPROM, даже если вызывающий код передал неверные параметры.
* Адрес EEPROM измеряется в октетах, адрес назначения SCI8 — в словах CPU. */
#include "f28335_boot_store.h"
#include <string.h>
/* Формат журнала фиксирован: u32 little endian. Нельзя сохранять C-структуру
* через memcpy: выравнивание и sizeof на C28x отличаются от обычного ПК. */
static uint32_t bs_get32(const pcan_boot_byte_t *p)
{ return (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24); }
static void bs_put32(pcan_boot_byte_t *p, uint32_t v)
{ unsigned int i; for(i=0;i<4;++i) p[i]=(pcan_boot_byte_t)((v>>(8*i))&255U); }
/* Потоковый CRC32: первый вызов получает crc=0, следующие — предыдущий результат.
* Внутренняя инверсия позволяет считать один CRC по нескольким порциям EEPROM.
* Маска 255 исключает старшие биты 16-битного представления октета на C28x.
* Это проверка повреждения данных, а не криптографическая подпись. */
uint32_t f28335_boot_crc(uint32_t crc, const pcan_boot_byte_t *p, unsigned int n)
{ unsigned int i,b; crc=~crc; for(i=0;i<n;++i) { crc^=p[i]&255U;
for(b=0;b<8;++b) crc=(crc>>1)^((crc&1U)?0xEDB88320UL:0); } return ~crc; }
static uint32_t bs_base(unsigned int slot) { return slot?F28335_AB_SLOT_B:F28335_AB_SLOT_A; }
/* Принимаем запись только при совпадении magic, версии, marker и CRC.
* Дополнительно проверяем смысл номеров слотов: pending не может быть active.
* Наличие правильного marker без правильного CRC не разрешает запуск. */
static bool bs_record_valid(const pcan_boot_byte_t *p)
{
return bs_get32(p)==0x32424146UL && bs_get32(p+4)==1 &&
bs_get32(p+60)==0x51AFC03DUL && bs_get32(p+56)==f28335_boot_crc(0,p,56) &&
(p[12]<2 || p[12]==F28335_AB_NONE) &&
(p[13]<2 || p[13]==F28335_AB_NONE) && p[14]<=1 &&
(p[13]==F28335_AB_NONE || p[13]!=p[12]);
}
/* Карта записи, смещения в октетах:
* 0..3 magic FAB2; 4..7 версия; 8..11 sequence; 12 active; 13 pending;
* 14 attempted; 15 резерв; 16..35 manifest A; 36..55 manifest B;
* 56..59 CRC первых 56 октетов; 60..63 marker завершённой публикации.
* Manifest: size, CRC, FW (по u32), Product (u16), HW min/max, 4 октета резерва. */
static void bs_decode(f28335_boot_store_t *s, const pcan_boot_byte_t *p)
{
unsigned int i; s->sequence=bs_get32(p+8); s->active=p[12];
s->pending=p[13]; s->attempted=p[14];
for(i=0;i<2;++i) {
const pcan_boot_byte_t *q=p+16+i*20;
s->image[i].image_size=bs_get32(q); s->image[i].image_crc32=bs_get32(q+4);
s->image[i].firmware_version=bs_get32(q+8);
s->image[i].product_type=(uint16_t)(q[12]|(q[13]<<8));
s->image[i].hardware_revision_min=q[14]; s->image[i].hardware_revision_max=q[15];
}
}
/* Читаем обе копии журнала. Нераспознанная разметка допускает CAN recovery
* с active=NONE; ошибка чтения запрещает запись, поскольку реальный active неизвестен.
* Эта функция не преобразует старый SCI8 по адресу 0 в новую A/B-разметку. */
bool f28335_boot_open(f28335_boot_store_t *s, const f28335_boot_io_t *io)
{
pcan_boot_byte_t a[64],b[64]; bool va,vb;
if(!s || !io || !io->read || !io->write) return false;
memset(s,0,sizeof(*s)); s->io=*io; s->active=s->pending=F28335_AB_NONE;
if(!io->read(io->user,0,a,64) || !io->read(io->user,256,b,64)) { s->io_fault=true; return false; }
va=bs_record_valid(a); vb=bs_record_valid(b);
if(!va && !vb) return true;
/* Сравнение sequence по модулю 2^32: разность менее половины диапазона
* считается более новой записью. При равенстве выбирается страница 1.
* Соседние публикации отличаются на единицу, в том числе после переполнения. */
s->page=vb && (!va || (uint32_t)(bs_get32(b+8)-bs_get32(a+8))<0x80000000UL)?1:0;
bs_decode(s,s->page?b:a); s->valid=true; return true;
}
/* Публикация выполняется в другую страницу, текущую действующую не трогаем.
* Порядок принципиален: обнулить marker -> проверить -> записать тело ->
* проверить тело -> записать marker последним -> проверить всю запись.
* При обрыве питания загрузчик выбирает прежнюю либо полностью новую запись.
* Каждый memcmp учитывает sizeof элемента, поскольку элемент здесь — октет
* протокола, но на C28x занимает адресуемое 16-битное слово. */
static bool bs_save(f28335_boot_store_t *s)
{
pcan_boot_byte_t p[64],check[64],invalid[4]={0,0,0,0}; unsigned int i;
unsigned int page=s->valid?(s->page^1U):0;
uint32_t addr=(uint32_t)page*256;
memset(p,0,sizeof(p)); bs_put32(p,0x32424146UL); bs_put32(p+4,1);
bs_put32(p+8,s->sequence+1); p[12]=s->active; p[13]=s->pending; p[14]=s->attempted;
for(i=0;i<2;++i) {
pcan_boot_byte_t *q=p+16+i*20;
bs_put32(q,s->image[i].image_size); bs_put32(q+4,s->image[i].image_crc32);
bs_put32(q+8,s->image[i].firmware_version);
q[12]=s->image[i].product_type&255U; q[13]=(s->image[i].product_type>>8)&255U;
q[14]=s->image[i].hardware_revision_min; q[15]=s->image[i].hardware_revision_max;
}
bs_put32(p+56,f28335_boot_crc(0,p,56)); bs_put32(p+60,0x51AFC03DUL);
/* The old page is never erased. A torn write cannot publish a partial record. */
if(!s->io.write(s->io.user,addr+60,invalid,4) ||
!s->io.read(s->io.user,addr+60,check,4) || memcmp(check,invalid,sizeof(invalid)) ||
!s->io.write(s->io.user,addr,p,60) ||
!s->io.read(s->io.user,addr,check,60) || memcmp(p,check,60*sizeof(*p)) ||
!s->io.write(s->io.user,addr+60,p+60,4) ||
!s->io.read(s->io.user,addr,check,64) || memcmp(p,check,sizeof(p))) return false;
s->page=page; s->sequence++; s->valid=true; return true;
}
/* Изменения сначала готовятся в копии состояния next. Рабочее состояние
* заменяем только после публикации. Если запись/контрольное чтение сорвались,
* перечитываем журнал: marker мог успеть записаться до ошибки readback.
* После неудачи чтения io_fault блокирует дальнейшие изменения хранилища. */
static bool bs_publish(f28335_boot_store_t *s, f28335_boot_store_t *next)
{
if(bs_save(next)) { *s=*next; return true; }
/* A failed final read may still have committed: reload before any more writes. */
{ f28335_boot_io_t io=s->io; if(!f28335_boot_open(s,&io)) {
s->valid=false; s->io_fault=true; return false; } }
return false;
}
/* Логический ERASE: обнуляем manifest неактивного слота в журнале.
* Саму EEPROM стирать целиком не требуется — каждый байт будет перезаписан.
* Active защищён всегда; наличие pending запрещает начало новой записи,
* пока кандидат не подтверждён либо не снят при следующем выборе загрузки. */
bool f28335_boot_prepare(f28335_boot_store_t *s, unsigned int slot)
{
f28335_boot_store_t next=*s;
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE) return false;
memset(&next.image[slot],0,sizeof(next.image[slot]));
return bs_publish(s,&next);
}
/* Записываем не более одного CAN-блока и немедленно сравниваем прочитанное.
* Проверка n <= capacity-off выполняется после off <= capacity: это исключает
* переполнение суммы off+n и выход из слота в соседний слот или параметры. */
bool f28335_boot_write(f28335_boot_store_t *s, unsigned int slot, uint32_t off,
const pcan_boot_byte_t *p, unsigned int n)
{
pcan_boot_byte_t check[8];
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE || !n || n>8 ||
off>F28335_AB_SLOT_BYTES || n>F28335_AB_SLOT_BYTES-off) return false;
return s->io.write(s->io.user,bs_base(slot)+off,p,n) &&
s->io.read(s->io.user,bs_base(slot)+off,check,n) && !memcmp(check,p,n*sizeof(*p));
}
/* Слово SCI8 занимает два октета файла, младший октет идёт первым.
* Смещение увеличиваем только после успешного чтения; усечённый файл отклоняем. */
static bool bs_u16(f28335_boot_store_t *s,uint32_t base,uint32_t size,uint32_t *off,uint16_t *v)
{
pcan_boot_byte_t p[2]; if(*off>size || size-*off<2 ||
!s->io.read(s->io.user,base+*off,p,2)) return false;
*v=(uint16_t)(p[0]|(p[1]<<8)); *off+=2; return true;
}
/* Особенность таблицы TI: адрес u32 передаётся старшим словом вперёд,
* при этом внутри каждого слова остаётся little endian. Не заменять на get32. */
static bool bs_addr(f28335_boot_store_t *s,uint32_t base,uint32_t size,uint32_t *off,uint32_t *v)
{
uint16_t hi,lo; if(!bs_u16(s,base,size,off,&hi) || !bs_u16(s,base,size,off,&lo)) return false;
*v=((uint32_t)hi<<16)|lo; return true;
}
/* The complete table is validated before any application RAM is written.
* Loader RAM is E000..FFFF and M0/M1. Flash, peripherals and XINTF are forbidden. */
/* writer=NULL: только разбираем и проверяем таблицу, RAM приложения не меняем.
* writer задан: повторно проверяем таблицу и переносим слова в разрешённую RAM.
* Массивы starts/ends ограничены MAX_SECTIONS; они нужны для обнаружения
* пересекающихся секций, в том числе секций с разным порядком адресов. */
static bool bs_table(f28335_boot_store_t *s,unsigned int slot,uint32_t size,
f28335_boot_ram_writer writer,void *user,uint32_t *entry)
{
uint32_t starts[F28335_AB_MAX_SECTIONS],ends[F28335_AB_MAX_SECTIONS];
uint32_t off=0,addr,end,base=bs_base(slot); unsigned int sections=0,i;
uint16_t count,word; bool entry_loaded=false;
if(!bs_u16(s,base,size,&off,&word) || word!=0x08AA) return false;
/* После ключа AA 08 пропускаем 16 резервных октетов; далее расположен entry.
* Нулевой count завершает таблицу строго на конце файла, без хвостовых данных. */
off=18; if(!bs_addr(s,base,size,&off,entry)) return false;
for(;;) {
if(!bs_u16(s,base,size,&off,&count)) return false;
if(!count) return off==size && entry_loaded;
if(sections==F28335_AB_MAX_SECTIONS || !bs_addr(s,base,size,&off,&addr)) return false;
end=addr+count;
/* Разрешён только полуинтервал RAM [0x8000,0xE000) в словах CPU.
* Верхняя граница защищает RAM работающего загрузчика; нижняя — стек,
* handoff и регистры. Проверка размера payload использует уже октеты файла. */
if(addr<0x8000UL || end>0xE000UL || end<=addr ||
off>size || (uint32_t)count*2>size-off) return false;
for(i=0;i<sections;++i) if(addr<ends[i] && end>starts[i]) return false;
starts[sections]=addr; ends[sections++]=end;
if(*entry>=addr && *entry<end) entry_loaded=true;
if(writer) { for(i=0;i<count;++i) {
if(!bs_u16(s,base,size,&off,&word) || !writer(user,addr+i,word)) return false;
} } else off+=(uint32_t)count*2;
}
}
/* VERIFY проверяет содержимое самой EEPROM, а не только CRC принятого потока.
* Затем проверяется структура SCI8 и попадание entry в загружаемую секцию.
* Выходной entry необязателен: COMMIT нужна проверка, но не адрес перехода. */
bool f28335_boot_verify(f28335_boot_store_t *s,unsigned int slot,
const pcan_boot_manifest_t *m,uint32_t *entry)
{
uint32_t off,crc=0,local_entry; unsigned int n; pcan_boot_byte_t p[32];
if(slot>1 || m->image_size<24 || m->image_size>F28335_AB_SLOT_BYTES ||
(m->image_size&1) || m->product_type!=0x2833 ||
m->hardware_revision_min>1 || m->hardware_revision_max<1) return false;
for(off=0;off<m->image_size;off+=n) {
n=(m->image_size-off<32)?(unsigned int)(m->image_size-off):32;
if(!s->io.read(s->io.user,bs_base(slot)+off,p,n)) return false;
crc=f28335_boot_crc(crc,p,n);
}
if(crc!=m->image_crc32 || !bs_table(s,slot,m->image_size,0,0,&local_entry)) return false;
if(entry) *entry=local_entry;
return true;
}
/* COMMIT публикует проверенный образ как pending, не меняя active.
* Повторная проверка EEPROM непосредственно перед публикацией не позволяет
* назначить кандидатом образ, испорченный после предыдущего VERIFY. */
bool f28335_boot_commit(f28335_boot_store_t *s,unsigned int slot,const pcan_boot_manifest_t *m)
{
f28335_boot_store_t next=*s;
if(s->io_fault || slot>1 || slot==s->active || s->pending!=F28335_AB_NONE ||
!f28335_boot_verify(s,slot,m,0)) return false;
next.image[slot]=*m; next.pending=slot; next.attempted=0;
return bs_publish(s,&next);
}
/* Подтверждать можно только действительно выбранный для пробного запуска слот.
* Требование attempted исключает подтверждение ещё ни разу не запускавшегося
* кандидата. Повтор CONFIRM уже активного слота возвращает успех без записи. */
bool f28335_boot_confirm(f28335_boot_store_t *s,unsigned int running_slot)
{
f28335_boot_store_t next=*s;
if(s->io_fault || running_slot>1) return false;
if(s->pending==F28335_AB_NONE) return s->valid && s->active==running_slot;
if(s->pending!=running_slot || !s->attempted) return false;
next.active=running_slot; next.pending=F28335_AB_NONE; next.attempted=0;
return bs_publish(s,&next);
}
/* Выбор выполняется при старте загрузчика. attempted сохраняется ДО перехода
* в приложение: после зависания, watchdog reset или потери питания следующий
* старт уже знает о незавершённой пробе и возвращает прежний active.
* Успех функции с slot=NONE означает recovery, а не разрешение перехода в RAM. */
bool f28335_boot_select(f28335_boot_store_t *s,unsigned int *slot)
{
f28335_boot_store_t next=*s; *slot=F28335_AB_NONE;
if(s->io_fault) return false;
if(s->pending!=F28335_AB_NONE) {
if(!s->attempted && f28335_boot_verify(s,s->pending,&s->image[s->pending],0)) {
next.attempted=1;
if(!bs_publish(s,&next)) return false;
*slot=s->pending; return true;
}
/* Кандидат уже запускался без подтверждения либо не прошёл проверку.
* Снимаем pending отдельной публикацией; подтверждённый active остаётся прежним. */
next.pending=F28335_AB_NONE; next.attempted=0;
if(!bs_publish(s,&next)) return false;
}
if(s->active<2 && f28335_boot_verify(s,s->active,&s->image[s->active],0)) *slot=s->active;
return true;
}
/* Сначала полная проверка CRC и всех адресов без записи, затем перенос слов.
* Платформа вызывает entry только при true; частичная загрузка при ошибке I/O
* не даёт разрешения на запуск приложения. */
bool f28335_boot_load(f28335_boot_store_t *s,unsigned int slot,
f28335_boot_ram_writer writer,void *user,uint32_t *entry)
{
return slot<2 && writer && entry && f28335_boot_verify(s,slot,&s->image[slot],entry) &&
bs_table(s,slot,s->image[slot].image_size,writer,user,entry);
}