高通QMI学习整理

1. 什么是QMI?

QMI,Qualcomm Messaging Interface一些场景下,也被视为Qualcomm Modem Interface,Qualcomm MSM Interface 之缩写,是作为AP与CP进行核间通信的接口,其通过smd传递数据;
smd(shared memory driver),对下,通过操作smem来实现共享内存的物理操作,对上提供多个数据通道以供消息传递;此处涉及底层功能较深,在此不再深究;

QMI是CS结构,分为client端和service端,一个service可以同时服务于多个client;在我们的代码中,一般client在AP侧的应用中注册,向CP中的service来发送req,以获取某些数据或者执行某些操作;

QMI的消息类型有三种:
Request : client 向 service 发送请求
Response : service 在收到请求后向 client 发送应答消息
Indication : service 主动向所有 client 广播通知消息

QCCI:Qualcomm common client interface
是一套用于客户端发送消息到服务器或者从服务器接收信息的API集合;

QCSI:Qualcomm common service interface
用于接收客户端的请求及对其做出响应。另外,它也用来发送指示消息(indication message)。

QMI协议常用service:

DMS(设备管理Device Management) (ATI)
提供查询设备信息功能;参考: qmi/device_management_service_v01.h

NAS(网络访问Network Access)
提供网络管理功能;参考:qmi/network_access_service_v01.h

WDS(数据连接);
提供数据连接相关功能;参考:qmi/wireless_data_service_v01.h

2. 添加QMI

2.1 添加及初始化client

主要函数为:

qmi_client_notifier_init
qmi_client_register_notify_cb
qmi_client_get_service_list
qmi_client_init
qmi_client_register_error_cb
qmi_client_release

2.2 添加及初始化service

主要函数为:

qmi_csi_register(service cookie) 
qmi_csi_register_return(service_provider)

(New Client First Message)Event/Signal 
qmi_csi_handle_event(service_provider)

qmi_csi_connect callback(client_handle,service_cookie)

qmi_csi_connect return(connection handle)

service注册的标准API为qmi_csi_register,其定义如下

qmi_csi_error qmi_csi_register (   qmi_idl_service_object_type  service_obj,   
									qmi_csi_connect  service_connect,    
									qmi_csi_disconnect  service_disconnect,    
									qmi_csi_process_req  service_process_req,   
									void *service_cookie,   
									qmi_csi_os_params   *os_params,    
									qmi_csi_service_handle  *service_provider  )

eg:

 rc = qmi_csi_register(ping_get_service_object_v01(),qc_connect_cb,
                      qc_disconnect_cb, qc_handle_req_cb, &service_cookie,
                      os_params, &service_cookie.service_handle);

ping_get_service_object_v01 这个结构体是所有该service用到的数据结构的入口,service根据各数据表内容来进行编解码、构建TLV等。
qc_connect_cb / qc_disconnect_cb ——
这两个callback分别在client连接和释放的时候调用,用于分配和释放资源;
qc_handle_req_cb ——
在接收到client的req后将调用此callback,再去调用消息ID对应的处理函数;

2.3 添加QMI消息

2.3.1 消息声明
#define QMI_PING_REQ_V01 0x0001
#define QMI_PING_RESP_V01 0x0001
#define QMI_PING_IND_V01 0x0001
2.3.2 消息数据结构

包含必选成员或可选成员;可选成员必须有对应的 valid 字段,valid 字段仅在编码时使用,最终编码的消息中并不包含 valid 字段;

QMI的编码后数据格式如下:
QMI message = Control Flags(1Byte) + Transaction ID(2) + Message ID(2) + Length(2) + TLV(n)
这里的Length表示的是TLV的实际长度;

TLV = Type(1) + Length(2) + Value(n)
注意:TLV针对的是结构体中的每一个成员,对每个成员进行TLV编码;

还可能会有可变长度数组,会自带 length 字段,此 length 与 TLV 中的 L 无关,仅代表所描述可变长度数组的实际数组项数,用于消息编解码,可编码进最终消息,也可不编码进最终消息,由.c 文件中的消息编码规则决定。

eg:

