///=====================================================================
//文件名称：ueupdate.c文件
//制作单位：SD-Arm(sumcu.suda.edu.cn)
//更新记录：20181201-20200627
//移植规则：【固定】
///=====================================================================

#include "ueupdate.h"

uint8_t uStartNo;    //用户程序起始扇区号
uint8_t uEndNo;      //用户程序结束扇区号
uint16_t uSectSize;  //MCU扇区大小
uint8_t uSectNo;     //需更新的代码所占扇区数
uint16_t uFrameNum;  //总帧数
uint16_t uCurFrame;  //当前接收帧号
uint8_t uStyle;      //更新方式，0：整体替换更新；1：整体保留更新；2：增量保留更新
uint8_t uCheck[32];  //帧接收状态数组，每一位表示一帧接收状态，一共可表示32*8（256）个帧状态
uint8_t uCheckFlag;  //更新检查标志，必须检查过后才能进行更新操作
uint8_t uUpdateFlag; //更新接收标志，0：更新接收操作成功；其他：更新接收操作失败

//========================内部函数声明处=====================================
uint8_t ueupdate_replaceRecv(uint8_t *buf,uint16_t len);
uint8_t ueupdate_reserveRecv(uint8_t *buf,uint16_t len);
void flagSet(uint8_t *flagBuf,uint16_t flag);
uint8_t flagGet(uint8_t *flagBuf,uint16_t flag);
uint8_t ueupdate_update();
//======================================================================

//======================================================================
//函数名称：ueupdate_init
//函数参数：startSector：用户程序起始扇区号；endSector：用户程序结束扇区号+1；
//       sectSize：芯片扇区大小
//函数返回：无
//功能概要：实现终端程序更新功能初始化，并设置用户程序起始和结束扇区号
//       flash模块的初始化和帧标志初始化
//======================================================================
void ueupdate_init(uint8_t startSector,uint8_t endSector,uint16_t sectSize)
{
	//（1）变量声明
	uint8_t i;

	//（2）初始化文件级全局变量
	uStartNo = startSector;  //设置用户程序起始扇区
	uEndNo = endSector;      //设置用户程序结束扇区
	uSectSize = sectSize;    //设置芯片扇区大小
	uFrameNum = 0;           //初始化总帧数为0
	uCurFrame = 0;           //初始化当前接收帧号为0
	for(i = 0;i < sizeof(uCheck);i++)
		uCheck[i] = 0;       //接收帧标志初始化为0
	uCheckFlag = 0;          //初始化更新检查标志为0

	//（3）初始化外设模块
	flash_init();
}

//======================================================================
//函数名称：ueupdate_reinit
//函数参数：buf:接收到的更新数据;len:接收到的更新数据长度
//函数返回：
//       1：开始整体替换更新；
//       4：丢帧；
//功能概要：根据开始更新数据帧，重新初始化Ueupdate构件。该函数仅用于整体替换更新情况，因为从User程序
//       跳回BIOS程序，ueupdate构件的初始状态未能保留，需要重新初始化更新状态变量。
//======================================================================
uint8_t ueupdate_reinit(uint8_t *buf,uint16_t len)
{
	//（1）变量声明
	uint8_t i;
	uint8_t rv;
	uint32_t codeSize;

	rv = 0;
	//（2）解析更新数据
	//（2.1）接收到更新开始帧数据
	if(buf[0] == 0)
	{
		if(len == sizeof(struct startData))  //数据长度满足更新开始帧
		{
			//重新初始化接收帧标志为0
			for(i = 0;i < sizeof(uCheck);i++)
				uCheck[i] = 0;
			//获取更新开始帧数据
			uStyle = ((struct startData *)buf)->style;
			uFrameNum = ((struct startData *)buf)->frameNum;
			uCurFrame = ((struct startData *)buf)->curFrame;
			codeSize = ((struct startData *)buf)->codeSize;
			//计算更新代码所占的扇区数
			if(codeSize%uSectSize == 0)
				uSectNo = codeSize/uSectSize;
			else
				uSectNo = codeSize/uSectSize+1;
			//根据更新方式的不同进行不同操作
			if(uStyle == 0)
			{
				rv = 1;  //开始整体替换更新
			}
			//修改当前帧接收状态
			flagSet(uCheck,uCurFrame);
		}
	}
	else
	{
		rv = 4;  //反馈帧数据丢失状态
	}
	uUpdateFlag = rv;  //用于提示更新返回
	return rv;
}

