/* 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 /* 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;i1) 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]=indexMSGCTRL.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