Android的 RIL 驱动模块, 在 hardware/ril 目录下,一共分 rild , libril.so 以及 librefrence_ril.so 三个部分,另有一 radiooptions 可供自动或手动调试使用。都依赖于 include 目录中 ril.h 头文件。目前 cupcake 分支上带的是 gsm 的支持,另有一 cdma 分支,这里分析的是 gsm 驱动。
GSM模块,由于 Modem 的历史原因, AP 一直是通过基于串口的 AT 命令与 BB 交互。包括到了目前的一些 edge 或 3g 模块,或像 omap 这类 ap,bp 集成的 芯片 , 已经使用了USB 或其他等高速总线通信,但大多仍然使用模拟串口机制来使用 AT 命令。这里的 RIL(Radio Interface Layer) 层,主要也就是基于 AT 命令的操作,如发命令, response 解析等。( gprs 等传输会用到的 MUX 协议等在这里并没有包含,也暂不作介 绍。)
以下是详细分析,本文主要涉及基本架构和初始化的内容:
首先介绍一下rild 与 libril.so 以及 librefrence_ril.so 的关系:
1. rild :
仅实现一main 函数作为整个 ril 层的入口点,负责完成初始化。
2. libril.so :
与rild 结合相当紧密,是其共享库,编译时就已经建立了这一关系。组成部分为 ril.cpp , ril_event.cpp 。 libril.so 驻留在 rild 这一守护进程中,主要完成同上层通信的工作,接受 ril 请求并传递给 librefrence_ril.so , 同时把来自 librefrence_ril.so 的反馈回传给调用进程。
3. librefrence_ril.so :
rild 通过手动的 dlopen 方式加载,结合稍微松散,这也是因为 librefrence.so 主要负责跟 Modem 硬件通信的缘故。这样做更方便替 换或修改以适配更多的 Modem 种类。它转换来自 libril.so 的请求为 AT 命令,同时监控 Modem 的反馈信息,并传递回 libril.so 。在初 始化时, rild 通过符号 RIL_Init 获取一组函数指针并以此与之建立联系。
4. radiooptions:
radiooptiongs 通过获取启动参数, 利用 socket 与 rild 通信,可供调试时配置 Modem 参数。
分析初始化流程,主入口是rild.c 中的 main 函数,主要完成三个任务:
1. 开启 libril.so 中的 event 机制, 在 RIL_startEventLoop 中,是最核心的由多路 I/O 驱动的消息循环。
2. 初始化 librefrence_ril.so ,也就是跟硬件或模拟硬件 modem 通信的部分(后面统一称硬件), 通过 RIL_Init 函数完成。
3. 通过 RIL_Init 获取一组函数指针 RIL_RadioFunctions , 并通过 RIL_register 完成注册,并打开接受上层命令的 socket 通道。
首先看第一个任务,也就是RIL_startEventLoop 函数。 RIL_startEventLoop 在 ril.cpp 中实现, 它的主要目的是通过 pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL) 建立一个 dispatch 线程,入口点在 eventLoop. 而 eventLoop 中,会调 ril_event.cpp 中的 ril_event_loop ()函数,建立起消息 (event) 队列机制。
我们来仔细看看这一消息队列的机制,这些代码都在ril_event.cpp 中。
void ril_event_init();
void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param);
void ril_event_add(struct ril_event * ev);
void ril_timer_add(struct ril_event * ev, struct timeval * tv);
void ril_event_del(struct ril_event * ev);
void ril_event_loop();
struct ril_event {
struct ril_event *next;
struct ril_event *prev;
int fd;
int index;
bool persist;
struct timeval timeout;
ril_event_cb func;
void *param;
};
每个ril_event 结构,与一个 fd 句柄绑定(可以是文件, socket ,管道等),并且带一个 func 指针去执行指定的操作。
具体流程是: ril_event_init 完成后,通过 ril_event_set 来配置一新 ril_event ,并通过 ril_event_add 加入队列之中(实 际通常用 rilEventAddWakeup 来添加), add 会把队列里所有 ril_event 的 fd ,放入一个 fd 集合 readFds 中。这样 ril_event_loop 能通过一个多路复用 I/O 的机制( select )来等待这些 fd , 如果任何一个 fd 有数据写入,则进入分析流程 processTimeouts() , processReadReadies(&rfds, n) , firePending() 。 后文会详细分析这些流程。
另外我们可以看到, 在进入ril_event_loop 之前, 已经挂入了一 s_wakeupfd_event , 通过 pipe 的机制实现的, 这个 event 的目的是可以在一些情况下,能内部唤醒 ril_event_loop 的多路复用阻塞,比如一些带 timeout 的命令 timeout 到期的 时候。
至此第一个任务分析完毕,这样便建立起了基于event 队列的消息循环,稍后便可以接受上层发来的的请求了(上层请求的 event 对象建立,在第三个任务中)。
接下来看第二个任务,这个任务的入口是RIL_Init, RIL_Init 首先通过参数获取硬件接口的设备文件或模拟硬件接口的 socket. 接下来便新开一个线程继续初始化, 即 mainLoop 。
mainLoop 的主要任务是建立起与硬件的通信,然后通过 read 方法阻塞等待硬件的主动上报或响应。在注册一些基础回调 ( timeout,readerclose )后, mainLoop 首先打开硬件设备文件,建立起与硬件的通信, s_device_path 和 s_port 是前面获取的设备路径参数,将其打开( 两者可以同时打开并拥有各自的reader ,这里也很容易添加双卡双待等支持 )。
接下来通过 at_open 函数建立起这一设备文件上的 reader 等待循环,这也是通过新建一个线程完成, ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr) ,入口点 readerLoop 。
AT 命令都是以 rn 或 nr 的换行符来作为分隔符的,所以 readerLoop 是 line 驱动的,除非出错,超时等,否则会读到一行完整的响应或主动上 报,才会返回。这个循环跑起来以后,我们基本的 AT 响应机制已经建立了起来。它的具体分析,包括 at_open 中挂接的 ATUnsolHandler, 我们都放到后面分析 response 的连载文章里去。
有了响应的机制(当然,能与硬件通信也已经可以发请求了),通过 RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0) ,跑到 initializeCallback 中,执行一些 Modem 的初始化命令,主要都是 AT 命令的方式。发 AT 命令的 流程,我们放到后面分析 request 的连载文章里。这里可以看到,主要是一些参数配置,以及网络状态的检查等。至此第二个任务分析完毕,硬件已经可以访 问了。
最后是第三个任务。第三个任务是由RIL_Init 的返回值开始的,这是一个 RIL_RadioFunctions 结构的指针。
typedef struct {
int version;
/* set to RIL_VERSION */
RIL_RequestFunc onRequest;
RIL_RadioStateRequest onStateRequest;
RIL_Supports supports;
RIL_Cancel onCancel;
RIL_GetVersion getVersion;
} RIL_RadioFunctions;
其中最重要的是onRequest 域,上层来的请求都由这个函数进行映射后转换成对应的 AT 命令发给硬件。
rild 通过 RIL_register 注册这一指针。
RIL_register 中要完成的另外一个任务,就是打开前面提到的跟上层通信的 socket 接口( s_fdListen 是主接口, s_fdDebug 供调试时使用)。
然后将这两个socket 接口使用任务一中实现的机制进行注册(仅列出 s_fdListen )
ril_event_set (&s_listen_event, s_fdListen, false,
listenCallback, NULL);
rilEventAddWakeup (&s_listen_event);
这样将两个socket 加到任务一中建立起来多路复用 I/O 的检查句柄集合中,一旦有上层来的(调试)请求, event 机制便能响应处理了。到这里启动流程已经分析完毕。
request流程
1. 多路复用
I/O
机制的运转
上文说到request
是接收
,
是通过
ril_event_loop
中的多路复用
I/O,
也对初始化做了分析
.
现在我们来仔细看看这个机制如何运转
.
ril_event_set负责配置一个
event,
主要有两种
event
:
ril_event_add添加使用多路
I/O
的
event,
它负责将其挂到队列
,
同时将
event
的通道句柄
fd
加入到
watch_table,
然后通过
select
等待
.
ril_timer_add添加
timer event,
它将其挂在队列
,
同时重新计算最短超时时间
.
无论哪种add,
最后都会调用
triggerEvLoop
来刷新队列
,
更新超时值或等待对象
.
刷新之后, ril_event_loop
从阻塞的位置
,select
返回
,
只有两种可能
,
一是超时
,
二是等待到了某
I/O
操作
.
超时的处理在processTimeouts
中
,
摘下超时的
event,
加入
pending_list.
检查有I/O
操作的通道的处理在
processReadReadies
中
,
将超时的
event
加入
pending_list.
最后在firePending
中
,
检索
pending_list
的
event
并依次执行
event->func.
这些操作完之后,
计算新超时时间
,
并重新
select
阻塞于多路
I/O.
前面的初始化流程已分析得知,
初始化完成以后
,
队列上挂了
3
个
event
对象
,
分别是:
s_listen_event: 名为
rild
的
socket,
主要
requeset & response
通道
s_debug_event: 名为
rild-debug
的
socket,
调试用
requeset & response
通道(流程与
s_listen_event
基本相同
,
后面仅分析
s_listen_event
)
s_wakeupfd_event: 无名管道
,
用于队列主动唤醒(前面提到的队列刷新
,
就用它来实现
,
请参考使用它的相关地方)
2. request的传入和
dispatch
明白了event
队列的基本运行流程
,
我们可以来看看
request
是怎么传入和
dispatch
的了
.
上 层的部分,
核心代码在
frameworks/base/telephony/java/com/android/internal/telephony /gsm/RIL.java,
这是
android java
框架处理
radio(gsm)
的核心组件
.
本文因为主要关注
rild,
也就是驱动部分
,
所以这里只作简单介绍
.
我们看一个具体的例子,RIL.java
中的
dial
函数:
public void
dial (String address, int clirMode, Message result)
{
RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);
rr.mp.writeString(address);
rr.mp.writeInt(clirMode);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
send(rr);
}
rr是以
RIL_REQUEST_DIAL
为
request
号而申请的一个
RILRequest
对象
.
这个
request
号在
java
框架和
rild
库中共享(参考
RILConstants.java
中这些值的由来
:)
)
RILRequest初始化的时候
,
会连接名为
rild
的
socket
(也就是
rild
中
s_listen_event
绑定的
socket
)
,
初始化数据传输的通道
.
rr.mp 是
Parcel
对象
,Parcel
是一套简单的序列化协议
,
用于将对象(或对象的成员)序列化成字节流
,
以供传递参数之用
.
这里可以看到
String address
和
int clirMode
都是将依次序列化的成员
.
在这之前
,rr
初始化的时候
,request
号跟
request
的序列号(自动生成的递增数)
,
已经成为头两个 将被序列化的成员
.
这为后面的
request
解析打下了基础
.
接下来是send
到
handleMessage
的流程
,send
将
rr
直接传递给另 一个线程的
handleMessage,handleMessage
执行
data = rr.mp.marshall()
执行序列化操作
,
并将
data
字节流写入到
rild socket.
接下来回到我们的rild,select
发现
rild socket
有了请求链接的信号
,
导致
s_listen_event
被挂入
pending_list,
执行
event->func,
即
static void listenCallback (int fd, short flags, void *param);
接下来,s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen),
获取传入的
socket
描述符
,
也就是上层的
java RIL
传入的连接
.
然 后,
通过
record_stream_new
建立起一个
record_stream,
将其与
s_fdCommand
绑定
,
这里我们不关注
record_stream
的具体流程
,
我们来关注
command event
的回调
, processCommandsCallback
函数
,
从前面的
event
机制分析
,
一旦
s_fdCommand
上有数据
,
此回调函数就会被调用
.
(略过
onNewCommandConnect
的分析)
processCommandsCallback通过
record_stream_get_next
阻塞读取
s_fdCommand
上发来的 数据
,
直到收到一完整的
request(request
包的完整性由
record_stream
的机制保证
),
然后将其送达
processCommandBuffer.
进入processCommandBuffer
以后
,
我们就正式进入了命令的解析部分
.
每个命令将以
RequestInfo
的形式存在
.
typedef struct RequestInfo {
int32_t token; //this is not RIL_Token
CommandInfo *pCI;
struct RequestInfo *p_next;
char cancelled;
char local; // responses to local commands do not go back to command process
} RequestInfo;
这 里的pRI
就是一个
RequestInfo
结构指针
,
从
socket
过来的数据流
,
前面提到是
Parcel
处理过的序列化字节流
,
这里会通过反序列化的方法提取出来
.
最前面的是
request
号
,
以及
token
域
(request
的递增序列号
).
我们更关注这个
request
号
,
前面提到
,
上层和
rild
之间
,
这个号是统一的
.
它的定义是一个包含
ril_commands.h
的枚举
,
在
ril.cpp
中
static CommandInfo s_commands[] = {
#include "ril_commands.h"
};
pRI直接访问这个数组
,
来获取自己的
pCI.
这是一个CommandInfo
结构
:
typedef struct {
int requestNumber;
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
int(*responseFunction) (Parcel &p, void *response, size_t responselen);
} CommandInfo;
基本解析到这里就完成了,
接下来
, pRI
被挂入
pending
的
request
队列
,
执行具体的
pCI->dispatchFunction,
进行详细解析
.
3. request的详细解析
对dial
而言
, CommandInfo
结构是这样初始化的
:
{RIL_REQUEST_DIAL, dispatchDial, responseVoid},
这 里执行dispatchFunction,
也就是
dispatchDial
这一函数
.
我们可以看到其实有很多种类的
dispatch function,
比如
dispatchVoid, dispatchStrings, dispatchSIM_IO
等等
,
这些函数的区别
,
在于
Parcel
传入的参数形式
,Void
就是不带参数的
,Strings
是以
string[]
做参数
,
又如
Dial
等
,
有自己的参数解析方式
,
以此类 推
.
request号和参数现在都有了
,
那么可以进行具体的
request
函数调用了
.
s_callbacks.onRequest(pRI->pCI->requestNumber, xxx, len, pRI)完成这一操作
.
s_callbacks 是上篇文章中提到的获取自
libreference-ril
的
RIL_RadioFunctions
结构指针
,request
请求在这里转入底层的
libreference-ril
处理
,handler
是
reference-ril.c
中的
onRequest.
onRequest进行一个简单的
switch
分发
,
我们依然来看
RIL_REQUEST_DIAL
流程是 onRequest-->requestDial-->at_send_command-->at_send_command_full-->at_send_command_full_nolock-->writeline
requestDial中将命令和参数转换成对应的
AT
命令
,
调用公共
send command
接口
at_send_command.
除 了这个接口之外,
还有
at_send_command_singleline,at_send_command_sms,at_send_command_multiline
等
,
这是根据
at
返回值
,
以及发命令流程的类型来区别的
.
比如
at+csq
这类
,
需要
at_send_command_singleline,
而发送短 信
,
因为有
prompt
提示符
">",
传裸数据
,
结束符等一系列操作
,
需要专门用
at_send_command_sms
来实现
.
然后执行at_send_command_full,
前面几个接口都会最终到这里
,
再通过一个互斥的
at_send_command_full_nolock
调用
,
然后完成最终的写出操作
,
在
writeline
中
,
写出到初始化时打开的设备中
.
writeline返回之后
,
还有一些操作
,
如保存
type
等信息
,
供
response
回来时候使用
,
以及一些超时处理
.
不再详述
.
到这里,request
的详细流程
,
就分析完毕了
.
response流程
前文对request
的分析, 终止在了
at_send_command_full_nolock
里的
writeline
操作,因为这里完成命令写出到硬件设备的操作,接下来就是等待硬件响应,也就是
response
的过程了。我们的分析也是从这里开始。
response信息的获取,是在第一篇初始化分析中,提到的
readerLoop
中。由
readline
函数以
‘
行
’
为单位接收上来。
AT的
response
有两种,一是主动上报的,比如网络状态,短信,来电等都不需要经过请求,有一
unsolicited
词语专门描述。另一种才是真正意义上的
response
,也就是命令的响应。
这 里我们可以看到,所有的行,首先经过sms
的自动上报筛选,因为短信的
AT
处理通常比较麻烦,无论收发都单独列出。这里是因为要即时处理这条短信消息(两 行,标志+
pdu
),而不能拆开处理。处理函数为
onUnsolicited
(由
s_unsolHandler
指向),我们等下介绍。
除开sms
的特例,所有的
line
都要经过
processLine
,我们来看看这个流程:
processLine
|----no cmd--->handleUnsolicited //主动上报
|----isFinalResponseSuccess--->handleFinalResponse //成功
,
标准响应
|----isFinalResponseError--->handleFinalResponse //失败,标准响应
|----get '>'--->send sms pdu //收到
>
符号,发送
sms
数据再继续等待响应
|----switch s_type--->具体响应
//
命令有具体的响应信息需要对应分析
我 们这里主要关注handleUnsolicited
自动上报(会调用到前面
smsUnsolicite
也调用的
onUnsolicite
),以及
switch s_type
具体响应信息,另外具体响应需要
handleFinalResponse
这样的标准响应来最终完成。
1. onUnsolicite(主动上报响应)
static void onUnsolicited (const char *s, const char *sms_pdu);
短信的AT
设计真是麻烦的主,以致这个函数的第二个参数完全就是为它准备的。
response 的主要的解析过程,由
at_tok.c
中的函数完成,其实就是字符串按块解析,具体的解析方式由每条命令或上报信息自行决定。这里不再详 述,
onUnsolicited
只解析出头部
(
一般是
+XXXX
的形式
)
,然后按类型决定下一步操作,操作为
RIL_onUnsolicitedResponse
和
RIL_requestTimedCallback
两种。
a)RIL_onUnsolicitedResponse:
将 unsolicited
的信息直接返回给上层。通过
Parcel
传递,将
RESPONSE_UNSOLICITED
,
unsolResponse
(
request
号)写入
Parcel
先,然后通过
s_unsolResponses
数组,查找到对应的
responseFunction
完成进一步的的解析,存入
Parcel
中。最终通过
sendResponse
将其传递回原进程。流程:
sendResponse-->sendResponseRaw-->blockingWrite-->write to s_fdCommand(前面建立起来的和上层框架的
socket
连接
)
这些步骤之后有一些唤醒系统等其他操作。不再详述。
b)RIL_requestTimedCallback:
通 过event
机制(参考文章二)实现的
timer
机制,回调对应的内部处理函数。通过
internalRequestTimedCallback
将回调添 加到
event
循环,最终完成
callback
上挂的函数的回调。比如
pollSIMState
,
onPDPContextListChanged
等回 调, 不用返回上层, 内部处理就可以。
2. switch s_type(命令的具体响应)及
handleFinalResponse
(标准响应)
命 令的类型(s_type
)在
send command
的时候设置(参考文章二),有
NO_RESULT
,
NUMERIC
,
SINGLELINE
,
MULTILINE
几种,供不同的
AT
使用。比 如
AT+CSQ
是
singleline,
返回
at+csq=xx,xx
,再加一行
OK
,比如一些设置命令,就是
no_result,
只有一行
OK
或
ERROR
。
这几个类型的解析都很相仿,通过一定的判断(比较AT
头标记等),如果是对应的响应,就通过
addIntermediate
挂到一个临时结果
sp_response->p_intermediates
队列里。如果不是对应响应,那它其实应 该是穿插其中的自动上报,用
onUnsolicite
来处理。
具体响应,只起一个获取响应信息到临时结果,等待具体分析的作用。无论有无具体响应,最终都得以标准响应handleFinalResponse
来完成,也就是接受到
OK,ERROR
等标准
response
来结束,这是大多数
AT
命令的规范。
handleFinalResponse 会设置
s_commandcond
这一
object
,也就是
at_send_command_full_nolock
等待的对象。到这里,响应的完整信息 已经完全获得,
send command
可以进一步处理返回的信息了(临时结果,以及标准返回的成功或失败,都在
sp_response
中)。
pp_outResponse参数将
sp_response
返回给调用
at_send_command_full_nolock
的函数。
继续我们在文章二的分析的话,这个函数其实是requestDial
,不过
requestDial
忽略了响应,所以我们另外看个例子,如
requestSignalStrength
,命令其实就是前面提到的
at+csq
:
可以看到确实是通过at_send_command_singleline
来进行的操作,
response
在
p_response
中。
p_response如果返回失败(也就是标准响应的
ERROR
等造成),则通过
RIL_onRequestComplete
发送返回数据给上层,结束命令。
如果成功,则进一步分析p_response->p_intermediates
, 同样是通过
at_tok.c
里的函数进行分析。并同样将结果通过
RIL_onRequestComplete
返回。
RIL_onRequestComplete:
RIL_onRequestComplete和
RIL_onUnsolicitedResponse
很相仿,功能也一致。
通 过Parcel
来传递回上层,同样是先写入
RESPONSE_SOLICITED
(区别于
RESPONSE_UNSOLICITED
),
pRI->token(
上层传下的
request
号),错误码(
send command
的错误,不是
AT
响应)。如果有
AT
响应,通过访问
pRI->pCI->responseFunction
来完成具体
response
的解析,并写入
Parcel
。
然后通过同样的途径:
sendResponse-->sendResponseRaw-->blockingWrite-->write to s_fdCommand
完成最终的响应传递。
1177

被折叠的 条评论
为什么被折叠?