//======================================================================
//函数名称：ueupdate_reframe
//函数参数：buf:接收到的更新数据;len:接收到的更新数据长度
//函数返回：0：接收并执行更新操作成功；
//       1：开始整体替换更新；
//       2：开始保留更新（保留整体更新、保留增量更新）；
//       3：更新中断，通知上位机重新开始；
//       4：丢帧；
//       5：接收数据帧标识错误；
//       6：替换更新操作成功；
//       7：保留更新操作成功；
//功能概要：接收并解析更新数据，并进行相应的操作，包括更新开始、更新复制、更新插入、更新检查、更新命令
//======================================================================
uint8_t ueupdate_reframe(uint8_t *buf,uint16_t len)
{
	//（1）变量声明
	uint8_t i;
	uint8_t rv;
	uint8_t flag;
	uint32_t codeSize;

	rv = 0;
	//（2）解析更新数据
	//（2.1）接收到更新开始帧数据
	if(buf[0] == 0)
	{
		if(len == sizeof(struct startData))  //数据长度满足更新开始帧
		{
			//重新初始化接收帧标志为0
			for(i = 0;i < sizeof(uCheck);i++)
				uCheck[i] = 0;
			//获取更新开始帧数据
			uStyle = ((struct startData *)buf)->style;
			uFrameNum = ((struct startData *)buf)->frameNum;
			uCurFrame = ((struct startData *)buf)->curFrame;
			codeSize = ((struct startData *)buf)->codeSize;
			//计算更新代码所占的扇区数
			if(codeSize%uSectSize == 0)
				uSectNo = codeSize/uSectSize;
			else
				uSectNo = codeSize/uSectSize+1;
			//根据更新方式的不同进行不同操作
			if(uStyle == 0)
			{
				//整体替换更新，直接将User程序擦除后写入
				for(i = uStartNo;i < (uStartNo+uSectNo);i++)
					flash_erase(i);
				rv = 1;  //开始整体替换更新
			}
			else
			{
				//保留更新，擦除User程序之后的扇区，更新成功后整体覆盖
				for(i = uEndNo+1;i < (uEndNo+uSectNo+1);i++)
					flash_erase(i);
				rv = 2;  //开始保留更新（保留整体更新、保留增量更新）
			}
			//修改当前帧接收状态
			flagSet(uCheck,uCurFrame);
		}
	}
	//（2.2）接收到其他更新数据（更新复制帧、更新插入帧 、更新检查帧、更新命令帧）
	else
	{
		//根据更新方式的不同进行不同操作
		if(uStyle == 0)  //整体替换更新
		{
			flag = ueupdate_replaceRecv(buf,len);
			//构造返回值，供应用层判断
			switch(flag)
			{
			case 0: rv = 0;break;  //当前更新操作成功
			case 1: rv = 3;break;  //更新中断，需通知上位机重新开始
			case 2: rv = 4;break;  //丢帧
			case 3: rv = 6;break;  //替换更新操作成功
			case 4: rv = 5;break;  //接收数据帧标识错误
			}
		}
		else  //保留更新
		{
			flag = ueupdate_reserveRecv(buf,len);
			//构造返回值，供应用层判断
			switch(flag)
			{
			case 0: rv = 0;break;  //当前更新操作成功
			case 1: rv = 3;break;  //更新中断，需通知上位机重新开始
			case 2: rv = 4;break;  //丢帧
			case 3: rv = 7;break;  //保留更新操作成功
			case 4: rv = 5;break;  //接收数据帧标识错误
			}
		}
	}

	uUpdateFlag = rv;  //用于提示更新返回
	return rv;
}

