高通android QMI机制

原文(有删改):https://blog.csdn.net/u012439416/category_7004974

概论

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_SOCKET_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,
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

/* Initialize Id fields */
txn->conn_id = conn_id;
txn->service_id = service_id;
txn->client_id = client_id;
txn->msg_id = msg_id;
txn->api_flag = api_flag;
/* Initialize fields */
txn->srvc_txn_info.txn_type = QMI_TXN_SYNC;
txn->srvc_txn_info.sync_async.sync.user_reply_buf = NULL;
txn->srvc_txn_info.sync_async.sync.user_reply_buf_size = 0;
txn->srvc_txn_info.sync_async.sync.rsp_rc = QMI_NO_ERR;
txn->srvc_txn_info.sync_async.sync.qmi_err_code = QMI_SERVICE_ERR_NONE;

这些完成之后,调用qmi_service_send_msg方法发送,该方法会判断服务ID和通道的conn_id是否有效:

if ((int)conn_id >= (int)QMI_MAX_CONN_IDS)
{
return QMI_INTERNAL_ERR;
}
if ( qmi_qcci_internal_public_service_id_to_bookkeeping_service_id ( service_id ) >=
QMI_MAX_SERVICES)
{
return QMI_INTERNAL_ERR;
}

首先调用qmi_service_write_std_srvc_msg_hdr,方法给发送过来的消息加上message ID和Length,

if (qmi_service_write_std_srvc_msg_hdr (&msg_buf,
&msg_buf_size,
msg_id,
msg_buf_size) < 0)

所以不难猜测,在QMI发送端的接口函数qmi_client_send_msg_sync()中编解码的原理是按照QMUX消息中的TLV格式进行编解码。

再走到这步加上message ID和Length,整个QMUX SDU中的QMI service message已完成。

qmi_service_write_std_srvc_msg_hdr方法如下:

static
int qmi_service_write_std_srvc_msg_hdr (unsigned char **msg_buf,
int *msg_buf_size, unsigned long msg_id, int length)
{
unsigned char *tmp_msg_buf;
/* Back pointer up by 4 bytes */
*msg_buf -= QMI_SRVC_STD_MSG_HDR_SIZE;
*msg_buf_size += QMI_SRVC_STD_MSG_HDR_SIZE;
tmp_msg_buf = *msg_buf;
/* Write the message ID field (16 bits) */
WRITE_16_BIT_VAL (tmp_msg_buf,msg_id);
/* Write the length field */
WRITE_16_BIT_VAL (tmp_msg_buf,length);
return 0;
}

加message ID和Length的方法:消息指针的偏移。指针向前偏移,消息长度增加。

随后用类似方法,通过函数qmi_service_write_std_txn_hdr_and_inc_txn_id ()将control flags和tranciation ID加上,完成整个QMUX SDU。

最后通过函数qmi_qmux_if_send_qmi_msg()发送到QMUX层,到这里,整个QMI interface的流程走完。

  • 主要作用:获取上层发送的请求,选择相应的serviceid,conn_id,client id,对消息完成整个QMUX SDU的封装,发送到QMUX。

函数qmi_qmux_if_send_qmi_msg()的工作是:

  • 对上层发过来打包好的QMUX SDU完成加QMUX HEADER的工作。添加QMUX header的方法同样是指针偏移。

qmi_qmux_if_send_qmi_msg方法直接调用qmi_qmux_if_send_to_qmux方法进行处理,首先将QMUX header里面的控制位,QMI服务ID,QMI客户端ID打包到结构体hdr,再通过Memcpy完成:

/* Set up message for sending */
memset(&hdr, 0, sizeof(qmi_qmux_if_msg_hdr_type));
hdr.msg_id = msg_id;
hdr.qmux_client_id = qmux_client_id;
hdr.qmux_txn_id = qmux_txn_id;
hdr.qmi_conn_id = qmi_conn_id;
hdr.qmi_service_id = qmi_service_id;
hdr.qmi_client_id = qmi_client_id;
hdr.control_flags = 0; // Unused for TX to QMUX, only valid for RX
/* Decrement msg pointer and increment msg_len */
msg -= QMI_QMUX_IF_HDR_SIZE;
msg_len += (int)QMI_QMUX_IF_HDR_SIZE;
/* Copy header into message buffer */
memcpy ((void *)msg, (void *)&hdr, QMI_QMUX_IF_HDR_SIZE);

