嵌入式系统双备份设计实现

环境:Hi3516eV100  + liteos + u-boot-2010.06

目标:

  1. 实现系统双备份机制,防止升级过程中断电而导致系统分区损坏,无法启动的问题(由低版本的备份分区启动)
  2. kernel 无法启动时,自动选择低版本的镜像分区启动

分区划分:

nor flash:共16M

|------1M------------|--------7M------------------|----------7M-----------------|----64K----|-----960K-----------|

|-----uboot(1M)---|--[0]Liteos+app(7M)---|---[1]Liteos+app(7M)---|--config---|------jffs0------------|

0x0              0x100000                   0x800000                     0xF00000   0xF10000      0x1000000

注意: config 占用 64K大小的空间

config分区数据结构:

//kernel + app的镜像文件描述
typedef struct _hle_image_info_t
{
    int 	image_version;  	//image镜像的版本描述(软件版本,后续再做调整)
    int 	damage_flag;		//损坏标记(FLAG_BAD 或者 FLAG_OK)
    ulong 	start_address;		//内核的加载地址
}hle_image_info_t;

//config 分区结构
typedef struct _config_info_t
{
	int             magic_flag;	//魔数,匹配上才能执行操作,否则使用默认配置
 hle_image_info_t 	image_info[2];  //两个分区镜像的描述信息 
}config_info_t;

总体思路:

在uboot起来后,最后执行 bootcmd 里边的命令,原本的参数如下:

hisilicon # printenv
bootargs=mem=96M console=ttyAMA0,115200
bootdelay=1
baudrate=115200
ethaddr=00:00:23:34:45:66
ipaddr=192.168.1.10
serverip=192.168.1.2
netmask=255.255.255.0
bootfile="uImage"
filesize=11582C
bootcmd=sf probe 0;sf read 0x80000000 0x100000 0x700000; go 0x80000000
stdin=serial
stdout=serial
stderr=serial
verify=n
ver=U-Boot 2010.06 (Jun 28 2018 - 14:05:15)

Environment size: 356/262140 bytes
hisilicon # 

我们只需要将 bootcmd 执行的操作改成自己注册的命令就行,每个命令后边都有对应的响应函数,我们建立一个自己的命令函数,实现双系统分区的选择性启动逻辑即可。

 

1.实现双分区选择启动的逻辑:

代码:

u-boot-2010.06/common/ 文件加下新建我们的命令函数文件:cmd_double_system_bootm.c

(拷贝一个"cmd_"开头的文件更名为我们自己的文件,在这个基础上边修改即可。)

 


/*
 * (C) Copyright 2000-2003
 * Wolfgang Denk, DENX Software Engineering, [email protected].
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

/*
 * Misc boot support
 */
#include <common.h>
#include <command.h>
#include <net.h>
#include <malloc.h>



extern int do_spi_flash_probe(int argc, char *argv[]);
extern int do_spi_flash_read_write(int argc, char *argv[]);
extern int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]);

/*---#分区双备份,选择性启动 -----------------------------------------------------------*/
/*
分区划分:
nor flash:共16M
	|------1M-----|--------7M---------|----------7M-------|----64K---|-----960K-----------|
	|-----uboot---|---[0]Liteos+app---|---[1]Liteos+app---|--config--|------jffs0---------|
	0x0	   0x100000             0x800000	   0xF00000    0xF10000		0x1000000

*/
// 0: kernel + app 分区
#define		IMAGE0_REGION_START		0x100000
#define		IMAGE0_REGION_SIZE		0x700000	//7M
// 1: kernel + app 分区
#define		IMAGE1_REGION_START		0x800000
#define		IMAGE1_REGION_SIZE		0x700000	//7M
// config 分区
#define 	CONFIG_REGION_START		0xF00000
#define		CONFIG_REGION_SIZE		0x10000		//64K
// jffs2 分区
#define 	CONFIG_JFFS2_START		0xF10000
#define 	CONFIG_JFFS2_SIZE		0xF0000		//960K


#define 	HLE_MAGIC 	0xB1A75   	//HLE ASCII(76 72 69-->767269-->0xB1A75)
#define 	FLAG_BAD  	0x97048c 	//损坏标记(“bad”的ASCII码:9897100-->0x97048c)
#define 	FLAG_OK		0x1b203		//正常标记(“ok”的ASCII码:111107--->0x1b203)

