qcril:第1章 RILD及Qcril初始化流程

第1章 RILD及Qcril初始化流程

    Rild是android提供的框架,位置位于AP侧(AP主处理器),对接Phone进程和BP侧modem。(BP从处理器)

qcril:第1章 RILD及Qcril初始化流程_第1张图片

图1

    双卡手机在phone进程中会有2个phone的实例对应各个卡槽,相应的RILD进程也会有2个,phone和RILD之间通过Socket保持连接,各卡槽之间的操作互相独立。

    高通用qcril+qmi机制实现,而MTK则直接使用AT指令与modem通信。

1.1 RILD进程的启动

    Rild是系统服务,在init.rc中能找到启动的描述。

/hardware/ril/rild/rild.rc中启动第一个rild

图1 rild.rc中启动第一个rild

/device/qcom/common/rootdir/etc/init.qcom.rc中启动第二个rild:

qcril:第1章 RILD及Qcril初始化流程_第2张图片

图2启动第二个rild(这里除了rild2还有rild3)

 

2.1 RILD主函数

         根据init.qcom.rc可以看出,rild的入口函数名字为main。/hardware/ril/rild/rild.c

接着看一下rild.c的main函数。

int main(int argc, char **argv) {

…….

//1.  从启动参数中获取clientID,代表几就是第几个RILD

for (i = 1; i < argc ;) {……

         if (0 == strcmp(argv[i], "-l") && (argc - i > 1)) {……

clientId = argv[i+1];

//2.  使用clientID设置RILD的socket名字

if (strncmp(clientId, "0", MAX_CLIENT_ID_LENGTH)) {……}

//3. 获取vendor ril库路径(3.1)

if (rilLibPath == NULL) {

    if ( 0 == property_get(LIB_PATH_PROPERTY, libPath, NULL)) {

        goto done;

} else {  rilLibPath = libPath;     }   }

//4. 打开vendor ril库(3.1)

dlHandle = dlopen(rilLibPath, RTLD_NOW);

//5. 启动消息循环,LIdRIL开始循环监听socket事件(3.2)

RIL_startEventLoop();

//6. 获取vendor RIL的初始化入口方法(3.3)

rilInit =(const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");

//7. 调用reference-ril.so动态链接库的RIL_Init方法,传递s_rilEnv参数给reference-ril.so,并返回funcs(3.3)

rilArgv[argc++] = "-c";

rilArgv[argc++] = (char*)clientId;

rilArgv[0] = argv[0];

funcs = rilInit(&s_rilEnv, argc, rilArgv);

//8. 调用libril.so的RIL_register函数,将funcs传递给libril.so(3.4)

RIL_register(funcs);

总结main函数的主要内容:

1)获取vendor ril并打开;(第3、4步,对应3.1)

2)启动消息循环;调用RIL_startEventLoop函数,LibRIL开启循环监听socket连接,即可开始接收RILJ发起的socket连接请求和RIL solicited消息请求。(第5步,对应3.2)

3)获得vendor ril入口方法,并取得vendor ril的消息处理函数列表;(6、7步,对应3.3)

4)注册vendor ril回调方法。(第8步,对应3.4)

S_rilEnv对应RIL_Env;

Funcs对应RIL_RadioFunctions。

LibRIL运行环境的加载过程,体现在rild.c代码的main函数中RIL_startEventLoop和RIL_register两个函数的调用。

3.1 深入分析main函数

3.1.1 获取vendor RIL库文件路径

         Main函数中通过getprop获取了vendor ril文件的路径,property名为“rild.libpath”。/hardware/ril/rild/rild.c文件中:

首先定义LIB_PATH_PROPERTY的值;

#define LIB_PATH_PROPERTY   "rild.libpath"

然后在main函数中获取vendor ril文件的路径:

property_get(LIB_PATH_PROPERTY, libPath, NULL)。

1. 找到libril-qc-qmi-1.so库文件路径;

2. 在/vendor/qcom/proprietary/qcril/qcril_qmi/qcril_qmi.mk中有描述;

图3库文件路径描述

    在取得了vendor ril库文件路径之后,打开库并取得文件句柄,流程似乎即将进入到vendor ril。但在此之前,rild会先启动一个子线程消息循环,然后再开始qcril的流程。

3.2 RIL_startEventLoop启动监听消息循环

    RIL_startEventLoop创建了一个线程,用于执行eventLoop这个函数。

1. Ril.cpp中的RIL_startEventLoop():