到这里整个QMUXMessage完成封装。然后会把封装好的QMUXmessage发到下层,通过调用宏QMI_QMUX_IF_PLATFORM_TX_MSG()。

这个宏定义了函数linux_qmi_qmux_if_client_tx_msg()。

qmi_platform_qmux_if.h中宏QMI_QMUX_IF_PLATFORM_TX_MSG定义如下:

#define QMI_QMUX_IF_PLATFORM_TX_MSG(client,msg,msg_len) \
linux_qmi_qmux_if_client_tx_msg (client,msg,msg_len)

因此,调用宏QMI_QMUX_IF_PLATFORM_TX_MSG就是调用linux_qmi_qmux_if_client_tx_msg方法,该方法通过socket,将消息发到linux_qmi_qmux_if_server的接口。

  if ((rc = send (client_fd,
(void *) msg,
(size_t) msg_len,
MSG_DONTWAIT | MSG_NOSIGNAL)) < 0)

底层消息发送

在linux_qmi_qmux_if_server.c文件的入口main()函数,通过一个select来监听所有从linux_qmi_client端发出的socket,通过for循环调用linux_qmi_qmux_if_server_process_client_msg()处理这些监听的消息。进入到函数linux_qmi_qmux_if_server_process_client_msg()后,

通过recv函数将监听的socket的消息写入buf_size这个buffer里面。调用流程图如下:

if ((buf_size = recv (fd, (void*)&platform_msg_hdr,
QMI_QMUX_IF_PLATFORM_SPECIFIC_HDR_SIZE,0))<= 0)

recv方法在接收socket时,并没有全部接收。而且只接收了platform_msg_hdr。这个platform_msg_hdr是在linux_qmi_qmux_client里面定义的,server接收到后,首先会判断从client端发过来的这个消息是否正常,包括client_id和消息长度。

remaining_bytes = (size_t) platform_msg_hdr.total_msg_size - QMI_QMUX_IF_PLATFORM_SPECIFIC_HDR_SIZE;
if ((buf_size = recv (fd, (void *)linux_qmi_qmux_if_rx_buf, remaining_bytes, 0)) <= 0)

如果client_id匹配不上 或时消息长度溢出,都会将消息丢弃,不会发送。如果判断消息没问题,就会将其余的消息(除去platform_msg_hdr)再次通过recv()函数从socket中接受,放到remaining_bytes这个buffer中,用qmi_qmux_tx_msg方法继续处理。

在qmi_qmux_tx_msg()函数中,又会对之前打包好的QMUX消息进行去头,拆分。

判断QMUX header中的message_id,如果是QMI_MSG,则会调用函数qmi_qmux_tx_to_modem(),将拆分后的service_id,client_id等发送到下层。

{
rc = qmi_qmux_tx_to_modem( msg_hdr.qmi_conn_id,
msg_hdr.qmi_service_id,
msg_hdr.qmi_client_id,
msg,
msg_len );
}

接下来在函数qmi_qmux_tx_to_modem(),对QMUX整个控制信道消息的头进行一个重组,包括I/F Type。

完成QMI整个control channel message的构建。

/* I/F type is 1 for a QMUX message */
WRITE_8_BIT_VAL (tmp_msg_ptr, 1); /* Length is length of message to send which includes the QMUX header, but since
** QMI_QMUX_HDR_SIZE includes the I/F byte and the length field in a QMUX
** message doesn't include this, we need to subtract 1
*/
WRITE_16_BIT_VAL (tmp_msg_ptr, (msg_len - 1)); /* Control flags byte should be set to 0 for control point */
WRITE_8_BIT_VAL (tmp_msg_ptr, 0); /* Now put in service type and client ID */
WRITE_8_BIT_VAL (tmp_msg_ptr, service_id);
WRITE_8_BIT_VAL (tmp_msg_ptr, client_id);

然后通过宏QMI_QMUX_IO_PLATFORM_SEND_QMI_MSG调用ARM侧进入共享内存和BP侧交互的IO口函数linux_qmi_qmux_io_send_qmi_msg(),

qmi_platform_qmux_io.h中的宏定义如下:

#define QMI_QMUX_IO_PLATFORM_SEND_QMI_MSG(conn_id,msg_buf,len) \
linux_qmi_qmux_io_send_qmi_msg (conn_id,msg_buf,len)