//kernel + app的镜像文件描述
typedef struct _hle_image_info_t
{
    int 	image_version;  	//image镜像的版本描述(软件版本,后续再做调整)
    int 	damage_flag;		//损坏标记(FLAG_BAD 或者 FLAG_OK)
    ulong 	start_address;		//内核的加载地址
}hle_image_info_t;

//config 分区结构
typedef struct _config_info_t
{
	int 				magic_flag;    //魔数,匹配上才能执行操作,否则使用默认配置
   	hle_image_info_t 	image_info[2]; //两个分区镜像的描述信息 
}config_info_t;

void printf_config_info(config_info_t* info)
{
	printf("---CONFIG REGION INFO:---------------------------------------\n");
	printf("info->magic_flag = %#x\n",info->magic_flag);
	printf("info->image_info[0].image_version = %d\n",info->image_info[0].image_version);
	printf("info->image_info[0].damage_flag   = %#x\n",info->image_info[0].damage_flag);
	printf("info->image_info[0].start_address = %#lx\n",info->image_info[0].start_address);
	
	printf("info->image_info[1].image_version = %d\n",info->image_info[1].image_version);
	printf("info->image_info[1].damage_flag   = %#x\n",info->image_info[1].damage_flag);
	printf("info->image_info[1].start_address = %#lx\n",info->image_info[1].start_address);
	printf("-------------------------------------------------------------\n");
}


extern int do_spi_flash_erase(int argc, char *argv[]);
/*******************************************************************************
*@ Description    :	初始化config分区(设备烧片后初次启动)
*@ Input          :
*@ Output         :
*@ Return         :成功:0  	失败:-1
*@ attention      :
*******************************************************************************/
int init_config_info(void)
{
	printf("---INIT CONFIG REGION INFO----------------------------------------\n");

	config_info_t * config_info = (config_info_t*)malloc(CONFIG_REGION_SIZE);
	if(NULL == config_info)
	{
		printf("\033[1;31m<ERR!!>init_config_info error!!\n\033[0m");
		return -1;
	}
	memset(config_info,0,CONFIG_REGION_SIZE);

	//出厂状态,默认只有 IMAGE0 分区有镜像文件
	config_info->magic_flag = HLE_MAGIC;
	config_info->image_info[0].image_version = 1;
	config_info->image_info[0].damage_flag = FLAG_OK;
	config_info->image_info[0].start_address = IMAGE0_REGION_START;

	config_info->image_info[1].image_version = 0;
	config_info->image_info[1].damage_flag = FLAG_BAD;
	config_info->image_info[1].start_address = IMAGE1_REGION_START;


	//擦除并写入norflash 的 config 分区
	char erase_off [32] = {0};
	char erase_size [32] = {0};
	sprintf(erase_off,"0x%x",CONFIG_REGION_START);
	sprintf(erase_size,"0x%x",CONFIG_REGION_SIZE);
	printf("## config: erase_off[] = %s\n",erase_off);
	printf("## config: erase_size[] = %s\n",erase_size);

	char* argv_erase[3] = {"erase",(char*)&erase_off,(char*)&erase_size};
	do_spi_flash_erase(3,argv_erase);
	
	//sf write 
	char write_from_addr[32] = {0};
	sprintf(write_from_addr,"0x%x",(int)config_info);
	char write_offset[32] = {0};
	sprintf(write_offset,"0x%x",CONFIG_REGION_START);
	char write_len[32] = {0};
	sprintf(write_len,"0x%x",CONFIG_REGION_SIZE);

	printf("write_from_addr[] = %s\n",write_from_addr);
	printf("write_offset[] = %s\n",write_offset);
	printf("write_len[] = %s\n",write_len);
	
	char* argv_write[4] = {"write",(char*)&write_from_addr,(char*)&write_offset,(char*)&write_len};
	do_spi_flash_read_write(4,argv_write);

	//进行一次 saveenv 操作,防止uboot 报警告:*** Warning - bad CRC, using default environment
	setenv("bootcmd","hle_bootm");
	saveenv();
	
	printf("------------------------------------------------------------------\n");

	return 0;
}