启动线程,执行eventLoop函数,在eventLoop函数中会将s_started置为1.

S_started会在eventLoop被执行后置为1,保证了线程启动后才离开这个函数

图4在while循环主要是通过判断s_started的值,保证线程已经启动且eventLoop已经开始被执行,因为在eventLoop函数中会将s_started值置为1。

2. eventLoop是Rild中消息获取和分发的中心,接下来来详细查看一下:

// 1.初始化fd_set(readFds)3.2.1;

// 2.初始化几个消息链表(watch_table/pending_list/timer_list);

//使RIL_startEventLoop退出;

//获得读&写管道3.2.2;

//初始化一个event,回调函数为processWakeupCallBack;

qcril:第1章 RILD及Qcril初始化流程_第3张图片

//1. 向watch_list加入这个event;

//2. 向fd_set加入s_fdWakeupRead,由于persist为true,此fd和event会一直保留;

//3. 调用triggerEvloop,向s_fdWakeupWrite写入字符,(将会唤醒select);

//无限循环的event loopselect fd_set(readFds);

qcril:第1章 RILD及Qcril初始化流程_第4张图片

//出现异常,kill自己完成重启。

图5

 

3.2.1 ril_event_init为eventLoop初始化数据

/hardware/ril/libril/ril_event.cpp

FD_ZERO(……)重置fd_set

然后就是init_list()初始化ril_event的timer_list、pending_list链表。

qcril:第1章 RILD及Qcril初始化流程_第5张图片

图6

看一下参数readFds,他是一个fd_set,后续连接客户端后,socket fd会加入到这个set去,监听输入。

/hardware/ril/libril/ril_event.h中的ril_event链表结构体,保存了前后2个ril_event的指针,元素中还有fd、persist值、回调函数func和空指针类型param。(func被具体调用的时候,参数就是ril_event自身的fd和param)

Watch_table是ril_event数组指针,保存需要观察的ril_event;

Timer_list是ril_event结构体,带有超时属性的event;通过ril_timer_add函数加入,在eventLoop中判断超时后处理。

Pending_list同样是ril_event结构体,存放需要马上被回调的event。eventLoop中select返回后,watch_tabletimer_list中需要进行回调的event都会加入到这里,在回调完成后就会移除

3.2.2 创建pipe

上一步中准备好基础数据后,接下来向fd_set加入读管道,同时加入一个ril_event到watch_table。(在3.2中eventLoop()函数执行完ril_event_init后,接着就会执行ret=pipe(filedes)…….)

执行pipe函数获得读写fd:s_fdWakeupRead及s_fdWakeupWrite。

qcril:第1章 RILD及Qcril初始化流程_第6张图片

图7 eventLoop()函数中接着执行创建管道的代码

3.2.3 ril_event_set

执行ril_event_set函数:初始化一个ril_event,fd为s_fdWakeupRead,回调函数为processWakeupCallback,persist值为true(决定了event的fd不会从fd_set移除,获得持续监听和触发回调的能力)---》看一下ril_event_set里面的参数processWakeupCallback,processWakeupCallback的作用就是从s_fdWakeupRead中读数据,相当于清空管道。这样看来这个ril_event存在的作用就是清空管道了。

3.2.4 rilEventAddWakeup

 

         Event建立完成后,接下来就是执行3.2中eventLoop函数中接下来要调用的rilEventAddWakeup()。rilEventAddWakeup:将s_fdWakeupRead加入readFds集合,event加入到watch_table,然后向s_fdWakeupWrite写入数据。

图8 rilEventAddWakeup()-----》ril_event_add()

再然后就是等待有程序过来select和处理这个event了。

3.2.5 ril_event_loop

(3.2中的代码按照顺序,这是应该执行ril_event_loop了。/hardware/ril/libril/ril_event.cpp中定义)Ril_event_loop监听socket消息循环。Ril_event_loop函数进入for循环之后,将等待select函数返回。(因为在3.2.2中已经加入了一个event,这里马上就会返回)

等select返回后,将timer_list中超时的event和watch_table中event加入到pending_list中等待处理。最后遍历pending_list,执行所有ril_event的回调函数。

然后,循环会进入到下一轮。

在ril_event_loop中将会看到event中的fd、persist、func等成员如何被使用。在ril_event.cpp的ril_event_loop中将会---》processTimeouts()、processReadReadies(&rfds, n)、firePending()。这三个函数会处理timer_list、watch_table、pending_list中的event。qcril:第1章 RILD及Qcril初始化流程_第7张图片

