嵌入式系統雙備份設計實現

環境: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 標誌

後續有好的方法再改進

 

 

 

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