在这个linux_qmi_qmux_io_send_qmi_msg读写函数中,判断如果当前AP侧和BP侧的连接通道是激活状态的话,就通过write函数,将打包好的QMI消息,写入连接通道信息里面的f_desc参数,

ret = write(conn_info->f_desc, (void*) msg_ptr, (size_t)msg_len);

到此,整个ARM流程结束。以上主要介绍AP侧要发送一个请求到BP侧,QMI是怎么对请求进行编码成QMUX消息,怎么将编码后的QMUX消息加头组合成一种AP和BP可共同识别的消息格式,最后是怎么发送到BP侧的。

Modem消息接收

消息初始化

初始化:qmi_modem_taskàqmii_init()àqmux_init()。qmux_init方法完成对控制通道的初始化后,

通过函数qmuxi_process_rx_sig方法开始从共享内存接收数据。调用流程如如下:

(void)qmi_set_sig_handler(QMI_QMUX_RX_SIGNAL, qmuxi_process_rx_sig, NULL);

在qmuxi_process_rx_sig方法中,首先通过dsm_dequeue()读取在队列中等待的QMUX消息,赋值给指针qmux_pdu表示QMUX消息的首地址。

qmux_pdu = dsm_dequeue( &qmux_s->io.rx_wm );

然后调用qmuxi_process_msg方法开始对AP侧发过来的QMUX消息解包。

qmuxi_process_msg( qmux_s, qmux_pdu );

qmuxi_process_msg方法首先拆分IF Type,通过函数dsm_pull8()进行解包。然后判断IF Type类型,然后把QMUX Message通过qmuxi_input()继续处理。

在qmuxi_input()中,会拆分QMUX的消息头,将消息头大卸八块,包括length,control_flags,client_id,service_id,然后调用qmi_framework_svc_recv方法将剩下的QMUX SDU从拆分出来的QMUX消息头中,找到BP侧相对应的service处理。

if((svci == NULL) || (!(svci->registered)))
{
status = qmi_framework_svc_recv( qmi_instance_by_qmux_state(qmux_s),
(qmux_service_e_type) qmux_hdr.svc_type, qmux_hdr.clid, *qmux_pdu );
*qmux_pdu = NULL;
return status;
}

在qmi_framework_svc_recv方法中,并不会马上根据service_id等对QMUX消息进行处理,这里也是一个接口,将消息打包成BP侧的QMI_framework的一个command,发送出去。

qmi_framework_msg_hdr_type       msg_hdr;
qmi_framework_svc_info_type * svc_info;
// ...
svc_info = qmi_framework_svc_state[service];

定义一个qmi_framework_svc_info_type的指针变量,对svc_info的操作就等于对qmi_framework_svc_state的操作。

打包到msg_hdr

 msg_hdr.common_hdr.service = service;
msg_hdr.common_hdr.client_id = client_id;
msg_hdr.common_hdr.qmi_instance = (int32)qmi_instance;
msg_hdr.common_hdr.transaction_id = msg_x_id;
msg_hdr.msg_ctl_flag = msg_ctl;
msg_hdr.msg_len = remaining_bytes;

最后

svc_info->cfg.cbs.cmd_hdlr(&msg_hdr,&sdu_in);

这个机制是怎么样呢?以phone相关的qmi_voice.c为例。

消息分类处理

qmi_voice.c在qmi_voice_init方法进行初始化时,

qmi_mmode_set_cmd_handler(QMI_MMODE_CMD_VOICE_FW_CB, qmi_voice_handle_fw_cmd);
// ...
qmi_voicei_cfg.cmn_svc_cfg.fw_cfg.cbs.alloc_clid = qmi_voice_fw_alloc_clid_cback;
qmi_voicei_cfg.cmn_svc_cfg.fw_cfg.cbs.dealloc_clid = qmi_voice_fw_dealloc_clid_cback;
qmi_voicei_cfg.cmn_svc_cfg.fw_cfg.cbs.init_cback = qmi_voice_fw_init_cback;
qmi_voicei_cfg.cmn_svc_cfg.fw_cfg.cbs.cmd_hdlr = qmi_voice_fw_req_cback; qmi_voicei_cfg.cmn_svc_cfg.cmd_hdlr_array = qmi_voicei_cmd_callbacks;
qmi_voicei_cfg.cmn_svc_cfg.cmd_num_entries = VOICEI_CMD_MAX;
qmi_voicei_cfg.cmn_svc_cfg.service_id = QMUX_SERVICE_VOICE; /*-----------------------------------------------------------------------
step 2: calling QMI Framework API to register the service.
----------------------------------------------------------------------*/
errval= qmi_framework_reg_service(QMUX_SERVICE_VOICE,
&qmi_voicei_cfg.cmn_svc_cfg.fw_cfg);