图9 RIL_startEventLoop函数流程解析图

1)Ril_event_init:初始化ril_event;

2)ret=pipe(fileds):创建管道;

执行pipe函数获得读写fd:s_fdWakeupRead及s_fdWakeupWrite。

3)Ril_event_set:设置新创建的ril_event事件参数;

4)rilEventAddWakeup—》ril_event_add:增加ril_event事件的监听;

        --》triggerEvLoop:函数获得读写s_fdWakeupWrite能力;

Ril_event_add会将前面已经准备好的ril_event保存到watch_table中;

Ril_event_add:里面会设置fd_set,每个结构体的文件描述符都设置到readFds的fd_set数据类型里面,完成了select多端口复用的准备工作。

再然后就是select唤醒ril_event_loop处理。

4)ril_event_loop:      

 

3.3 初始化QCRIL并获取回调函数

2.1中的RILDmain()中有:( rild.c加载驱动动态)

dlHandle = dlopen(rilLibPath, RTLD_NOW);

rilInit =(const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");

funcs = rilInit(&s_rilEnv, argc, rilArgv);

rild和qcril之间通过确定的几个接口函数来互相交流,现在rild需要和qcril相互交换各自的接口列表。Rild.c调用qcril的RIL_Init完成了这个工作。

rilInit的第一个参数是s_rilEnv,就是作为rild执行response处理的函数列表,qcril需要上报消息给ril时,就可以通过这些函数实现。

1s_rilEnv结构定义:

Qcril.c可以回调ril的方法。

图1 s_rilEnv结构定义,接下来就看qcril中的ril_init

2qcril.cRIL_init方法:

方法主要作用:

a. 保存rild传入的回调函数;

b. 初始化qcril环境;

c. 启动子线程执行循环,接收从modem上报的消息,并处理(类似于rild的eventLoop)

d. 返回qcril的回调函数给rild,作为rild下发request的入口。

主要代码:

// 设置线程的名字

qmi_ril_set_thread_name( pthread_self() , QMI_RIL_QMI_RILD_THREAD_NAME);

// 初始化接收modem消息的EventLoop(详见3.3.1)

qcril_event_init();

// 初始化QCRIL的各个模块(3.3.2)

qcril_init();

// 启动线程()(3.3.3)

qcril_event_start();

// 其他初始化(3.3.4)

qmi_ril_initiate_bootup();

// 返回接口函数()(3.3.5)

return &qcril_request_api[ QCRIL_DEFAULT_INSTANCE_ID ];

3.3.1 初始化EventLoop过程

在QCRIL中搭建了EventLoop循环用于检测modem上报的消息,而EventLoop机制的初始化工作是在qcril_event_init()中完成的。/vendor/qcom/proprietary/qcril/qcril_qmi/qcril_event.c

qcril_event.c中的qcril_event_init()主要代码:

// 创建线程,入口方法为qcril_event_main

ret = pthread_create(&qcril_event.tid, &attr, qcril_event_main, NULL);

pthread_attr_destroy( &attr );

……

// 设置线程的名字为event

qmi_ril_set_thread_name(qcril_event.tid, QMI_RIL_EVENT_THREAD_NAME);

//等待qcril_event_main

while (qcril_event.started == 0){

pthread_cond_wait(&qcril_event_startupCond, &qcril_event.startup_mutex);}

注意:QMI_RIL_EVENT_THREAD_NAME在qcrili.h文件中定义的值为event。

Qcril_event_main所执行的操作:

a.初始化qcril_event_list,这是名为qcril_event_buf的结构体,以链的方式    关联消息。

b.初始化fd_set,创建读写管道。

c.启动消息循环,使用select fd_set方式监听消息。

d.select返回后,清空读管道,并循环处理所有消息。

在初始化过程中,通过pthread_create()函数创建了EventLoop线程,并且指出该线程的入口方法为qcril_event_main(),qcril_event_main()主要逻辑如下:

// 初始化qcril_event.list链表

qcril_event_init_list(&qcril_event.list);

// 创建管道

ret = pipe(filedes);

//fdWakeupRead加入到readFds

FD_SET(qcril_event.fdWakeupRead, &qcril_event.readFds);

// 阻塞等待qcril初始化,qcril_event_start被调用后才能继续执行

while (qcril_event.started < 2) {

QCRIL_LOG_VERBOSE("Event thread waiting for started == 2 (%d)", qcril_event.started );

pthread_cond_wait(&qcril_event_startupCond, &qcril_event.startup_mutex); }

//  for循环读取qmi底层发送的请求

for(;;){

// 阻塞等待接受内容

n = select(qcril_event.fdWakeupRead + 1, &rfds, NULL, NULL, NULL);

do {// 清空管道

ret = read(qcril_event.fdWakeupRead, &buff, sizeof(buff));

……} while (ret > 0 || (ret < 0 && errno == EINTR));

//处理消息

Do{ if(){

//从qcril_event.list中去掉本次的event

qcril_event_remove_from_list( ev );

//处理event

err_no = qcril_process_event( ev->instance_id, ev->modem_id, ev->event_id, ev->data, ev->datalen, ev->t );

//释放资源

if ( to_check_ev_data_free && ev->data_must_be_freed && ev->data ){

qcril_free( ev->data );} qcril_free( ev );

//循环处理完qcril_event.list中所有消息

}while(go_on)

}……

在以上过程中,完成qcril_event.list链表的初始化,然后通过pthread_cond_wait进入阻塞状态,当被解锁后以及进入EventLoop循环,检测到事件后,通过qcril_process_event处理。

注意:这里用到的消息列表qcril_event.list是名为qcril_event_buf的结构体,以链的方式关联消息。qcril:第1章 RILD及Qcril初始化流程_第8张图片

qcril_event_main()主要逻辑图

 

3.3.2 初始化qcril各个模块

Qcril在接到RILC的请求之后,需要根据请求的类型将消息派发给不同的负责模块,而qcril_init()就是完成各个模块的初始化工作。/vendor/qcom/proprietary/qcril/qcril_qmi/qcril.c

Qcril_init方法部分代码如下:

qcril:第1章 RILD及Qcril初始化流程_第9张图片

图2 qcril_init部分代码

    在这里对qcril的各个模块进行初始化。其中完成了很重要的一步就是将qcril_event_table表拷贝给qcril_hash_table,用于onRequest时对各种请求进行处理,qcril_init_hash_table方法如下:

qcril:第1章 RILD及Qcril初始化流程_第10张图片

图3将qcril_event_table表拷贝给qcril_hash_table

Qcril_event_table是一个静态表单,里面保存了所有RILC下发请求的id以及相应的处理函数,表单部分内容如下:

图3 表单部分内容

表单里面的每一项都包含两个元素:事件ID和处理函数,在处理这些消息时将根据事件的id查找并执行相应的处理函数。

比如,对于得到当前SIM卡状态这个请求,对应的id为RIL_REQUEST_GET_SIM_STATUS,处理方法qcril_uim_request_get_sim_status。

3.3.3 EventLoop线程启动

    qcril_event_start(),初始化EventLoop时,在完成其链表的初始化过程后,通过pthread_cond_wait()将其阻塞,而现在要做的就是取消其阻塞状态,时期进入消息检测循环。这是在qcril_event_start()中完成的。/vendor/qcom/proprietary/qcril/qcril_qmi/qcril_event.c

图4

由于EventLoop被初始化后一直处于阻塞状态,所以在这里将started状态置为2后,对qcril_event_startupCond进行解锁,从而使EventLoop进入循环。

3.3.4 其他初始化过程

在qmi_ril_initate_bootup方法如下:/vendor/qcom/proprietary/qcril/qcril_qmi/qcril.c

图5

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();

3.4 qcril回调

 

3.4.1 将回调函数注册给RILC

在QRIL的初始化完毕后,将自己的函数列表返回给RilC,也就是qcril_request_api。qcril_request_api就是提供给rild下发的request的入口。return &qcril_request_api[ QCRIL_DEFAULT_INSTANCE_ID ];

图6 qcril.c参数里面有一个onRequest_rid,接下来的2.1就是点击该参数出现的

这样的话,在RIL中调用的接口就会进入该函数列表中进行处理。准备工作也就完成了。

3.4.2 QCRIL对请求的处理过程

    当ril有请求过来时,就会调用ril库的onRequest()方法,此时就根据当前Qcril注册的函数列表进入到qcril_request_api的onRequest()函数中。/vendor/qcom/proprietary/qcril/qcril_qmi/qcril.c

qcril:第1章 RILD及Qcril初始化流程_第11张图片

图7

然后就会进入到onRequest()中进行处理,接着看一下onRequest的主要方法:

【可以看一下onRequest()的qmi_ril_fw_android_request_render_execution】

……

执行完qmi_ril_fw_android_request_render_execution()

qcril:第1章 RILD及Qcril初始化流程_第12张图片 ……

经过一系列的判断最后会派发该event

qcril:第1章 RILD及Qcril初始化流程_第13张图片

图8看一下qmi_ril_fw_android_request_render_execution

看一下qmi_ril_fw_android_request_render_execution

……

图9调用qcril_execute_event()

看一下qcril_execute_event()

qcril:第1章 RILD及Qcril初始化流程_第14张图片 //处理当前request

图10

图10中,通过entry_ptr—》handler调用当前event的处理函数,这里的handler对应qcril_hash_table中的某一项。从前面的将qcril_event_table表中的数据拷贝给了qcril_hash_table,所以这里的handler可以理解为qcril_event_table中的某一项。

之后的流程就会进入到某一个具体的请求的处理函数中,比如对于得到当前SIM卡状态这个请求,其处理函数为:qcril_uim_request_get_sim_status()。

{ QCRIL_REG_ALL_ACTIVE_STATES( RIL_REQUEST_HANGUP, qcril_qmi_voice_request_hangup ) },

比如对于挂断电话对应的请求是RIL_REQUEST_HANGUP,其处理函数为qcril_qmi_voice_request_hangup;

qcril_qmi_voice_request_hangupàqcril_qmi_client_send_msg_async_ex(发送到QMI层)

qcril_qmi_client_send_msg_async:异步处理

qcril_qmi_client_send_msg_sync:同步处理

最后,这两个方法都会调用qmi_client_send_msg_sync完成发送,一些其他的处理方法或者会调用这两个方法发送到QMI层,或者直接调用qmi_client_msg_sync发送。

 

rild与phone进程通过socket连接,并创建了一个消息循环来监听处理phone下发的指令;而qcril也启动了一个消息循环,监听处理modem上报的消息。

 

3.4.3 QMI层消息处理

qcril_qmi_voice_request_hangupàqcril_qmi_client_send_msg_async_exà qmi_client_send_msg_sync_with_shmà qmi_client_send_msg_sync

qcril:第1章 RILD及Qcril初始化流程_第15张图片

这里选择的qmi_service是voice,之所以选择voice作为发送通道,是因为第二个参数QMI_VOICE_ANSWER_CALL_REQ_V02

qcril_qmi_client_send_msg_async方法主要逻辑如下:

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方法,同步消息在QMUX层发送到BP侧后,会一直等待BP的响应所以函数的最后一个参数是timeout,而异步消息不需要。

qmi_client_send_msg_sync方法首先计算请求消息的长度和设定返回消息的最大长度,然后对请求消息按QMUX格式进行编码,然后通过函数qmi_service_send_msg_sync_millisec()发送出去,最后等待BP侧返回的响应,将返回的response进行解码。

3.4.4 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

qcril:第1章 RILD及Qcril初始化流程_第16张图片

在完成这些后,调用qmi_service_send_msg方法发送,该方法会:

1.判断服务ID和通道的conn_id是否有效

2.调用qmi_service_write_std_srvc_msg_hdr方法给发送过来的消息加上message ID和Length,所以不难猜测,在QMI发送端的接口函数qmi_client_send_msg_sync()中编解码的原理是按照QMUX消息中的TLV格式进行编解码,再走到这步加上message IDLength,整个QMUX SDU 中的QMI service message已完成。

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

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

4.最后通过函数qmi_qmux_if_send_qmi_msg()发送到QMUX层,到这里,整个QMI interface的流程走完,主要作用:获取上层发送的请求,选择相应的serviceidconn_idclient id,对消息完成整个QMUX SDU的封装,发送到QMUX

qmi_qmux_if_send_qmi_msg():对上层发过来打包好的QMUX SDU完成加QMUX HEADER的工作,直接调用qmi_qmux_if_send_to_qmux进行处理

qmi_qmux_if_send_to_qmux:首先将QMUX header里面的控制位,QMI服务ID,QMI客户端ID打包到结构体hdr,再通过memcpy完成。到这里整个QMUXmessage完成封装,然后会把封装好的QMUXmessage发到下层:通过调用QMI_QMUX_IF_PLATFORM_TX_MSG()àlinux_qmi_qmux_if_client_tx_msg(),该方法通过socket将消息发到linux_qmi_qmux_if_server的接口。

 

 

 

你可能感兴趣的:(qcril:第1章 RILD及Qcril初始化流程)