前言
本文档主要介绍固件升级流程和接口的使用方法,指导用户应用及开发升级特性。
与本文档相对应的产品版本如下。
本文档主要适用于以下工程师:
技术支持工程师
软件开发工程师
在本文中可能出现下列标志,它们所代表的含义如下。
|
||
|
||
概述
功能描述
固件升级功能可用于对设备芯片的固件进行更新。
WS63的固件包含多个固件镜像,不同镜像可能采用不同的升级方式,包括全镜像升级、压缩升级,用户可以根据情况选择合适的升级方式。
全镜像升级:新的固件镜像不做处理,直接打包到固件升级包(以下简称为“升级包”)中,在设备上直接将其更新至目标位置。这种方式一般适用于固件镜像较小的场景。
压缩升级:新的固件镜像经过压缩处理后,打包到升级包中,首先在设备上解压恢复,再更新至目标位置。这种方式一般适用于固件镜像较大的场景。
AB面升级:不制作升级包,直接将镜像传输到指定分区,然后切换使用的镜像分区,每次升级都在A面与B面之间切换,且互为备份,该方式一般适用于有一定安全性考虑的场景。该升级方式与前两种不兼容,使用方法请直接参考当前文档“AB面升级”章节,“升级包制作”~“升级包本地升级”章节不适用于该升级方式。
固件升级流程
从端到端来看,整个芯片固件的升级操作包括以下过程,如图1所示。
升级包制作:固件开发完成后,根据实际情况,将要升级的固件镜像按照配置文件中对应的升级方式,生成固件升级包文件的过程。
升级包传输:将升级包传输到设备上。根据实际情况,可能有多种传输方式,如蓝牙、串口等。
升级包保存:将传输到设备的固件升级包保存至本地存储器,在WS63中,固件升级包存储在FLash中。
升级包本地校验:升级程序替换前,在设备上校验升级包的完整性和合法性。
升级包本地升级:APP程序中在设备上将新的固件更新至目标位置,重启进入flashboot继续执行升级,升级完成后重启。
开发接口
根据所在的程序的不同,升级接口可分为两部分。
升级包存储,由APP程序执行。
初始化接口。
升级包写入本地存储器和从本地存储器读取的接口。
申请开始进行本地升级接口。
升级包本地升级,分两部分执行,执行流程是一致的,NV镜像的升级由APP程序完成,其他镜像的升级在flashboot程序中完成。
初始化接口。
升级包校验接口。
升级进度通知函数注册接口。
开始执行本地升级接口。
获取升级结果接口。
升级模块提供的接口如表1和表2所示。参见头文件:include\middleware\utils\upg.h。接口参数及返回值说明如表3所示。
表 1 升级接口(升级包存储部分)描述
表 2 升级接口(本地升级部分)描述
升级包结构中预留了48Byte用于用户自定义数据的校验。注册自定义校验函数后,被注册的函数会在调用uapi_upg_verify_file_head和uapi_upg_verify_file函数时被调用到。如果自定义数据校验失败uapi_upg_verify_file_head和uapi_upg_verify_file会返回失败。 |
表 3 升级接口入参及返回值描述
升级包制作
流程原理
升级包的制作流程示意图如图1所示,具体操作步骤如下:
编译生成带签名的明文新镜像。编译生成镜像请参考《WS63V100 SDK开发环境搭建》“编译SDK(Cmake)-编译方法”章节。
-
将新镜像压缩并加密,生成加密的压缩新镜像。
将新镜像压缩不加密,生成压缩的新镜像。
新镜像不做处理。
对步骤2中生成的镜像添加升级镜像头。
将生成的所有带升级镜像头的升级镜像文件合并到最终升级包中,并对整包添加升级包头,进行数字签名。
说明: 2中的场景,需根据实际情况做选择。如WS63不支持镜像加密,所有需要加密的场景则不能选择。
开发流程
提供基于Windows/Linux系统下的Python入口编译能力,升级包可以在所有镜像编译完成后,通过执行“build/config/target_config/ws63/build_ws63_update.py”来制作,脚本及配置文件说明如下:
在“build_ws63_update.py”文件中配置各个镜像的路径以及其他脚本所需的参数,并调用升级包制作脚本“build_upg_pkg.py”,如下所示。“build_ws63_update.py”中配置的各个路径可以根据实际路径修改。
“build_ws63_update.py”调用“build_upg_pkg.py”代码示例如下:
import os import sys import argparse file_dir = os.path.dirname(os.path.realpath(__file__)) g_root = os.path.realpath(os.path.join(file_dir, "..", "..", "..", "..")) sys.path.append(os.path.join(g_root, 'build', 'script')) from build_upg_pkg import begin class upg_base_info: def __init__(self): self.root_path = g_root # 升级包结构配置文件 self.fota_format_path = os.path.join(self.root_path, "build", "config", "target_config", "ws63", "fota") # 产品升级配置文件 self.fota_cfg = os.path.join(self.root_path, "build", "config", "target_config", "ws63", "fota", "fota.cfg") # 产品镜像输出路径 self.output = os.path.join(self.root_path, "output", "ws63") # 产品升级镜像包输出路径 self.upg_output = os.path.join(self.output, "upgrade") # 产品升级制作临时文件输出路径 self.temp_dir = os.path.join(self.upg_output, "temp_dir") # 产品镜像路径 self.flashboot = os.path.join(self.output, "acore", "ws63-flashboot", "flashboot_sign.bin") self.app_bin = os.path.join(self.output, "acore", "ws63-liteos-app", "ws63-liteos-app-sign.bin") self.nv_bin = os.path.join(self.output, "acore", "nv_bin", "ws63_all_nv.bin") self.flashboot_old_bin = os.path.join(self.output, "acore", "old_version", "flashboot_sign.bin") self.app_old_bin = os.path.join(self.output, "acore", "old_version", "ws63-liteos-app-sign.bin") self.nv_old_bin = os.path.join(self.output, "acore", "old_version", "ws63_all_nv.bin") def get_new_image(input,info): image_list = [] if 'app' in input: image_list.append("=".join([info.app_bin, "application"])) print(1) if 'boot' in input: image_list.append("=".join([info.flashboot, "flashboot"])) print(2) if 'nv' in input: image_list.append("=".join([info.nv_bin, "nv"])) print(3) new_image = "|".join(image_list) return new_image def get_old_image(input,info): image_list = [] if 'app' in input: image_list.append("=".join([info.app_old_bin, "application"])) print(11) if 'boot' in input: image_list.append("=".join([info.flashboot_old_bin, "flashboot"])) print(22) if 'nv' in input: image_list.append("=".join([info.nv_old_bin, "nv"])) print(33) old_image = "|".join(image_list) return old_image def get_parameters(): parser = argparse.ArgumentParser() parser.add_argument('--pkt', type=str, default = 'app', help='需要生成的镜像,包括: app,boot,nv') config = parser.parse_args() return config if __name__ == '__main__': info = upg_base_info() conf = get_parameters() input = conf.pkt.split(",") conf.app_name = "update" conf.upg_format_path = info.fota_format_path conf.base = info.fota_cfg conf.temp_dir = info.temp_dir conf.new_images = get_new_image(input,info) conf.old_images = get_old_image(input,info) conf.output_dir = info.upg_output conf.type = 0 begin(conf)
升级包制作脚本为:build/script/build_upg_pkg.py。
配置文件为:build/config/target_config/ws63/fota/fota.cfg,配置文件的各个字段说明如表1所示。
配置文件“fota.cfg”中涉及到镜像ID,请参考表2,表中的ID均为特殊的魔术字。
配置文件“fota.cfg”中可配置升级校验方式,。
安全校验使用ECC256_SHA256密钥算法,密钥文件使用openssl工具(https://www.openssl.org)进行生成,执行命令为:openssl ecparam -genkey -name brainpoolP256r1 -out "ec_bp256_key.pem"
表 1 fota.cfg主要内容说明
|
||
|
||
升级镜像ID,与原始镜像的ImageId相同,镜像ID请参考表2。 |
||
|
||
|
表 2 镜像ID列表
开发实例
以下提供制作升级包的一个实例,一切操作都是在SDK包中的默认配置上进行修改。
修改配置文件“build/config/target_config/ws63/fota/fota.cfg”(可选)。
升级方式
配置选项[FOTA_INFO_AREA]之后的每一项[xxx]都是对应镜像、镜像签名的配置项,根据升级方式选择修改 DecompressFlag参数。
如:
flashboot的镜像以全量(原镜像不作处理)升级的方式,则[flashboot]的DecompressFlag设置为0。
app的镜像签名以压缩升级的方式,则[application]的DecompressFlag设置为0x3C7896E1。
nv的镜像的升级方式由NV特性自己完成,对配置方式不感知。
app镜像可以开启加密升级,在开启flash在线解密功能的前提下,配置[application]的[ReRncFlag]为0x3C7896E1
防回滚号
若此次升级不希望用户进行回退版本,则可以在升级包中修改对应镜像、镜像签名配置项中的防回滚版本号version_ext。
防回滚版本号可以单独对某镜像进行设置。
防回滚版本号按照二进制向高位逐个置一的规则进行增加;如:0x0(0000) -> 0x1(0001) -> 0x3(0011) ->0x7(0111) -> 0xF(1111) ->0x1F(0001 1111)。
核对并修改升级包生成脚本“build/config/target_config/ws63/build_ws63_update.py”。
核对产品镜像路径。确保要升级的产品镜像路径是正确的,路径不限于脚本中默认填写的路径,可根据实际情况进行修改。
生成升级包文件。
运行脚本“build/config/target_config/ws63/build_ws63_update.py”生成升级包,本脚本支持在Linux或Windows上运行,请在运行环境安装python3.7级以上版本Python;根据需要,可添加入参 --pkt=<image1>,<image2>,不添加入参时,默认入参为app。可添加--ver=<ota_version>版本号参数,不添加参数时,默认入参为空(全0)。
Windows环境请在cmd窗口SDK根目录下执行以下指令:
python build\config\target_config\ws63\build_ws63_update.py
Linux环境请在SDK根目录下执行以下指令:
python3 build/config/target_config/ws63/build_ws63_update.py
执行完成后,在默认路径“output/ws63/upgrade”生成升级包“update.fwpkg”。
注意事项
若开启flash在线解密功能,编译出来的镜像为密文,则OTA镜像配置也需要配置为加密;
若烧写镜像为非密,则OTA镜像配置也需要配置为非密。sdk默认配置为非密,以压缩升级为例:
非密配置编译步骤:
-
“build/config/target_config/ws63/sign_config/liteos_app_bin_ecc.cfg”配置文件中配置“SignSuite=0”。
将“Iv=FF000000000000000000000000000000”注释或删除掉。
执行“./build.py -c ws63-liteos-app”,此时编译APP镜像为非密。将该镜像烧录单单板中。
OTA镜像非密配置打包。
“build/config/target_config/ws63/fota/fota.cfg”配置文件中配置[application]中的“ReRncFlag=0x0”。
此时执行“./build_ws63_update.py”,将1中编译生成的app镜像打包成OTA升级包为非密升级包。
加密配置编译步骤:
烧写镜像加密配置编译。
“build/config/target_config/ws63/sign_config/liteos_app_bin_ecc.cfg”配置文件中配置“SignSuite=1”。
将Iv值配置为“Iv=FF000000000000000000000000000000”。
执行“./build.py -c ws63-liteos-app”,此时编译APP镜像为加密镜像;将该加密镜像烧录到单板中。
OTA镜像非密配置打包
将“build/config/target_config/ws63/sign_config/liteos_app_bin_ecc.cfg”配置文件中配置“SignSuite=0”。
执行“./build.py -c ws63-liteos-app”编译出带加密Iv、但未加密的APP镜像(由于镜像加密后无法压缩,故此处需要先编译出带Iv但不加密的原始镜像,该镜像仅能用于OTA打包,不支持直接烧录,仅压缩升级需要该配置编译)。
配置“build/config/target_config/ws63/fota/fota.cfg”文件中配置[application]中的“ReRncFlag=0x3C7896E1”。
此时执行“./build_ws63_update.py 将步骤2 2.中编译生成的app镜像打包成压缩加密OTA升级包。
说明: 开启镜像加密功能时,推荐使用数字签名服务器对OTA镜像签名加密,可参考《WS63V100 二次开发网络安全 注意事项》中数字签名服务器章节打包ota镜像
升级包传输
升级包的传输,由应用程序实现,可以有多种方式,本文中不做详细描述。
说明: 升级包的存放位置可能会需要根据实际情况有所差异,以实际的Flash分区表中FOTA分区的地址为准。
升级包保存
开发流程
在WS63中,使用文件系统保存升级包,因此升级包是以二进制数据保存在flash中。
保存升级包文件有以下两种典型场景,可以根据升级包传输的实现来选择合适的开发场景。
调用uapi_upg_init初始化升级模块。
调用uapi_upg_prepare函数,执行本地存储器的准备工作,包括初始化升级标记等。
应用程序开始接收升级包,调用uapi_upg_write_package_sync函数,将内存中的分包数据写到flash对应位置。
继续接收下一个分包数据,再次调用uapi_upg_write_package_sync函数,写入到对应位置。直到所有数据全部写入完成。如果该接口返回错误,则停止升级流程。
调用uapi_upg_request_upgrade,传入重启参数后可以开始升级流程。
第5步中传入参数不包含重启时,需要进行手动重启,进入flashboot自动开始升级流程。
注意事项
升级包的分包数据必须按照顺序传输和保存,已经保存的分包不能再次传输。例如:第一次传输并保存了0~1023的数据,下一次必须从1024开始,否则uapi_upg_write_package_sync接口会报错。
升级包文件的路径和文件名是固定的,不允许改变。
在传输过程中是否有数据的分段校验,由传输过程保证。如果传输过程中数据出现错误而没有校验和重传机制,则只能等到全部保存完成后,在本地升级启动过程中调用uapi_upg_verify_file校验时会失败,导致升级失败。
需要在flash上配置对应的分区分别给运行程序和升级包数据,在适配压缩升级时,推荐运行程序的flash分区大小与升级包数据存放区的flash分区大小比例为1:0.7,该比例数据来源于压缩升级中的压缩比
当前WS63上flash分区如下图
用户工厂区,本分区设计产测时写入,产测结束后,后续app只读,可以用于存放客户规划的比较重要的数据,该分区内容,完全又客户自行管理规划
分区ID从0x00开始到0x09,共计6个分区,设计为在产测阶段写入,在后续app运行过程中处于被写保护状态,只读。
分区ID最大规格支持16个,从0x30开始的分区ID,可以定制开发,无特殊要求,不建议删除分区ID,如果预留区空间过小,希望增加预留区空间,可以从imageA/imageB/fota data分区切割部分空间,调整方法可以参考《WS63V100 SDK开发环境搭建 用户指南》文档中'2.2.5 Flash分区表配置'章节。
编程实例
static void upg_serial_putc(const char c)
{
printf("%c", c);
}
void test_update(void)
{
uint32_t file_size = 0x2000; /* 升级文件大小(实际大小由APP获取) */
uint32_t max_len;
uint32_t read_len;
uint32_t write_len = 0;
errcode_t ret;
/* 依赖分区模块 */
ret = uapi_partition_init(); /* 该接口可以重复调用 */
if (ret != ERRCODE_SUCC){
printf("uapi_partition_init error. ret = 0x%08x\r\n", ret);
}
/* 1. 初始化update模块 */
/* 2. 获取APP升级文件大小上限. */
max_len = uapi_upg_get_storage_size();
if (file_size > max_len) {
return ERRCODE_FAIL;
}
upg_prepare_info_t prepare_info;
/* 3. 将升级文件的大小,传给uapi_upg_prepare函数,执行升级准备工作 */
prepare_info.package_len = file_size;
ret = uapi_upg_prepare(&prepare_info);
if (ret != ERRCODE_SUCC) {
printf("uapi_upg_prepare error = 0x%x\r\n", ret);
}
/* 4. 用户自行实现:通过串口或网络下载升级文件,调用uapi_upg_write_package_sync函数存储(场景一),
或直接保存至文件系统中(场景二) */
char *buf = (char *)malloc(0x1000 * sizeof(char)); /* 单次读取分包的长度可以自行调整 */
memset_s(buf, 0x1000 * sizeof(char), 0, 0x1000 * sizeof(char));
while (write_len < file_size) {
通过网络或者其他方式获取升级包(&read_len, buf);
if (write_len + read_len > max_len) {
free(buf);
return;
}
ret = uapi_upg_write_package_sync(write_len,(uint8_t *)buf, read_len);
if (ret != ERRCODE_SUCC) {
free(buf);
return;
}
write_len += write_len;
memset_s(buf, 0x1000 * sizeof(char), 0, 0x1000 * sizeof(char));
}
/* 5. 升级文件下载完成后,申请开始本地升级 */
ret = uapi_upg_request_upgrade(false);
if (ret != ERRCODE_SUCC) {
printf("uapi_upg_request_upgrade error = 0x%x\r\n", ret);
}
/* 6. 升级准备工作完成后,复位开始本地升级 */
upg_reboot();
return;
}
升级包本地升级
开发流程
在WS63中,本地升级程序在被称为flashboot的程序中运行。
调用uapi_upg_init初始化升级模块。
调用uapi_upg_register_progress_callback函数注册进度通知回调函数。
如果用户有自定义的校验数据,调用uapi_upg_register_user_defined_verify_func注册自定义数据校验函数。
调用uapi_upg_start开始本地升级。
复位重启。
注意事项
flashboot中调用的uapi_upg_init与升级包保存中的uapi_upg_init虽然是同一个函数,但是运行在不同的程序,互相不受影响,均需要调用。
虽然升级模块提供了数据校验的接口,但由于调用uapi_upg_start开始本地升级后,内部会首先调用校验的接口校验升级包,因此若无特殊需求(例如只做校验不做本地升级),正常情况下不必单独调用校验接口。
如果注册了进度通知回调函数,在升级过程中会调用回调函数,并传入当前进度值。在回调函数中减少执行耗时较长的操作,否则会影响升级流程的时间。
本地升级过程中不要断电,升级开始后不支持停止升级。
uapi_upg_start函数如果返回成功,则说明所有镜像都成功更新。更新成功后会重启重新进入flashboot,随后正常模式启动。
uapi_upg_start函数如果返回失败,则有两种情况:
如果是更新镜像之前的校验失败,则说明升级包传输出错,或者收到的是不适合本设备的升级包,则直接退出升级流程,不会再进入flashboot,正常模式启动。
如果是更新镜像已经开始后出现错误导致失败,则重启后仍然会进入flashboot进行重试,最多重试三次。恢复正常成功升级或者三次均失败后,不再重启进入flashboot,正常模式启动。
app和flashboot的升级在flashboot中完成,NV在app程序中升级。
编程实例
static void upg_serial_putc(const char c)
{
printf("%c", c);
}
static void upg_progress_callback(uint32_t percent)
{
printf("%d%% ", percent);
}
void app_init(void)
{
uint32_t file_size = 0x2000; /* 升级文件大小(实际大小由APP获取) */
uint32_t max_len;
/* 文件系统相关的初始化 */
/* 依赖flash */
/* 依赖分区模块 */
ret = uapi_partition_init(); /* 该接口可以重复初始化 */
if(ret != ERRCODE_SUCC){
printf("uapi_partition_init error. ret = 0x%08x\r\n", ret);
}
/* 注册进度通知函数 */
(td_void)uapi_upg_register_progress_callback(upg_progress_callback); /* 此处回调函数需业务实现 */
/* 开始本地升级 */
ret = uapi_upg_start();
if (!(ret == ERRCODE_UPG_NOT_NEED_TO_UPDATE || ret == ERRCODE_UPG_NOT_INIT || ret == ERRCODE_SUCC)) {
printf("uapi_upg_start error = 0x%x\r\n", ret);
}
/* 复位 */
upg_reboot();
return;
}
OTA版本号配置/获取实例:
1)编译APP镜像:./build.py -c ws63-liteos-app
2)打OTA包:build/config/target_config/ws63/build_ws63_update.py --ver=hellword_654321
3)获取OTA版本号:
#include "upg_common.h"
void show_ota_ver(void)
{
upg_package_header_t *pkg_header = NULL;
char ota_ver[0xF] = {0};
errcode_t ret = upg_get_package_header(&pkg_header);
if (ret != ERRCODE_SUCC || pkg_header == NULL) {
PRINT("upg_get_package_header err = 0x%x \n", ret);
return;
}
memcpy_s(ota_ver, 0xF, pkg_header->info_area.user_defined, 0xF);
PRINT("show_ota_ver: %s \n", ota_ver);
}
AB面升级
接口说明
表 1 AB面升级接口入参及返回值描述
开发实例
void app_init(void)
{
upg_region_index upg_region = upg_get_upg_region(); /* 获取要升级的分区 */
errcode_t ret = upg_ab_start(upg_region); /* 该接口会擦除对应分区 */
if(ret != ERRCODE_SUCC){
printf("upg_ab_start error. ret = 0x%08x\r\n", ret);
}
/* 写入镜像数据 */
uint32_t offset;
uint32_t max_size = 0;
uint32_t buf_size = 0;
uint8_t upg_buf[0x1000];
net_get_upg_package_size(&max_size);/* 此处需要通过云端获取镜像大小,需要自行实现 */
for (offset = 0; offset < max_size;) {
buf_size = ((max_size - offset) < 0x1000) ? (max_size - offset) : 0x1000;
net_get_upg_package_buf(offset, upg_buf, buf_size); /* 此处需要通过云端获取镜像数据,需要自行实现 */
ret = upg_ab_image_write(upg_region, offset, upg_buf, buf_size);
if (ret != ERRCODE_SUCC){
printf("upg_ab_image_write error. ret = 0x%08x\r\n", ret);
}
offset += buf_size;
}
/* 写入完成后切换启动分区 */
ret = upg_set_run_region(upg_region);
if (ret != ERRCODE_SUCC) {
printf("upg_set_run_region error = 0x%x\r\n", ret);
}
/* 复位 */
upg_reboot();
return;
}
使用AB面升级方式的方法:
SDK下执行 ./build.py ws63-flashboot menuconfig 命令配置flashboot的menuconfig,Middleware -> Chips -> Chip Configurations for ws63 -> 选中 FOTA for AB;然后保存即可
同步骤1方式,执行 ./build.py ws63_liteos_app menuconfig 命令配置flashboot的menuconfig,Middleware -> Chips -> Chip Configurations for ws63 -> 选中 FOTA for AB;然后保存即可
参考上述示例代码,编写AB面升级流程代码
配置完成后重新编译flashboot以及ws63_liteos_app即可
注意事项
写入新镜像前,需要确认镜像大小没有超过对应分区大小。
写入镜像时,需要确认镜像数据正常,无错传漏传。
写入镜像与实际烧录镜像一致,不需要封装镜像,增加包头等。
AB面升级方式对flash的利用率较低,且当前已有足够的措施保证压缩升级过程的安全性以及后续运行阶段的稳定性,更推荐使用压缩升级方式。
可根据自身需求选择AB面升级或者压缩升级,但选定并完成过一次相应的升级之后,不建议再切换到另外一种升级方式。
版本兼容性说明
对OTA升级镜像包校验方式更新后,SDK版本使用OTA方式更新存在如下限制:使用1.10.101及之前的SDK版本无法直接OTA升级到1.10.105及之后的SDK版本。
解决方案1:
先OTA升级到1.10.102或1.10.103版本,再OTA升级到1.10.106及之后的版本
解决方案2:按如下配置OTA升级后,可兼容后续SDK版本
修改新SDK版本中配置文件sdk\build\config\target_config\ws63\fota\fota.cfg中对应字段值为SignSuite=1和KeyAlg=0x2A13C812
将旧SDK版本中sdk\build\config\target_config\ws63\sign_config目录下的ec_bp256_app_private_key.pem和ec_bp256_oem_root_private_key.pem文件拷贝到新SDK版本中对应目录sdk\build\config\target_config\ws63\sign_config
新SDK版本打包OTA升级包时增加boot选项:python build/config/target_config/ws63/build_ws63_update.py --pkt=app,boot (不带参数默认打包app)
FAQ
uapi_upg_prepare返回错误码:ERRCODE_PARTITION_CONFIG_NOT_FOUND。
请检查是否烧写分区表。
新增了bin文件需要通过OTA进行升级,怎么扩展:
upg_common_porting.c文件中 g_img_partition_map 中增加新配置,格式为 {新增镜像ID, 新增镜像分区表ID}。
镜像ID 在upg_definitions_porting.h文件中进行定义。
UPDATE_IMAGE_SET 宏定义后增加新镜像ID,对应UPDATE_IMAGE_SET_CNT 需要修正。
fota.cfg 可参考 [hilink]新增配置。
build_ws63_update.py修改升级包生成脚本。
newbin的字段与fota.cfg中配置的需要对齐,否则打包会报错。
可以调整不输入入参时,脚本默认打包的内容。