通過自定義升級程序,更直觀的理解ota升級原理。
一、模擬計算hash,驗證簽名,判斷激活分區,并通過dd命令,寫入對應分區
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <openssl/sha.h>
#include <json-c/json.h>// 1. 檢測當前激活分區(A或B)
char get_active_slot() {FILE *fp = fopen("/proc/mounts", "r");if (!fp) return 'A'; // 默認A分區char line[256];while (fgets(line, sizeof(line), fp)) {if (strstr(line, "system_a")) {fclose(fp);return 'A';}if (strstr(line, "system_b")) {fclose(fp);return 'B';}}fclose(fp);return 'A';
}// 2. 計算文件SHA256哈希
void calculate_sha256(const char *file_path, char *sha_str) {unsigned char sha_hash[SHA256_DIGEST_LENGTH];SHA256_CTX sha_ctx;SHA256_Init(&sha_ctx);FILE *fp = fopen(file_path, "rb");if (!fp) {strcpy(sha_str, "");return;}unsigned char buffer[4096];size_t bytes_read;while ((bytes_read = fread(buffer, 1, sizeof(buffer), fp)) > 0) {SHA256_Update(&sha_ctx, buffer, bytes_read);}fclose(fp);SHA256_Final(sha_hash, &sha_ctx);// 轉換為字符串for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {sprintf(sha_str + (i * 2), "%02x", sha_hash[i]);}sha_str[SHA256_DIGEST_LENGTH * 2] = '\0';
}// 3. 驗證升級包簽名(簡化示例)
int verify_signature(const char *manifest_path) {// 實際應使用OpenSSL驗證簽名,此處簡化為返回成功printf("驗證簽名: 成功\n");return 0;
}// 4. 寫入分區
int write_partition(const char *img_path, const char *part_name) {char cmd[256];snprintf(cmd, sizeof(cmd), "dd if=%s of=/dev/disk/by-name/%s bs=4M status=progress", img_path, part_name);printf("執行命令: %s\n", cmd);return system(cmd);
}// 5. 設置misc分區標志
int set_misc_flag(const char *flag) {int fd = open("/dev/disk/by-name/misc", O_WRONLY);if (fd < 0) {perror("打開misc分區失敗");return -1;}ssize_t bytes_written = write(fd, flag, strlen(flag));close(fd);if (bytes_written != strlen(flag)) {perror("寫入misc標志失敗");return -1;}printf("寫入misc標志: %s\n", flag);return 0;
}// 主函數:OTA升級流程
int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "用法: %s <ota_package_url>\n", argv[0]);return 1;}const char *ota_url = argv[1];// 步驟1:檢測當前激活分區char current_slot = get_active_slot();char target_slot = (current_slot == 'A') ? 'B' : 'A';printf("當前分區: %c, 目標分區: %c\n", current_slot, target_slot);// 步驟2:下載OTA包(簡化為本地文件)printf("下載升級包: %s\n", ota_url);// 實際應使用libcurl等工具下載,此處假設已下載到本地const char *ota_package = "ota_package.zip";system("unzip -q ota_package.zip -d ota_temp"); // 解壓到臨時目錄// 步驟3:解析manifest并驗證struct json_object *manifest = json_object_from_file("ota_temp/manifest.json");if (!manifest) {fprintf(stderr, "解析manifest失敗\n");return 1;}// 驗證簽名if (verify_signature("ota_temp/manifest.json") != 0) {fprintf(stderr, "簽名驗證失敗\n");return 1;}// 步驟4:寫入目標分區struct json_object *partitions;json_object_object_get_ex(manifest, "partitions", &partitions);int n = json_object_array_length(partitions);for (int i = 0; i < n; i++) {struct json_object *part = json_object_array_get_idx(partitions, i);const char *name = json_object_get_string(json_object_object_get(part, "name"));const char *img = json_object_get_string(json_object_object_get(part, "image"));const char *expected_sha = json_object_get_string(json_object_object_get(part, "sha256"));// 拼接目標分區名(如boot -> boot_b)char target_part[32];snprintf(target_part, sizeof(target_part), "%s_%c", name, target_slot);// 校驗鏡像哈希char actual_sha[65];char img_path[64];snprintf(img_path, sizeof(img_path), "ota_temp/%s", img);calculate_sha256(img_path, actual_sha);if (strcmp(actual_sha, expected_sha) != 0) {fprintf(stderr, "%s 哈希校驗失敗\n", img);return 1;}// 寫入分區if (write_partition(img_path, target_part) != 0) {fprintf(stderr, "寫入%s失敗\n", target_part);return 1;}}// 步驟5:設置啟動標志,重啟char misc_flag[128];snprintf(misc_flag, sizeof(misc_flag), "target_slot=%c\nboot_retry_count=3\n", target_slot);if (set_misc_flag(misc_flag) != 0) {fprintf(stderr, "設置misc標志失敗\n");return 1;}printf("升級準備完成,重啟中...\n");system("reboot");return 0;
}
二、rk3568上開啟ota,ab分區
將每個分區的hash寫入json,打包進入下載包。
寫一個ota下載,驗證進程。
通過fuse里或某個只讀分區,讀出公鑰的哈希,計算升級包里的公鑰哈希并與前面的哈希對比
驗證公鑰的安全,然后對鏡像包的哈希進行解密,獲得哈希,并計算升級包的鏡像分區哈希,進行對比
通過則升級。
寫入完后,還需要讀寫出來,進行比較,失敗則重新刷寫3次為止。
這里還需要做一個超時判斷,若超時則失敗,回滾到a分區,
斷電保護:寫入分區時建議分塊寫入并記錄日志,斷電電后可從斷點續傳;
四、參考demo
build_ota_package.sh,打包升級包的腳本,打包為zip
#!/bin/bash
# 生成OTA升級包(包含鏡像、manifest.json和簽名)# 配置
OTA_DIR="ota_temp"
OTA_PACKAGE="ota_v1.1.zip"
TARGET_SLOT="B" # 目標分區(實際應根據當前版本動態設置)# 創建臨時目錄
rm -rf $OTA_DIR
mkdir -p $OTA_DIR# 復制鏡像文件(假設已編譯好)
cp ../build/boot.img $OTA_DIR/
cp ../build/system.img $OTA_DIR/
cp ../build/vendor.img $OTA_DIR/# 生成manifest.json
cat > $OTA_DIR/manifest.json << EOF
{"version": "v1.1","target_slot": "$TARGET_SLOT","partitions": [{"name": "boot","image": "boot.img","sha256": "$(sha256sum $OTA_DIR/boot.img | awk '{print $1}')"},{"name": "system","image": "system.img","sha256": "$(sha256sum $OTA_DIR/system.img | awk '{print $1}')"},{"name": "vendor","image": "vendor.img","sha256": "$(sha256sum $OTA_DIR/vendor.img | awk '{print $1}')"}],"signature": "dummy_signature" # 實際應替換為RSA簽名
}
EOF# 生成簽名(示例:使用openssl生成RSA簽名)
# openssl dgst -sha256 -sign private.key -out $OTA_DIR/manifest.sig $OTA_DIR/manifest.json# 打包為OTA包
cd $OTA_DIR && zip -r ../$OTA_PACKAGE ./* && cd ..echo "OTA包生成完成: $OTA_PACKAGE"
rm -rf $OTA_DIR
ota_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <openssl/sha.h>
#include <json-c/json.h>// 1. 檢測當前激活分區(A或B)
char get_active_slot() {FILE *fp = fopen("/proc/mounts", "r");if (!fp) return 'A'; // 默認A分區char line[256];while (fgets(line, sizeof(line), fp)) {if (strstr(line, "system_a")) {fclose(fp);return 'A';}if (strstr(line, "system_b")) {fclose(fp);return 'B';}}fclose(fp);return 'A';
}// 2. 計算文件SHA256哈希
void calculate_sha256(const char *file_path, char *sha_str) {unsigned char sha_hash[SHA256_DIGEST_LENGTH];SHA256_CTX sha_ctx;SHA256_Init(&sha_ctx);FILE *fp = fopen(file_path, "rb");if (!fp) {strcpy(sha_str, "");return;}unsigned char buffer[4096];size_t bytes_read;while ((bytes_read = fread(buffer, 1, sizeof(buffer), fp)) > 0) {SHA256_Update(&sha_ctx, buffer, bytes_read);}fclose(fp);SHA256_Final(sha_hash, &sha_ctx);// 轉換為字符串for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {sprintf(sha_str + (i * 2), "%02x", sha_hash[i]);}sha_str[SHA256_DIGEST_LENGTH * 2] = '\0';
}// 3. 驗證升級包簽名(簡化示例)
int verify_signature(const char *manifest_path) {// 實際應使用OpenSSL驗證簽名,此處簡化為返回成功printf("驗證簽名: 成功\n");return 0;
}// 4. 寫入分區
int write_partition(const char *img_path, const char *part_name) {char cmd[256];snprintf(cmd, sizeof(cmd), "dd if=%s of=/dev/disk/by-name/%s bs=4M status=progress", img_path, part_name);printf("執行命令: %s\n", cmd);return system(cmd);
}// 5. 設置misc分區標志
int set_misc_flag(const char *flag) {int fd = open("/dev/disk/by-name/misc", O_WRONLY);if (fd < 0) {perror("打開misc分區失敗");return -1;}ssize_t bytes_written = write(fd, flag, strlen(flag));close(fd);if (bytes_written != strlen(flag)) {perror("寫入misc標志失敗");return -1;}printf("寫入misc標志: %s\n", flag);return 0;
}// 主函數:OTA升級流程
int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "用法: %s <ota_package_url>\n", argv[0]);return 1;}const char *ota_url = argv[1];// 步驟1:檢測當前激活分區char current_slot = get_active_slot();char target_slot = (current_slot == 'A') ? 'B' : 'A';printf("當前分區: %c, 目標分區: %c\n", current_slot, target_slot);// 步驟2:下載OTA包(簡化為本地文件)printf("下載升級包: %s\n", ota_url);// 實際應使用libcurl等工具下載,此處假設已下載到本地const char *ota_package = "ota_package.zip";system("unzip -q ota_package.zip -d ota_temp"); // 解壓到臨時目錄// 步驟3:解析manifest并驗證struct json_object *manifest = json_object_from_file("ota_temp/manifest.json");if (!manifest) {fprintf(stderr, "解析manifest失敗\n");return 1;}// 驗證簽名if (verify_signature("ota_temp/manifest.json") != 0) {fprintf(stderr, "簽名驗證失敗\n");return 1;}// 步驟4:寫入目標分區struct json_object *partitions;json_object_object_get_ex(manifest, "partitions", &partitions);int n = json_object_array_length(partitions);for (int i = 0; i < n; i++) {struct json_object *part = json_object_array_get_idx(partitions, i);const char *name = json_object_get_string(json_object_object_get(part, "name"));const char *img = json_object_get_string(json_object_object_get(part, "image"));const char *expected_sha = json_object_get_string(json_object_object_get(part, "sha256"));// 拼接目標分區名(如boot -> boot_b)char target_part[32];snprintf(target_part, sizeof(target_part), "%s_%c", name, target_slot);// 校驗鏡像哈希char actual_sha[65];char img_path[64];snprintf(img_path, sizeof(img_path), "ota_temp/%s", img);calculate_sha256(img_path, actual_sha);if (strcmp(actual_sha, expected_sha) != 0) {fprintf(stderr, "%s 哈希校驗失敗\n", img);return 1;}// 寫入分區if (write_partition(img_path, target_part) != 0) {fprintf(stderr, "寫入%s失敗\n", target_part);return 1;}}// 步驟5:設置啟動標志,重啟char misc_flag[128];snprintf(misc_flag, sizeof(misc_flag), "target_slot=%c\nboot_retry_count=3\n", target_slot);if (set_misc_flag(misc_flag) != 0) {fprintf(stderr, "設置misc標志失敗\n");return 1;}printf("升級準備完成,重啟中...\n");system("reboot");return 0;
}
verify_new_system.c,重啟后進行校驗是否能正常啟動。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>// 檢查關鍵服務是否啟動
int check_critical_services() {// 檢查sshd服務if (system("pgrep sshd > /dev/null 2>&1") != 0) {printf("sshd服務未啟動\n");return -1;}// 檢查應用程序if (system("pgrep my_app > /dev/null 2>&1") != 0) {printf("應用程序my_app未啟動\n");return -1;}return 0;
}// 設置misc分區標志
int set_misc_flag(const char *flag) {int fd = open("/dev/disk/by-name/misc", O_WRONLY);if (fd < 0) {perror("打開misc分區失敗");return -1;}ssize_t bytes_written = write(fd, flag, strlen(flag));close(fd);return (bytes_written == strlen(flag)) ? 0 : -1;
}// 檢測當前啟動的分區
char get_current_slot() {FILE *fp = fopen("/proc/mounts", "r");if (!fp) return 'A';char line[256];while (fgets(line, sizeof(line), fp)) {if (strstr(line, "system_a")) {fclose(fp);return 'A';}if (strstr(line, "system_b")) {fclose(fp);return 'B';}}fclose(fp);return 'A';
}int main() {char current_slot = get_current_slot();printf("當前啟動分區: %c,開始驗證...\n", current_slot);// 驗證系統狀態if (check_critical_services() != 0) {printf("系統驗證失敗,準備回滾\n");char flag[64];snprintf(flag, sizeof(flag), "boot_failed=%c\n", current_slot);set_misc_flag(flag);system("reboot"); // 重啟后U-Boot會回滾return 1;}// 驗證成功,標記分區為穩定printf("系統驗證成功,更新分區狀態\n");char flag[128];snprintf(flag, sizeof(flag), "slot_successful=%c\ntarget_slot=%c\n", current_slot, current_slot);set_misc_flag(flag);return 0;
}
有了ab分區,在uboot啟動時,運行一段時間后,如出現崩潰,可以在uboot失敗多次,嘗試回滾到a區
總結,rk和nvidia的方案,是有些區別的,nvidia是可以更新uboot,kernel,system,因為nvidia把uboot分出去了
到cboot,cboot代替uboot。rk的芯片是沒有cboot的,所以uboot是不能更新的。