初始化的时候,用结构体指针&qmi_voicei _cfg,到qmi_framework里面注册服务,获取AP测发送过来的qmux,可以关注给cmd_hdlr赋值的函数qmi_voice_fw_req_cback,这是从qmi_framework返回的回调函数。

因此,对于phone消息, qmi_framework_svc_recv都是回调qmi_voice_fw_req_cback进行处理,qmi_voice_fw_req_cback方法读取QMUX消息的头指针,和QMUX SDU的首地址。发送到voice服务里面专门的command_handle。

qmi_mmode_send_cmd(QMI_MMODE_CMD_VOICE_FW_CB, cmd_ptr);

在voice初始化可找到QMI_MMODE_CMD_VOICE_FW_CB对应的方法为qmi_voice_handle_fw_cmd。

qmi_mmode_send_cmd方法会调用qmi_voice_handle_fw_cmd方法,该方法根据不同的消息类型调用不同的方法进行处理,

switch(qmi_info->id)
{
case QMI_MMODE_FW_INIT_CB:
qmi_voicei_fw_init_cback_hdlr(qmi_info->data.qmi_fw_info.init_cb.num_instances);
break;
case QMI_MMODE_FW_ALLOC_CLID_CB:
qmi_voicei_fw_alloc_clid_hdlr(&qmi_info->data.qmi_fw_info.alloc_clid.msg_hdr);
break;
case QMI_MMODE_FW_DEALLOC_CLID_CB:
qmi_voicei_fw_dealloc_clid_hdlr(&qmi_info->data.qmi_fw_info.dealloc_clid.msg_hdr);
break;
case QMI_MMODE_FW_REQ_CB:
qmi_voicei_fw_req_hdlr(&qmi_info->data.qmi_fw_info.req_cb.msg_hdr,
qmi_info->data.qmi_fw_info.req_cb.sdu_in);
break;
default:
QM_MSG_ERROR("Unsupported qmi-voice fw cmd");
break;
}

qmi_voicei_fw_req_hdlr并不会马上对获取的消息进行处理,而是判断头消息中的client_id是否符合,符合则通过函数qmi_mmode_svc_req_hdlr发送到request_handler中,

if( msg_hdr->common_hdr.client_id > 0 )
{
cl_sp = (qmi_voicei_client_state_type *)
qmi_voice_state.client[msg_hdr->common_hdr.client_id - 1];
ASSERT(cl_sp);
/*-------------------------------------------------------------------------
Invoke the common svc request handler
-------------------------------------------------------------------------*/
qmi_mmode_svc_req_hdlr(&qmi_voicei_cfg.cmn_svc_cfg, msg_hdr, &cl_sp->common,
sdu_in);

到此,几乎所有的消息类型最后都会调用qmi_mmode_svc_req_hdlr方法发送到request_handler中。

消息分发

在qmi_mmode_svc_req_hdlr函数中,真正对QMUX消息进行处理。首先拆分头消息中的service_id,control_flag等。

svc_id = svc_cfg->service_id;

然后通过一个while循环开始对QMUX SDU进行解包。

while (sdu_in)
{
/*-----------------------------------------------------------------------
Extract service message header
-----------------------------------------------------------------------*/
temp = dsm_pull16( &sdu_in );
}

解包函数dsm_pull16()作用,按照一定的大小,从&sdu_in这个地址中获取数据。获取完后,跳出while循环,调用qmi_mmode_svci_dispatch_transaction方法进行处理。

qmi_mmode_svci_dispatch_transaction(&msg_hdr->common_hdr,svc_cfg,x_p);

其中参数x_p是解包后的qmux_sdu存放的buffer。

qmi_mmode_svci_input方法会通过request_handler对消息进行处理分发。

response_ptr = cmd_hdlr->request_hdlr( svc_cfg->svc_sp, cmd_buf_p, cl_sp, sdu_in );

request_hdlr方法是被封装了的,没法继续跟进,BP侧的接收处理到此结束。

modem消息发送

一般BP侧处理完请求后,都会回应一个响应给AP,一般是用宏QMI_SVC_PKT_PUSH将要作为响应的消息发送出去。qmi_svc_utils.h中QMI_SVC_PKT_PUSH定义如下:

#define QMI_SVC_PKT_PUSH(pkt,val,len)  ( len == dsm_pushdown_packed(pkt,\
val,\ len,\ DSM_DS_SMALL_ITEM_POOL ) )