//======================================================================
//函数名称：ueupdate_replaceRecv
//函数参数：buf:接收到的更新数据;len:接收到的更新数据长度
//函数返回：0：接收并执行替换更新操作成功；
//       1：更新中断，需通知上位机重新开始；
//       2：丢帧；
//       3：代码替换更新完毕；
//       4：帧标识错误；
//功能概要：接收更新数据并进行整体替换更新操作，包括更新插入、更新检查、更新命令
//======================================================================
uint8_t ueupdate_replaceRecv(uint8_t *buf,uint16_t len)
{
	//（1）变量声明
	uint8_t i;
	uint8_t rv;
	uint8_t segNum;
	uint32_t insertTo;
	uint16_t insertLen;
	uint16_t insertCurLen;
	uint8_t *iData;

	rv = 0;
	//（2）根据帧标识来进行更新操作
	switch(buf[0])
	{
	case 0:  //更新开始帧
		//本函数无需进行更新开始帧操作
		break;
	case 1:  //更新复制帧
		//替换更新无更新复制帧
		break;
	case 2:  //更新插入帧
		if(len == sizeof(struct insertData))  //数据长度满足更新插入帧
		{
			//获取更新插入帧数据
			uCurFrame = ((struct insertData *)buf)->curFrame;
			segNum = ((struct insertData *)buf)->segNum;
			//更新中断，通知上位机重新开始
			if(uFrameNum == 0 || uCurFrame >= uFrameNum)
			{
				rv = 1;
				goto ueupdate_replaceRecv_EXIT;
			}
			//进行代码插入操作
			insertCurLen = 0;
			for(i = 0;i < segNum;i++)
			{
				insertTo = ((struct insertData *)buf)->addInfo[i];  //目标地址
				insertLen = ((struct insertData *)buf)->lenInfo[i];   //复制数据长度
				iData = ((struct insertData *)buf)->data + insertCurLen;        //插入数据
				//插入数据
				flash_write_physical(insertTo,insertLen,iData);
				insertCurLen += insertLen;
			}
			//修改当前帧接收状态
			flagSet(uCheck,uCurFrame);
		}
		break;
	case 3:  //更新检查帧
		if(len == sizeof(struct checkData))  //数据长度满足更新检查帧
		{
			//获取更新检查帧数据
			uCurFrame = ((struct checkData *)buf)->curFrame;
			//更新中断，通知上位机重新开始
			if(uFrameNum == 0 || uCurFrame >= uFrameNum)
			{
				rv = 1;
				goto ueupdate_replaceRecv_EXIT;
			}
			//修改当前帧接收状态
			flagSet(uCheck,uCurFrame);
			//检查是否丢帧
			for(i = 0;i < uFrameNum-1;i++)
			{
				if(flagGet(uCheck,i) == 0)  //出现丢帧状况
				{
					rv = 2;
					goto ueupdate_replaceRecv_EXIT;
				}
			}
			//检查过后设置更新检查标志
			uCheckFlag = 1;
		}
		break;
	case 4:  //更新命令帧
		if(len == sizeof(struct updateData))  //数据长度满足更新命令帧
		{
			if(uCheckFlag == 1)  //检查过，可以更新
			{
				rv = 3;
				goto ueupdate_replaceRecv_EXIT;
			}
		}
		break;
	default:  //其他错误帧标识
		rv = 4;
		goto ueupdate_replaceRecv_EXIT;
	}

	ueupdate_replaceRecv_EXIT:
	return rv ;
}
//======================================================================
//函数名称：ueupdate_reserveRecv
//函数参数：buf:接收到的更新数据;len:接收到的更新数据长度
//函数返回：0：接收并执行保留更新操作成功；
//       1：更新中断，需通知上位机重新开始；
//       2：丢帧；
//       3：代码保留更新完毕；
//       4：帧标识错误；
//功能概要：接收更新数据并进行保留更新操作，包括更新复制、更新插入、更新检查、更新命令
//======================================================================
uint8_t ueupdate_reserveRecv(uint8_t *buf,uint16_t len)
{
	//（1）变量声明
	uint8_t i;
	uint8_t rv;
	uint8_t segNum;
	uint32_t copyFrom;
	uint32_t copyTo;
	uint16_t copyLen;
	uint8_t cData[512];
	uint32_t insertTo;
	uint16_t insertLen;
	uint16_t insertCurLen;
	uint8_t *iData;

	rv = 0;
	//（2）根据帧标识来进行更新操作
	switch(buf[0])
	{
	case 0:  //更新开始帧
		//本函数无需进行更新开始帧操作
		break;
	case 1:  //更新复制帧
		if(len == sizeof(struct copyData))  //数据长度满足更新复制帧
		{
			//获取更新复制帧数据
			uCurFrame = ((struct copyData *)buf)->curFrame;
			segNum = ((struct copyData *)buf)->segNum;
			//更新中断，通知上位机重新开始
			if(uFrameNum == 0 || uCurFrame >= uFrameNum)
			{
				rv = 1;
				goto ueupdate_reserveRecv_EXIT;
			}

			//进行代码复制操作
			for(i = 0;i < segNum;i++)
			{
				copyFrom = ((struct copyData *)buf)->addInfo[i*2];  //源地址
				copyTo = ((struct copyData *)buf)->addInfo[i*2+1];  //目标地址
				copyLen = ((struct copyData *)buf)->lenInfo[i];     //复制数据长度
				//保留更新，目标地址需进行一定的地址偏移
				copyTo += (uEndNo-uStartNo+1)*uSectSize;
				//分块复制数据，每块最多512字节
				while(copyLen)
				{
					if(copyLen > 512)  //当前复制长度大于512，仅复制512字节
					{
						flash_read_physical(cData,copyFrom,512);
						flash_write_physical(copyTo,512,cData);
						copyFrom += 512;
						copyTo += 512;
						copyLen -= 512;
					}
					else  //当前复制长度小于512，全部复制
					{
						flash_read_physical(cData,copyFrom,copyLen);
						flash_write_physical(copyTo,copyLen,cData);
						copyLen = 0;
					}
				}
			}
			//修改当前帧接收状态
			flagSet(uCheck,uCurFrame);
		}
		break;
	case 2:  //更新插入帧
		if(len == sizeof(struct insertData))  //数据长度满足更新插入帧
		{
			//获取更新插入帧数据
			uCurFrame = ((struct insertData *)buf)->curFrame;
			segNum = ((struct insertData *)buf)->segNum;
			//更新中断，通知上位机重新开始
			if(uFrameNum == 0 || uCurFrame >= uFrameNum)
			{
				rv = 1;
				goto ueupdate_reserveRecv_EXIT;
			}
			//进行代码插入操作
			insertCurLen = 0;
			for(i = 0;i < segNum;i++)
			{
				insertTo = ((struct insertData *)buf)->addInfo[i];  //目标地址
				insertLen = ((struct insertData *)buf)->lenInfo[i];   //复制数据长度
				iData = ((struct insertData *)buf)->data + insertCurLen;        //插入数据
				//保留更新，目标地址需进行一定的地址偏移
				insertTo += (uEndNo-uStartNo+1)*uSectSize;
				//插入数据
				flash_write_physical(insertTo,insertLen,iData);
				insertCurLen += insertLen;
			}
			//修改当前帧接收状态
			flagSet(uCheck,uCurFrame);
		}
		break;
	case 3:  //更新检查帧
		if(len == sizeof(struct checkData))  //数据长度满足更新检查帧
		{
			//获取更新检查帧数据
			uCurFrame = ((struct checkData *)buf)->curFrame;
			//更新中断，通知上位机重新开始
			if(uFrameNum == 0 || uCurFrame >= uFrameNum)
			{
				rv = 1;
				goto ueupdate_reserveRecv_EXIT;
			}
			//修改当前帧接收状态
			flagSet(uCheck,uCurFrame);
			//检查是否丢帧
			for(i = 0;i < uFrameNum-1;i++)
			{
				if(flagGet(uCheck,i) == 0)  //出现丢帧状况
				{
					rv = 2;
					goto ueupdate_reserveRecv_EXIT;
				}
			}
			//检查过后设置更新检查标志
			uCheckFlag = 1;
		}
		break;
	case 4:  //更新命令帧
		if(len == sizeof(struct updateData))  //数据长度满足更新命令帧
		{
			if(uCheckFlag == 1)  //检查过，可以更新
			{
				rv = 3;
				ueupdate_update();
				goto ueupdate_reserveRecv_EXIT;
			}
		}
		break;
	default:  //其他错误帧标识
		rv = 4;
		goto ueupdate_reserveRecv_EXIT;
	}

	ueupdate_reserveRecv_EXIT:
	return rv;
}