typedef struct {
/* 必选成员 */
char ping[4];
/* 可选成员 */
uint8_t client_name_valid; //当需要传递 client_name 时,name_valid 必须为 TRUE,也就是 1;
ping_name_type_v01 client_name;
}ping_req_msg_v01; /* Message */

自定义类型说明

#define PING_MAX_NAME_SIZE_V01 255 //声明可变数组 name[]的最大数组项数
typedef struct {
uint32_t name_len; //用于描述可变数组 name[]的真实数组项数
char name[PING_MAX_NAME_SIZE_V01];
}ping_name_type_v01; /* Type */
2.3.3 Service Object

Client 通过下面的宏获取 service object,具体实现经由下面的函数声明,其中涉及到服务适用的 IDL 主版本号,次版本号,IDL 工具版本号

#define ping_get_service_object_v01( ) \
ping_get_service_object_internal_v01( \
PING_V01_IDL_MAJOR_VERS, PING_V01_IDL_MINOR_VERS, \
PING_V01_IDL_TOOL_VERS )

qmi_idl_service_object_type ping_get_service_object_internal_v01
( int32_t idl_maj_version, int32_t idl_min_version, int32_t library_version )
{
...
	return (qmi_idl_service_object_type)&ping_qmi_idl_service_object_v01;
};

2.3.3.1 表对象
/*Service Object*/
const struct qmi_idl_service_object ping_qmi_idl_service_object_v01 = {
0x02, //library_version 对应 PING_V01_IDL_TOOL_VERS
0x01, //idl_version 对应 PING_V01_IDL_MAJOR_VERS
0x0F, //service ID,由 Qualcomm 定义,预留部分 ID 给 ODM 使用
8463, //最大消息长度

//request/response/indication
{ sizeof(ping_service_command_messages_v01)/sizeof(qmi_idl_service_message_table_entry),
sizeof(ping_service_response_messages_v01)/sizeof(qmi_idl_service_message_table_entry),
sizeof(ping_service_indication_messages_v01)/sizeof(qmi_idl_service_message_table_entry) },
//request/response/indication 消息 id 与相应数据结构映射列表数组,相关的消息按顺序定义,可以按数组
索引到具体的消息

{ ping_service_command_messages_v01, ping_service_response_messages_v01,
ping_service_indication_messages_v01},
// qmi_idl_type_table_object 类型表对象,用于索引相应的自定义或预定义类型
&ping_qmi_idl_type_table_object_v01 //类型表对象,类型表,消息表,引用表索引的总入口
};

结构定义:
struct qmi_idl_service_object {
uint32_t library_version;
uint32_t idl_version;
uint32_t service_id;
uint32_t max_msg_len;
uint16_t n_msgs[QMI_IDL_NUM_MSG_TYPES];
const qmi_idl_service_message_table_entry *msgid_to_msg[QMI_IDL_NUM_MSG_TYPES]; //消息索
引的入口

const qmi_idl_type_table_object *p_type_table; //消息列表中的索引入口
uint32_t idl_minor_version;
struct qmi_idl_service_object *parent_service_obj;
};

类型表对象:

ping_qmi_idl_type_table_object_v01 数据结构:
struct qmi_idl_type_table_object {
uint16_t n_types; //类型个数

uint16_t n_messages; //消息个数
uint8_t n_referenced_tables; //引用表个数
const qmi_idl_type_table_entry *p_types; //类型表数组
const qmi_idl_message_table_entry *p_messages; //消息表数组
const struct qmi_idl_type_table_object **p_referenced_tables; //引用表二级数组
const qmi_idl_range_table_entry *p_ranges;
};


类型表对象定义:
static const qmi_idl_type_table_object ping_qmi_idl_type_table_object_v01 = {
sizeof(ping_type_table_v01)/sizeof(qmi_idl_type_table_entry ),
sizeof(ping_message_table_v01)/sizeof(qmi_idl_message_table_entry),
1,
ping_type_table_v01,
ping_message_table_v01,
ping_qmi_idl_type_table_object_referenced_tables_v01
};

引用表定义:

static const qmi_idl_type_table_object *ping_qmi_idl_type_table_object_referenced_tables_v01[] =
{&ping_qmi_idl_type_table_object_v01, //该 service 类型表对象
&common_qmi_idl_type_table_object_v01}; //同类型表对象,主要用于索引默认的 response 类型;

