255 lines
18 KiB
C
255 lines
18 KiB
C
/* Хранилище 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);
|
||
}
|