//======================================================================
//函数名称：ueupdate_backGet
//函数参数：buf:更新返回数据，len:更新返回数据长度
//函数返回：
//功能概要：解析接收到的数据，对其进行回应，提示上位机更新操作执行状态
//======================================================================
uint8_t ueupdate_backGet(uint8_t *buf,uint16_t *len)
{
	//（1）变量声明
	uint8_t rv;
	struct backData bdata;

	rv = 0;
	//（2）更新返回帧赋值
	bdata.type = 5;
	bdata.curFrame = uCurFrame;
	bdata.flag = uUpdateFlag;

	//（3）返回数据
	*len = sizeof(struct backData);
	memcpy(buf,(uint8_t *)&bdata,*len);

	return rv;
}

//======================================================================
//函数名称：ueupdate_checkGet
//函数参数：buf:更新检查数据，len:更新检查数据长度
//函数返回：
//功能概要：返回更新检查帧数据，提示上位机更新过程中的丢帧状态
//======================================================================
uint8_t ueupdate_checkGet(uint8_t *buf,uint16_t *len)
{
	//（1）变量声明
	uint8_t i;
	uint8_t rv;
	uint8_t loseNum;
	struct checkData cdata;

	rv = 0;
	loseNum = 0;
	//（2）更新检查帧赋值
	cdata.type = 3;
	cdata.curFrame = uCurFrame;
	//遍历帧接收状态数组，查找丢帧数据
	for(i = 0;i < uFrameNum-1;i++)
	{
		if(flagGet(uCheck,i) == 0)  //出现丢帧状况
		{
			cdata.loseFrame[loseNum++] = i;
		}
	}
	cdata.loseNum = loseNum;

	//（3）返回数据
	*len = sizeof(struct checkData);
	memcpy(buf,(uint8_t *)&cdata,*len);

	return rv;
}

