1# OTA升级 2 3 4## 概述 5 6 7### 简介 8 9随着设备系统日新月异,用户如何及时获取系统的更新,体验新版本带来的新的体验,以及提升系统的稳定和安全性成为了每个厂商都面临的严峻问题。 10 11OTA(Over the Air)提供对设备远程升级的能力。升级子系统对用户屏蔽了底层芯片的差异,对外提供了统一的升级接口。基于接口进行二次开发后,可以让厂商的设备(如IP摄像头等)轻松支持远程升级能力。 12 13 14### 基本概念 15 16- 全量升级包:将所有目标版本的镜像均通过全量镜像的方式打包获得的升级包。 17 18- 差分升级包:对源版本和目标版本差分,获得两个版本镜像之间的差异,以这种方式打包制作升级包。 19 20 21### 实现原理 22 23OTA 的升级原理是利用升级包制作工具,将编译出的版本打包生成升级包。厂商设备集成 OTA 升级能力后,将升级包上传至服务器,通过升级应用下载升级包,触发并完成升级。 24 25<a href="#ab-升级场景">AB 升级</a>:是 OTA 升级的一个场景,原理是设备有一套备份的B系统,在A系统运行时,可以在正常使用的状态下,静默更新B系统,升级成功后,重启切换新系统,实现版本更新的机制。 26 27 28### 约束与限制 29 30- 支持基于Hi3861/Hi3516DV300/RK3568芯片的开源套件。 31 32- 对Hi3516DV300/RK3568开源套件,设备需要支持SD卡(VFAT格式)。 33 34- 生成升级包需要在linux系统下面执行。 35 36- 目前轻量和小型系统仅支持全量包升级,暂不支持差分包、变分区包升级。 37 38- AB 升级只适用于标准系统支持 AB 分区启动的设备。 39 40 41## 环境准备 42 43- 在Windows上,下载安装OpenSSL工具,并配置环境变量。 44- 准备升级包制作工具 45- 编译出版本镜像文件 46 47 48## 开发流程 49 50<a href="#生成公私钥对">1. 使用OpenSSL工具生成公私钥对</a> 51 52<a href="#制作升级包">2. 使用升级包制作工具制作升级包</a> 53 54  <a href="#轻量与小型系统升级包制作">2.1 轻量与小型系统升级包</a> 55 56  <a href="#标准系统升级包制作">2.2 标准系统升级包</a> 57 58<a href="#上传升级包">3. 将升级包上传到厂商的OTA服务器</a> 59 60<a href="#下载升级包">4. 厂商应用从OTA服务器下载升级包</a> 61 62<a href="#厂商应用集成ota能力">5. 厂商应用集成OTA能力</a> 63 64  <a href="#api-应用默认场景">5.1 API 应用默认场景</a> 65 66  <a href="#api-应用定制场景">5.2 API 应用定制场景</a> 67 68  <a href="#ab-升级场景">5.2 AB 升级场景</a> 69 70 71## 开发步骤 72 73 74### 生成公私钥对 751. 使用OpenSSL工具生成公私钥对。 76 773. 请妥善保管私钥文件,在升级包制作过程中将私钥文件作为制作命令的参数带入,用于升级包签名,公钥用于升级时对升级包进行签名校验,公钥的放置如下: 轻量和小型系统将生成的公钥内容预置在代码中,需要厂商实现 HotaHalGetPubKey 这个接口来获取公钥。标准系统需要将生成的公钥放在device或vendor目录下的 /hisilicon/hi3516dv300/build/updater_config/signing_cert.crt 这个文件中。 78 795. 对使用 Hi3516DV300 套件的小型系统,在上一步的基础上,还需用public_arr.txt里面的全部内容替换uboot模块third_party\u-boot\u-boot-2020.01\product\hiupdate\verify\update_public_key.c 中的g_pub_key中的全部内容。 80 示例,uboot模块的公钥: 81 82 ```c 83 static unsigned char g_pub_key[] = { 84 0x30, 0x82, 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01, 85 0x00, 0xBF, 0xAA, 0xA5, 0xB3, 0xC2, 0x78, 0x5E, 86 } 87 ``` 88 89 90### 制作升级包 91 92 93#### 轻量与小型系统升级包制作 94 951. 创建目标版本(target_package)文件夹,文件格式如下: 96 97 轻量级系统和AB升级的小型系统不需要 OTA.tag 和 config。 98 99 ```text 100 target_package 101 ├── OTA.tag 102 ├── config 103 ├── {component_1} 104 ├── {component_2} 105 ├── ...... 106 ├── {component_N} 107 └── updater_config 108 └── updater_specified_config.xml 109 ``` 110 1112. 将待升级的组件,包括镜像文件(例如:rootfs.img)等放入目标版本文件夹的根目录下,代替上文结构中的{component_N}部分。 112 1133. 填写“updater_config”文件夹中的“updater_specified_config.xml”组件配置文件。 114 组件配置文件“updater_specified_config.xml”,格式如下: 115 116 117 ```xml 118 <?xml version="1.0"?> 119 <package> 120 <head name="Component header information"> 121 <info fileVersion="01" prdID="hisi" softVersion="OpenHarmony x.x" date="202x.xx.xx" time="xx:xx:xx">head info</info> 122 </head> 123 <group name="Component information"> 124 <component compAddr="ota_tag" compId="27" resType="5" compType="0" compVer="1.0">./OTA.tag</component> 125 <component compAddr="config" compId="23" resType="5" compType="0" compVer="1.0">./config</component> 126 <component compAddr="bootloader" compId="24" resType="5" compType="0" compVer="1.0">./u-boot-xxxx.bin</component> 127 </group> 128 </package> 129 ``` 130 131 **表1** 组件配置文件节点说明 132 133 | 信息类别 | 节点名称 | 节点标签 | 是否必填 | 内容说明 | 134 | -------- | -------- | -------- | -------- | -------- | 135 | 头信息(head节点) | info节点 | / | 必填 | 该节点内容配置为:head info | 136 | 头信息(head节点) | info节点 | fileVersion | 必填 | 保留字段,内容不影响升级包生成 | 137 | 头信息(head节点) | info节点 | prdID | 必填 | 保留字段,内容不影响升级包生成 | 138 | 头信息(head节点) | info节点 | softVersion | 必填 | 软件版本号,即升级包版本号,版本必须比基础版本大,且OpenHarmony后没有其他字母,否则无法生产升级 | 139 | 头信息(head节点) | info节点 | _date_ | _必填_ | 升级包制作日期,保留字段,不影响升级包生成 | 140 | 头信息(head节点) | info节点 | _time_ | _必填_ | 升级包制作时间,保留字段,不影响升级包生成 | 141 | 组件信息(group节点) | component节点 | / | 必填 | 该节点内容配置为:要打入升级包的组件/镜像文件的路径,默认为版本包根路径 | 142 | 组件信息(group节点) | component节点 | compAddr | 必填 | 该组件所对应的分区名称,例如:system、vendor等。 | 143 | 组件信息(group节点) | component节点 | compId | 必填 | 组件Id,不同组件Id不重复 | 144 | 组件信息(group节点) | component节点 | resType | 必填 | 保留字段,不影响升级包生成 | 145 | 组件信息(group节点) | component节点 | compType | 必填 | 处理方式全量/差分,配置镜像处理方式的,0为全量处理、1为差分处理。 | 146 147 > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** 148 > 对轻量系统/小型系统,不支持做差分升级,component标签中,属性compType值,不能配为 1,必须全部配置为 0。 149 > 150 > 对轻量系统/小型系统,不支持变分区升级包的制作。 151 1524. 创建“OTA.tag文件”,内容为OTA升级包的魔数,固定如下: 153 154 ```text 155 package_type:ota1234567890qwertw 156 ``` 157 1585. 创建“config文件”,内容为设置bootargs以及bootcmd的信息。 159 配置例如下: 160 161 162 ```text 163 setenv bootargs 'mem=128M console=ttyAMA0,115200 root=/dev/mmcblk0p3 rw rootfstype=ext4 rootwait blkdevparts=mmcblk0:1M 164 (u-boot.bin),9M(kernel.bin),50M(rootfs_ext4.img),50M(userfs.img)' setenv bootcmd 'mmc read 0x0 0x82000000 0x800 0x4800;bootm 0x82000000' 165 ``` 166 1676. 执行升级包制作命令。 168 169 ```text 170 python build_update.py ./target_package/ ./output_package/ -pk ./rsa_private_key3072.pem -nz -nl2 171 ``` 172 173 - ./target_package/:指定target_package路径。 174 - ./output_package/:指定升级包输出路径。 175 - -pk ./rsa_private_key3072.pem:指定私钥路径。 176 - -nz:打开not zip模式开关 177 - -nl2:打开非“标准系统”模式开关 178 179 180#### 标准系统升级包制作 1811. 创建目标版本(target_package)文件夹,文件格式如下: 182 183 184 ```text 185 target_package 186 ├── {component_1} 187 ├── {component_2} 188 ├── ...... 189 ├── {component_N} 190 └── updater_config 191 ├── BOARD.list 192 ├── VERSION.mbn 193 └── updater_specified_config.xml 194 ``` 195 1962. 将待升级的组件(包括镜像文件和updater_binary文件)放入目标版本文件夹的根目录下,代替上文结构中的{component_N}部分。 197 198 以RK3568为例,镜像文件的构建位置为:out/rk3568/packages/phone/images/ 199 200 二进制升级文件updater_binary位置为:out/rk3568/packages/phone/system/bin/ 201 2023. 填写“updater_config”文件夹中的组件配置文件。 203 2044. 配置“updater_config”文件夹中当前升级包支持的产品list:**BOARD.list**。 205 206 例如配置如下: 207 208 209 ```text 210 HI3516 211 RK3568 212 ``` 213 214 厂家可在路径base/updater/updater/utils/utils.cpp文件中的GetLocalBoardId()接口进行Local BoardId的配置。请确保utils.cpp文件中的Local BoardId包含在BOARD.list中,否则无法升级。 215 2165. 配置“updater_config”文件夹中当前升级包所支持的版本范围:**VERSION.mbn**。 217 218 版本名称格式:Hi3516DV300-eng 10 QP1A.XXXXXX.{大版本号(6位)}.XXX{小版本号(3位)}。 219 220 例如:Hi3516DV300-eng 10 QP1A.190711.020。名称中“190711”为大版本号,“020”为小版本号。 221 222 配置例如下: 223 224 225 ```text 226 Hi3516DV300-eng 10 QP1A.190711.001 227 Hi3516DV300-eng 10 QP1A.190711.020 228 ``` 229 230 请确保基础版本号包含在VERSION.mbn中 231 2326. 针对增量(差分)升级包,还需要准备一个源版本(source_package),文件内容格式与目标版本(target_package)相同,需要打包成zip格式,即为:source_package.zip。 233 2347. 针对变分区升级包,还需要提供分区表文件“partition_file.xml”,partition_file.xml配置节点说明如下,可通过-pf参数指定。 235 236 分区表会随镜像一起生成,格式如下: 237 238 239 ```xml 240 <?xml version="1.0" encoding="GB2312" ?> 241 <Partition_Info> 242 <Part Sel="1" PartitionName="镜像名称1" FlashType="flash磁盘类型" FileSystem="文件系统类型" Start="该分区起始地址" Length="该分区大小" SelectFile="实际镜像所在路径"/> 243 <Part Sel="1" PartitionName="镜像名称2" FlashType="flash磁盘类型" FileSystem="文件系统类型" Start="该分区起始地址" Length="该分区大小" SelectFile="实际镜像所在路径"/> 244 </Partition_Info> 245 ``` 246 247 **表2** 分区表Part标签说明 248 249 | 标签名称 | 标签说明 | 250 | -------- | -------- | 251 | Sel | 该分区是否生效,1表明生效,0表明不生效。 | 252 | PartitionName | 分区名称,例如:fastboot、boot等。 | 253 | FlashType | flash磁盘类型,例如emmc、ufs等。 | 254 | FileSystem | 文件系统类型,例如ext3/4、f2fs等,也可能为none。 | 255 | Start | 分区起始位置,所有分区最起始为0,单位为兆(M)。 | 256 | Length | 分区占用长度,单位为兆(M)。 | 257 | SelectFile | 实际镜像或文件所在路径。 | 258 2598. 执行升级包制作命令。 260 261 **全量升级包** 262 263 命令如下: 264 265 266 ```text 267 python build_update.py ./target_package/ ./output_package/ -pk ./rsa_private_key2048.pem 268 ``` 269 270 - ./target_package/:指定target_package路径。 271 - ./output_package/:指定升级包输出路径。 272 - -pk ./rsa_private_key2048.pem:指定私钥文件路径。 273 274 **增量(差分)升级包** 275 276 命令如下: 277 278 279 ```text 280 python build_update.py ./target_package/ ./output_package/ -s ./source_package.zip -pk ./rsa_private_key2048.pem 281 ``` 282 283 - ./target_package/:指定target_package路径。 284 - ./output_package/:指定升级包输出路径。 285 - -s ./source_package.zip:指定“source_package.zip”路径,当存在镜像需要进行差分处理时,必须使用-s参数指定source版本包。 286 - -pk ./rsa_private_key2048.pem:指定私钥文件路径。 287 288 **变分区升级包** 289 290 命令如下: 291 292 293 ```text 294 python build_update.py ./target_package/ ./output_package/ -pk ./rsa_private_key2048.pem -pf ./partition_file.xml 295 ``` 296 297 - ./target_package/:指定target_package路径。 298 - ./output_package/:指定升级包路径。 299 - -pk ./rsa_private_key2048.pem:指定私钥文件路径。 300 - -pf ./partition_file.xml:指定分区表文件路径。 301 302 303### 上传升级包 304 305将升级包上传到厂商的OTA服务器。 306 307 308### 下载升级包 309 3101. 厂商应用从OTA服务器下载升级包。 311 3122. 对Hi3516DV300开源套件,需要插入SD卡(容量>100MBytes)。 313 314 315### 厂商应用集成OTA能力 316 3171. 轻量与小型系统 318 319 - 调用OTA模块的动态库libhota.so,对应头文件hota_partition.h和hota_updater.h路径:base\update\sys_installer_lite\interfaces\kits\。 320 - libhota.so对应的源码路径为:base\update\sys_installer_lite\frameworks\source。 321 - API的使用方法,见本文“API应用场景”和API文档的OTA接口章节。 322 - 如果需要适配开发板,请参考HAL层头文件:base\update\sys_installer_lite\hals\hal_hota_board.h。 323 3242. 标准系统请参考[JS参考规范](../../application-dev/reference/apis/js-apis-update.md)指导中的升级接口参考规范。 325 326 327#### API 应用默认场景 328 329升级包是按照上文“生成公私钥对”和“生成升级包”章节制作的。 330 331 332##### 开发指导 333 3341. 应用侧通过下载,获取当前设备升级包后,调用HotaInit接口初始化OTA模块。 335 3362. 调用HotaWrite接口传入升级包数据流,接口内部实现校验、解析及写入升级数据流。 337 3383. 写入完成后,调用HotaRestart接口重启系统,升级过程中,使用HotaCancel接口可以取消升级。 339 340 341##### 示例代码 342 343 使用OpenHarmony的“升级包格式和校验方法”进行升级。 344 345```cpp 346int main(int argc, char **argv) 347{ 348 printf("this is update print!\r\n"); 349 if (HotaInit(NULL, NULL) < 0) { 350 printf("ota update init fail!\r\n"); 351 return -1; 352 } 353 int fd = open(OTA_PKG_FILE, O_RDWR, S_IRUSR | S_IWUSR); 354 if (fd < 0) { 355 printf("file open failed, fd = %d\r\n", fd); 356 (void)HotaCancel(); 357 return -1; 358 } 359 int offset = 0; 360 int fileLen = lseek(fd, 0, SEEK_END); 361 int leftLen = fileLen; 362 while (leftLen > 0) { 363 if (lseek(fd, offset, SEEK_SET) < 0) { 364 close(fd); 365 printf("lseek fail!\r\n"); 366 (void)HotaCancel(); 367 return -1; 368 } 369 int tmpLen = leftLen >= READ_BUF_LEN ? READ_BUF_LEN : leftLen; 370 (void)memset_s(g_readBuf, READ_BUF_LEN, 0, READ_BUF_LEN); 371 if (read(fd, g_readBuf, tmpLen) < 0) { 372 close(fd); 373 printf("read fail!\r\n"); 374 (void)HotaCancel(); 375 return -1; 376 } 377 if (HotaWrite((unsigned char *)g_readBuf, offset, tmpLen) != 0) { 378 printf("ota write fail!\r\n"); 379 close(fd); 380 (void)HotaCancel(); 381 return -1; 382 } 383 offset += READ_BUF_LEN; 384 leftLen -= tmpLen; 385 } 386 close(fd); 387 printf("ota write finish!\r\n"); 388 printf("device will reboot in 10s...\r\n"); 389 sleep(10); 390 (void)HotaRestart(); 391 return 0; 392} 393``` 394 395 396#### API 应用定制场景 397 398升级包不是按照上文“生成公私钥对”和“生成升级包”章节制作的,是通过其它方式制作的。 399 400 401##### 开发指导 402 4031. 应用侧通过下载,获取当前设备升级包后,调用HotaInit接口初始化。 404 4052. 使用HotaSetPackageType接口设置NOT_USE_DEFAULT_PKG,使用"定制"流程。 406 4073. 调用HotaWrite接口传入升级包数据流,写入设备。 408 4094. 写入完成后,调用HotaRead接口读取数据,厂商可以自行校验升级包。 410 4115. 调用HotaSetBootSettings设置启动标记,在重启后需要进入uboot模式时使用(可选)。 412 4136. 调用HotaRestart接口,进行重启,升级过程中,使用HotaCancel接口可以取消升级。 414 415 416##### 示例代码 417 418 使用非OpenHarmony的“升级包格式和校验方法“进行升级。 419 420```cpp 421int main(int argc, char **argv) 422{ 423 printf("this is update print!\r\n"); 424 if (HotaInit(NULL, NULL) < 0) { 425 printf("ota update init fail!\r\n"); 426 (void)HotaCancel(); 427 return -1; 428 } 429 (void)HotaSetPackageType(NOT_USE_DEFAULT_PKG); 430 int fd = open(OTA_PKG_FILE, O_RDWR, S_IRUSR | S_IWUSR); 431 if (fd < 0) { 432 printf("file open failed, fd = %d\r\n", fd); 433 (void)HotaCancel(); 434 return -1; 435 } 436 int offset = 0; 437 int fileLen = lseek(fd, 0, SEEK_END); 438 int leftLen = fileLen; 439 while (leftLen > 0) { 440 if (lseek(fd, offset, SEEK_SET) < 0) { 441 close(fd); 442 printf("lseek fail!\r\n"); 443 (void)HotaCancel(); 444 return -1; 445 } 446 int tmpLen = leftLen >= READ_BUF_LEN ? READ_BUF_LEN : leftLen; 447 (void)memset_s(g_readBuf, READ_BUF_LEN, 0, READ_BUF_LEN); 448 if (read(fd, g_readBuf, tmpLen) < 0) { 449 close(fd); 450 printf("read fail!\r\n"); 451 (void)HotaCancel(); 452 return -1; 453 } 454 if (HotaWrite((unsigned char *)g_readBuf, offset, tmpLen) != 0) { 455 printf("ota write fail!\r\n"); 456 close(fd); 457 (void)HotaCancel(); 458 return -1; 459 } 460 offset += READ_BUF_LEN; 461 leftLen -= tmpLen; 462 } 463 close(fd); 464 printf("ota write finish!\r\n"); 465 leftLen = fileLen; 466 while (leftLen > 0) { 467 int tmpLen = leftLen >= READ_BUF_LEN ? READ_BUF_LEN : leftLen; 468 (void)memset_s(g_readBuf, READ_BUF_LEN, 0, READ_BUF_LEN); 469 if (HotaRead(offset, READ_BUF_LEN, (unsigned char *)g_readBuf) != 0) { 470 printf("ota write fail!\r\n"); 471 (void)HotaCancel(); 472 return -1; 473 } 474 /* do your verify and parse */ 475 offset += READ_BUF_LEN; 476 leftLen -= tmpLen; 477 } 478 /* set your boot settings */ 479 (void)HotaSetBootSettings(); 480 printf("device will reboot in 10s...\r\n"); 481 sleep(10); 482 (void)HotaRestart(); 483 return 0; 484} 485``` 486 487 488##### 系统升级 489 490厂商应用调用OTA模块的API,OTA模块执行升级包的签名验证、版本防回滚、烧写落盘功能,升级完成后自动重启系统。 491 492对于使用Hi3516DV300开源套件的轻量和小型系统,在需要实现防回滚功能的版本中,需要增加LOCAL_VERSION的值,如"ohos default 1.0"->"ohos default 1.1",LOCAL_VERSION在device\hisilicon\third_party\uboot\u-boot-2020.01\product\hiupdate\ota_update\ota_local_info.c中。 493 494 示例,增加版本号。 495 496```cpp 497const char *get_local_version(void) 498{ 499#if defined(CONFIG_TARGET_HI3516EV200) || \ 500 defined(CONFIG_TARGET_HI3516DV300) 501#define LOCAL_VERSION "ohos default 1.0" /* increase: default release version */ 502``` 503 504 505#### AB 升级场景 506 507 508##### 开发流程 509 5101. 应用侧下载获取当前设备升级包 5112. update_service 通过 SAMGR 将系统安装部件拉起 5123. 由系统安装部件完成静默热安装 5134. 下一次重启时激活新版本 514 515 516##### 开发步骤 517 518- JS API 通过 update_service 模块处理AB升级相关流程 519 520 1.升级包安装进度显示接口: 521 ```cpp 522 on(eventType: "upgradeProgress", callback: UpdateProgressCallback): void; 523 ``` 524 525 2.设置激活策略(立即重启,夜间重启,随下次重启激活)接口: 526 ```cpp 527 upgrade(apply) 528 ``` 529 530 531- update_service 通过 SAMGR 将系统安装服务拉起 532 533 1.拉起系统安装服务,并建立IPC连接: 534 ```cpp 535 int SysInstallerInit(void* callback) 536 ``` 537 538 2.安装指定路径的AB升级包: 539 ```cpp 540 int StartUpdatePackageZip(string path) 541 ``` 542 543 3.设置进度回调: 544 ```cpp 545 int SetUpdateProgressCallback(void* callback) 546 ``` 547 548 4.获取升级包安装状态(0 未开始,1 安装中,2 安装结束): 549 ```cpp 550 int GetUpdateStatus() 551 ``` 552 553 554- 使用 HDI 接口南向激活新版本 555 556 1.获取当前启动的slot,来决策待升级的分区: 557 ```cpp 558 int GetCurrentSlot() 559 ``` 560 561 2.在升级结束后,将已升级好的slot进行切换,重启完成新版本更新: 562 ```cpp 563 int SetActiveBootSlot(int slot) 564 ``` 565 566 3.在升级开始时,将待升级的分区slot设置成unbootable状态: 567 ```cpp 568 int setSlotUnbootable(int slot) 569 ``` 570 571 4.获取slot个数,1位非AB,2为AB分区,用例兼容AB和非AB的流程判断: 572 ```cpp 573 int32 GetSlotNum(void) 574 ``` 575 576 577##### 常见问题 578 5791. 升级包下载,安装过程出现异常 580<br>系统保持当前版本继续运行,在下一个搜包周期重新完成版本升级过程 581 5822. 升级包完成非启动分区的包安装,在激活过程中出现异常 583<br>需要进行异常回滚,并将无法启动的分区设置为 unbootable,下次则不从该分区启动 584 585 586##### 调测验证 587 588设备能在正常使用的情况下,在后台从服务器下载升级包,完成静默升级,并按照厂商设置的策略重启激活版本。