環境:Hi3516eV100 + liteos + u-boot-2010.06
目標:
- 實現系統雙備份機制,防止升級過程中斷電而導致系統分區損壞,無法啓動的問題(由低版本的備份分區啓動)
- 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,®ion);
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 標誌
後續有好的方法再改進