//======================================================================
//函数名称：ueupdate_dataJudge
//函数参数：data:接收到的上位机数据，len:待检查的数据长度
//函数返回：0：数据满足更新帧结构；其他：数据不满足更新帧结构
//功能概要：检查传入数据是否满足更新上位机传输过来的数据
//======================================================================
uint8_t ueupdate_dataJudge(uint8_t *data,uint16_t len)
{
	//（1）变量声明
	uint8_t rv;

	rv = 0;  // 默认成功
	//（2）判断数据是否满足更新开始帧
	if(len == sizeof(struct startData) && data[0] == 0)  //是更新开始帧
		goto ueupdate_dataJudge_EXIT;
	//（3）判断数据是否满足更新复制帧
	else if(len == sizeof(struct copyData) && data[0] == 1)  //是更新复制帧
		goto ueupdate_dataJudge_EXIT;
	//（4）判断数据是否满足更新插入帧
	else if(len == sizeof(struct insertData) && data[0] == 2)  //是更新插入帧
		goto ueupdate_dataJudge_EXIT;
	//（5）判断数据是否满足更新检查帧
	else if(len == sizeof(struct checkData) && data[0] == 3)  //是更新检查帧
		goto ueupdate_dataJudge_EXIT;
	//（6）判断数据是否满足更新命令帧
	else if(len == sizeof(struct updateData) && data[0] == 4)  //是更新命令帧
		goto ueupdate_dataJudge_EXIT;
	else
		rv= 1;  //其他情况皆为不满足更新帧数据

ueupdate_dataJudge_EXIT:
	return rv;
}


