⾼通androidQMI机制
⾼通android QMI机制
概论
Qualcomm MSM Interface,作⽤⽤于AP和BP侧的交互,通俗说法就是让设备终端TE(可以是⼿机,PDA,计算机)
对⾼通BP侧的AMSS系统进⾏操作,如调⽤函数,读取数据,设置其中的NV项等。
QMI的核⼼称之为QMI框架(QMI Framework),其主要功能包括以下3点:
连接MSM模块和设备终端,提供⼀个正交的控制和数据通道。在QMI的消息⽤有两种定义,⼀种是QMIControl Message;另⼀种是QMI DataMessage,⽀持这两种消息并发,不会互相⼲扰导致出错。
列举⼀系列的枚举逻辑设备,提供给连接使⽤。QMI机制类似于⼀个服务器机制,有相应的client端和services端,对应于QMI的control point和service。在AP向BP发送请求时,AP作为client端,当AP接收BP侧返回的响应时,AP作为services端。QMI包含了⼀系列的QMI Service,例如nas,voice,wds等,这些不同的services相当于不同逻辑设备,给不同的app调⽤。
QMI有相应的消息和消息的协议,设备终端就是通过这些消息来访问AMSS。对于不同的qmi消息,消息长度不⼀样,可⾃⼰定义消息长度,不同的qmi消息,消息格式是相同的。
上图是QMIFramework的⼀个软件结构图。
从图中可以看出,上层控制点打包对应类型的QMI消息或通过其他操作系统的框架,将要发出的数据传到AP侧底层的逻辑设备,最后逻辑设备通过内联的总线接⼝,传到BP侧的AMSS。在代码中可以到从控制点发送到逻辑设备的函数。
rrno_enum_type qcril_qmi_client_send_msg_sync {
qcril_qmi_client_e_type svctype,
unsigned long msg id,
*void req_c struct,
int reg_c_struct_len,
void *resp_c_struct,
int resp_c_struct_ len
}
这个是控制点向BP侧发送同步消息的函数,参数包括⾛的QMI_Service类型,Service⾥⾯消息的名称,请求消息的初始地址,长度,返回相应的初始地址和长度。逻辑设备和BP侧内联的总线也可以分很多种:
USB,SDIO,共享内存,⽆线协议802.11等都可以作为总线连接AP和BP。
咱们现在开发的MSM平台⽤的是共享内存。代码中qmi_port_defs.h中的枚举qmi_connection_id_type定义了AP侧QMI和BP侧的连接通道,包括集成modem的MSM平台和独⽴modem的MDM。
QMI_CONN_ID_RMNET_O = QMI_CONN_ID_FIRST,//Correspond
QMI_cONN_ID_RMNET_1, //corresponds to SMD DAT,
QMI_cONN_ID_RMNET_2; //Corresponds to SMD DAT,
QMI_cONN_ID_RMNET_3,
QMI_cONN_IDLRMNET_4,
QMI_cONN_ID_RMNET_6,
QMI_cONN_ID__RMNET_7,
在代码中的vendor\qcom\proprietary\qmi\platform⽬录,linux_qmi_qmux_if_client.c,定义了和BP侧通信的逻辑设备种类。
static linux_qmi_qmux_if_conn_sock_info_t linux_qmi_qmux_if_client_conn_socks[]=
#ifdef FEATURE_QMI_ANDROID
/*Radio Group Client Process */
{ "radio", QMI_QMUX_IF_RADIO_CLIENT_SOCKET_PATH, QMIL_QMUx_IF_RADIO_CONN_SOCKET_PATH },
/* Audio Group Client Process */
{ "audio", QMI_QMUX_IF_AUD1O_CLIENT_SOCKET_PATH, QMI_QMUX_IF_AUD1O_CONN_SOCK
ET_PATH },
/* Bluetooth Group Client Process */
{ "bluetooth",QMil_QMUX_IF_BLLETOOTH_CLIENT_SOCKET_PATH,QMIl_QMUX_IF_BLETOOTH_CON_SOCKET_PATH },
/*GPS Group Client Process */
{ "gps", QMI_QMUX_IF_GPS_CLIENT_SOCKET_PATH, QMI_QMUX_IF_GPS_CONN_SOCKET_PATH }
#endif
⽬前我们QMI⽀持的逻辑设备有图中四种,电话系统,⾳频,蓝⽛,GPS。
TE和MSM通信原理图:
两个特点:
1.单⼀的物理链接总线,必须被多个逻辑设备所复⽤。
2.不同的逻辑设备要求独⽴的控制信道和数据信道。
QMI终端原理图如下:
从图中可看出,整个QMI架构中,主要是通过QMUX层完成软件上的TE和MSM的交互。
1,⼀个服务可以对应多个控制点,⼀个控制点只能对应⼀个服务。
2,控制点与服务的关系就好⽐C/S模型中的客户端与服务器关系。
3,如果某程序使⽤⼏种QMI服务,那么它就要为每种服务构建⼀个控制点。
可以看出QMI并不是⼀个简单的⼀对⼀传输通信⽅式,⽽是⼀个服务可以同时接受⼏个控制点发出的消息,
其实现的原理也是对传输信道的复⽤。
复⽤协议QMUX
QMI Multiplexing Protocol(QMUX):QMI的复⽤协议
消息从控制点经过类似socket的线程传到QMI接⼝后,QMI负责对数据进⾏封装,加上QMUX消息的头,发送到QMUX层,再通过QMUX层传到共享内存到BP侧。
QMUX消息的格式
整个QMUX控制信道的结构如上图,
I/FType:QMI将控制点数据封装后,发送到QMUX前,加的消息头,长度为⼀个byte,值通常为0x01,表⽰这个消息为QMUX消息,如果是其他值,则为其他消息。
Length: QMUX消息的长度,不包括I/F Type。
ControlFlags:控制位,表⽰消息传输的⽅向。长度为1个byte,只有第7个bit是标志位,其他位为0,bit7=1说明QMUX消息由服务端发送,bit7=0由控制点发送。
Clien ID: 控制点的标识,在控制点和服务端都需要赋值,当在服务端发出的消息Client ID的值为0xFF,表⽰该消息为⼴播消息,由服务端主动发出,被所有控制点搜到。
QMUX SDU和TLV结构:
在整个控制信道的消息中,出去消息标识头I/F Type,和QMUX消息头,数据传输在QMUXSDU中完成,QMUX SDU⾥⾯的数据需要⽀持Type Length Value(TLV)的格式。
TLV格式的数据存放在QMI Service Message⾥⾯的Value中。
Control Flags:表⽰消息是请求、响应还是指⽰。Datasheet见⽂档。
TLV结构图:
QMUX消息类型
1、请求:请求消息⽤于设置参数、查询参数值或配置指⽰的产⽣。请求消息由控制点产⽣,⼀个有效
的请求通常会产⽣来⾃服务的应答。
2、响应:响应由服务产⽣,为回应接收到的请求。每个响应⾄少包含指⽰请求成功或失败的结果参数以及错误状态。
3、指⽰:指⽰由服务端主动发出,为了让控制点知道底层状态的变更,类似于信号强度,掉⽹Out of Service都是服务端主动发出给控制点的指⽰。
Qcril初始化流程
rild守护进程的rild.c⽂件中main⽅法有关加载动态库代码如下:
dlHandle = dlopen(rilLibPath, RTLD_NOW);//加载库
// ...
funcs = rilInit(&s_rilEnv, argc, rilArgv);//初始化实际调⽤的是RIL_Init⽅法
s_rilEnv结构体定义如下:也就是qcril.c可以回调ril的⽅法:
static struct RIL_Env s_rilEnv = {
RIL_onRequestComplete,
RIL_onUnsolicitedResponse,
RIL_requestTimedCallback
};
Android平台不同⼚商的AP侧可以相同,但是Modem侧肯定会有很⼤的差异,RIL层要解决⼀个问题:就是适配不同⼚商的Modem,为了达到兼容性要求,android在AP与Modem之间搭建了RILC的框架,由不同的Modem⼚商将⾃⼰的协议连接到AP侧。
对于⾼通平台来说,RILC就是QCRIL。
qcril.c的RIL_Init⽅法主要步骤如下:
//设置线程的名字
qmi_ril_set_thread_name( pthread_self() , QMI_RIL_QMI_RILD_THREAD_NAME);
//初始化接收Modem消息的EventLoop
qcril_event_init();
// 初始化QCRIL各个模块
qcril_init(c_argc, c_argv);
// 启动线程
qcril_event_start();
//其他初始化
qmi_ril_initiate_bootup();
//返回接⼝函数
return &qcril_request_api[ QCRIL_DEFAULT_INSTANCE_ID ];
初始化流程图如下:
初始化EventLoop过程
在Qcril中搭建了EventLoop循环⽤于检测Modem上报的消息,⽽EventLoop机制的初始化⼯作是在qcril_event_init()中完成的。
qcril_event.c的qcril_event_init⽅法主要部分如下:
//创建线程,⼊⼝⽅法为qcril_event_main
ret = pthread_create(&qcril_event.tid, &attr, qcril_event_main, NULL);
// ...
//设置线程的名字为event
qmi_ril_set_thread_name(qcril_event.tid, QMI_RIL_EVENT_THREAD_NAME);
在初始化过程中,通过pthread_create()函数创建了EventLoop线程,并且指出该线程的⼊⼝⽅法为qcril_event_main(),主要逻辑如下:
qcril_event_init_list(&qcril_event.list); //初始化qcril_event.list链表
// ...
ret = pipe(filedes); //创建管道
// ...
while (qcril_event.started < 2) //阻塞等待qcril初始化
{
QCRIL_LOG_VERBOSE("Event thread waiting for started == 2 (%d)", qcril_event.started );
pthread_cond_wait(&qcril_event_startupCond, &qcril_event.startup_mutex);
}
// ...
for (;;) // for循环读取qmi底层发送的请求
{
/
/ ...
//阻塞等待接收内容
n = select(qcril_event.fdWakeupRead + 1, &rfds, NULL, NULL, NULL);
// ...
do //读取qmi内容
{
ret = read(qcril_event.fdWakeupRead, &buff, sizeof(buff));
// ...
//处理qmi发送的请求
err_no = qcril_process_event( ev->instance_id, ev->modem_id, ev->event_id,
ev->data, ev->datalen, ev->t );
/
/ ...
在以上过程中,完成qcril_event.list链表的初始化,然后通过pthread_cond_wait进⼊阻塞状态,当被解锁后以及进⼊EventLoop循环,检测到事件后,通过qcril_process_event处理。
初始化qcril各个模块
Qcril在接到RILC的请求后,需要根据请求的类型将消息派发给不同的负责模块,⽽qcril_init()就是完成各个模块的初始化⼯作。
qcril_init⽅法部分代码如下:
qcril_arb_init();
qcril_init_state();
qmi_ril_oem_hook_init();
qcril_db_init();
qcril_init_hash_table();//初始化Event table
qcril_reqlist_init();
// ...
在这⾥对qcril的各个模块进⾏初始化。其中完成了很重要的⼀步就是将qcril_event_table表拷贝给qcril_hash_table,⽤于onRequest时对各种请求进⾏处理, qcril_init_hash_table⽅法如下:
for (reg_index = 0; reg_index < QCRIL_ARR_SIZE( qcril_event_table ); reg_index++)
// ...
qcril_hash_table[hash_index] = &qcril_event_table[reg_index];
// ...
qcril_event_table是⼀个静态表单, ⾥⾯保存了所有RILC中下发请求的ID以及相应的处理函数,表单部分内容如下:
{ QCRIL_REG_ALL_STATES( QCRIL_EVT_UIM_QMI_COMMAND_CALLBACK,
qcril_uim_process_qmi_callback ) },
⾥⾯每⼀项都包含两个元素:事件ID和处理函数,在处理这些消息时将会根据事件的ID查并执⾏相应的处理函数。
⽐如,对于得到当前SIM卡状态这个请求,对应的ID为RIL_REQUEST_GET_SIM_STATUS,⽽其处理⽅法为
qcril_uim_request_get_sim_status。
启动EventLoop线程
初始化EventLoop时,在完成其链表的初始化过程后,通过pthread_cond_wait()将其阻塞,⽽现在要做的就是取消其阻塞状态,使其进⼊消息检测循环。
这是在qcril_event_start⽅法中完成的:
void qcril_event_start( void )
{
QCRIL_MUTEX_LOCK( &qcril_event.startup_mutex, "[Main Thread]
qcril_event.startup_mutex" );
qcril_event.started = 2; //更新状态
pthread_cond_broadcast(&qcril_event_startupCond);
//释放EventLoop锁
QCRIL_MUTEX_UNLOCK( &qcril_event.startup_mutex, "[Main Thread]
qcril_event.startup_mutex" );
}
由于EventLoop被初始化后⼀直处于阻塞状态,所以在这⾥将started状态置为2后,对qcril_event_startupCond进⾏解锁,从⽽使EventLoop 进⼊循环。
3.4其他初始化过程
在qmi_ril_initiate_bootup⽅法如下:
qcril_setup_timed_callback( QCRIL_DEFAULT_INSTANCE_ID,
QCRIL_DEFAULT_MODEM_ID, qmi_ril_bootup_perform_core_or_start_polling, NULL,
NULL );
qmi_ril_bootup_perform_core_or_start_polling⽅法部分代码如下:
init_res = qmi_ril_core_init();//qmi初始化
qmi_ril_core_init⽅法调⽤qcril_qmi_client_init⽅法完成qcril客户端的初始化。
res = qcril_qmi_client_init();
将回调函数注册给RILC
在Qcril的初始化完毕后,将⾃⼰的函数列表返回给RilC,也就是qcril_request_api:
static const RIL_RadioFunctions qcril_request_api[] = {
{ RIL_VERSION, onRequest_rid, currentState_rid, onSupports_rid, onCancel_rid, getVersion_rid }
};
这样的话,在RIL中调⽤的接⼝就会进⼊该函数列表中进⾏处理。
这样准备⼯作就完成了
QCRIL消息发送
当ril有请求过来时,就会调⽤ril库的onRequest()⽅法,此时就会根据当前Qcril注册的函数列表进⼊到qcril_request_api的onRequest_rid⽅法中,因此, onRequest_rid⽅法是QCRIL中的⼊⼝⽅法。调⽤的流程如如下:
qcril_execute_event⾸先调⽤qcril_hash_table_lookup⽅法从表中查当前的Event,如果没有到当前的Request,就认为⾮法,到之后,进⼊qcril_dispatch_event()中派发该Event
(entry_ptr->handler)(params_ptr, &ret);
ret是返回的结果,通过entry_ptr->handler调⽤当前Event的处理函数。这⾥的handler对应qcril_hash_table中的某⼀项。第⼀章中将
qcril_event_table表中的数据拷贝给了qcril_hash_table,所以这⾥的handler可以理解为qcril_event_table中的某⼀项。
之后的流程就会进⼊到某个具体请求的处理函数中,⽐如打电话是对应的请求是RIL_REQUEST_DIAL,其处理函数为:
qcril_qmi_voice_request_dial;挂断电话对应的请求是RIL_REQUEST_HANGUP, 其处理函数为qcril_qmi_voice_request_hangup;
qcril_qmi_voice_request_hangup⽅法进⼀步调⽤qcril_qmi_client_send_msg_async发送到QMI层。
当然, qcril_qmi_client_send_msg_async是异步处理的, qcril_qmi_client_send_msg_sync是同步处理的。
最后,这2个⽅法都会调⽤qmi_client_send_msg_sync完成发送。
⼀些其他的处理⽅法或者会调⽤这两个⽅法发送到QMI层,或者直接调⽤qmi_client_send_msg_sync发送。
2.1 QMI层消息处理
调⽤流程图如下:
ril_err = qcril_qmi_client_send_msg_async ( QCRIL_QMI_CLIENT_VOICE,
QMI_VOICE_ANSWER_CALL_REQ_V02,
&ans_call_req_msg,
sizeof(ans_call_req_msg),
ans_call_resp_msg_ptr,
sizeof(*ans_call_resp_msg_ptr),
(void*)(uintptr_t)user_data);
这⾥选择的qmi_service是voice。之所以选择voice作为发送通道,是因为第⼆个参数QMI_VOICE_GET_CONFIG_REQ_V02。
qcril_qmi_client_send_msg_async⽅法主要逻辑如下:
if (NULL != client_info.qmi_svc_clients[svc_type])
{
qmi_error = qmi_client_send_msg_async_with_shm(client_info.qmi_svc_clients[svc_type],
// ...
}
这个函数⾸先会去判断我们调⽤的这个voice的服务类型是否存在于QMI定义的服务列表中,如果不存在,还需要⾃⼰添加该service,如果存在,执⾏QMI client端发送消息的接⼝函数qmi_client_send_msg_async_with_shm。
该⽅法直接调⽤qmi_client_send_msg_sync⽅法,
rc = qmi_client_send_msg_sync(user_handle,
msg_id,
req_c_struct,
linux和安卓的关系
req_c_struct_len,
resp_c_struct,
resp_c_struct_len,
timeout_msecs);
同步消息在QMUX层发送到BP侧后,会⼀直等待BP的响应所以函数的最后⼀个参数是⼀个timeout,⽽异步消息不需要。
qmi_client_send_msg_sync⽅法⾸先计算请求消息的长度和设定返回消息的最⼤长度,然后对请求消息按QMUX格式进⾏编码通过函数qmi_service_send_msg_sync_millisec()发送出去,最后等待BP侧返回的响应,将返回的response进⾏解码。
2.2 QMUX层消息处理
QMUX消息处理流程图如下:
qmi_service_send_msg_sync_millisec⽅法⾸先去获取⼀些QMUX层所需要的消息,发送通道的conn_id。
QMUX消息格式⽤到的client_id
conn_id = QMI_SRVC_CLIENT_HANDLE_TO_CONN_ID (user_handle);
client_id = QMI_SRVC_CLIENT_HANDLE_TO_CLIENT_ID (user_handle);
然后打包到⼀个传输消息的结构体qmi_service_txn_info_type *txn,这个txn在稍后跟进代码中会发现,就是QMUX消息中的tranciationID
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论