/* Хранилище A/B без зависимости от регистров F28335. * Здесь находятся правила сохранения образа при сбое питания и выбора слота. * CAN отвечает за порядок команд; этот слой повторно защищает active/pending * и границы EEPROM, даже если вызывающий код передал неверные параметры. * Адрес EEPROM измеряется в октетах, адрес назначения SCI8 — в словах CPU. */ #include "f28335_boot_store.h" #include /* Формат журнала фиксирован: 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>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;istarts[i]) return false; starts[sections]=addr; ends[sections++]=end; if(*entry>=addr && *entry1 || 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;offimage_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); }