类型表定义:

static const qmi_idl_type_table_entry ping_type_table_v01[] = {
{sizeof(ping_name_type_v01), ping_name_type_data_v01} //本 service 仅自定义一个类型,索引只有一
项;
};

消息表定义:

static const qmi_idl_message_table_entry ping_message_table_v01[] = {
{sizeof(ping_req_msg_v01), ping_req_msg_data_v01},
{sizeof(ping_resp_msg_v01), ping_resp_msg_data_v01},
{sizeof(ping_ind_msg_v01), ping_ind_msg_data_v01},
}

消息 id 与消息表,消息最大长度组成消息表项数组,其成员为

typedef struct {
uint16_t qmi_message_id; //消息 ID
uint16_t message_table_message_id; //该消息在哪个 message table 中的索引;
uint16_t max_msg_len; //该消息 id 支持的最大消息长度
} qmi_idl_service_message_table_entry;

static const qmi_idl_service_message_table_entry ping_service_command_messages_v01[] = {
{QMI_PING_REQ_V01, TYPE16(0, 0), 266},
{QMI_PING_DATA_REQ_V01, TYPE16(0, 3), 8456},
{QMI_PING_DATA_IND_REG_REQ_V01, TYPE16(0, 5), 279},
{QMI_PING_GET_SERVICE_NAME_REQ_V01, TYPE16(0, 8), 259},
{QMI_PING_NULL_REQ_V01, TYPE16(0, 10), 0}
};

需要说明的是经由类型表对象索引消息表,进而索引消息 id 的方法:

TYPE16(0, 0)
#define TYPE16(table, type) QMI_IDL_TYPE16(table, type)
#define QMI_IDL_TYPE16(table, type) (((table) << 12) | (type))

基于上述宏定义,可见,table 索引占 16bit 的高 4bit,type 索引占 16bit 的低 12bit;table 用于查找表,type
用于在高四位找到的表中查找相应的消息;

以消息 QMI_PING_REQ_V01 为例,其 message_table_message_id 为 TYPE16(0,0);
在引用表 ping_qmi_idl_type_table_object_referenced_tables_v01,索引 0 为本 service 所定义的类型表对
象 ping_qmi_idl_type_table_object_v01,在其中查找消息表 p_messages,并在其消息表中索引第 0 个消
息。

下图描述了各结构之间的关系:
在这里插入图片描述

消息表项(即 qmi_idl_service_message_table_entry)最大消息长度:
该字段需要基于消息编码规则计算消息对应数据结构最大长度,含 TLV 中的 Type 和 Length 部分,但不含
可选成员的 valid 字段,不含字节对齐涉及的长度,QMI 编码不需要字节对齐,仅在编码前涉及字节对齐
该字段会影响 QMI 消息编码的成功与否,需要仔细对待;也存在 QMI 通信正常,但出现一些奇怪现象,其
原因很可能是这部分计算有问题;

消息编码规则实际上是基于这些规则,从 client 或 service 传入的 struct 数据结构中解析、提取用于
TLV 编码的数据的依据;同时也是 QMI 解码时,将 TLV 结构解码为 client 或 service 可识别数据结构的依
据。

消息每一个成员,编码为一个 TLV 结构,自定义类型,会以递归形式进行 TLV 编码,也就是说自定义
类型,会在 TLV 的 V 中包含一个 TLV 结构

必选且固定长度成员编码规则:

Byte1: 成员类型 TLV 之 T,一般自 0x01 开始,顺序递增
Byte2: 成员标记声明(一般表示成员单位长度占用字节数),如果为数组,需添加
QMI_IDL_FLAGS_IS_ARRAY 标记
Byte3: 成员起始地址在数据结构中的偏移 OFFSET8,也可能 OFFSET16,需要占用 2Bytes
Byte4: 数组项数,如果成员为数组,需要 Byte4 声明数组项数,如果为元类型,Byte4 不需要

可选且固定长度成员编码规则:

