Android HAL层通过Diag接口备份与恢复高通NV分区数据实战

张开发
2026/4/17 12:21:15 15 分钟阅读

分享文章

Android HAL层通过Diag接口备份与恢复高通NV分区数据实战
1. 为什么需要备份高通NV分区数据第一次接触高通NV分区是在2015年做手机产线测试工具时。产线上经常遇到需要重新校准设备参数的情况但每次刷机都会导致IMEI、Wi-Fi MAC地址等关键数据丢失。当时产线工人不得不手动记录这些数据效率低下且容易出错。后来发现这些数据都存储在高通的NV分区中。NV(Non-Volatile)分区是高通芯片特有的存储区域用于保存设备的永久性配置数据。它有点类似PC主板上的CMOS芯片但功能更强大。常见的NV数据包括设备标识信息IMEI、MEID、SN号网络参数Wi-Fi MAC地址、蓝牙地址射频校准数据PA增益表、频率补偿值传感器校准数据加速度计、陀螺仪偏移量这些数据的特点是设备唯一性每台设备的这些参数都不相同生产环节写入通常在工厂校准环节生成刷机易丢失使用QFIL等工具刷机时如果勾选erase all选项就会清除我遇到过最棘手的情况是产线批量刷机后200台设备的Wi-Fi MAC地址全部丢失。由于没有备份只能重新申请地址段导致交付延迟一周。这也是为什么我现在做任何刷机操作前都会先备份NV数据。2. NV分区工作原理与Diag接口2.1 NV分区的存储机制高通的NV数据实际上存储在modem分区的一个特殊区域。与普通文件系统不同它采用类似数据库的键值对存储方式。每个NV项都有唯一ID如2497对应FACTORY_DATA_1固定长度根据数据类型从几个字节到几KB不等访问权限部分NV项需要特殊权限才能读写在Android系统中这些数据通过QMI接口与modem通信。但更底层的访问是通过Diag(诊断)接口实现的。Diag是高通芯片的调试接口类似于汽车行业的OBD接口可以访问芯片的深层功能。2.2 Diag接口的通信协议Diag协议基于简单的请求-响应模型。以读取NV数据为例客户端发送请求包命令字0x26DIAG_NV_READ_FNV项ID2字节如0x09 0xC1对应2497Modem返回响应包状态码数据内容在实际操作中我发现几个关键点需要先初始化Diag服务Diag_LSM_Init每次操作后最好调用efs_sync()同步到存储写入操作有校验机制错误数据会被拒绝3. 搭建HAL层操作环境3.1 准备开发环境要操作Diag接口需要配置以下环境内核配置确保CONFIG_DIAG_CHAR和CONFIG_DIAG_OVER_USB已启用权限配置在selinux中添加diag权限# device.te allow my_hal_domain diag_device:chr_file rw_file_perms;依赖库在Android.mk中添加LOCAL_SHARED_LIBRARIES : \ libdiag \ libmmi3.2 HAL层代码结构设计我通常采用分层设计hal_nv_manager/ ├── Android.bp ├── include/ │ └── NvManager.h └── src/ ├── NvManager.cpp └── diag_wrapper.cpp其中diag_wrapper.cpp封装底层Diag操作class DiagWrapper { public: static bool init(); static bool deinit(); static int readNvItem(uint16_t itemId, void* buffer, size_t len); static int writeNvItem(uint16_t itemId, const void* data, size_t len); };4. 实现NV数据备份与恢复4.1 完整备份流程初始化Diag服务if (!Diag_LSM_Init(NULL)) { ALOGE(Diag init failed); return -1; }读取关键NV项uint8_t imei_data[16] {0}; diag_nv_read(NV_UE_IMEI_I, imei_data, sizeof(imei_data));生成备份文件void generateBackup(const char* path) { FILE* fp fopen(path, wb); fwrite(imei_data, 1, sizeof(imei_data), fp); // 写入其他NV数据... fclose(fp); }4.2 恢复数据时的注意事项在多次实践中我总结了几个关键点写入顺序很重要先写IMEI等标识信息再写校准数据需要重启生效部分NV项需要重启modem才能生效校验机制bool verifyData(uint16_t itemId, const void* expected, size_t len) { uint8_t actual[256] {0}; diag_nv_read(itemId, actual, len); return memcmp(expected, actual, len) 0; }5. 实际应用中的问题排查5.1 常见错误代码错误现象可能原因解决方案Diag初始化失败权限不足检查selinux策略NV读取返回-1项ID错误用QXDM验证ID写入后不生效未同步调用efs_sync()5.2 性能优化技巧批量操作合并多次读写请求void batchRead(const std::vectoruint16_t itemIds) { // 使用单个Diag会话处理所有请求 }缓存机制对频繁读取的数据缓存异步处理将耗时操作放到工作线程6. 进阶应用场景6.1 产线自动化工具集成我们开发了一套基于Python的自动化工具链class NvTool: def backup_all(self, output_dir): # 自动识别设备型号 # 读取所有关键NV项 # 生成带时间戳的备份文件 def restore_from_backup(self, backup_file): # 校验备份文件完整性 # 分步写入数据 # 生成校验报告6.2 与QFIL配合使用在刷机脚本中加入自动备份/恢复# 刷机前备份 adb shell nv_backup /sdcard/nvbackup.bin # 使用QFIL刷机 qfil --portCOM3 --buildmsm8998 # 刷机后恢复 adb push nvbackup.bin /sdcard/ adb shell nv_restore /sdcard/nvbackup.bin7. 安全注意事项数据加密备份文件应加密存储void encryptData(uint8_t* data, size_t len, const char* key) { // 使用AES等算法加密 }访问控制限制NV读写权限日志审计记录所有关键操作在最近一个项目中我们实现了完整的审计追踪2023-05-15 14:30 [INFO] NV备份开始 2023-05-15 14:31 [INFO] IMEI备份完成 2023-05-15 14:32 [INFO] MAC地址备份完成 2023-05-15 14:33 [INFO] 备份文件已加密存储通过HAL层封装Diag接口操作NV分区确实解决了我们长期面临的产线数据维护难题。现在新员工培训时我都会强调在做任何刷机操作前先用这套工具做好备份。有些经验真的是踩过坑才能深刻体会。

更多文章