Android Phone分析

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
完成最终的响应传递。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值