第1章 RILD及Qcril初始化流程
Rild是android提供的框架,位置位于AP侧(AP主处理器),对接Phone进程和BP侧modem。(BP从处理器)
图1
双卡手机在phone进程中会有2个phone的实例对应各个卡槽,相应的RILD进程也会有2个,phone和RILD之间通过Socket保持连接,各卡槽之间的操作互相独立。
高通用qcril+qmi机制实现,而MTK则直接使用AT指令与modem通信。
Rild是系统服务,在init.rc中能找到启动的描述。
/hardware/ril/rild/rild.rc中启动第一个rild
图1 rild.rc中启动第一个rild
/device/qcom/common/rootdir/etc/init.qcom.rc中启动第二个rild:
图2启动第二个rild(这里除了rild2还有rild3)
根据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两个函数的调用。
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的流程。
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;
//1. 向watch_list加入这个event;
//2. 向fd_set加入s_fdWakeupRead,由于persist为true,此fd和event会一直保留;
//3. 调用triggerEvloop,向s_fdWakeupWrite写入字符,(将会唤醒select);
//无限循环的event loop,select fd_set(readFds);
//出现异常,kill自己完成重启。
图5
/hardware/ril/libril/ril_event.cpp
FD_ZERO(……)重置fd_set
然后就是init_list()初始化ril_event的timer_list、pending_list链表。
图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_table和timer_list中需要进行回调的event都会加入到这里,在回调完成后就会移除。
上一步中准备好基础数据后,接下来向fd_set加入读管道,同时加入一个ril_event到watch_table。(在3.2中eventLoop()函数执行完ril_event_init后,接着就会执行ret=pipe(filedes)…….)
执行pipe函数获得读写fd:s_fdWakeupRead及s_fdWakeupWrite。
图7 eventLoop()函数中接着执行创建管道的代码
执行ril_event_set函数:初始化一个ril_event,fd为s_fdWakeupRead,回调函数为processWakeupCallback,persist值为true(决定了event的fd不会从fd_set移除,获得持续监听和触发回调的能力)---》看一下ril_event_set里面的参数processWakeupCallback,processWakeupCallback的作用就是从s_fdWakeupRead中读数据,相当于清空管道。这样看来这个ril_event存在的作用就是清空管道了。
Event建立完成后,接下来就是执行3.2中eventLoop函数中接下来要调用的rilEventAddWakeup()。rilEventAddWakeup:将s_fdWakeupRead加入readFds集合,event加入到watch_table,然后向s_fdWakeupWrite写入数据。
图8 rilEventAddWakeup()-----》ril_event_add()
再然后就是等待有程序过来select和处理这个event了。
(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。
图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:
2.1中的RILD的main()中有:( 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时,就可以通过这些函数实现。
1)s_rilEnv结构定义:
Qcril.c可以回调ril的方法。
图1 s_rilEnv结构定义,接下来就看qcril中的ril_init
2)qcril.c的RIL_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 ];
在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_event_main()主要逻辑图
Qcril在接到RILC的请求之后,需要根据请求的类型将消息派发给不同的负责模块,而qcril_init()就是完成各个模块的初始化工作。/vendor/qcom/proprietary/qcril/qcril_qmi/qcril.c
Qcril_init方法部分代码如下:
图2 qcril_init部分代码
在这里对qcril的各个模块进行初始化。其中完成了很重要的一步就是将qcril_event_table表拷贝给qcril_hash_table,用于onRequest时对各种请求进行处理,qcril_init_hash_table方法如下:
图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。
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进入循环。
在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();
在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中调用的接口就会进入该函数列表中进行处理。准备工作也就完成了。
当ril有请求过来时,就会调用ril库的onRequest()方法,此时就根据当前Qcril注册的函数列表进入到qcril_request_api的onRequest()函数中。/vendor/qcom/proprietary/qcril/qcril_qmi/qcril.c
图7
然后就会进入到onRequest()中进行处理,接着看一下onRequest的主要方法:
【可以看一下onRequest()的qmi_ril_fw_android_request_render_execution】
……
执行完qmi_ril_fw_android_request_render_execution()
经过一系列的判断最后会派发该event
图8看一下qmi_ril_fw_android_request_render_execution
看一下qmi_ril_fw_android_request_render_execution
……
图9调用qcril_execute_event()
看一下qcril_execute_event()
图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上报的消息。
qcril_qmi_voice_request_hangupàqcril_qmi_client_send_msg_async_exà qmi_client_send_msg_sync_with_shmà qmi_client_send_msg_sync
这里选择的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进行解码。
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
在完成这些后,调用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 ID和Length,整个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的流程走完,主要作用:获取上层发送的请求,选择相应的serviceid、conn_id、client 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的接口。