/*******************************************************************************
*@ Description    :选择启动镜像(kernel + app)分区
*@ Input          :
*@ Output         :
*@ Return         :镜像的起始地址
*@ attention      :
*******************************************************************************/
ulong select_boot_system_partition(void)
{
	//读取 norflash 配置分区的数据到内存
	config_info_t *config_info = ((config_info_t*)malloc(CONFIG_REGION_SIZE));
	if(NULL == config_info)
	{
		printf("\033[1;31m<ERR!!>select_boot_system_partition malloc failed!!\n\033[0m");
		return (ulong)IMAGE0_REGION_START;//默认返回分区0的image
	}
	memset(config_info,0,CONFIG_REGION_SIZE);

	char config_size[32] = {0};
	sprintf(config_size,"0x%x",CONFIG_REGION_SIZE);
	//printf("## config_size[] = %s\n",config_size);

	char mem_addr[32] = {0};
	sprintf(mem_addr,"0x%x",(int)config_info);
	//printf("## mem_addr[] = %s\n",mem_addr);

	char src_flash_addr[32] = {0};
	sprintf(src_flash_addr,"0x%x",CONFIG_REGION_START);
	//printf("## src_flash_addr[] = %s\n",src_flash_addr);
	
	//sf read 0x80000000 config_addr 0x100000
	char* argv[4] = {"read",(char*)&mem_addr,(char*)&src_flash_addr,(char*)&config_size};
	do_spi_flash_read_write(4,argv);

	
    if(config_info->magic_flag != HLE_MAGIC||
        config_info->image_info[0].start_address != IMAGE0_REGION_START||
        config_info->image_info[1].start_address != IMAGE1_REGION_START)
	{ 
		printf("config_info->magic_flag = %#x\n",config_info->magic_flag);
		printf("\033[1;31m<ERR!!>config_info.magic_flag error!! start to init CONFIG REGION...\n\033[0m");
		init_config_info();//出厂初次启动,初始化 config 分区
		return (ulong)IMAGE0_REGION_START;//默认返回分区0的image
	}

	/*打印两个分区的详细信息*/
	printf_config_info(config_info);
	
	//情况1:两个都为坏的,默认返回分区0的image	
	if(config_info->image_info[0].damage_flag != FLAG_OK &&
	   config_info->image_info[1].damage_flag != FLAG_OK)
	{
		printf("\033[1;31m<ERR!!>Both image[0] and image[1] is bad !!\n\033[0m");
		return (ulong)IMAGE0_REGION_START;
	}

	//情况2:两个都是好的,谁的版本高用哪个
	if(config_info->image_info[0].damage_flag == FLAG_OK &&
	   config_info->image_info[1].damage_flag == FLAG_OK) 
	{
		printf("## Both image[0] and image[1] is OK!!\n");
		if(config_info->image_info[0].image_version > 
		   config_info->image_info[1].image_version)
		{
			return config_info->image_info[0].start_address;
		}
		else
		{
			return config_info->image_info[1].start_address;
		}
	}
	   
	//情况3:只有一个是好的,只能用这个好的
	if(config_info->image_info[0].damage_flag == FLAG_OK)
	{
		printf("##Just image[0] is OK!!\n");
		return config_info->image_info[0].start_address;
	}
	else //if(config_info.image_info[1].damage_flag == FLAG_OK)
	{
		printf("##Just image[1] is OK!!\n");
		return config_info->image_info[1].start_address;
	}

}

extern void udelay(unsigned long usec);
//bootcmd=sf probe 0; sf read 0x80000000 0x100000 0x700000; go 0x80000000
int do_double_system_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	if (argc > 1) 
	{
		cmd_usage(cmdtp);
		return 1;
	}

	//sf probe 0;
	char* argv1[2] = {"probe","0"};
	do_spi_flash_probe(2,argv1);

	//选择启动分区(获取分区首地址)
	char image_addr[32] = {0};
	ulong addr = select_boot_system_partition();
	sprintf(image_addr,"0x%lx",addr);
	printf("###### boot image_addr = %s ######\n",image_addr);

	//sf read 0x80000000 addr 0x700000
	char*argv2[4] = {"read","0x80000000",(char*)&image_addr,"0x700000"};
	do_spi_flash_read_write(4,argv2);
	
	//go 0x80000000
	char*argv3[2] = {"go","0x80000000"};
	do_go(NULL,0,2,argv3);
	
	return 0;
	
}


U_BOOT_CMD(
	hle_bootm, 1, 0,	do_double_system_bootm,
	"select (kernel + app) image and boot it , No input parameters are required",
	""
);



