"""Run the actual V1/V2/V3 CAN update and SaveFwInfo C functions on Windows. Requires Visual Studio 2022 C tools. Flash, CAN, and reset are simulated; the firmware functions are extracted from the current sources, not reimplemented. """ from pathlib import Path import re import subprocess import tempfile ROOT = Path(__file__).resolve().parents[1] VCVARS = Path(r"C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvars32.bat") VARIANTS = [ ("V1", "mBMS_APP_V3117/APP", "ExtId", 0x08080000, 0x080E0000, 0x08010000), ("V2", "APP_R3221/App/Source", "ExtID", 0x60000, 0x80000, 0x10000), ("V3", "APP_R3323/App/Source", "ExtID", 0x60000, 0x80000, 0x10000), ] def function(source, name): match = re.search(r"^[ \t]*(?:void|u8|uint8_t|uint32_t) " + name + r"\([^;]*?\)\s*(?://[^\n]*)?\s*\{", source, re.M) assert match, name end = source.index("{", match.start()) + 1 depth = 1 while depth: depth += (source[end] == "{") - (source[end] == "}") end += 1 return source[match.start():end] STUBS = r""" #include #include #include #include #include #include typedef uint8_t u8; typedef uint16_t u16; typedef int16_t s16; typedef struct { uint32_t ExtId; uint8_t Data[8]; uint8_t DLC; } CanRxMsg; typedef struct { uint32_t ExtID; uint8_t Data[8]; struct { struct { uint8_t DLC; } Control_f; } Cst; } stc_can_rxframe_t; typedef struct { uint32_t AppFileSize; uint8_t BootVer[4], AppVer[4], FileName[30], BootUpdate, AppUpdate; } FW_INFO; FW_INFO fwinfo; #define ADDR_FLASH_SECTOR_8 IMAGE_BASE #define ADDR_FLASH_SECTOR_10 (IMAGE_END - 0x20000) #define ADDR_FLASH_SECTOR_11 IMAGE_END #define mBMS_FW_ADDRESS IMAGE_BASE #define mBMS_FW_END_ADDRESS IMAGE_END #define mBMS_FW_DN_ADDRESS IMAGE_BASE #define mBMS_FW_DN_END_ADDRESS IMAGE_END #define mBMS_FW_FLASH_SIZE (IMAGE_END - IMAGE_BASE) #define mBMS_FW_PACKET_SIZE 2048 #define mBMS_FW_RN_ADDRESS 0x20000 #define SECTOR_SIZE 8192 #define mBMS_FW_INFO_BASE_ADDR INFO_BASE static int fail_mode, erase_count, write_count, reset_count, response_count; static uint8_t last_response[8]; uint8_t GetDevAddrIO(void) { return 1; } void FLASH_If_Init(void) {} uint32_t FLASH_If_SingleSectorErase(uint32_t addr) { erase_count++; if (fail_mode == 1) return 1; memset((void *)addr, 0xFF, addr == INFO_BASE ? sizeof(FW_INFO) : 8192); return 0; } uint32_t FLASH_If_Write(volatile uint32_t *addr, uint32_t *data, uint32_t words) { write_count++; if (fail_mode == 2) return 1; if (fail_mode == 4) return 0; /* Driver claims success without writing. */ memcpy((void *)*addr, data, words * 4); if (fail_mode == 3) ((uint8_t *)*addr)[0] ^= 1; *addr += words * 4; return 0; } void SendPacket(uint32_t id, uint8_t *data) { memcpy(last_response, data, 8); response_count++; } void delay_os_ms(uint32_t ms) {} void NVIC_SystemReset(void) { reset_count++; } void Sys_Soft_Reset(void) { reset_count++; } uint32_t SaveFwInfo(void); """ CASES = r""" #define CHECK(x) do { if (!(x)) { fprintf(stderr, "line %d: %s\n", __LINE__, #x); exit(1); } } while (0) static void be32(uint8_t *p, uint32_t n) { p[0]=n>>24; p[1]=n>>16; p[2]=n>>8; p[3]=n; } static PACKET packet; static uint32_t byte_sum(const uint8_t *p, uint32_t n) { uint32_t sum=0; for (uint32_t i=0; iBV.NO=number; SET_DLC(packet,8); for (uint32_t i=0;i<8;i++) packet.Data[i]=(uint8_t)((number*8+i)*13); if (number==0) { ((uint32_t *)packet.Data)[0]=0x20001000; ((uint32_t *)packet.Data)[1]=mBMS_FW_RN_ADDRESS|1; } } static uint32_t block_sum(void) { return byte_sum(DownloadBlock.bytes,CanDLInfor.WriteLength*8); } static uint32_t check_block(uint32_t sum) { be32(packet.Data,wire_addr()); be32(packet.Data+4,sum); return WriteDataChkSumByCan(&packet); } static void block_tests(void) { /* A complete 256-frame block may arrive out of order, with exact duplicates. */ prepare_block(256); for (int i=255;i>=0;i--) { fill_frame(i); CHECK(WriteDataByCan(&packet)==0); } fill_frame(137); CHECK(WriteDataByCan(&packet)==0); CHECK(DownloadPacketCount==256 && write_count==0); uint32_t sum=block_sum(); CHECK(check_block(sum+1)==5 && write_count==0); CHECK(check_block(sum)==0 && write_count==1); CHECK(check_block(sum)==0 && write_count==1); /* Lost ACK: no reprogramming. */ verify_image(); /* Missing frame is distinguishable from a checksum error and never reaches flash. */ prepare_block(256); for (int i=0;i<256;i++) if (i!=137) { fill_frame(i); CHECK(WriteDataByCan(&packet)==0); } CHECK(check_block(0)==12 && write_count==0); fill_frame(137); CHECK(WriteDataByCan(&packet)==0); CHECK(check_block(block_sum())==0); prepare_block(1); fill_frame(0); CHECK(WriteDataByCan(&packet)==0); packet.Data[0]^=1; CHECK(WriteDataByCan(&packet)==13); CHECK(check_block(0)==13 && write_count==0); prepare_block(1); fill_frame(1); CHECK(WriteDataByCan(&packet)==12); CHECK(check_block(0)==12 && write_count==0); prepare_block(1); fill_frame(0); SET_DLC(packet,7); CHECK(WriteDataByCan(&packet)==12 && write_count==0); for (int mode=2;mode<=4;mode++) { prepare_block(1); fill_frame(0); CHECK(WriteDataByCan(&packet)==0); sum=block_sum(); fail_mode=mode; CHECK(check_block(sum)==14); CHECK(!DownloadSessionActive && fwinfo.AppUpdate==0); commit_rejected(); } /* Multi-block image with a 3-byte tail: padding belongs to the block, not image sum. */ prepare_block(256); fwinfo.AppFileSize=2051; for (int i=0;i<256;i++) { fill_frame(i); CHECK(WriteDataByCan(&packet)==0); } CHECK(check_block(block_sum())==0); be32(packet.Data,2051); be32(packet.Data+4,image_sum()); CHECK(FwImageChkSumByCan(&packet)==9); /* Tail has not been committed. */ be32(packet.Data,wire_addr()+2048); be32(packet.Data+4,1); CHECK(WriteAddressSetByCan(&packet)==0); PACKET_ID(packet)=0; memset(packet.Data,0,8); packet.Data[0]=7; packet.Data[1]=8; packet.Data[2]=9; CHECK(WriteDataByCan(&packet)==0); be32(packet.Data,wire_addr()+2048); be32(packet.Data+4,24); CHECK(WriteDataChkSumByCan(&packet)==0); verify_image(); } int main(void) { uint32_t allocation_base = IMAGE_BASE < 0x08000000 ? 0x10000 : 0x08000000; CHECK(VirtualAlloc((void *)allocation_base, IMAGE_END-allocation_base, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE)==(void *)allocation_base); reset_test(); commit_rejected(); fwinfo.AppUpdate=3; commit_rejected(); /* Persisted state is not a live session. */ reset_test(); start_download(); commit_rejected(); reset_test(); receive_image(); commit_rejected(); be32(packet.Data,8); be32(packet.Data+4,image_sum()+1); CHECK(FwImageChkSumByCan(&packet)==2); commit_rejected(); be32(packet.Data,7); be32(packet.Data+4,8); CHECK(FwImageChkSumByCan(&packet)==1); commit_rejected(); reset_test(); receive_image(); verify_image(); { int erases=erase_count, writes=write_count; CHECK(SectorEraseProcessByCan(&packet)==9); CHECK(WriteAddressSetByCan(&packet)==9); CHECK(WriteDataByCan(&packet)==9); CHECK(WriteDataChkSumByCan(&packet)==9); CHECK(FwImageChkSumByCan(&packet)==9); CHECK(erase_count==erases && write_count==writes); CHECK(fwinfo.AppUpdate==3 && DownloadImageVerified); } start_download(); CHECK(!DownloadImageVerified && CanDLInfor.WriteLength==0); commit_rejected(); CHECK(WriteDataByCan(&packet)==9); /* Previous block address was cleared. */ reset_test(); receive_image(); verify_image(); memset(packet.Data,0,8); /* Zero length start invalidates verification. */ CHECK(FwUpdateStartByCan(&packet)==2); commit_rejected(); reset_test(); receive_image(); verify_image(); be32(packet.Data+4,0xFFFFFFFF); CHECK(FwUpdateStartByCan(&packet)==2); commit_rejected(); for (int mode=1; mode<=4; mode++) { reset_test(); receive_image(); verify_image(); fail_mode=mode; packet.Data[0]=3; CHECK(UpdateRestartByCan(&packet)==11); CHECK(last_response[7]==11 && !reset_count && fwinfo.AppUpdate==3); CHECK(DownloadImageVerified && WriteDataByCan(&packet)==9); fail_mode=0; packet.Data[0]=3; CHECK(UpdateRestartByCan(&packet)==0); CHECK(last_response[7]==0 && reset_count==1); CHECK(memcmp((void *)INFO_BASE,&fwinfo,sizeof(fwinfo))==0); CHECK(fwinfo.AppUpdate==9 && !DownloadSessionActive); } block_tests(); queue_tests(); puts("PASS: update guards, 256-frame RAM staging, missing/duplicate/short frames, flash failures, partial tail, queue overflow/wrap and bounded draining."); return 0; } """ QUEUE_STUBS = r""" #define __DMB() ((void)0) #define SUCCESS 1 #define ERROR 0 #define CAN_FIFO0 0 #define CAN_IT_FMP0 0 #define CanRxIrqFlg 1 #define CanTxPrimaryIrqFlg 2 #define CanBusErrorIrqFlg 3 #define CanArbiLostIrqFlg 4 #define Int005_IRQn 5 typedef struct { volatile uint16_t RxInPos,RxOutPos; volatile uint32_t RxOverflowCount; QueuePacket RxBuf[RX_BUFFER_SIZE]; uint32_t can; } QueueInfo; static QueueInfo Can; static QueuePacket hardware_packet, processed_packet; static uint32_t processed_count; int CAN_IrqFlgGet(int flag) { return flag==CanRxIrqFlg; } void CAN_IrqFlgClr(int flag) {} void CAN_ClearITPendingBit(uint32_t can,int flag) {} void NVIC_ClearPendingIRQ(int flag) {} #if IS_V1 void CAN_Receive(uint32_t can,int fifo,QueuePacket *p) { *p=hardware_packet; } #else void CAN_Receive(QueuePacket *p) { *p=hardware_packet; } #endif uint8_t CAN_GetPacket(QueuePacket *p); uint8_t GetCanPacket(void) { return CAN_GetPacket(&processed_packet); } void CanRxProcess(void) { processed_count++; } """ QUEUE_CASES = r""" static void queue_push(uint32_t number) { memcpy(hardware_packet.Data,&number,4); RECEIVE_IRQ(); } static void queue_tests(void) { QueuePacket p; memset(&Can,0,sizeof(Can)); processed_count=0; for (uint32_t i=0;inul\n@cl /nologo /TC /std:c11 /Od /W3 "{test_c}" /Fe:"{tmp / (name + ".exe")}" /Fo:"{tmp / (name + ".obj")}"\n', encoding="ascii") build = subprocess.run(["cmd", "/d", "/c", str(batch)], cwd=tmp, capture_output=True) if build.returncode: raise RuntimeError(build.stdout.decode(errors="replace") + build.stderr.decode(errors="replace")) result = subprocess.run([str(tmp / (name + ".exe"))], cwd=tmp, capture_output=True) if result.returncode: raise RuntimeError(name + ": " + result.stdout.decode(errors="replace") + result.stderr.decode(errors="replace")) print(name + ": " + result.stdout.decode(errors="replace").splitlines()[-1]) if __name__ == "__main__": main()