//内部函数
//======================================================================
//函数名称：ueupdate_update
//函数参数：无
//函数返回：无
//功能概要：将更新代码复制到User程序指定位置，待系统复位
//======================================================================
uint8_t ueupdate_update()
{
	//（1）变量声明
	uint8_t i;
	uint32_t j;
	uint32_t userAddress,updateAddress;
	uint8_t data[512];

	//（2）获取User程序和更新程序首地址
	userAddress = uStartNo*uSectSize;
	updateAddress = (uEndNo+1)*uSectSize;

	//（3）删除User程序
	for(i = uStartNo;i <= uEndNo;i++)
		flash_erase(i);

	//（4）复制flash数据
	for(j = 0;j < uSectNo*(uSectSize/512);j++)  //分块复制，每块512字节
	{
		flash_read_physical(data,updateAddress+j*512,512);
		flash_write_physical(userAddress+j*512,512,data);
	}

//	//（5）系统复位
//	NVIC_SystemReset();

	return 0;
}

//======================================================================
//函数名称：flagSet
//函数参数：flagBuf:标志数据;flag:标志位数
//函数返回：无
//功能概要：按照位的方式设置标志，如flagBuf[0]中的8位分别代表0~7的状态，
//       flagBuf[1]中的8位分别代表8~15的状态，...
//======================================================================
void flagSet(uint8_t *flagBuf,uint16_t flag)
{
	uint16_t i;
	uint8_t j;

	i = flag/8;  //标志所处字节数
	j = flag%8;  //字节内位置

	flagBuf[i] |= (1 << j);  //设置标志位
}

//======================================================================
//函数名称：flagGet
//函数参数：flagBuf:标志数据;flag:标志位数
//函数返回：第flag位的标志状态
//功能概要：获取flagBuf地址开始的第flag位状态
//======================================================================
uint8_t flagGet(uint8_t *flagBuf,uint16_t flag)
{
	uint16_t i;
	uint8_t j;

	i = flag/8;  //标志所处字节数
	j = flag%8;  //字节内位置

	return flagBuf[i] & (1 << j);
}

//=========================================================================
//函数名称：updateJudge
//参数说明：无
//函数返回：0：无需更新；1：串口更新；2：远程更新；3：无User程序
//功能概要：进行更新状态判断，若需更新则进一步明确是串口更新还是远程更新
//=========================================================================
uint8_t updateJudge()
{
    
	//（1）变量声明
	uint8_t uflag;     //更新接收标志
	uint32_t msp;      //保存在flash中的主栈指针
	uint8_t rv;        //返回值
	uint8_t buf[100];  //100字节数据用于存放返回数据
	uint16_t bufLen;   //返回数据长度

	rv = 0;  //默认无需更新
	if (dynamic_cmd(gcRecvBuf,&gcRecvLen))  return 1;
	//（2）是否需要进行更新操作
	if(strcmp((char*)gcUpdateFlag,"uart") == 0)  //需进行串口更新操作
	{
		gcUpdateFlag[0] = 0; //清uart标志
		//整体更新跳回BIOS后，ueupdate需重新初始化
		ueupdate_init(GEC_USER_SECTOR_START,GEC_USER_SECTOR_END,MCU_SECTORSIZE);
		uflag = ueupdate_reinit((uint8_t*)gcRecvBuf,gcRecvLen);
		if(uflag == 1) //收到更新开始后，返回信息提示上位机可发送其他数据
		{
			ueupdate_backGet(buf,&bufLen);
			for (int ys=0;ys<4000;ys++) {__asm ( "nop");}   //空延时，最小2000，取4000
			emuart_sendN(bufLen,buf);  //返回信息
		}
		gcRecvLen = 0;
		rv = 1;  //需进行串口更新
	}
	else if(strcmp((char*)gcUpdateFlag,"RESET>=6") == 0)  //60秒内是否按“复位按钮”超过5次
	{
		rv = 3;  //当作无User程序
	}
	else if(strcmp((char*)gcUpdateFlag,"BIOSUPDA") == 0)  //BIOS更新
	{
		rv = 3;  //当作无User程序
	}
	//-------------------
	else      //无需更新User程序
	{
		//读取User中断向量表首地址
		flash_read_logic((uint8_t*)&msp,GEC_USER_SECTOR_START,0,4);
		//判断User程序是否存在（若msp不等于栈顶，则没有用户程序）
		if(msp == MCU_STACKTOP)  //存在User程序
		{
			rv = 0;  //无需更新且存在User程序
		}
		else         //不存在User程序
		{
			gcRecvLen = 0;
			rv = 3;  //无User程序
		}
	}
	return rv;
}