其中 U_BOOT_CMD 为 uboot 中 命令注册的统一格式。

U_BOOT_CMD(
	hle_bootm, 1, 0,	do_double_system_bootm,
	"select (kernel + app) image and boot it , No input parameters are required",
	""
);

2.在 makefile 中添加我们新增的文件:

完成之后需要在 u-boot-2010.06/common/ 下的 Makfile 中添加一行,为了将新增文件编译进去:

       COBJS-y += cmd_double_system_bootm.c.o

3. 修改 uboot 的默认 环境参数:

CONFIG_BOOTCOMMAND 为 "hle_bootm"

这样uboot启动后就会自动调用都我们实现的命令去启动内核。

文件路径:uboot1.1.6\include\configs\hi3516ev100.h (找对应型号的配置文件)

 

/*-----------------------------------------------------------------------
 *  Environment   Configuration
 *-----------------------------------------------------------------------*/
//#define CONFIG_BOOTCOMMAND    "sf probe 0; sf read 0x80000000 0x100000 0x700000; go 0x80000000"
#define CONFIG_BOOTCOMMAND      "hle_bootm"  
#define CONFIG_BOOTDELAY        1
#define CONFIG_BOOTARGS         "mem=96M console=ttyAMA0,115200"
#define CONFIG_NETMASK  255.255.255.0       /* talk on MY local net */
#define CONFIG_IPADDR   192.168.1.10        /* static IP I currently own */
#define CONFIG_SERVERIP 192.168.1.2     /* current IP of tftp server ip */
#define CONFIG_ETHADDR  00:00:23:34:45:66
#define CONFIG_BOOTFILE         "uImage"        /* file to load */
#define CONFIG_BAUDRATE         115200

最后重新编译并烧写 uboot,uboot 部分完成!

 

4.实现升级部分“选择擦写镜像分区” 的逻辑(该部分处在app应用层):

配合uboot部分的选择分区的代码实现。

/*******************************************************************************
*@ Description    :选择需要升级的系统分区
*@ Input          :
*@ Output         :<addr>返回分区的地址
<region>升级的分区号(0/1)
*@ Return         :状态信息(错误码)
*@ attention      :
*******************************************************************************/
static config_info_t config_info = {0};
int selet_upgrade_region(unsigned long* addr,int* region)
{
	memset(&config_info,0,sizeof(config_info));
	
	hispinor_read(&config_info , CONFIG_REGION_START, sizeof(config_info)/*CONFIG_REGION_SIZE*/);
	if(config_info.magic_flag != HLE_MAGIC)
	{
		ERROR_LOG("config_info.magic_flag  not equal HLE_MAGIC\n");
		return -1;
	}

	/*打印两个分区的详细信息*/
	printf_config_info(&config_info);
	
	//情况1:两个分区都为坏的,返回版本低的分区
	if(config_info.image_info[0].damage_flag != FLAG_OK &&
	   config_info.image_info[1].damage_flag != FLAG_OK)
	{
		*addr = (config_info.image_info[0].image_version > config_info.image_info[1].image_version)?
				config_info.image_info[1].start_address : config_info.image_info[0].start_address;
		*region = (config_info.image_info[0].image_version > config_info.image_info[1].image_version)?1:0;
		return 0;
	}

	//情况2:两个都是好的,返回版本低的分区
	if(config_info.image_info[0].damage_flag == FLAG_OK &&
	   config_info.image_info[1].damage_flag == FLAG_OK) 
	{
		
		if(config_info.image_info[0].image_version > 
		   config_info.image_info[1].image_version)
		{
			*addr = config_info.image_info[1].start_address;
			*region = 1;
		}
		else
		{
			*addr = config_info.image_info[0].start_address;
			*region = 0;
		}
		return 0;
	}
	   
	//情况3:只有一个是好的,返回坏的这个分区
	if(config_info.image_info[0].damage_flag == FLAG_OK)
	{
		DEBUG_LOG("##Just image[0] is OK!!\n");
		*addr = config_info.image_info[1].start_address;
		*region = 1;
		return 0;
	}
	else //if(config_info.image_info[1].damage_flag == FLAG_OK)
	{
		DEBUG_LOG("##Just image[1] is OK!!\n");
		*addr = config_info.image_info[0].start_address;
		*region = 0;
		return 0;
	}
	
	return -1;
}


