ril.cpp中定义的ril_event有5个,分别为:s_commands_event、s_wakeupfd_event、s_listen_event、s_wake_timeout_event、s_debug_event。其中最为重要的是s_listen_event和s_commands_event。
① s_listen_event (s_fdListen, listenCallback)
listenCallback处理函数用于接收客户端连接,其实现如下:s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen)。其次用于添加s_commands_event,重新建立s_listen_event,等待下一次连接。
②s_commands_event(s_fdCommand,processCommandsCallback)
从s_fdCommand Socket连接中读取record_stream,使用processCommandBuffer()处理数据。s_listen_event在大的功能上处理客户端连接(RIL-JAVA层发起的connect),并建立s_commands_event去处理Socket连接发来的RIL命令。ProcessCommandBuffer()包含了RIL指令的下行过程。
(4)下行命令的翻译及其组织—processCommandBuffer
Ril-JAVA传递的命令格式—Parcel,由命令号、令牌、内容组成。RIL-JAVA到达RIL-C时转换为构建本地RequestInfo,并被翻译成具体的AT指令。由于每条AT命令的参数是不同的,所以对不同的AT指令,有不同的转换函数。在此Android做了抽象,做了个分发框架,通过命令号,利用sCommand数组,获得该命令的处理函数。sCommand存在于ril_commands.h中。&sCommand[ ]=
<
{RIL_REQUEST_DIAL, dispatchDial, responseVoid},
{RIL_REQUEST_HANGUP,dispatchInts,responseVoid},
{……} >
ril.cpp中dispatchxxx函数的具体实现放在reference-ril.c中,reference-ril.c就是我们需要根据不同的Modem来修改的文件。
(5)at_send_command
at_send_command是同步的,其函数功能具体实现在atchannel.c中。 命令发送后,at_send_command将等待在s_commandcond,直到有p_response->finalResponse。
2.2 Read Loop
Read Loop解决的问题是:解析从Modem发过来的回应。具体实现依赖于atchannel.c文件,其工作机制如下图所示。如果遇到URC则通过handleUnsolicited函数上报给RIL-JAVA;如果是命令的应答,则通过handFinalResponse函数通知at_send_command有应答结果。handleUnsolicited函数原型和handFinalResponse函数原型在atchannel.c中的定义如下: static void handleUnsolicited(const char *line)
{
if (s_unsolHandler != NULL) { s_unsolHandler(line, NULL); }
}
static void handleFinalResponse(const char *line) {
sp_response->finalResponse = strdup(line); pthread_cond_signal(&s_commandcond); }
Serial
Read Loop
readline
ProcessLine
at_send_command
等待应答
Event Loop
Socket
URC
通知应答
3 Android RIL 研究之RIL-Java
RIL-Java质上就是一个RIL代理,起到一个转发的作用,在RILD的分析中,我们知道RILD建立了一个监听套接字,等待RIL-Java的连接。一旦连接成功,RIL-Java就可以发起一个请求,并等待应答,并将结构发送到目标处理对象。在
RIL-Java中,这个请求称为RILRequest。RIL-Java的框架图如下:
RIL-Java
ReceiverSender
Command Interface
RIL-Java框架包含了四个方面:Receiver,Sender,Command Interface和异步通知机制。 3.1 Command Interface
包括诸如getCurrentCalls、getIccCardStatus、dial、 acceptCall、rejectCall、sendDtmf、sendSMS、setupDataCall、setRadioPower等命令接口函数,这些函数的实现在frameworks/base/telephony/java/com/android/internal/telephony /RIL.java中。这些命令接口函数都是对电话基本功能的描述,是对Modem AT指令的提炼与抽象。 3.2 Receiver
Receiver对Response Parcel的处理机制如下图所示。
XXX.notifyRegistran通知注册表中的Handler
processUnsolicitedprocessSolicited
组合结果
Result.sendToTarget()将结果发送到
目标Handler
processResponse
URC
Receiver 连接到RILD的服务套接口,接收并读取RILD传递过来的Response Parcel。Response分为两种类型,一种是URC,一种是命令应答。对于URC将会直接分发到通知注册表中的Handler。而命令应答则通过Receiver的异步通知机制传递到命令的发送者进行相应处理。
3.3 Sender
Sender应该分为两部分框架:上层函数调用Command Interface将请求消息发送到Sender的框架;Sender接收到EVENT_SEND消息后,将请求发送到RILD的架构。具体实现流程如下图所示。
Command Interface
具体实现
doCommandXXX(data,resultxxx)RILRequest RRRR.mSerial=XXXRR.mResult=xxx
RR.mRequest=RIL_REQUEST_xxxdata->RRsend(RR)
handleMessage@RILSender
EVENT_SEND
mRequestList.add(RR)
RR->Parcel
writeToRILD(parcel)
Sender
3.4 异步通知
对于异步应答来讲,命令的发起者发送命令请求后,并不等待应答就返回,应答的回应是异步的,处理结果通过消息的方式返回。对于异步系统,首先应该考虑的是如何标识命令和结果,让命令和结果有个对应的关系,其次对于命令没有响应了,应该如何管理命令超时?
Android系统设计者利用了Result Message 和RILRequest对象来完成Request和Result的对应关系。在上层做调用时生成Result Message对象传递到RIL-Java,并在Modem有应答后,通过Result Message对象带回结果。为了保证该应答是该RILRequest的,系统设计者还提供了一个Token(令牌)的概念。在源代码中RILRequest的mSerial就用作了Token。Token用来唯一标识每次发送的请求,并且Token将被传送到RILD,RILD在组装应答时将Token写入,并传回到RIL-Java,RIL-Java根据该Token找到相应的Request对象。
(1) RIL命令的发送模式
协议的真正实现是在RILD中,RIL-Java更多的是一个抽象和代理,本文在研究源代码的过程中发现RIL-Java中的命令函数都有一个共同的框架:
SendxxxCmd(传入参数data,传出参数result){
组合RILRequest(请求号,result,mSerial) data->RR
send(RILRequest) }
请求号将被传递到RILD用以标识命令,请求号代表某个电话功能。例如拨号的request号为:RIL_REQUEST_DIAL。在hardware/ril/libril/
ril_commands.h中有定义。RILRequest obtain(int request, Message result)函数根据命令请求号,传入参数Message result。Message result将带回应答信息回到命令的发起者。mSerial则构造了一个RILRequest。
Android使用了一个RILRequest对象池来管理RILRequest。mSerial是一个递增的变量,用来唯一标识一个RILRequest。在发送时正是用了该变量为Token,在RILD层看到的token就是该mSerial。
RIL命令的具体发送步骤如下:第一步:生成RILRequest,此时将生成mSerial(请求的Token),并将请求号及其Message result对象填入到RILRequest中;第二步:使用send将RILRequest打包到EVENT_SEND消息中发送到RIL Sender Handler;第三步:RILSender接收到EVENT_SEND消息后,将RILRequest通过套接口发送到RILD,同时将RILRequest保存在mRequest中以便应答消息的返回。
(2)RIL命令的接收模式
RIL命令的具体接收步骤如下:第一步:分析接收到得Pacel(type|token|error|response|data),根据类型不同进行处理;第二步:根据数据中的Token(mSerial),反查mRequest,找到对应的请求信息;第三步:将数据转换成结果数据;第四步:将结果数据放在RequestMessage中发回到请求的发起者。
4 Android RIL研究之GsmCallTracker
GsmCallTracker在本质上是一个Handler。GsmCallTracker是Android的通话管理层。GsmCallTracker建立了ConnectionList来管理现行的通话连接,并向上层提供电话调用接口。
在GsmCallTracker中维护着通话列表:connections。顺序记录了正连接上的通话。这些通话包括:ACTIVE 、IDLE、INCOMING、WAITING、 HOLDING 、ALERTING 、DIALING等连接状态。GsmCallTracke将这些连接分为了三类进行管理:
Ringing call: INCOMING,WAITING
Foreground call:ACTIVE、DIALING、ALERTING
Background call:HOLDING 上层函数通过getRingCall(),getForegroundCall()等来获得电话系统中特定通话连接。
为了管理电话状态,GsmCallTracker在构造时就将自己登记到了电话状态变化通知列表中。RIL-Java一旦收到电话状态变化的通知时,就会使用EVENT_CALL_STATE_CHANGE通知到GsmCallTracker。
在一般的实现中,通话的Call Table是通过AT+CLCC命令查询得到的,CPI可以通知到电话的改变,但 CPI在各个Modem的实现中差别比较大,所以在RIL实现中都没用到CPI这样的电话连接改变通知,而是使用最为传统的CLCC查询Call Table。在GsmCallTracker中使用connections来管理Android电话系统中的通话连接。每次电话状态发生变化时GsmCallTracker就会使用CLCC查询更新connections内容,如果内容有发生变化时,则向上层发起电话状态改变的通知。
4.1 RIL-Java中发起的电话连接列表操作
在RIL-Java中涉及到的CurrentCallList查询的有以下几个操作:hangup 、dial、acceptCall、rejectCall。GsmCallTracker在发起这些调用的时都有一个共同的ResultMessage构造函数—obtainCompleteMessage()。obtainCompleteMessage()实际上是调用:obtainCompleteMessage(EVENT_OPERATION_COMPLETE)。这就意味着在这些电话操作后,GsmCallTracker会收到EVENT_OPERATION_COM PLETE的消息。于是把目标转移到handleMessage()函数的EVENT_OPERATION_ COMPLETE case中。相关核心代码如下:
public void handleMessage (Message msg) { ……
switch (msg.what) { ……
case EVENT_OPERATION_COMPLETE: ar = (AsyncResult)msg.obj; operationComplete(); break; ……
} …… }
operationComplete()操作会使用cm.getCurrentCalls(lastRelevantPoll)调用,向RILD发起RIL_REQUEST_GET_CURRENT_CALLS调用,这个最终就是向Modem发起AT+CLCC,获得真正的电话列表。
4.2 在RILD中引起getCurrentCalls调用
在RILD中,会收到诸如+CRING、RING、NO CARRIER、+CCWA等URC消息 ,此时将会调用RIL_onUnsolicitedResponse(RIL_UNSOL_RESPONSE_CA LL_ STATE_CHANGED)主动向RIL-Java上报RIL_UNSOL_RESPONSE_CALL_ STATE_CHANGED消息。
在处理requestCurrentCalls时,使用CLCC查询通话连接列表—Call Table后,如发现有Call Table不为空则开启一个定时器,主动上报RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED消息,直到没有电话连接为止。
在RIL-Java层收到 RIL_UNSOL_RESPONSE_CALL_STATE_CHANGE 这个URC,并利用mCallStateRegistrants. notifyRegistrants()来通知电话状态的变化,此时GsmCallTracker会接收到EVENT_CALL_STATE_CHANGE消息,并使用pollCallsWhenSafe() cm.getCurrentCalls(lastRelevantPoll)来发起查询,并更新Java层的电话列表。
4.3 handlePollCalls电话列表刷新
上面分析了Android电话系统中所有引起电话连接列表更新的条件及其处理,它们共同调用了cm.getCurrentCalls(lastRelevantPoll)来完成电话列表的获取。lastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT),从这里可以看到获取到的电话列表result使用handlePollCalls()进行了处理。result实际上是一个DriverCall列表,handlePollCalls函数的工作就是将当前电话列表与RIL-Java的电话列表对比,使用DriverCall列表更新GsmCallTracker电话列表的connections,并向上传递电话状态改变的通知。
第二章 移植与实现部分
本文研究基于Android 2.3操作系统,开发宿主平台为Ubuntu 9.04,目标平台为freescale I.MX51 EVK for Android。I.MX51是基于ARM Cortex-A8应用处理器。I.MX51 EVK有Mini-PCI接口。无线通讯模块采用的是基于CDMA 2000 1x/EvDo Rev.A技术的MI800模块,即通常所说的Modem。无线通讯模块通过Mini-PCI接口与目标平台连接。
TE
MT
AT 命令
Information text
Result codes
2.1 AT命令简介[
AT即Attention,AT命令集是从TE(Terminal Equipment)或DTE(Data Terminal Equipment )向TA(Terminal Adapter)或DCE(Data Circuit Terminal Equipment)发送的。其对所传输的数据包大小有定义:即对于AT指令的发送,除AT两个字符外,最多可以接收1056个字符的长度(包括最后的空字符)。每个AT命令行中只能包含一条AT指令,对于由终端设备主动向PC端报告的URC指示或者response响应,也要求一行最多有一个,不允许上报的一行中有多条指示或者响应。AT指令以回车作为结尾,响应或上报以回车换行为结尾。 2.2 通话相关AT命令介绍
(一)语音呼叫接口
(1)、呼叫发起指示^ORIG
^ORIG表示MT正在发起呼叫,其语法结构如下表: Command Possible responses <CR><LF>^ORIG:<call_x>,<call_type><CR><LF>
<call_x>:呼叫ID,范围1~7。如果连续拨打多次,ID由1~7循环往复。
<call_type>:呼叫类型。为0时代表语音呼叫,为9时代表紧急呼叫。
(2)、呼叫接通指示^CONN
当呼叫接通后,MT向TE上报此指示,表明当前状态已经变为通话状态。其语法结构和取值说明同上。
(3)、通话结束指示^CEND
当呼叫结束后,MT(Mobile Terminal)向TE(Terminal Equipment)上报此指示,告知TE通话结束原因和通话时长。其语法结构如下表:
Command Possible responses
<CR><LF>^CEND:<call_x>,<duration>,<end_status>[,<cc_cause>] <CR><LF>
<call_x>:呼叫ID,范围1~7。
<duration>:通话时长,以s为单位。
<end_status>:呼叫结束原因。原因有21种,如当该字段取值为28表示来电时收到了振铃停止的信令,为105时表明话费用完,为106时表示不在服务区。由于篇幅有限,在这里不再赘述。
<cc_cause>:呼叫控制信息,该字段为可选字段。如果为网侧引发的呼叫结束才会有此字段;如果本地发出的呼叫,还没得到网侧的响应呼叫就结束,此时不会有<cc_cause>上报。该字段取值情况有68个。如当取值为17时表示USER_BUSY,当取值为28时表示错误的号码格式。
(二)呼叫控制命令与方法
(1)、来电指示RING
当移动终端有被叫来电时,MT会周期性(T=5s)地上报此指示通知TE。
(2)、接听命令A
当移动终端有来电时,TE用此命令告知MT接听电话。
(3)、呼叫发起命令+CDV
本接口用于TE通过MT向网络侧发起语音呼叫。其语法结构如下: Command Possible responses +CDV[digits] <CR><LF>OK<CR><LF>; 与MT相关错误时:<CR>
<LF>+CME ERROR:<err><CR><LF>
<digits>:被叫的电话号码,ASCII字符,合法的字符包括:‘0‘-‗9‘、‘*‘、 ‗#‘、‘+‘。‘+‘只能出现在号码的最前面,号码的最大长度不能超过24(不包括―+‖)。
(4)、呼叫挂断命令+CHV
本接口用于在CDMA系统中挂断语音呼叫。
(5)、呼叫状态查询命令+CLCC
该命令用于查询当前存在几个呼叫及各个呼叫的状态,指令只有在呼叫过程中和通话过程中查询才有效。其语法结构如下: Command Possible responses +CLCC <CR><LF>+CLCC:<idx>,<dir>,<state>,<mode>,<mpty>,<nu
mber>,<type><CR><LF>
<idx>:呼叫ID。
<dir>:呼叫方向,0表示MO,1表示MT。
<state>:呼叫状态,取值如下:0表示激活状态(active);1表示呼叫保持状态(held);2表示发起呼叫,拨号状态(dialing);3表示发起呼叫,振铃状态(alerting);4表示来电振铃状态(incoming);5表示等待状态(waiting)。
<mode>:呼叫类型,取值如下:0表示语音呼叫(voice);1表示数据呼叫(data);2表示传真(fax)。
<mpty>:多方通话。取值如下:0表示非多方通话,1表多方通话。
<number>:呼叫号码,ASCII字符,合法的字符包括:‘0‘-‗9‘、‘*‘、 ‗#‘、‘+‘。‘+‘只能出现在号码的最前面。
<type>:呼叫号码类型,一般为129。
2.3 通话功能在RIL中的具体实现
Android的RIL驱动模块加载后,只需在系统调用的对应接口函数中,接收上层传递过来的参数并实现相应的AT命令的发送即可。下图[16]表明了Android系统一个主动请求的电话过程。
lemote-RIL
Phone
rildRIL implbaseband
4 Start Dial
Dial complete Dial()Unix scokets
1
Dial()
func call2
onRequest()
AT+CDV
3
Listen thread
6
onRequestComplete()
7
Async return
8
Immediate return
5 Dial FinishAT+CHV
Android
当MT主动发起呼叫时(此时Android 系统界面表现行为为用户选中了拨打电话键),RIL-Java通过Scoket传递请求给RILD守护进程,RILD加载Vendor-RIL,调用Vendor-RIL中的请求函数—requestDial(),从而实现电话的呼叫。此时MT终端通过网络侧会上报呼叫发起指示^ORIG,以表示MT正在发起呼叫。当被呼叫方接通电话时,MT向TE上报呼叫接通指示^CONN,表明当前状态已经变为通话状态。当通话结束时,MT向TE上报通话结束指示^CEND。具体上报内容可以在MT终端输入―adb logcat –b radio‖命令查看。requestDial()函数具体实现代码如下所示:
static void requestDial(void *data, size_t datalen, RIL_Token t) {
RIL_Dial *p_dial;
char *cmd; //用于保存要发送AT命令的字符串 const char *clir; int ret;
p_dial = (RIL_Dial *)data; //接收data字段,并强制转换成RIL_Dial类型 switch (p_dial->clir) {
case 1: clir = "I"; break; /*invocation*/ case 2: clir = "i"; break; /*suppression*/ default:
case 0: clir = ""; break; /*subscription default*/ }
asprintf(&cmd, "AT+CDV%s%s;", p_dial->address, clir); // p_dial->address为被呼叫方电话号码
ret = at_send_command(cmd, NULL); //发送呼叫命令 free(cmd);
RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
//调用onRequestComplete接口,通知RIL-Java层电话状态变化
}
MT主动发起呼叫时或通话过程中,通话双方任意一方可以按挂机键实现挂机。根据挂断时机不同,RIL层在具体实现时会调用到不同的接口函数:requestHangup()、requestHangupWaitingOrBackground()、 requestHangupForegroundResumeBackground()、
requestUDUB()。以requestHangup()为例,当MT主动发起呼叫但通话尚未接通时,呼叫方主动挂机会调用此函数,函数体具体实现如下:
static void requestHangup(void *data, size_t datalen, RIL_Token t) {
at_send_command("AT+CHV", NULL); //直接发送呼叫挂断命令 RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
调用onRequestComplete接口,通知RIL-Java层电话状态变化
}
其他函数实现与此类似,在此不再赘述。
Android系统一个被动请求的电话过程如下图[16]所示:
lemote-RIL
当移动终端有被叫来电时,MT会周期性(T=5s)地上报RING
指示。 此时MT可以选择拒接(挂断)或者接听,当用户选择接听键时, 在RIL层实现时会调用到requestAnswer()函数,该函数体具体实现如下:
static void requestAnswer(RIL_Token t) {
at_send_command("ATA", NULL); //发送ATA命令用于接听 RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0); //通知RIL-Java层电话状态变化
}
此外在MI800 AT命令手册中,还设定了一条AT命令—AT+CLCC,用来查询当前的通话状态。在RIL具体实现该功能时,会调用到requestGetCurrentCalls()函数用以完成电话列表的刷新操作。
首先第
1
个参数即为我们之前所定义的标识,即
MARK
。
第
2
个参数是下
层的从数据流中解出数据的函数,
这里要和上层所传下来的类型对应,
例如
上层传下来的是
int
数组,这里也得是
dispathInts
,
否则数据会出错
第
3
个参数是该函数所要返回的值,
这里的
<type>
和第
2
个