将val里面的数据,传len个长度到pkt的buffer里面,然后发送出去。Val和len是我们要作为响应的数据和数据包大小。QMI_SVC_PKT_PUSH()只能传单一的参数,根据上文说明的QMUX消息中TLV格式的原理,可以用多个QMI_SVC_PKT_PUSH连着用,这样可以将多个数据或参数打包到同一条消息中发送。

例如,在voice服务中,用多个QMI_SVC_PKT_PUSH回传消息。

QMI_SVC_PKT_PUSH(&response, (void*)&info->call_id, sizeof(info->call_id))) )
// ...
QMI_SVC_PKT_PUSH(&response, (void *)&tag, VOICEI_TLV_TAG_SIZE)
// ...

最后还要记得写上TLV的总长度,和标志位。标志位必须和AP侧发出的请求相对应,如AP侧发出请求为0x21,返回的标志位rec_tag也必须为0x21,方便返回AP后查表处理。将TLV消息放入response这个buffer后,会继续打包QMUX的消息头,然后通过函数qmi_mmode_svc_send_response()发送出去。

调用流程图如下:

/* Send the response */
status = qmi_mmode_svc_send_response( &common_hdr,cmd_buf_p, msg_ptr);

回传的消息也是需要严格按照QMUX消息的格式的, qmi_mmode_svc_send_response方法中按照格式对QMUX消息进行重组,

msg_hdr.common_hdr.client_id      = common_hdr->client_id;
msg_hdr.common_hdr.qmi_instance = common_hdr->qmi_instance;
msg_hdr.common_hdr.service = common_hdr->service;
msg_hdr.common_hdr.transaction_id = x_p->x_id;
msg_hdr.msg_ctl_flag = QMI_FLAG_MSGTYPE_RESP;
if( x_p->n_cmds > 1 )
{
msg_hdr.msg_ctl_flag |= QMI_FLAG_MASK_COMPOUND;
}
msg_hdr.msg_len = (uint16) dsm_length_packet(msg_ptr);
qmi_framework_svc_send_response( &msg_hdr, msg_ptr );

指针msg_ptr读取x_p->resp_list[]中的响应消息,msg_hdr为qmux消息的头。

通过函数qmi_framework_svc_send_respnse()发到qmi_framework,这只是qmi_framework的一个接口,会调用qmi_frameworki_svc_send方法,直接调用qmi_frameworki_svc_send方法,

status = qmi_frameworki_svc_send( msg_hdr,
QMI_CMD_FRAMEWORK_SEND_RESPONSE,
msg_ptr );

qmi_frameworki_svc_send方法首先对接收到QMUX消息进行处理,将消息头和内容放到结构体qmi_framework_msg_buf的成员变量中,

memscpy( &qmi_framework_msg_buf->msg_hdr, sizeof (*msg_hdr), msg_hdr,
sizeof (*msg_hdr) );
qmi_framework_msg_buf->dsm_item = msg_ptr;

然后调用qmi_framework_process_svc_send方法,

return qmi_framework_process_svc_send((void *)qmi_framework_msg_buf, qmi_cmd );

qmi_framework_process_svc_send方法首先校验QMI服务给qmi_framework发出的command,判断这次从BP侧发出的消息是一个响应AP侧请求的response还是主动发给AP的一个indicated。