/*******************************************************************************
*@ Description    :升级业务,写分区部分(双系统)
*@ Input          :<buf> 升级文件的buf位置
                    <buf_lem>升级文件的buf大小(注意不能超过 IMAGE0/1 分区大小)
*@ Output         :
*@ Return         :
*@ attention      :
*******************************************************************************/
upgrade_status_e upgrade_write_norflash(void *buf,unsigned int buf_len)
{
	if(NULL == buf || buf_len <= 0)
	{
		return  UP_ILLEGAL_PARAMETER;
	}
	
	int ret = 0;


	//CRC校验

	//版本校验


	//选择 IMAGE 分区
	unsigned long upgrade_addr = 0;
	int region = 0; //IMAGE 分区序号
	ret = selet_upgrade_region(&upgrade_addr,&region);
	if(ret < 0)
	{
		ERROR_LOG("selet_upgrade_region failed !\n");
		return UP_SELECT_REGION_FAILED;
	}
	
	DEBUG_LOG("erase region is : %d  addr:%#x\n",region,upgrade_addr);
	
	unsigned long region_size = 0;
	if(0 == region)
		region_size = IMAGE0_REGION_SIZE;
	else
		region_size = IMAGE1_REGION_SIZE;

	if(buf_len > region_size)
	{
		ERROR_LOG("upgrade buf is too long , upgrade region overflow!!\n");
		return UP_REGION_OVERFLOW;
	}

	//1.擦除   CONFIG 分区
	//DEBUG_LOG("hispinor_erase CONFIG...\n");
	hispinor_erase((unsigned long)CONFIG_REGION_START,(unsigned long)CONFIG_REGION_SIZE);
	
	//2.写 CONFIG 分区(非升级分区的参数)
	if(0 == region)
	{
		hispinor_write(&config_info.magic_flag,CONFIG_REGION_START,sizeof(config_info.magic_flag));
		//空出中间 IMAGE0 的部分
		unsigned long start = CONFIG_REGION_START + sizeof(config_info.magic_flag) + sizeof(hle_image_info_t);
		hispinor_write(&config_info.image_info[1],start,sizeof(hle_image_info_t));
	}
	else
	{
		hispinor_write(&config_info,CONFIG_REGION_START,sizeof(config_info)-sizeof(hle_image_info_t));
		//空出后部分 IMAGE1 的部分
	}
	
	//3.擦除 IMAGE 分区(升级文件)
	DEBUG_LOG("hispinor_erase IMAGE ... \n");
	hispinor_erase(upgrade_addr,region_size);
	
	//4.写 IMAGE 分区 (升级文件)
	DEBUG_LOG("hispinor_write IMAGE ... \n");
	hispinor_write(buf,upgrade_addr, buf_len);

	//5.写 CONFIG 分区(升级分区的参数)
	config_info.image_info[region].image_version = config_info.image_info[1 - region].image_version + 1;
	config_info.image_info[region].damage_flag = FLAG_OK;
	if(0 == region)
	{
		config_info.image_info[region].start_address = IMAGE0_REGION_START;
		//写中间 IMAGE0 的部分
		unsigned long start = CONFIG_REGION_START + sizeof(config_info.magic_flag);
		hispinor_write(&config_info.image_info[0],start,sizeof(hle_image_info_t));
	}
	else
	{
		config_info.image_info[region].start_address = IMAGE1_REGION_START;
		//写后部分 IMAGE1 的部分
		unsigned long start = CONFIG_REGION_START + sizeof(config_info.magic_flag) + sizeof(hle_image_info_t);
		hispinor_write(&config_info.image_info[1],start,sizeof(hle_image_info_t));
	}
	
	
	return 0;

}

 

5.内核损坏的标记变量:

也就是上方的 damage_flag 成员变量

int damage_flag; //损坏标记(FLAG_BAD 或者 FLAG_OK)

关于 kernel 什么情况下算启动异常,以及FLAG_BAD标记 由谁来写,我没有想到好的标记策略,只有些想法如下:

目前的思路是:

思路1:等内核起来后,app正常运行1min 后自动对 nor flash 的该位置进行一次标记操作,写入

FLAG_OK (如若发现已经是FLAG_OK就不写,减少norflash的擦写次数),但是什么时候由谁来标记 FLAG_BAD

没有合适的方法。 

思路2:系统发生故障时,看门狗重启,触发写  FLAG_BAD 标志

后续有好的方法再改进

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章