Principle Analysis
First, let's talk about the program image. A complete program image can be divided into two parts: "interrupt vector table" and "logic program" (let's name it this way for the convenience of explanation).
The "interrupt vector table" occupies the first 512 bytes, which is one page of Flash, and the rest is the "logic program".
As pointed out in the previous article , CH579 divides the flash into two areas, each area occupies 125K Flash (0x1F400), which are used to store ImageA and ImageB respectively.
Now we will find a problem, because the startup address selected by the chip is fixed (the starting address of the Flash). If the Flash is arranged as above, the chip will always only run ImageA and not ImageB.
Therefore, when upgrading ImageB, the program will move the "interrupt vector table" of ImageB to the first 512 bytes of Flash, as shown in the following figure.
In this way, ImageB can run. However, there is a conflict with what I said in the previous article: "Even if the upgrade fails, the old image is still available." That is, when upgrading ImageB, the "interrupt vector table" of ImageA is overwritten and permanently lost. If it is found that ImageB has defects and cannot run at this time, the device will be "bricked." I think the official routine is only for demonstration, so it does not provide a complete solution.
Source code analysis
The device creates an OTA SERVICE, under which there is a CHARACTER with UUID 0xFEE1, which is the Bluetooth Profile used for OTA communication. We do not have the source code of the upgraded APP here, but by analyzing the code of CH579, we can infer the communication process between APP and CH579:
First, the APP writes relevant commands (erase, program, get information, etc.) to the CHARACTER of 0xFEE1. Then, after CH579 parses the command, it writes the return result to the data cache of the CHARACTER of 0xFEE1. Then, the APP reads the data of 0xFEE1 through polling to obtain the return result, thus constituting a round-trip communication.
Let's take a look at the CH579 communication parsing code: Rec_OTA_IAP_DataDeal.
The entire OTA process is mainly divided into three steps: erasing, programming and verification.
First, erase:
case CMD_IAP_ERASE:
{
OpAdd = (UINT32)(iap_rec_data.erase.addr[0]);
OpAdd |= ((UINT32)(iap_rec_data.erase.addr[1]) << 8);
OpAdd = OpAdd * 4;
EraseBlockNum = (UINT32)(iap_rec_data.erase.block_num[0]);
EraseBlockNum |= ((UINT32)(iap_rec_data.erase.block_num[1]) << 8);
EraseAdd = OpAdd;
EraseBlockCnt = 0;
/* 检验就放在擦除里清0 */
VerifyStatus = 0;
PRINT("IAP_ERASE start:%08x num:%d\r\n",(int)OpAdd,(int)EraseBlockNum);
/* 当前是ImageB的话需要保留ImageA地址第一块 */
if( CurrImageFlag == IMAGE_B_FLAG )
{
EraseBlockNum -= 1;
EraseAdd += FLASH_BLOCK_SIZE;
}
/* 启动擦除 */
tmos_set_event( Peripheral_TaskID, OTA_FLASH_ERASE_EVT );
break;
}
This step mainly records the erase address (EraseAdd) and erase size (EraseBlockNum), and then starts the erase event (OTA_FLASH_ERASE_EVT).
Then comes programming:
case CMD_IAP_PROM:
{
UINT32 i;
UINT8 status;
OpParaDataLen = iap_rec_data.program.len;
OpAdd = (UINT32)(iap_rec_data.program.addr[0]);
OpAdd |= ((UINT32)(iap_rec_data.program.addr[1]) << 8);
OpAdd = OpAdd * 4;
PRINT("IAP_PROM: %08x len:%d \r\n",(int)OpAdd,(int)OpParaDataLen);
for(i=0; i<(OpParaDataLen/4); i++)
{
OpParaData[i] = (UINT32)(iap_rec_data.program.buf[0+4*i]);
OpParaData[i] |= ((UINT32)(iap_rec_data.program.buf[1+4*i]) << 8);
OpParaData[i] |= ((UINT32)(iap_rec_data.program.buf[2+4*i]) << 16);
OpParaData[i] |= ((UINT32)(iap_rec_data.program.buf[3+4*i]) << 24);
}
/* 当前是ImageA,直接编程 */
if(CurrImageFlag == IMAGE_A_FLAG)
{
status = FlashWriteBuf(OpAdd, OpParaData, (UINT16) OpParaDataLen);
}
/* 当前是ImageB,除了第一块直接编程 */
else
{
/* 第一块内容 */
if((OpAdd + OpParaDataLen) <= FLASH_BLOCK_SIZE)
{
for(i = 0; i < OpParaDataLen; i++)
{
vectors_block_buf[OpAdd + i] = iap_rec_data.program.buf[i];
}
status = SUCCESS;
OTA_IAP_SendCMDDealSta(status);
}
/* 其他块 */
else
{
status = FlashWriteBuf(OpAdd, (PUINT32)OpParaData, (UINT16) OpParaDataLen);
OTA_IAP_SendCMDDealSta(status);
}
}
break;
}
This process is to write the program sent by APP directly into Flash. It is worth noting that if the current execution is ImageB, in other words, the newly upgraded program is ImageA, then we need to temporarily save its first block of content (512 bytes of interrupt vector table) (vectors_block_buf) instead of directly writing the first 512 bytes of Flash, otherwise the current program will run away directly if the interrupt is not turned off.
Finally, the verification:
case CMD_IAP_VERIFY:
{
UINT32 i;
UINT8 *p_flash;
UINT8 status = 0;
OpParaDataLen = iap_rec_data.verify.len;
OpAdd = (UINT32)(iap_rec_data.verify.addr[0]);
OpAdd |= ((UINT32)(iap_rec_data.verify.addr[1]) << 8);
OpAdd = OpAdd * 4;
PRINT("IAP_VERIFY: %08x len:%d \r\n",(int)OpAdd,(int)OpParaDataLen);
p_flash = (UINT8 *)OpAdd;
/* 当前是ImageA,直接读取ImageB校验 */
if(CurrImageFlag == IMAGE_A_FLAG)
{
for(i=0; i<OpParaDataLen; i++)
{
if(p_flash[i] != iap_rec_data.verify.buf[i]) break;
}
if(i == OpParaDataLen) status = SUCCESS;
else status = 0xff;
VerifyStatus |= status;
OTA_IAP_SendCMDDealSta(VerifyStatus);
}
/* 当前是ImageB,第一块和buf里对比,其他直接和ImageA校验 */
else
{
/* 第一块和RAM保存的参数对比 */
if( (OpAdd + OpParaDataLen) <= FLASH_BLOCK_SIZE )
{
for(i=0; i<OpParaDataLen; i++)
{
if(vectors_block_buf[OpAdd+i] != iap_rec_data.verify.buf[i])break;
}
if(i == OpParaDataLen) status = SUCCESS;
else status = 0xff;
}
/* 其他和Flash里对比 */
else
{
for(i=0; i<OpParaDataLen; i++)
{
if(p_flash[i] != iap_rec_data.verify.buf[i]) break;
}
if(i == OpParaDataLen) status = SUCCESS;
else status = 0xff;
}
VerifyStatus |= status;
OTA_IAP_SendCMDDealSta(VerifyStatus);
}
break;
}
That’s pretty much the general analysis.
I have said so much, but I just want to clarify.