环境:Hi3516eV100 + liteos + u-boot-2010.06
目标:
分区划分:
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
#include
#include
#include
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;31minit_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;31mselect_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;31mconfig_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;31mBoth 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 :返回分区的地址
升级的分区号(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大小(注意不能超过 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 标志
后续有好的方法再改进