zl程序教程

您现在的位置是:首页 >  后端

当前栏目

高通SDX12:跨子系统数据共享实例分享

实例 分享 高通 子系统 数据共享
2023-06-13 09:15:11 时间

高通SDX12:跨子系统数据共享实例分享

1. 实例背景

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侧管理参数、客制化信息等。客户可以对这三个镜像或分区进行二次开发。

1.1 问题现象

客户按照自己指定的版本号、SVN号等规则,生成新的版本号、SVN号等,并存储在system分区的文件系统中,这会存在一个问题,给网络侧上报的信息如IMEISV和客户提供的releasenote不符,导致认证失败。 如客户版本号为XX.XX.XX.XX_04,根据其自己定制的SVN号规则要求,SVN号应为04,而空口实际上报的Modem存储的模组厂商的SVN号,如01。

1.2 初步分析

如我们在前面提到的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.3 客户SDK版本显示SDK版本 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.4 SDK版本、模组厂商版本均显示SDK版本 svn号

由于方案1存在注网无法获取到最新svn,方案1行不通。因此提出方案2:模组厂商版本也按照SDK版本显示,每次给客户出版本时和客户确认其版本号,手动修改svn编译版本。但经讨论,如果客户不更新sdk,仅仅更新他们的应用升级版本,svn号还是无法改变。方案2也行不通。

2. raw flash存储svn号

根据第一章节的分析,通过qmi读写nv的方式、SDK版本显示客户版本号方式均存在问题。因此我们还是需要找到可以跨子系统的数据共享方案,我们进一步分析: 1、 首先x12是多子系统交互,多分区共存 2、 在x12中boot分区优先加载,内核先启动 3、 然后文件系统、data分区挂载,文件系统中应用服务启动 4、 modem分区挂载,modem应用服务启动 5、 其他分区挂载

2.1 X12分区简析

在x12中我们存在20个分区,其中ubi类型的有两个,而剩下的的18个均为裸分区,裸分区就是能够跨子系统进行访问的,而这18个裸分区中又因为空间大小、存储信息内容等因素无法供我们使用,经筛选,misc、appnv、oeminfo等分区可供我们选择。又因为misc分区通常会存储升级后的标志,我们读写可能会导致系统启动异常;appnv存储数据较多,且仅供system使用,没有跨子系统的读写机制,需要在modem重新读写适配,会耗费一定的时间,也不推荐使用;而oeminfo分区存储的是备份还原标志和次数,存储数据较少,剩余空间也足够我们使用,另外这个分区本身就一直在跨modem、aboot、app子系统访问,读写机制比较完善,可以直接复用。 这里仅列举部分分区信息:

2.2 读写oeminfo raw分区实例

经过分析,我们整理出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;

2.3 oeminfo raw分区存储svn号

根据前面几节的分析,我们最终分析出在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
		…
       }
   }
}

3. 验证

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,也正确:

需求目标实现,调试完成。