277 lines
17 KiB
C
277 lines
17 KiB
C
/* Separate Flash recovery loader and application service adapter. */
|
||
#include "DSP2833x_Device.h"
|
||
#include "f28335_can_boot.h"
|
||
#if EEPROM_M95M02
|
||
#include "f28335_boot_platform.h"
|
||
#include <string.h>
|
||
/* boot хранит текущую CAN-сессию в RAM, store — прочитанное состояние журнала.
|
||
* Сброс сессии не удаляет active/pending из EEPROM. */
|
||
static pcan_boot_t boot;
|
||
static f28335_boot_store_t store;
|
||
/* Кольцевая очередь: ISR производит кадры, main потребляет. Из 32 элементов
|
||
* используется 31, чтобы head==tail однозначно означало пустую очередь.
|
||
* Долгие операции SPI запрещены внутри обработчика CAN. */
|
||
static volatile unsigned int head,tail,overflow;
|
||
static struct { uint32_t id; unsigned int dlc; pcan_boot_byte_t data[8]; } queue[32];
|
||
/* reset_requested только планирует reset; Service ждёт завершения TX.
|
||
* ready сообщает об окончании старта приложения, confirmed — о сохранённом
|
||
* подтверждении слота. healthy отражает результат открытия журнала при Init. */
|
||
static unsigned int reset_requested,reset_session,ready,healthy,confirmed;
|
||
static uint32_t sent_at,ready_at;
|
||
static unsigned int main_passes;
|
||
static int tx_busy,tx_canceling;
|
||
/* Один исходящий mailbox исключает перезапись ещё передаваемого ответа.
|
||
* MDL содержит первые четыре октета, MDH — следующие четыре, младшим вперёд.
|
||
* Запрос CANTRS ещё не означает успешную доставку: CANTA проверяется в Service.
|
||
* Аппаратный CAN ACK не является отдельным прикладным подтверждением от ПК. */
|
||
static bool transmit(void *u, uint32_t id, const pcan_boot_byte_t *d, pcan_boot_byte_t n)
|
||
{
|
||
unsigned int i; unsigned long lo=0, hi=0; (void)u;
|
||
if (tx_busy) return false;
|
||
for(i=0;i<n;++i) {
|
||
if(i<4) lo|=(unsigned long)(d[i]&255U)<<(8U*i);
|
||
else hi|=(unsigned long)(d[i]&255U)<<(8U*(i-4U));
|
||
}
|
||
EALLOW;
|
||
ECanaRegs.CANME.all &= ~16UL;
|
||
ECanaMboxes.MBOX4.MSGID.all=0x80000000UL|id;
|
||
ECanaMboxes.MBOX4.MSGCTRL.all=n;
|
||
ECanaMboxes.MBOX4.MDL.all=lo; ECanaMboxes.MBOX4.MDH.all=hi;
|
||
ECanaRegs.CANME.all |=16UL;
|
||
ECanaRegs.CANTA.all=16UL; ECanaRegs.CANAA.all=16UL;
|
||
ECanaRegs.CANTRS.all=16UL;
|
||
EDIS;
|
||
sent_at=F28335Boot_Now(); tx_busy=1; tx_canceling=0; return true;
|
||
}
|
||
|
||
/* Изменение образа разрешено только сборке Flash-загрузчика. В приложении
|
||
* эти callbacks возвращают отказ, даже если им удастся передать команду записи.
|
||
* ERASE для EEPROM означает инвалидирование manifest выбранного слота. */
|
||
static bool erase(void *u,pcan_boot_byte_t slot)
|
||
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_prepare(&store,slot); }
|
||
static bool write_block(void *u,pcan_boot_byte_t slot,uint32_t offset,const pcan_boot_byte_t *d,pcan_boot_byte_t n)
|
||
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_write(&store,slot,offset,d,n); }
|
||
/* Проверка включает CRC EEPROM и структуру SCI8. Сейчас отказ этого слоя
|
||
* кодируется CRC_ERROR как общий отказ проверки образа; причины внутри
|
||
* парсера SCI8 отдельными статусами протокола не различаются. */
|
||
static pcan_boot_status_t verify(void *u,pcan_boot_byte_t slot,const pcan_boot_manifest_t *m)
|
||
{
|
||
(void)u;
|
||
if(!F28335_BOOT_LOADER || !healthy) return PCAN_BOOT_STATUS_INVALID_STATE;
|
||
return f28335_boot_verify(&store,slot,m,0)?PCAN_BOOT_STATUS_OK:PCAN_BOOT_STATUS_CRC_ERROR;
|
||
}
|
||
static bool commit(void *u,pcan_boot_byte_t slot,const pcan_boot_manifest_t *m)
|
||
{ (void)u; return F28335_BOOT_LOADER && healthy && f28335_boot_commit(&store,slot,m); }
|
||
/* Подтверждение принадлежит приложению после READY. Загрузчик не может
|
||
* подтвердить кандидата сам: иначе исчезнет смысл проверки пробного запуска.
|
||
* Разрешение кормления watchdog из ISR открываем только после записи журнала. */
|
||
static bool confirm(void *u)
|
||
{
|
||
unsigned int running=F28335Boot_RunningSlot(); (void)u;
|
||
if(F28335_BOOT_LOADER || !healthy || !ready || running>1) return false;
|
||
if(!f28335_boot_confirm(&store,running)) return false;
|
||
boot.config.active_slot=running; confirmed=1; return true;
|
||
}
|
||
static bool can_reboot(void *u) { (void)u; return healthy; }
|
||
static void reboot(void *u) { (void)u; reset_session=0; reset_requested=1; }
|
||
/* Контракт enter_boot: 0 — загрузчик готов и ядро может отправить ACK;
|
||
* 1 — переход отложен, ACK придёт после reset; 2 — вход запрещён.
|
||
* Приложение требует handoff нашего загрузчика и подтверждённый текущий слот,
|
||
* чтобы не начать обновление из старого SPI-ROM старта или незавершённой пробы. */
|
||
static unsigned int enter(void *u,pcan_boot_byte_t session)
|
||
{
|
||
(void)u;
|
||
if(!healthy) return 2;
|
||
if(F28335_BOOT_LOADER) return 0;
|
||
/* No destructive fallback on boards still starting through the SPI ROM. */
|
||
if(F28335Boot_RunningSlot()>1 || !confirmed) return 2;
|
||
reset_session=session; reset_requested=1; return 1;
|
||
}
|
||
static void info32(pcan_boot_byte_t *p,uint32_t v)
|
||
{ unsigned int i; for(i=0;i<4;++i) p[i]=(pcan_boot_byte_t)((v>>(i*8))&255U); }
|
||
/* Паспорт помещается в classic CAN по страницам из восьми октетов.
|
||
* NAME возвращает фрагменты строки с завершающим NUL; неизвестная страница
|
||
* отклоняется. MEMORY: 0 — ёмкость/страница/режим/running, 1 — базы A/B,
|
||
* 2 — active/pending/attempted/healthy/тайм-аут, 3 — область параметров. */
|
||
static bool info(void *u,pcan_boot_byte_t command,pcan_boot_byte_t page,pcan_boot_byte_t *out)
|
||
{
|
||
static const char name[]=F28335_CAN_BOOT_DEVICE_NAME;
|
||
unsigned int i,index; (void)u;
|
||
if(command==PCAN_BOOT_CMD_CAPABILITIES && page==0) {
|
||
/* bits: ping, discovery, identity, name, memory, A/B, confirm, rollback,
|
||
* separate loader, session timeout, readback, SCI8 validation. */
|
||
info32(out,(F28335_BOOT_LOADER || F28335Boot_RunningSlot()<2)?0xFFFUL:0x1FUL);
|
||
info32(out+4,F28335_AB_SLOT_BYTES); return true;
|
||
}
|
||
if(command==PCAN_BOOT_CMD_DEVICE_NAME) {
|
||
if((unsigned int)page*8U>=sizeof(name)) return false;
|
||
for(i=0;i<8;++i) { index=(unsigned int)page*8U+i;
|
||
out[i]=index<sizeof(name)?(pcan_boot_byte_t)name[index]:0; }
|
||
return true;
|
||
}
|
||
if(command!=PCAN_BOOT_CMD_MEMORY_INFO) return false;
|
||
if(page==0) { info32(out,0x40000UL); out[4]=0; out[5]=1;
|
||
out[6]=F28335_BOOT_LOADER?1:0; out[7]=F28335Boot_RunningSlot(); return true; }
|
||
if(page==1) { info32(out,F28335_AB_SLOT_A); info32(out+4,F28335_AB_SLOT_B); return true; }
|
||
if(page==2) { out[0]=store.active; out[1]=store.pending; out[2]=store.attempted;
|
||
out[3]=healthy?1:0; info32(out+4,boot.config.session_timeout_ms); return true; }
|
||
if(page==3) { info32(out,F28335_AB_PARAMS); info32(out+4,258); return true; }
|
||
return false;
|
||
}
|
||
/* Вызывается до разрешения прикладных IRQ. Нельзя полагаться на случайное
|
||
* содержимое RAM или оставшееся состояние confirmed от предыдущей программы. */
|
||
void F28335CanBoot_EarlyInit(void) { confirmed=0; ready=0; }
|
||
/* Общий Init для приложения и загрузчика: открываем журнал, привязываем
|
||
* callbacks, задаём идентификацию, ёмкость слота, окно 16 и тайм-аут 30 секунд.
|
||
* В загрузчике FW берётся из active manifest; приложение сообщает версию сборки. */
|
||
void F28335CanBoot_Init(void)
|
||
{
|
||
pcan_boot_config_t cfg; pcan_boot_port_t port;
|
||
f28335_boot_io_t io; unsigned int running=F28335Boot_RunningSlot();
|
||
memset(&cfg,0,sizeof(cfg)); memset(&port,0,sizeof(port));
|
||
io.read=F28335Boot_Read; io.write=F28335Boot_Write; io.user=0;
|
||
healthy=f28335_boot_open(&store,&io);
|
||
cfg.device_type=F28335_CAN_BOOT_DEVICE_TYPE; cfg.device_id=F28335_CAN_BOOT_DEVICE_ID;
|
||
cfg.product_type=F28335_CAN_BOOT_PRODUCT; cfg.hardware_revision=1;
|
||
#if F28335_BOOT_LOADER
|
||
cfg.firmware_version=store.active<2?store.image[store.active].firmware_version:0;
|
||
#else
|
||
cfg.firmware_version=F28335_CAN_BOOT_FIRMWARE_VERSION;
|
||
#endif
|
||
head=tail=overflow=reset_requested=reset_session=ready=main_passes=0; tx_busy=tx_canceling=0;
|
||
confirmed=healthy && running<2 && store.active==running && store.pending==F28335_AB_NONE;
|
||
cfg.active_slot=store.active;
|
||
cfg.slot_count=2; cfg.max_image_size=F28335_AB_SLOT_BYTES; cfg.ack_window=16;
|
||
cfg.session_timeout_ms=30000UL;
|
||
port.send=transmit; port.erase_slot=erase; port.write_slot=write_block;
|
||
port.verify_status=verify; port.set_pending_slot=commit; port.reboot=reboot;
|
||
port.confirm_running_slot=confirm; port.enter_boot=enter;
|
||
port.can_reboot=can_reboot; port.service_info=info;
|
||
if(!pcan_boot_init(&boot,&cfg,&port,0)) return;
|
||
EALLOW;
|
||
/* Меняем только MBOX3/4/6: маска 88=8+16+64. MBOX0..2 — старый обмен,
|
||
* MBOX5 — heartbeat. Приём broadcast выделен в отдельный точный mailbox,
|
||
* чтобы слишком широкая маска Boot не перехватывала сообщения приложения.
|
||
* 72=8+64 — оба RX, 16 — TX. Регистры флагов очищаются записью единицы. */
|
||
ECanaRegs.CANME.all &= ~88UL;
|
||
ECanaMboxes.MBOX3.MSGID.all=0xC0000000UL|pcan_boot_make_id(0,0,cfg.device_type,cfg.device_id,0,0);
|
||
ECanaLAMRegs.LAM3.all=0x100FFFFFUL;
|
||
ECanaMboxes.MBOX3.MSGCTRL.all=8;
|
||
/* MBOX5 belongs to the application's heartbeat. MBOX6 is discovery only. */
|
||
ECanaMboxes.MBOX6.MSGID.all=0xC0000000UL|pcan_boot_make_id(0,0,7,15,13,0);
|
||
ECanaLAMRegs.LAM6.all=0x10000000UL; /* Ignore priority only. */
|
||
ECanaMboxes.MBOX6.MSGCTRL.all=0;
|
||
ECanaRegs.CANMD.all=(ECanaRegs.CANMD.all|72UL)&~16UL;
|
||
ECanaRegs.CANRMP.all=72UL;
|
||
ECanaRegs.CANMIM.all |=72UL;
|
||
ECanaRegs.CANME.all |=88UL;
|
||
EDIS;
|
||
}
|
||
/* ISR только проверяет заголовок и копирует кадр. Чужие адреса и RTR
|
||
* отбрасываются до очереди. Глобальный адрес допускается только для discovery;
|
||
* DLC и смысл команды окончательно проверит ядро в основном цикле. */
|
||
static void receive_box(volatile struct MBOX *box,uint32_t mask)
|
||
{
|
||
unsigned int next=(head+1U)&31U, i;
|
||
unsigned long lo, hi;
|
||
uint32_t id; unsigned int type;
|
||
if(!(ECanaRegs.CANRMP.all&mask)) return;
|
||
if(box->MSGCTRL.bit.RTR) { ECanaRegs.CANRMP.all=mask; return; }
|
||
id=box->MSGID.all&0x1FFFFFFFUL;
|
||
type=pcan_boot_id_msg_type(id);
|
||
if(!(box->MSGID.all&0x80000000UL) || pcan_boot_id_route(id)!=0 ||
|
||
(type!=9 && type!=10 && type!=11 && type!=13) ||
|
||
!((pcan_boot_id_device_type(id)==F28335_CAN_BOOT_DEVICE_TYPE &&
|
||
pcan_boot_id_device_id(id)==F28335_CAN_BOOT_DEVICE_ID) ||
|
||
(type==13 && pcan_boot_id_device_type(id)==7 && pcan_boot_id_device_id(id)==15 &&
|
||
pcan_boot_id_msg_body(id)==0))) { ECanaRegs.CANRMP.all=mask; return; }
|
||
if(next==tail) overflow=1;
|
||
else {
|
||
queue[head].id=box->MSGID.all&0x1FFFFFFFUL;
|
||
queue[head].dlc=box->MSGCTRL.bit.DLC;
|
||
lo=box->MDL.all; hi=box->MDH.all;
|
||
for(i=0;i<4;++i) { queue[head].data[i]=(lo>>(8U*i))&255U;
|
||
queue[head].data[i+4]=(hi>>(8U*i))&255U; }
|
||
head=next;
|
||
}
|
||
ECanaRegs.CANRMP.all=mask;
|
||
}
|
||
|
||
void F28335CanBoot_RxISR(void)
|
||
{
|
||
receive_box(&ECanaMboxes.MBOX3,8UL);
|
||
receive_box(&ECanaMboxes.MBOX6,64UL);
|
||
}
|
||
/* Вызывать после инициализации параметров, датчиков и прикладного режима.
|
||
* От этого момента отсчитывается интервал наблюдения перед авто-CONFIRM. */
|
||
void F28335CanBoot_Ready(void)
|
||
{ ready=1; ready_at=F28335Boot_Now(); }
|
||
/* Используется таймерными ISR приложения. Во время пробы кормление watchdog
|
||
* должно доказывать выполнение main: работа одного ISR не доказывает готовность.
|
||
* Старый старт без handoff сохраняет прежнюю политику watchdog. */
|
||
unsigned int F28335CanBoot_WatchdogAllowed(void)
|
||
{ return F28335Boot_RunningSlot()>1 || confirmed; }
|
||
/* Неблокирующий диспетчер относительно ожидания CAN: вызывать в каждом проходе main.
|
||
* Сами callbacks проверки/записи EEPROM синхронные. Порядок здесь важен:
|
||
* проба приложения -> завершение TX -> reset -> poll -> ошибки RX -> один кадр. */
|
||
void F28335CanBoot_Service(void)
|
||
{
|
||
uint32_t now=F28335Boot_Now();
|
||
if(!F28335_BOOT_LOADER && ready && !confirmed && healthy && F28335Boot_RunningSlot()<2) {
|
||
/* A trial must keep executing main, not merely a timer ISR. */
|
||
/* После 100 проходов main и 5 секунд пробуем сохранить CONFIRM.
|
||
* Если до 10 секунд сохранение не удалось, main перестаёт кормить watchdog,
|
||
* что позволяет следующему старту откатиться к подтверждённому образу. */
|
||
if((uint32_t)(now-ready_at)<10000UL) ServiceDog();
|
||
if(main_passes<100) ++main_passes;
|
||
if(main_passes==100 && (uint32_t)(now-ready_at)>=5000UL) (void)confirm(0);
|
||
}
|
||
if(tx_busy) {
|
||
/* После CANTRR ждём завершения отмены или запоздавшего ACK, прежде чем
|
||
* повторно использовать mailbox. reset_requested к этому моменту уже снят,
|
||
* поэтому позднее подтверждение не должно перезапустить устройство. */
|
||
if(tx_canceling) {
|
||
if(!((ECanaRegs.CANAA.all|ECanaRegs.CANTA.all)&16UL)) return;
|
||
ECanaRegs.CANAA.all=16UL; ECanaRegs.CANTA.all=16UL;
|
||
tx_busy=tx_canceling=0;
|
||
} else if((uint32_t)(now-sent_at)>250UL) {
|
||
/* Cancel reset immediately, even if a late CAN ACK races CANTRR. */
|
||
reset_requested=0; pcan_boot_abort(&boot); tail=head;
|
||
tx_canceling=1; ECanaRegs.CANTRR.all=16UL; return;
|
||
} else if(ECanaRegs.CANTA.all&16UL) {
|
||
ECanaRegs.CANTA.all=16UL; tx_busy=0;
|
||
} else return;
|
||
}
|
||
if(reset_requested) { F28335Boot_Reset(reset_session); return; }
|
||
pcan_boot_poll(&boot,now);
|
||
if(tx_busy) return;
|
||
/* После потери кадра нельзя молча продолжать запись: сбрасываем RAM-сессию
|
||
* и накопленную очередь. Подтверждённый образ и опубликованный pending сохраняются. */
|
||
if(overflow || (ECanaRegs.CANRML.all&72UL)) {
|
||
overflow=0; ECanaRegs.CANRML.all=72UL;
|
||
pcan_boot_abort(&boot); tail=head; return;
|
||
}
|
||
if(tail!=head) {
|
||
pcan_boot_process(&boot,queue[tail].id,queue[tail].data,queue[tail].dlc);
|
||
tail=(tail+1U)&31U;
|
||
}
|
||
}
|
||
/* Используем session из NOINIT только для нового ENTER_BOOT после reset.
|
||
* Это отправка отложенного ACK готовности, а не восстановление принятых блоков
|
||
* или CRC незавершённой передачи: данные сессии через reset не сохраняются. */
|
||
void F28335CanBoot_Resume(unsigned int session)
|
||
{
|
||
if(F28335_BOOT_LOADER && session && session<256)
|
||
(void)pcan_boot_process(&boot,pcan_boot_make_id(1,0,
|
||
F28335_CAN_BOOT_DEVICE_TYPE,F28335_CAN_BOOT_DEVICE_ID,9,(session<<8)|2),0,0);
|
||
}
|
||
#else
|
||
void F28335CanBoot_EarlyInit(void) {}
|
||
void F28335CanBoot_Init(void) {}
|
||
void F28335CanBoot_RxISR(void) {}
|
||
void F28335CanBoot_Service(void) {}
|
||
void F28335CanBoot_Ready(void) {}
|
||
unsigned int F28335CanBoot_WatchdogAllowed(void) { return 1; }
|
||
void F28335CanBoot_Resume(unsigned int session) { (void)session; }
|
||
#endif
|