if((qmi_cmd_id_e_type)qmi_cmd == QMI_CMD_FRAMEWORK_SEND_RESPONSE )
{
msg_type = QMI_FLAG_MSGTYPE_RESP;
}
else if((qmi_cmd_id_e_type)qmi_cmd == QMI_CMD_FRAMEWORK_SEND_IND)
{
msg_type = QMI_FLAG_MSGTYPE_IND;

然后判断消息头的有效性,最后通过函数qmi_frameworki_qmux_send()发送到BP侧QMI的接口,

if (FALSE == qmi_frameworki_validate_msg_hdr(msg_hdr, msg_type ))
{
LOG_MSG_ERROR_1 ("Msg header validation failed - unable to send Response for cmd %d",
msg_hdr->msg_ctl_flag);
goto func_end;
}
return qmi_frameworki_qmux_send(msg_buf, msg_type, ind_cmd_id, msg_ptr);

qmi_frameworki_qmux_send方法首先会对QMUX消息进行一个过滤,如果消息异常,则丢弃。

if ( qmi_svc_filter_message( (qmi_instance_e_type)
msg_buf->msg_hdr.common_hdr.qmi_instance, msg_buf->msg_hdr.common_hdr.service,
msg_type, int_cmd_id) )
{
// ...

然后调用QMUX层的接口qmux_sio_send方法进行处理,

qmux_sio_send ( (qmi_instance_e_type)msg_buf->msg_hdr.common_hdr.qmi_instance,
msg_buf->msg_hdr.common_hdr.service,
msg_buf->msg_hdr.common_hdr.client_id,
msg_ptr);

qmux_sio_send方法首先调用dsm_length_packet方法对消息进行组装,

if( dsm_length_packet(qmux_sdu) == 0)

然后组合上IF Type完成整个消息重组,

iftype = (byte)qmux_state[ qmi_instance ].io.port_info.frame_mode;

最后通过IO口的传输函数发送到AP侧,

if( QMUX_DEVSTREAM_CONTROL != qmux_state[qmi_instance].io.port_info.qmi_stream )
{
sio_transmit( qmux_state[qmi_instance].io.sio_handle, qmux_sdu );
}
else
{
sio_control_transmit( qmux_state[qmi_instance].io.sio_handle, qmux_sdu );
}

这样,消息就从modem侧发出来了。

8 QCRLC消息接收

在qmi_qmux.c的qmi_qmux_pwr_up_init方法中,

QMI_QMUX_IO_PLATFORM_PWR_UP_INIT(qmi_qmux_rx_msg,qmi_qmux_event_cb);

qmi_qmux_rx_msg会接收处理BP侧回发的消息, 主要是对从BP侧接收到的消息进行解包,判断BP侧的消息类型是响应还是指示,转发给上层client端处理。根据QMUX消息结构的长度,选择是用1个字节的方式读取还是2个字节的方式读取,

/* Read the I/F byte, make sure it is a 1 */
READ_8_BIT_VAL(msg_ptr,i_f_byte); if (i_f_byte != 1)
{
QMI_ERR_MSG_1 ("qmi_qmux: Received invalid I/F byte = %d\n",i_f_byte);
return;
}
/* Read the message length */
READ_16_BIT_VAL(msg_ptr, length);

判断消息类型是BP侧群发的广播,还是专门针对某个client端的响应。

if (client_id == QMI_QMUX_INVALID_QMI_CLIENT_ID)
{
qmi_qmux_rx_client_broadcast (conn_id, service_id, client_id, control_flags,
msg_ptr, msg_len);
}
else
{
qmi_qmux_rx_client_msg (conn_id, service_id, client_id, control_flags,
msg_ptr, msg_len);
}

接下去的流程,和之前AP发送的流程相似,不详细介绍。

高通android QMI机制的更多相关文章

  1. 高通Android display架构分析

    目录(?)[-] Kernel Space Display架构介绍 函数和数据结构介绍 函数和数据结构介绍 函数和数据结构介绍 数据流分析 初始化过程分析 User Space display接口 K ...

  2. 高通Android display分析【转】

    本文转载自:http://blog.csdn.net/zhangchiytu/article/details/6777039 高通7系列硬件架构分析 如上图,高通7系列 Display的硬件部分主要由 ...

  3. 高通Android平台硬件调试之Camera篇

    之前一段时间有幸在高通android平台上调试2款camera sensor,一款是OV的5M YUV sensor,支持jpeg out,同时也支持AF,调试比较比较简单,因为别的项目已经在使用了, ...

  4. Android QMI机制

    android QMI机制---概论 android QMI机制---QMUX android QMI机制---Qcril初始化流程 android QMI机制---QCRIL消息发送 android ...

  5. 高通android开发摘要

    一部分是开源的,可以从codeaurora.org上下载,还有一部分是高通产权的,需要从高通的网站上下载. 将高通产权的代码放到:vendor/qcom/proprietary 1. 设置bms一些参 ...

  6. 高通Android camera运行流程【转】

    本文转载自:http://blog.csdn.net/unicornkylin/article/details/13293295 1.总体架构 Android Camera 框架从整体上看是一个 cl ...

  7. 高通android开发缩写

    1.TLMM MSM TLMM pinmux controller,Qualcomm MSM integrates a GPIO and Pin mux/config hardware, (TOP L ...

  8. 高通 android平台LCD驱动分析

    目前手机芯片厂家提供的源码里包含整个LCD驱动框架,一般厂家会定义一个xxx_fb.c的源文件,注册一个平台设备和平台驱动,在驱动的probe函数中来调用register_framebuffer(), ...

  9. 【转】高通平台android 环境配置编译及开发经验总结

    原文网址:http://blog.csdn.net/dongwuming/article/details/12784535 1.高通平台android开发总结 1.1 搭建高通平台环境开发环境 在高通 ...

  10. 在高通平台Android环境下编译内核模块【转】

    本文转载自:http://blog.xeonxu.info/blog/2012/12/04/zai-gao-tong-ping-tai-androidhuan-jing-xia-bian-yi-nei ...

随机推荐

  1. Docker的基本命令

    1.docker使用的优点 1.更快速的交付和部署 对于开发和运维人员来说,最希望的是保持所有环境一致,这样不会导致,开发在自己的环境里程序运行正常而运维跑的服务器环境里就不正常:对于运维来说,可以使 ...

  2. 【2023微博评论爬虫】用python爬上千条微博评论,突破15页限制!

    目录 一.爬取目标 二.展示爬取结果 三.爬虫代码 四.同步视频 五.获取完整源码 您好,我是@马哥python说,一枚10年程序猿. 一.爬取目标 前些天我分享过一篇微博的爬虫: 马哥python说 ...

  3. SpringBoot 二维码生成

    一.基于Google开发工具包ZXing生成二维码 1.引入需要的依赖 <!-- zxing生成二维码 --> <dependency> <groupId>com. ...

  4. postgresql 主键id配序列

    一.手动创建序列 1.表格id字段,设置主键(PRIMARY KEY),类型为int4 2.创建序列 CREATE SEQUENCE public.moni_wzhour_warn_id_seq IN ...

  5. 密码学—Vigenere破解Python程序

    文章目录 概要 预备知识点学习 整体流程 技术名词解释 技术细节 小结 代码 概要 破解Vigenere需要Kasiski测试法与重合指数法的理论基础 具体知识点细节看下面这两篇文章 预备知识点学习 ...

  6. C 语言编程 — 高级数据类型 — 结构体与位域

    目录 文章目录 目录 前文列表 结构体 定义结构体 初始化结构体变量 访问结构体成员 将结构体作为实参传入函数 指向结构体变量的指针 位域 定义位域 使用位域结构体的成员 前文列表 <程序编译流 ...

  7. 5GC 关键技术之 SBA(基于服务的软件架构)

    目录 文章目录 目录 前文列表 5GC 的关键技术 SBA(基于服务的软件架构) 微服务架构 NF 的模块化 NF Service 的服务化 前文列表 <简述移动通信网络的演进之路> &l ...

  8. mvn install:install-file -Dfile=D:\Clipper1.jar -DgroupId=de.lighti -DartifactId=Clipper -Dversion=6.4.2 -Dpackaging=jar

    mvn install:install-file -Dfile=D:\Clipper1.jar -DgroupId=de.lighti -DartifactId=Clipper -Dversion=6 ...

  9. oidc-client.js踩坑吐槽贴

    前言 前面选用了IdentityServer4做为认证授权的基础框架,感兴趣的可以看上篇<微服务下认证授权框架的探讨>,已经初步完成了authorization-code与implicit ...

  10. itest(爱测试)开源接口测试&敏捷测试管理平台8.1.0发布

    (一)itest 简介 itest 开源敏捷测试管理,testOps 践行者,极简的任务管理,测试管理,缺陷管理,测试环境管理,接口测试,接口Mock 6合1,又有丰富的统计分析.可按测试包分配测试用 ...