Byte1: 可选成员标记,以及该成员 valid 字段相对该成员起始地址的偏移
Byte2: 成员类型 TLV 之 T,可选成员一般自 0x10 开始,顺序递增
Byte3: 成员标记声明(一般表示成员单位长度占用字节数)),如果为数组,需添加
QMI_IDL_FLAGS_IS_ARRAY 标记
Byte4: 成员起始地址在数据结构中的偏移 OFFSET8,也可能 OFFSET16,需要占用 2Bytes
Byte5: 数组项数,如果成员为数组,需要 Byte5 声明数组项数,如果为元类型,Byte5 不需要

可选且可变长度成员编码规则:

Byte1: 可选成员标记,以及该成员 valid 字段相对该成员起始地址的偏移
Byte2: 成员类型 TLV 之 T,可选成员一般自 0x10 开始,顺序递增
Byte3: 成员标记声明(一般表示成员单位长度占用字节数),可变成员 Byte3 需添加QMI_IDL_FLAGS_IS_VARIABLE_LEN 标记
Byte4: 成员起始地址在数据结构中的偏移 OFFSET8,也可能 OFFSET16,需要占用 2Bytes
Byte5~6: 成员最大项数,如需两个字节表示,需在 Byte3 添加 QMI_IDL_FLAGS_OFFSET_IS_16
Byte7: 成员 len 字段(表述数组实际项数)相对与成员起始地址的偏移

必选且固定成员,含自定义数据结构,不含数组的消息数据结构编码规则:

Byte1: 成员类型 TLV 之 T,一般自 0x01 开始,顺序递增
Byte2: 成员标记声明(一般表示成员单位长度占用字节数)
Byte3: 成员起始地址在数据结构中的偏移 OFFSET8,也可能 OFFSET16,需要占用 2Bytes
Byte4:成员类型在类型表数组 ping_type_table_v01[]中的索引;
Byte5:成员类型在引用表中的表索引 ID;

简单示例:

/*Message Definitions*/
static const uint8_t ping_req_msg_data_v01[] = {
0x01, //消息类型,出现在 TLV 的 type 字段
QMI_IDL_FLAGS_IS_ARRAY | QMI_IDL_GENERIC_1_BYTE, //该字段是一个数组,数组成员占用
1 个字节
QMI_IDL_OFFSET8(ping_req_msg_v01, ping), //该字段起始地址在该数据结构中的偏移位置
4, 该字段数组项数
//最后一个 TLV 结构
QMI_IDL_TLV_FLAGS_LAST_TLV | QMI_IDL_TLV_FLAGS_OPTIONAL | //该字段为可选成员
(QMI_IDL_OFFSET8(ping_req_msg_v01, client_name) - QMI_IDL_OFFSET8(ping_req_msg_v01,
client_name_valid)), //该字段 valid 字段相对该字段起始位置的偏移,可选字段的 valid 是基于字段起始地址
进行计算的;
0x10, //消息类型,可选成员,一般以 0x10 开始定义消息类型,暂未看到明确定义
QMI_IDL_AGGREGATE, //声明该字段为自定义数据结构
QMI_IDL_OFFSET8(ping_req_msg_v01, client_name), //声明该字段起始地址在该数据结构的偏移位
置;
0, 0 //client_name 成员的类型 ping_name_type_v01 的索引,其位于引用表 0 的类型表数组的第 0 个类型
};

具体qmi添加示例:

1)添加massage ID,REQ, RESP, IND共用一个消息ID

#define QMI_TEST_REQ_V01 0x0001
#define QMI_TEST_RESP_V01 0x0001
#define QMI_TEST_IND_V01 0x0001

2) 添加数据结构

typedef struct {
  uint8_t test;
}qmi_test_req_msg_v01;  /* Message */

typedef struct {
  qmi_response_type_v01 resp;
}qmi_test_resp_msg_v01;  /* Message */

3) 添加编码规则
编码规则主要针对的是结构体中的每一项,而非结构体本身;

static const uint8_t qmi_test_req_msg_data_v01[] = {
  QMI_IDL_TLV_FLAGS_LAST_TLV | 0x01,
   QMI_IDL_GENERIC_1_BYTE,
  QMI_IDL_OFFSET8(qmi_test_req_msg_v01, test)
};

