SVN英文全称software version number,直译软件版本号,通常为两位数字,取值也必须是0~9的数字,而且99这个值是被保留的。高通平台的SVN号通常存储在Modem镜像中,X12项目也不例外,一般是modem在初始化时读取预编译就已经定义好的SVN号,并且同时从nv中读取到svn号,进行对比,若不一致,则将新svn号写入nv,这样就可以确保svn号能够一直随版本更新,且能够与imei号组成16位的IMEISV,在注网时通过空口上报给网络侧。
通常各通信模组厂商有一套自己定义的规则,用于定义软件版本号和SVN之间的对应关系,如取软件全版本号末两位作为SVN号,后续将以此为例;但通信模组通常会被用于MIFI、CPE、工业网关、工业路由器等场景,由于通信模组本身就是多核,CPU处理性能较强,尤其是高速通信模组,如高通SDX12、SDX55、SDX62、SDX65等平台,其处理能力优越,完全可以作为独立的处理器使用,无需再借助于host设备,这就催生了OpenCPU的方案,很多MIFI、CPE等厂商会直接基于上述平台进行二次开发,并且重新制定自己的版本号、SVN号规则。
但通常SDK仅会给第三方厂商开放boot、system、user等分区,boot分区存储kernel镜像,客户可以集成外设驱动和应用,如wifi、phy等;system是文件系统,客户可以增加自己的应用,删除一些不必要的应用,如网络管理相关、webui、网关配置等;user是客户存储客制化数据的分区,如客户的wifi配置、lan侧管理参数、客制化信息等。客户可以对这三个镜像或分区进行二次开发。
客户按照自己指定的版本号、SVN号等规则,生成新的版本号、SVN号等,并存储在system分区的文件系统中,这会存在一个问题,给网络侧上报的信息如IMEISV和客户提供的releasenote不符,导致认证失败。
如客户版本号为XX.XX.XX.XX_04,根据其自己定制的SVN号规则要求,SVN号应为04,而空口实际上报的Modem存储的模组厂商的SVN号,如01。
如我们在前面提到的SVN号显示规则取软件版本号末两位,那当我们的版本号是XX.XX.XX.03时,SVN号需为03,但我们在实际测试时发现并没有按照我们的预期显示,SVN号实际是01。经分析发现,在SDX12的生成版本文件的脚本中SVN号固定成了01:
sdx12-le-1-0\odm\tools\version_build.py
import sys
import os
import shutil
import time
import datetime
import traceback
import optparse;
import argparse;
import cmd
import re
...
sMOB_sVER_PROJRCT = 'XX_XX_XX.XX_04'
if sProName == 'PROJRCT':
sMOB_sVER = sMOB_sVER_PROJRCT
else:
print "The Project Name is ERROR."
sys.exit(-1)
...
sMOB_LAST_sVER = '01'
...
CUST_COMMENT=(... + ...+mMOB_SW_REL_SS+Blank+dQuotes+sMOB_LAST_sVER+dQuotes+Newline ...)
try:
file = open("../../modem_proc/build/cust/cust_version.h", 'w')
file.write(CUST_COMMENT)
所以我们在ati查询或空口log查看均是01:
我们修改为releasenote中所规定的:
sdx12-le-1-0\odm\tools\version_build.py
...
sMOB_LAST_sVER = sMOB_sVER[12:]
...
经编译验证,发现svn还是01,未按照版本号变化,但我们在改脚本生成文件中确实看到了svn号的变化,因此需进一步排查modem侧是否相应的规则去读取和写入nv:
通过排查发现,X12项目的modem侧缺少svn号的读写逻辑,仅有modem侧AT命令GSN查询imeisvn和svn时才是正确的:
而ap侧的AT处理应用fwa发送的ati查询是发送qmi消息QMI_DMS_GET_DEVICE_SERIAL_NUMBERS_REQ_V01到modem:
sdx12-ap\XXX-fwa\XXX-fwa-application-framework\XXX-app-pal\src\qualcomm\device_pal\XXX_device_pal.c
static int XXX_qmi_dms_get_device_serial_number(XXX_device_ser_num_t *ser_num_buf)
{
qmi_client_error_type rc;
dms_get_device_serial_numbers_resp_msg_v01 get_device_serial_numbers_resp_msg;
...
XXX_MEMSET(&get_device_serial_numbers_resp_msg, 0, sizeof(dms_get_device_serial_numbers_resp_msg_v01));
get_device_serial_numbers_resp_msg.imei_valid = 1;
get_device_serial_numbers_resp_msg.imeisv_svn_valid = 1;
rc = qmi_client_send_msg_sync(g_qmi_dms->user_handle,
QMI_DMS_GET_DEVICE_SERIAL_NUMBERS_REQ_V01,
NULL,
0,
(void*)&get_device_serial_numbers_resp_msg,
sizeof(dms_get_device_serial_numbers_resp_msg_v01),
5000 );
if (E_ERROR_NONE == rc)
{
if (QMI_RESULT_SUCCESS_V01 == get_device_serial_numbers_resp_msg.resp.result )
{
if (get_device_serial_numbers_resp_msg.imei_valid)
{
XXX_OEM_LOG_INFO(XXX_OEM_LOG_HIGH, "IMEI: %s", get_device_serial_numbers_resp_msg.imei);
XXX_string_memcpy(ser_num_buf->imei,get_device_serial_numbers_resp_msg.imei,sizeof(ser_num_buf->imei));
}
else
{
XXX_OEM_LOG_INFO(XXX_OEM_LOG_ERROR, " imei info not valid\r\n");
}
if (get_device_serial_numbers_resp_msg.imeisv_svn_valid)
{
int svn = 0;
XXX_SSCANF(get_device_serial_numbers_resp_msg.imeisv_svn, "%x", &svn);
if(svn<10)
{
XXX_SPRINTF(ser_num_buf->imeisv_svn, "0%d", svn);
}
else
{
XXX_SPRINTF(ser_num_buf->imeisv_svn, "%d", svn);
}
}
else
{
XXX_OEM_LOG_INFO(XXX_OEM_LOG_ERROR, " svn info not valid\r\n");
}
}
}
...
}
modem中直接读取nv,会读取到无人更新的svn号:
按照上述分析,我们在modem侧初始化时增加svn的读取和更新逻辑:
sdx12-le-1-0/modem_proc/datamodem/interface/atcop/src/dsatetsime_ex.c
…
#include "cust_version.h"
void odm_init_imsisvn_nv()
{
…
nv_status = dsatutil_get_nv_item(NV_UE_IMEISV_SVN_I, &ds_nv_item);//从nv读取svn
…
if((ds_nv_item.ue_imeisv_svn != (uint8)atoi(MOB_SW_REL_SS)) && ((uint8)atoi(MOB_SW_REL_SS) < 99))//对比nv中svn号和版本号的差异,若不同且svn号合法,便写入nv中
{
ds_nv_item.ue_imeisv_svn = (uint8)atoi(MOB_SW_REL_SS);
nv_status = dsatutil_put_nv_item(NV_UE_IMEISV_SVN_I, &ds_nv_item);
…
}
}
…
void dsatetsime_init_me ( void )
{
…
dsatetsime_init_auto_nitz_setting_from_nv();
//write sw verison to nv5153 to report correct imeisvn
odm_init_imsisvn_nv();
return;
}/* dsatetsime_init_me */
增加上述逻辑,我们再次验证,发现svn号可以按照我们的软件版本releasenote同步更新,符合模组厂商svn显示规范,并且在at查询和空包上报都是ok的:
但上述分析修改验证,仅仅满足了显示通信模组厂商版本的SVN号,无法满足客户需求。需进一步分析。
通过1.2章节的分析和修改,我们首先满足了模组厂商 svn显示规则,但是客户需求还要求sdk显示其版本号对应的svn号。针对这一需求,我们优先考虑svn显示规则修改为:模组厂商版本显示模组厂商版本对应的svn号,第三方厂商SDK版本显示SDK版本对应的svn号。这样对于内部和客户都互不影响,也可满足双方规范。
首先SDK版本的版本号、SVN号等信息是存在文件系统/sdk/cust_version文件中:
这就需要我们首先读取到SDK版本号,再写到nv里,我们梳理处以下几点比较重要的信息:
1、 SDK版本号存在文件系统中
2、 能读取文件系统的应用是运行在ap侧
3、 存储svn的nv在modem侧
4、 Modem侧和ap侧交互通常使用的是qmi
根据以上分析,我们初步给出如下方案1:模组厂商版本显示模组厂商的svn号,SSDK版本显示SDK的svn号,在fwa中增加逻辑,去判断是否存在SDK版本号存储文件,若存在,则发送qmi到modem侧将cu版本svn号写入nv。具体实现如下:
fwa-platform / XXX-fwa-application-framework/XXX-app-modules/device/XXX_app_device.c
…
void XXX_app_device_set_svn(void)
{
…
if((XXX_ACCESS(CUST_VER_FILE, F_OK) == 0))//查看客户版本号文件是否存在
{
XXX_SYSTEM_CALL("sed -n '/^VERSION_SOFTWARE_VER/p' /sdk/cust_version | cut -d ':' -f 2 > /tmp/svn_tmp_file");//获取客户版本号并存入临时文件
ret = XXX_read_file(CUST_SVN_TMP_FILE, cust_ver, sizeof(cust_ver));
if (ret == E_ERROR_NONE)
{
XXX_MEMSET(imeisv_svn, 0, sizeof(imeisv_svn));
XXX_STRNCPY(imeisv_svn, &cust_ver[CUST_SVN_LOC], XXXIMEI_SVN_LEN_MAX);//获取svn号
val_len = strlen((const char*)imeisv_svn);
if((val_len <= XXX_DEVICE_NVRW_MAX_VAL * 2) && (0 == val_len%2))
{
ret = XXX_device_change_hexstr_to_str(imeisv_svn, content);//转16进制
…
val_len = val_len/2;
}
…
ret = XXX_m2m_pal_write_nv_by_nvid((uint8_t)op_type, XXX_DEVICE_NVID_SVN, (uint8*)content, val_len);//调用接口,通过qmi写nv
…
}
void XXX_app_device_early_init(void)
{
…
XXX_app_device_set_svn();
return;
}
验证上述方案,发现SDK版本ati查询svn号确实改变了,nv也写入ok,但是空口中svn号为模组厂商版本:
经分析,由于svn号是存在nv里的,modem启动比较早,会立马读取nv进行注网,使用的是旧的svn号,fwa启动晚,再去写nv也无法改变当前注网使用的svn,且即使重新cfun=0/1是模块重新发起拨号,也不会再去读取nv获取新的svn。因此当前的方案仅仅改变的ati能获取到svn值,而无法改变注网的空口svn信息。
另外modem侧qmi消息服务启动是在modem初始化后的,而modem一旦初始化ok,便会读取nv发起注网,而当前的方案中ap和modem使用的是qmi消息,这会导致无论如何都无法在注网前更新nv。因此方案1行不通。
由于方案1存在注网无法获取到最新svn,方案1行不通。因此提出方案2:模组厂商版本也按照SDK版本显示,每次给客户出版本时和客户确认其版本号,手动修改svn编译版本。但经讨论,如果客户不更新sdk,仅仅更新他们的应用升级版本,svn号还是无法改变。方案2也行不通。
根据第一章节的分析,通过qmi读写nv的方式、SDK版本显示客户版本号方式均存在问题。因此我们还是需要找到可以跨子系统的数据共享方案,我们进一步分析:
1、 首先x12是多子系统交互,多分区共存
2、 在x12中boot分区优先加载,内核先启动
3、 然后文件系统、data分区挂载,文件系统中应用服务启动
4、 modem分区挂载,modem应用服务启动
5、 其他分区挂载
在x12中我们存在20个分区,其中ubi类型的有两个,而剩下的的18个均为裸分区,裸分区就是能够跨子系统进行访问的,而这18个裸分区中又因为空间大小、存储信息内容等因素无法供我们使用,经筛选,misc、appnv、oeminfo等分区可供我们选择。又因为misc分区通常会存储升级后的标志,我们读写可能会导致系统启动异常;appnv存储数据较多,且仅供system使用,没有跨子系统的读写机制,需要在modem重新读写适配,会耗费一定的时间,也不推荐使用;而oeminfo分区存储的是备份还原标志和次数,存储数据较少,剩余空间也足够我们使用,另外这个分区本身就一直在跨modem、aboot、app子系统访问,读写机制比较完善,可以直接复用。
这里仅列举部分分区信息:
经过分析,我们整理出modem和app两个子系统分别读写oeminfo raw分区的方法和例子。
modem侧:
在modem侧有备份还原相关的AT会去读写备份还原标志和次数,就会读写oeminfo raw分区;举例如下:
sdx12-le-1-0\modem_proc\odm\vendor_at\XXX_backup_restore\src\XXX_backup_restore.c
int XXX_exec_backup_rfnv(dsm_item_type *res_ptr)
{
…
if (odm_read_oeminfo(&oem_ops)) {//从oeminfo读取备份还原标记和次数
…
}
sdx12-le-1-0\modem_proc\datamodem\interface\atcop\src\XXX_flashop.c
int odm_read_oeminfo(oeminfo_ops_t *p_oeminfo)
{
…
if (flashop_get_PartiInfo(PARTI_NAME_OEMINFO, &oeminfo_pi)) {
…
nand_dev = XXX_flash_open(MIBIB_DEVICE_ALL_PARTI_NAME, 0);
…
block_size = nand_dev->block_size(nand_dev);
page_size = nand_dev->page_size(nand_dev);
page_buf = malloc(page_size);
…
for (nand_index = oeminfo_pi.start_block; nand_index <= oeminfo_pi.end_block; nand_index++)
{
dog_kick();
if (nand_dev->bad_block_check(nand_dev, nand_index) != FS_DEVICE_BAD_BLOCK) {
break;
}
}
…
dog_kick();
if (nand_dev->read_page(nand_dev, nand_index * block_size, page_buf) != FS_DEVICE_OK) {
…
memcpy(p_oeminfo, page_buf, sizeof(oeminfo_ops_t));
…
XXX_flash_close(nand_dev);
return ret;
}
在app侧有odm_upgrader应用专门根据标记去实现备份还原,其中有备份还原还会去读写备份还原标志和次数,就会读写oeminfo raw分区;举例如下:
sdx12-le-1-0\apps_proc\data\odm_upgrader\odm_upgrader.c
int odm_exec_restore_appnv(void)
{
..
if (odm_read_oeminfo(&oem_ops)) {//从oeminfo读取备份还原标记和次数
…
oem_ops.appnv_restore_times++;//修改标记和次数
oem_ops.rfnv_restore_flag = 1;
if (odm_write_oeminfo(&oem_ops)) {//写回oeminfo分区
…
return 0;
}
static int odm_write_oeminfo(oeminfo_ops_t *p_oeminfo)
{
…
if (mtd_scan_partitions() <= 0) {
…
mtd_part = mtd_find_partition_by_name(OEMINFO_PTN);
…
MtdWriteContext *pwrite = mtd_write_partition(mtd_part);
…
if (mtd_write_data(pwrite, (char *)p_oeminfo, sizeof(oeminfo_ops_t)) != sizeof(oeminfo_ops_t)) {
…
mtd_write_close(pwrite);
return 0;
}
通过上面的梳理,我们可以看到app和modem读写flash的方式不同,app使用mtd的方式,而modem是直接读写flash页,但他们都会以下面这种格式去读写,来确保数据存储读写格式一致,不会互相破坏数据的格式:
typedef struct {
uint32_t cfun7_restore_count;
uint32_t fw_crash_count;
uint32_t edl_mode;
uint32_t efs_restore_flag;
uint32_t efs_restore_count;
uint32_t efs_restore_times;
uint32_t rfnv_backup_flag;
uint32_t rfnv_backup_times;
uint32_t rfnv_restore_flag;
uint32_t rfnv_restore_times;
uint32_t appnv_backup_flag;
uint32_t appnv_backup_times;
uint32_t appnv_restore_flag;
uint32_t appnv_restore_times;
uint32_t reboot_mode;
uint32_t Reserved;
} oeminfo_ops_t;
根据前面几节的分析,我们最终分析出在oeminfo raw分区存储svn号供modem和app去共享svn号是具有可行性的,最终方案为:
1、 在app侧新增应用用于读取SDK版本号存储文件,获取SVN号
2、 Modem分区挂载时先调用新增应用将SVN号写到oeminfo raw分区
3、 Modem初始化时从oeminfo raw分区读取SVN号,并判断是否更新到nv中
具体实现如下:
根据2.2章节,我们已经知道app侧有odm_upgrader应用专门根据标记去实现备份还原,其中有备份还原还会去读写备份还原标志和次数,就会读写oeminfo raw分区,因此我们可以继续扩展该应用,用于读取CU版本号存储文件,获取SVN号,并写入oeminfo raw分区:
首先扩展数据存储读写格式,增加SVN号:
typedef struct {
uint32_t cfun7_restore_count;
uint32_t fw_crash_count;
...
uint32_t svn_num;
uint32_t Reserved;
} oeminfo_ops_t;
在odm_upgrader应用中增加获取svn号相关逻辑:
sdx12-le-1-0\apps_proc\data\odm_upgrader\odm_upgrader.c
static int odm_set_svn_to_oem(void)
{
oeminfo_ops_t oem_ops;
…
if (odm_read_oeminfo(&oem_ops)) {//先读取oeminfo中数值
…
ret = xxx_device_set_svn(cust_svn_ver);//从文件系统中获取CU版本SVN号
…
oem_ops.svn_num = atoi(cust_svn_ver);//更新SVN号
if (odm_write_oeminfo(&oem_ops)) {//将更新后的SVN号写回oeminfo分区
…
return 0;
}
int xxx_device_set_svn(char *cust_svn_ver)
{
if((access(CUST_VER_FILE, F_OK) == 0))
{
system("sed -n '/^VERSION_SOFTWARE_VER/p' /sdk/cust_version | cut -d '_' -f 5 > /tmp/svn_tmp_file");//读取CU版本号并截取SVN号存在临时文件
ret = xxx_read_file(CUST_SVN_TMP_FILE, cust_svn_ver, xxx_SVN_LEN_MAX+1);
…
return 0;
}
…
}
const odm_cmds_t odm_cmds[] =
{
{"backup", odm_exec_backup_appnv},
{"restore", odm_exec_restore_appnv},
{"set_svn",odm_set_svn_to_oem},//增加set_svn命令
…
};
modem分区挂载时执行odm_upgrade工具更新SDK版本svn号到oeminfo分区:
sdx12/sdx12-linux/quic/le/meta-qti-bsp/recipes-core/systemd/systemd-machine-units/firmware-ubi-mount.service
[Unit]
Description=Mount firmware partition to /firmware mount point
SourcePath=/etc/initscripts/firmware-ubi-mount.sh
After=data-mount.service
DefaultDependencies=no
[Service]
Type=oneshot
ExecStartPre=install -D -m 0664 -o radio -g radio /dev/null /run/systemd/resolve/resolv.conf
ExecStartPre=/usr/bin/odm_upgrader set_svn//挂载modem分区前执行odm_upgrader工具携带set_svn参数
After=systemrw.mount
Requires=systemrw.mount
RequiresMountsFor=/systemrw
ExecStart=/etc/initscripts/firmware-ubi-mount.sh
Nice=-20
[Install]
WantedBy=local-fs.target
modem初始化中增加更新nv逻辑:
sdx12-le-1-0\modem_proc\datamodem\interface\atcop\src\dsatetsime_ex.c
#include "fibo_flashop.h"//增加头文件,用于读写raw分区
void odm_init_imsisvn_nv()
{
…
oeminfo_ops_t oem_ops;
nv_status = dsatutil_get_nv_item(NV_UE_IMEISV_SVN_I, &ds_nv_item);//从nv读取svn号
…
if (odm_read_oeminfo(&oem_ops)) {//从oeminfo分区读取svn号
…
if(oem_ops.svn_num == 0)//如果sdk版本SVN号为0,则代表不是sdk版本,为模组厂商版本软件
{
if((ds_nv_item.ue_imeisv_svn != (uint8)atoi(MOB_SW_REL_SS)) && ((uint8)atoi(MOB_SW_REL_SS) < 99))//模组厂商版本SVN号有效性判断,并判断是否与nv中svn号一致,不一致再更新
{
ds_nv_item.ue_imeisv_svn = (uint8)atoi(MOB_SW_REL_SS);
nv_status = dsatutil_put_nv_item(NV_UE_IMEISV_SVN_I, &ds_nv_item);//模组厂商版本svn号写入nv
…
}
}
else//sdk版本
{
if((ds_nv_item.ue_imeisv_svn != oem_ops.svn_num) && (oem_ops.svn_num < 99)) //sdk版本SVN号有效性判断,并判断是否与nv中svn号一致,不一致再更新
{
ds_nv_item.ue_imeisv_svn = oem_ops.svn_num;
nv_status = dsatutil_put_nv_item(NV_UE_IMEISV_SVN_I, &ds_nv_item); //sdk版本svn号写入nv
…
}
}
}
1、升级SDK版本,SDK版本号XX_XX_02.00_08,对应模组厂商版本号为XX_XX_02.00_16,我们可以用ati查询发现SVN号为SDK版本号对应的SVN号,查看qxdm空口log,也正确:
2、升级模组厂商版本,模组厂商版本号为XX_XX_02.00_16,我们可以用ati查询发现SVN号为模组厂商版本号对应的SVN号,查看qxdm空口log,也正确:
需求目标实现,调试完成。