OTA远程升级
适用平台: SHIMETAPI-M5-R2A V1.0底板 + C-3588LQ V1.0核心板
Linux内核: 6.1 (Debian 12 / Ubuntu 22.04)
文档版本: V2.0
难度等级: 高级
1. 概述
1.1 功能简介
OTA(Over-The-Air,空中升级)指的是设备通过网络远程更新固件,不必拆机接烧录器。RK3588 的 OTA 系统为嵌入式设备提供安全、可靠的固件更新能力,基于 A/B 双分区方案(eMMC 上同时保留两套系统分区,升级时把新固件写进不活动的那套,重启后切换过去,出问题再切回旧分区),通过 eMMC 5.1(sdhci@fe2e0000 控制器)管理双启动槽,支持多种升级框架:
- RAUC(Robust Auto-Update Controller): 推荐方案,提供 A/B 分区管理、签名验证、自动回滚
- swupdate: 轻量级嵌入式 Linux 软件更新框架,支持 Hawkbit 集成
- Mender: 基于 Yocto 的 OTA 方案,支持增量升级和设备组管理
- sysupgrade(OpenWrt): 适用于 OpenWrt 体系的升级方案
核心能力如下:
- A/B 分区无缝切换,升级过程无业务中断
- 固件签名验证(RSA-2048/4096),防止未授权固件写入
- 自动回滚机制,升级失败时恢复上一版本
- 断点续传与差量升级(delta update,只下载新旧版本的差异部分),减少下载数据量
- HTTPS 安全传输,保障固件下载安全
- U-Boot Bootloader 在线升级
1.2 工作原理
┌──────────────────────────────────────────────────────────────────┐
│ RK3588 OTA升级架构 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ OTA服务器 │──▶│ 客户端Agent│──▶│ 签名验证 │──▶│ 写入非活动分区│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ HTTPS/TLS RSA/ECDSA eMMC写入 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ U-Boot │──▶│ 重启选择 │──▶│ 启动验证 │ │
│ │ slot切换 │ │ 新分区启动│ │ 标记good │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 失败回滚 │ │
│ └──────────┘ │
└──────────────────────────────────────────────────────────────────┘A/B 分区切换原理:
eMMC 5.1 提供 boot0/boot1 两个独立启动分区用于 U-Boot SPL 存储,配合 rootfs-A/rootfs-B 两个系统分区实现完整的 A/B 方案。U-Boot 通过环境变量 bootpart 选择启动槽,内核侧通过 rauc 或 mender 标记启动状态。
1.3 升级框架对比
| 特性 | RAUC | swupdate | Mender | sysupgrade |
|---|---|---|---|---|
| 分区方案 | A/B | A/B/单分区 | A/B | 单分区覆盖 |
| 签名验证 | 支持 | 支持 | 支持 | 支持 |
| 差量升级 | 插件支持 | webupdate | 支持 | 不支持 |
| U-Boot升级 | 支持 | 支持 | 支持 | 部分支持 |
| 回滚机制 | 自动 | 手动/自动 | 自动 | 无 |
| 适用场景 | 通用嵌入式 | 轻量级 | Yocto体系 | OpenWrt |
| RK3588推荐度 | 推荐 | 可选 | 可选 | 特定场景 |
2. 硬件准备
2.3 运行环境(本机)
下表是本机结果:
| 项 | 实际值 |
|---|---|
| RAUC / swupdate / Mender / update-engine | 全部未安装(which 无输出) |
| Rockchip upgrade_tool | 已装(/usr/bin/upgrade_tool,线刷用) |
| eMMC 分区(PARTLABEL 实际值) | uboot(4M) misc(4M) boot(64M) recovery(128M) backup(32M) rootfs(14G) oem(128M) userdata(14.8G) |
| 内核 cmdline | root=PARTUUID=614e0000-0000 ... androidboot.verifiedbootstate=orange |
结论:出厂分区是单 rootfs 加独立 recovery 分区的布局(Rockchip recovery 升级方案),不是 4.1 节的 A/B 双 rootfs 布局;要让 RAUC/swupdate 落地,需要自行安装并重新分区。本章 4.x 的变更类操作(改分区、切换槽位、刷 bootloader)在压测机上没有执行过:这些都是破坏性操作,会直接中断压测,还可能把设备刷成砖,请在备用设备上验证。
2.1 硬件清单
| 序号 | 硬件名称 | 型号/规格 | 数量 | 用途 |
|---|---|---|---|---|
| 1 | 核心板 | C-3588LQ V1.0 | 1 | 主控制器(RK3588 SoC) |
| 2 | 底板 | SHIMETAPI-M5-R2A V1.0 | 1 | 外设接口 |
| 3 | eMMC存储 | 32GB+ eMMC 5.1 | 1 | A/B双分区固件存储 |
| 4 | 以太网 | 千兆网口(底板RJ45) | 1 | OTA网络下载 |
| 5 | USB Type-C线 | 数据线 | 1 | OTG烧录恢复(底板J6) |
| 6 | 电源适配器 | 12V/3A DC | 1 | 稳定供电 |
| 7 | 调试串口线 | TTL转USB | 1 | 串口调试(1500000波特率) |
2.2 硬件确认
升级方案依赖 eMMC 分区和网络,动手前先逐项确认:
# RK3588 OTA硬件检查
echo "=== eMMC设备 ==="
lsblk | grep mmcblk0
cat /sys/block/mmcblk0/device/name 2>/dev/null
cat /sys/block/mmcblk0/size 2>/dev/null | awk '{printf "容量: %.1f GB\n", $1*512/1024/1024/1024}'
echo "=== 网络接口 ==="
ip -br link show | grep -E "eth0|end0"
echo "=== eMMC分区表 ==="
sudo fdisk -l /dev/mmcblk0 2>/dev/null
echo "=== 启动槽状态 ==="
cat /sys/class/block/mmcblk0boot0/ro 2>/dev/null && echo "boot0只读状态: $(cat /sys/class/block/mmcblk0boot0/ro)"
cat /sys/class/block/mmcblk0boot1/ro 2>/dev/null && echo "boot1只读状态: $(cat /sys/class/block/mmcblk0boot1/ro)"3. 软件准备
3.1 RAUC安装(推荐方案)
RAUC 是本章的推荐方案。编译安装、启动守护进程后,rauc --version 能正常输出说明安装成功:
# 安装依赖
sudo apt update
sudo apt install -y \
libglib2.0-dev libcurl4-openssl-dev libssl-dev \
libsystemd-dev libdbus-1-dev libjson-glib-dev \
meson ninja-build squashfs-tools
# 编译安装RAUC
git clone --recursive https://github.com/rauc/rauc.git
cd rauc && meson setup builddir
ninja -C builddir && sudo ninja -C builddir install
# 启动RAUC守护进程
sudo systemctl enable raucd
sudo systemctl start raucd
rauc --version3.2 swupdate安装(备选方案)
# 安装swupdate依赖
sudo apt install -y \
libconfig-dev libcurl4-openssl-dev libarchive-dev \
libsystemd-dev libdbus-1-dev libxml2-dev \
lua5.3 liblua5.3-dev
# 编译安装swupdate
git clone https://github.com/sbabic/swupdate.git
cd swupdate
make menuconfig # 启用A/B分区、HTTPS、Hawkbit支持
make -j$(nproc)
sudo make install
# 启动swupdate
sudo systemctl enable swupdate
sudo systemctl start swupdate
swupdate --version3.3 Mender Agent安装(备选方案)
# Mender客户端安装
wget https://downloads.mender.io/client/debian/11/arm64/mender-client_4.0.0+debian_arm64.deb
sudo dpkg -i mender-client_4.0.0+debian_arm64.deb
# 配置Mender
sudo tee /etc/mender/mender.conf << 'EOF'
{
"InventoryPollIntervalSeconds": 300,
"UpdatePollIntervalSeconds": 900,
"RetryPollIntervalSeconds": 300,
"RootfsPartA": "/dev/mmcblk0p3",
"RootfsPartB": "/dev/mmcblk0p4",
"ServerURL": "https://mender.example.com",
"TenantToken": "<your-tenant-token>"
}
EOF
sudo systemctl enable mender-client
sudo systemctl start mender-client3.4 签名工具与密钥生成
固件要用私钥签名、设备端用公钥验证,先在打包机上生成这套密钥:
# 生成RSA-4096签名密钥对
mkdir -p /etc/ota/keys && cd /etc/ota/keys
# 生成私钥(签名服务器使用,设备端不存储)
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:4096
# 生成公钥(设备端验证使用)
openssl rsa -pubout -in private.pem -out public.pem
# 生成CA证书
openssl req -new -x509 -key private.pem -out ca.pem \
-days 3650 -subj "/CN=RK3588 OTA CA/O=ShimetaPi"
# 验证密钥
openssl rsa -in private.pem -check -noout && echo "私钥有效"
openssl rsa -pubin -in public.pem -noout -text | head -24. 操作步骤
4.1 eMMC分区方案
RK3588 eMMC A/B 分区典型布局如下,第一段是分区方案概览,第二段是实际创建命令。注意创建分区会清除 eMMC 上所有数据,动手前先备份:
# 分区方案概览
cat << 'EOF'
eMMC 5.1 A/B分区布局 (sdhci@fe2e0000):
┌──────────┬───────────┬────────┬──────────────────────┐
│ 分区 │ 起始块 │ 大小 │ 用途 │
├──────────┼───────────┼────────┼──────────────────────┤
│ boot0 │ 0x000000 │ 4MB │ U-Boot SPL A │
│ boot1 │ 0x004000 │ 4MB │ U-Boot SPL B │
│ rootfsA │ 0x008000 │ 8GB │ 系统分区A │
│ rootfsB │ 0x208000 │ 8GB │ 系统分区B │
│ data │ 0x408000 │ 剩余 │ 用户数据(不随OTA擦除)│
└──────────┴───────────┴────────┴──────────────────────┘
EOF# 创建A/B分区(警告:会清除所有数据)
sudo parted -s /dev/mmcblk0 mklabel gpt
sudo parted -s /dev/mmcblk0 mkpart primary 1MiB 5MiB # boot0
sudo parted -s /dev/mmcblk0 mkpart primary 5MiB 9MiB # boot1
sudo parted -s /dev/mmcblk0 mkpart primary 9MiB 8193MiB # rootfsA
sudo parted -s /dev/mmcblk0 mkpart primary 8193MiB 16385MiB # rootfsB
sudo parted -s /dev/mmcblk0 mkpart primary 16385MiB 100% # data
# 格式化系统分区
sudo mkfs.ext4 -L rootfsA /dev/mmcblk0p3
sudo mkfs.ext4 -L rootfsB /dev/mmcblk0p4
sudo mkfs.ext4 -L data /dev/mmcblk0p5
# 格式化boot分区(原始分区,由U-Boot直接访问)
sudo dd if=/dev/zero of=/dev/mmcblk0boot0 bs=4M count=1
sudo dd if=/dev/zero of=/dev/mmcblk0boot1 bs=4M count=1分完后用 lsblk 或 sudo fdisk -l /dev/mmcblk0 复查分区表,确认 5 个分区和预期一致。
4.2 RAUC系统配置
# /etc/rauc/system.conf
[system]
compatible = "shimetapi,rk3588"
variant = ""
bootloader = "custom"
[keyring]
path = "/etc/rauc/keyring.pem"
[slot.bootloader.0]
device = "/dev/mmcblk0boot0"
type = "raw"
bootname = "0"
[slot.bootloader.1]
device = "/dev/mmcblk0boot1"
type = "raw"
bootname = "1"
[slot.rootfs.0]
device = "/dev/mmcblk0p3"
type = "ext4"
bootname = "0"
bootargs = "root=/dev/mmcblk0p3 rw rootwait console=ttyS2,1500000"
[slot.rootfs.1]
device = "/dev/mmcblk0p4"
type = "ext4"
bootname = "1"
bootargs = "root=/dev/mmcblk0p4 rw rootwait console=ttyS2,1500000"
[data]
device = "/dev/mmcblk0p5"
type = "ext4"
[mark-good]
service = "rauc-mark-good.service"4.3 U-Boot Bootloader升级
U-Boot 是 OTA 升级中最关键也最危险的环节,刷坏 bootloader 设备就直接起不来了。操作前确认镜像来源可靠,并准备好 USB OTG 恢复手段(底板 J6 口线刷):
# 打包RAUC固件包(含bootloader)
#!/bin/bash
VERSION="$1"
BUILD_DIR="/tmp/rauc-build"
OUTPUT_DIR="/opt/ota/firmware"
mkdir -p $BUILD_DIR/{bootloader,rootfs}
# 复制bootloader镜像
cp u-boot.img $BUILD_DIR/bootloader/
cp idbloader.img $BUILD_DIR/bootloader/
cp trust.img $BUILD_DIR/bootloader/
# 打包rootfs为ext4镜像
sudo dd if=/dev/zero of=$BUILD_DIR/rootfs/rootfs.img bs=1M count=8192
sudo mkfs.ext4 -F $BUILD_DIR/rootfs/rootfs.img
mkdir -p /tmp/rootfs-mount
sudo mount $BUILD_DIR/rootfs/rootfs.img /tmp/rootfs-mount
sudo tar -cf - -C /path/to/rootfs . | sudo tar -xf - -C /tmp/rootfs-mount
sudo umount /tmp/rootfs-mount
# 创建RAUC manifest
cat > $BUILD_DIR/manifest.raucm << EOF
[update]
compatible=shimetapi,rk3588
version=$VERSION
[image.bootloader]
filename=bootloader.img
size=$(stat -c%s $BUILD_DIR/bootloader/u-boot.img)
[image.rootfs]
filename=rootfs.img
size=$(stat -c%s $BUILD_DIR/rootfs/rootfs.img)
EOF
# 生成RAUC bundle
rauc bundle \
--cert=/etc/ota/keys/ca.pem \
--key=/etc/ota/keys/private.pem \
$BUILD_DIR \
$OUTPUT_DIR/update-${VERSION}.raucb4.4 安全OTA传输(HTTPS)
固件走公网传输,必须用 HTTPS 加密通道,否则固件可能被窃听或替换:
# 使用Let's Encrypt证书配置HTTPS OTA服务器
sudo apt install -y certbot nginx
# 申请证书
sudo certbot certonly --standalone -d ota.example.com
# Nginx OTA服务器配置
sudo tee /etc/nginx/sites-available/ota << 'EOF'
server {
listen 443 ssl;
server_name ota.example.com;
ssl_certificate /etc/letsencrypt/live/ota.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ota.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
location /api/v1/check {
proxy_pass http://127.0.0.1:8080/api/v1/check;
}
location /firmware/ {
alias /opt/ota/firmware/;
autoindex off;
add_header Content-Disposition "attachment";
}
}
EOF
sudo ln -s /etc/nginx/sites-available/ota /etc/nginx/sites-enabled/
sudo systemctl reload nginx4.5 差量升级(Delta Update)
差量升级通过比较新旧固件生成 delta 包,设备端只需下载差异部分,能明显减少下载数据量:
# 使用xdelta3生成差量包
sudo apt install -y xdelta3
# 生成差量包(需旧版本固件作为参考)
xdelta3 -e -s old-rootfs.img new-rootfs.img delta.bin
# 在设备端应用差量包
xdelta3 -d -s old-rootfs.img delta.bin recovered-new-rootfs.img
# 验证恢复后的镜像
sha256sum recovered-new-rootfs.img new-rootfs.img
# 两个SHA256值应相同最后一步两个 SHA256 值相同,说明差量恢复出的镜像和原始新固件完全一致。
4.6 自动升级与回滚
升级成功与否要靠系统自己判断:启动成功就把当前分区标记为 good,连续多次启动失败则回滚到另一个分区。
# /etc/rauc/rauc-hook.sh - 启动成功标记脚本
#!/bin/bash
# 系统启动成功后调用,标记当前分区为good
rauc status mark-good
logger "OTA: 启动成功,已标记当前分区为good"
# /etc/systemd/system/rauc-mark-good.service
[Unit]
Description=Mark boot as good
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/etc/rauc/rauc-hook.sh
[Install]
WantedBy=multi-user.target
# 启用服务
sudo systemctl enable rauc-mark-good.service# 自动回滚检查脚本
#!/bin/bash
# /opt/ota/check-rollback.sh
BOOT_COUNT=$(cat /sys/class/block/mmcblk0boot0/boot_count 2>/dev/null || echo "0")
MAX_BOOT=3
if [ "$BOOT_COUNT" -ge "$MAX_BOOT" ]; then
echo "启动失败次数过多($BOOT_COUNT),执行回滚..."
CURRENT_SLOT=$(rauc status --output-format=compact | grep -o '[01]' | head -1)
NEW_SLOT=$((1 - CURRENT_SLOT))
# 切换U-Boot启动槽
fw_setenv bootpart $NEW_SLOT
# 重启
reboot
fi5. 代码详解
5.1 RAUC升级客户端
/* /opt/ota/ota_client.c - RK3588 OTA客户端核心逻辑 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <openssl/sha.h>
#include <json-c/json.h>
#define OTA_SERVER "https://ota.example.com"
#define CERT_PATH "/etc/ota/ca.pem"
#define FIRMWARE_DIR "/tmp/ota"
/* 下载进度回调 */
static size_t progress_callback(void *ptr, size_t size, size_t nmemb, void *data) {
double *total = (double *)data;
*total += (double)(size * nmemb);
fprintf(stderr, "\r下载进度: %.1f%%", *total / (1024*1024) * 100);
return size * nmemb;
}
/* HTTPS下载固件 */
int download_firmware(const char *url, const char *output) {
CURL *curl;
CURLcode res;
double downloaded = 0;
curl = curl_easy_init();
if (!curl) return -1;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CAINFO, CERT_PATH);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &downloaded);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
FILE *fp = fopen(output, "wb");
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl);
fclose(fp);
curl_easy_cleanup(curl);
return (res == CURLE_OK) ? 0 : -1;
}
/* SHA-256校验 */
int verify_sha256(const char *file, const char *expected) {
unsigned char hash[SHA256_DIGEST_LENGTH];
unsigned char buf[8192];
SHA256_CTX ctx;
FILE *fp;
SHA256_Init(&ctx);
fp = fopen(file, "rb");
while (fread(buf, 1, sizeof(buf), fp) > 0)
SHA256_Update(&ctx, buf, sizeof(buf));
SHA256_Final(hash, &ctx);
fclose(fp);
char hex[65];
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
sprintf(hex + i*2, "%02x", hash[i]);
hex[64] = '\0';
return (strcmp(hex, expected) == 0) ? 0 : -1;
}
/* 检查更新 */
int check_update(const char *server, char *version, char *url) {
char api_url[256];
snprintf(api_url, sizeof(api_url), "%s/api/v1/check", server);
/* 使用libcurl获取JSON响应并解析 */
/* ... HTTP GET请求省略 ... */
return 0;
}
int main(int argc, char *argv[]) {
curl_global_init(CURL_GLOBAL_DEFAULT);
char version[32], url[512];
if (check_update(OTA_SERVER, version, url) == 0) {
printf("发现新版本: %s\n", version);
char output[256];
snprintf(output, sizeof(output), "%s/update-%s.raucb", FIRMWARE_DIR, version);
if (download_firmware(url, output) == 0) {
/* 调用RAUC安装 */
char cmd[512];
snprintf(cmd, sizeof(cmd), "rauc install %s", output);
system(cmd);
}
}
curl_global_cleanup();
return 0;
}5.2 分区管理程序
/* /opt/ota/partition_mgr.c - eMMC A/B分区管理 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define EMMC_BOOT0 "/sys/class/block/mmcblk0boot0/ro"
#define EMMC_BOOT1 "/sys/class/block/mmcblk0boot1/ro"
/* 获取当前活动槽位 (0=A, 1=B) */
int get_active_slot(void) {
FILE *fp = fopen(EMMC_BOOT0, "r");
if (!fp) return 0;
int ro;
fscanf(fp, "%d", &ro);
fclose(fp);
return ro; /* boot0只读=0表示A活动, =1表示B活动 */
}
/* 切换活动槽位 */
int switch_slot(int new_slot) {
FILE *fp;
int old_slot = 1 - new_slot;
/* 旧槽设为只读 */
fp = fopen(new_slot == 0 ? EMMC_BOOT1 : EMMC_BOOT0, "w");
if (fp) { fprintf(fp, "1"); fclose(fp); }
/* 新槽设为读写 */
fp = fopen(new_slot == 0 ? EMMC_BOOT0 : EMMC_BOOT1, "w");
if (fp) { fprintf(fp, "0"); fclose(fp); }
printf("槽位切换: %d -> %d\n", old_slot, new_slot);
return 0;
}
/* 打印分区状态 */
void print_status(void) {
FILE *fp;
int ro;
printf("=== RK3588 A/B分区状态 ===\n");
printf("当前活动槽位: %d (%s)\n", get_active_slot(),
get_active_slot() == 0 ? "A" : "B");
fp = fopen(EMMC_BOOT0, "r");
if (fp) { fscanf(fp, "%d", &ro); fclose(fp);
printf("boot0 (槽A): %s\n", ro ? "非活动" : "活动"); }
fp = fopen(EMMC_BOOT1, "r");
if (fp) { fscanf(fp, "%d", &ro); fclose(fp);
printf("boot1 (槽B): %s\n", ro ? "非活动" : "活动"); }
}
int main(int argc, char *argv[]) {
if (argc < 2) { print_status(); return 0; }
if (strcmp(argv[1], "switch") == 0 && argc >= 3) {
int slot = atoi(argv[2]);
return switch_slot(slot);
}
return 0;
}5.3 固件签名验证
/* /opt/ota/firmware_verify.c - 固件完整性验证 */
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
/* 验证固件签名 */
int verify_firmware_signature(const char *firmware,
const char *sig_file,
const char *pubkey_pem) {
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
EVP_PKEY *pkey = NULL;
FILE *fp;
unsigned char buf[8192];
size_t len;
int result = -1;
/* 加载公钥 */
fp = fopen(pubkey_pem, "r");
if (!fp) goto cleanup;
pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
fclose(fp);
if (!pkey) goto cleanup;
/* 初始化验证上下文 */
EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey);
/* 读取固件并更新摘要 */
fp = fopen(firmware, "rb");
if (!fp) goto cleanup;
while ((len = fread(buf, 1, sizeof(buf), fp)) > 0)
EVP_DigestVerifyUpdate(mdctx, buf, len);
fclose(fp);
/* 读取签名 */
unsigned char sig[512];
fp = fopen(sig_file, "rb");
if (!fp) goto cleanup;
int sig_len = fread(sig, 1, sizeof(sig), fp);
fclose(fp);
/* 执行验证 */
result = EVP_DigestVerifyFinal(mdctx, sig, sig_len);
cleanup:
EVP_MD_CTX_free(mdctx);
EVP_PKEY_free(pkey);
return (result == 1) ? 0 : -1;
}
int main(int argc, char *argv[]) {
if (argc != 4) {
fprintf(stderr, "用法: %s <固件> <签名文件> <公钥>\n", argv[0]);
return 1;
}
return verify_firmware_signature(argv[1], argv[2], argv[3]) == 0
? (printf("签名验证通过\n"), 0)
: (fprintf(stderr, "签名验证失败\n"), 1);
}6. 常见问题
6.1 OTA升级后系统无法启动
原因: 新固件不兼容,启动槽配置错误。
解决方案:
# 在U-Boot命令行中手动切换启动槽
# 串口终端按任意键中断自动启动
printenv bootpart # 查看当前启动槽
setenv bootpart 0 # 切换到槽A (0或1)
saveenv
reset
# 如果仍无法启动,使用USB OTG恢复
# 连接底板J6口,使用rkdevtool重新烧录6.2 固件签名验证失败
原因: 公钥与签名服务器私钥不匹配,或证书过期。
# 检查RAUC日志
sudo journalctl -u raucd -n 50
# 验证密钥匹配
openssl rsa -pubin -in /etc/rauc/keyring.pem -text -noout | head -5
# 检查证书有效期
openssl x509 -in /etc/rauc/keyring.pem -noout -dates
# 重新导入密钥环
rauc status mark-active6.3 差量升级失败
原因: 参考镜像不匹配,磁盘空间不足。
# 检查当前系统镜像
md5sum /dev/mmcblk0p3 # 与delta参考对比
# 检查/tmp空间
df -h /tmp
# 清理临时文件后重试
rm -rf /tmp/rauc-*
rauc install --force firmware.raucb6.4 swupdate Web界面无法访问
原因: 网络配置或防火墙问题。
# 检查swupdate进程
ps aux | grep swupdate
# 检查端口监听
ss -tlnp | grep 8080
# 检查防火墙
sudo ufw status
sudo iptables -L INPUT -n
# 手动启动swupdate(带Web界面)
swupdate -l port=8080 -L www6.5 eMMC写入失败
原因: eMMC坏块、权限不足。
# 检查eMMC健康状态
cat /sys/block/mmcblk0/device/life_time_est_typ_a
cat /sys/block/mmcblk0/device/life_time_est_typ_b
# 检查写保护状态
cat /sys/block/mmcblk0boot0/ro
cat /sys/block/mmcblk0boot1/ro
# 切换为读写模式
echo 0 > /sys/class/block/mmcblk0boot0/ro6.6 网络中断后固件下载不完整
原因: 断点续传未启用或服务器不支持。
# 使用wget断点续传下载
wget -c -O /tmp/firmware.raucb https://ota.example.com/firmware/update.raucb
# 校验下载完整性
sha256sum /tmp/firmware.raucb
# 对比服务器提供的SHA256值7. 相关文档
| 文档名称 | 文件路径 | 说明 |
|---|---|---|
| RK3588 Linux开发指南 | Linux/Rockchip_Developer_Guide_Linux_Software_CN.pdf | 分区管理、OTA流程 |
| RK3588 TRM手册 | HW/TRM/RK3588_TRM_CN_V1.0-20220926.pdf | eMMC控制器寄存器 |
| 核心板规格书 | HW/0-核心板/C-3588LQ核心板硬件规格书-V1.0- Beta版本-20240606.pdf | eMMC接口、存储规格 |
| 底板原理图 | HW/1-底板/SHIMETAPI-M5-R2A底板原理图_V1_0_20241217-lceda.pdf | 网口、USB-OTG连接 |
| RAUC官方文档 | https://rauc.io/docs/latest/ | RAUC配置与使用 |
| swupdate官方文档 | https://sbabic.github.io/swupdate/ | swupdate集成指南 |
| Mender官方文档 | https://docs.mender.io/ | Mender客户端配置 |