static const uint8_t qmi_test_resp_msg_data_v01[] = {
  QMI_IDL_TLV_FLAGS_LAST_TLV | 0x02,
   QMI_IDL_AGGREGATE,
  QMI_IDL_OFFSET8(qmi_test_resp_msg_v01, resp),
  QMI_IDL_TYPE88(1, 0)
};

qmi_test_req_msg_v01 中test为uint8_t,故成员单位长度占用字节数取QMI_IDL_GENERIC_1_BYTE,接下来是数据偏移;
qmi_test_resp_msg_v01 中resp为自定义数据结构,添加标志QMI_IDL_AGGREGATE,再添加偏移之后,通过QMI_IDL_TYPE88(1, 0)来获取自定义数据结构的定义;QMI_IDL_TYPE88(x, y),x表示类型表对象 索引,此例中1 为 common_qmi_idl_type_table_object_v01;y表示类型表对象中类型表中的索引,在本例中0为 common_type_table_v01 –> qmi_response_type_v01,为resp的类型;

4)消息表添加

static const qmi_idl_message_table_entry qmi_test_message_table_v01[] = {
...
  {sizeof(qmi_test_req_msg_v01), qmi_test_req_msg_data_v01},
  {sizeof(qmi_test_resp_msg_v01), qmi_test_resp_msg_data_v01},
  ...
}

5)消息表项数组添加(req 和 resp 分别添加对应的表):

static const qmi_idl_service_message_table_entry qmi_test_service_command_messages_v01[] = {
...
  {QMI_TEST_REQ_V01 , QMI_IDL_TYPE16(0, 66), 4},
...
}
static const qmi_idl_service_message_table_entry qmi_test_service_response_messages_v01[] = {  
...
 {QMI_TEST_RESP_V01 , QMI_IDL_TYPE16(0, 67), 11},
 ...
 }

(注意顺序)

QMI_IDL_TYPE16(x, y) : x为对应类型表对象索引,y为此消息在消息表中的ID;
最后一个参数为max_massage_len,表示所有TLV的最大长度。

6)添加处理函数

一般是在AP侧添加发送相关qmi消息的函数,然后modem添加处理该消息的函数

AP:

qmi_client_send_msg_sync()

qmi_client_send_msg_sync(g_qmi_test->user_handle,
                         QMI_TEST_REQ_V01 ,
                         req_item_ptr,
                         sizeof(qmi_test_req_msg_v01),
                         resp_item_ptr,
						 sizeof(qmi_test_resp_msg_v01),
                         COMM_LONG_PLATFORM_TIMEOUT);

CP:

static qmi_csi_cb_error qmi_test
(
    void                     *conn_handle,
    qmi_req_handle           req_handle,
    unsigned int             msg_id,
    void                     *req_c_struct,
    unsigned int             req_c_struct_len,
    void                     *service_cookie
);

static qmi_csi_cb_error (* const qmi_test_req_handle_table[])
(
 void                     *conn_handle,
 qmi_req_handle           req_handle,
 unsigned int             msg_id,
 void                     *req_c_struct,
 unsigned int             req_c_struct_len,
 void                     *service_cookie
) = 
{
...,
  &qmi_test,  // 与消息ID对应
  ...,
}

处理函数中一般会获取数据、执行操作、返回resp等
函数指针数组中添加处理函数,注意需要与message ID顺序对应

3 如何调试

QMI中的常见问题:

  1. AP调用qmi send函数,未能收到resp;
    通过QXDM查看modem是否有收到qmi消息,通过选择 Log Packets->Common->QMI,来筛选QMI消息;

注意:自己添加的service可能无法在QXDM中找到日志;
如果未能找到消息,需要确认发送函数及数据结构有没有问题;
如果有收到相应的消息,并且QXDM中的数据也对,那么可以确定req的数据结构是没有问题的,需要检查resp的数据结构及CP侧的处理函数,是否有正确返回resp;可以在处理函数中添加日志打印数据

  1. AP发送了数据,CP收到了但是从结构体中获取的值不对;
    首先确认发出去的值对不对

AP侧编码前添加打印: 在 函数 encode_and_send中
编码后添加打印,%x循环打印出MSG发送出去的实际数据。

  • 1
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值