Android的RIL驱动模块telephony启动流程

  1. #include <stdio.h>
  2. #include <fcntl.h>
  3. #include <errno.h>
  4. int main()
  5. {
  6.     int ret;
  7.     int fd = open("/dev/tty1", O_RDONLY);
  8.     fcntl(fd, F_SETFL, O_NONBLOCK);
  9.     ret = read(fd, NULL, 2);
  10.     printf("ret=%d, errno=%d, %s\n",ret, errno, strerror(errno));
  11.     return 0;
  12. }
执行结果为:
luther@gliethttp:~$ sudo ./a.out
ret=-1, errno=11, Resource temporarily unavailable

processCommandsCallback ==> record_stream_get_next因为 O_NONBLOCK读取,所以
如果没有数据可读的话,他将按上面的例子直接返回-1

从bionic/libc/arch-arm/syscalls/read.S
  1. read:
  2. .save {r4, r7}
  3. stmfd sp!, {r4, r7}
  4. ldr r7, =__NR_read
  5. swi #0
  6. ldmfd sp!, {r4, r7}
  7. movs r0, r0
  8. bxpl lr
  9. b __set_syscall_errno
可以看到如果失败每次都是返回-1,只是errno将不同
  1. int __set_errno(int n)
  2. {
  3.     errno = n;
  4.     return -1;
  5. }



  6. int __set_syscall_errno(int n)
  7. {
  8.         
  9.     if(n > -256) {
  10.         return __set_errno(-n);
  11.     } else {
  12.         return n;
  13.     }
  14. }

  1. hardware/ril/rild/rild.c
  2. ==> main
  3. // 如果rild启动时没有传入-l参数指定lib库路径,那么
  4. // 我们将读取rild.libpath这个property指向的库路径[luther.gliethttp]
  5. dlHandle = dlopen(rilLibPath, RTLD_NOW);

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

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

  8. RIL_Init

  9. RIL_register(funcs);
  10. RIL_register
  11. RIL_startEventLoop

  12. RIL_Init ==> mainLoop ==> at_open(fd, onUnsolicited); 这里的onUnsolicited就是来电话和来短信的上传处理函数



可以使用logcat -b radio &在logcat上显示rild的调试信息



hardware/ril/reference-ril/atchannel.c
==> readerLoop专门负责从串口读取modem数据
hardware/ril/libril/ril.cpp

ril_event_set (&s_listen_event, s_fdListen, false,listenCallback, NULL);
==> listenCallback专门负责/dev/socket/rild这个unix文件listen客户client链接
===> processCommandsCallback当有client链接时,该函数是client的callback

==> debugCallback专门负责/dev/socket/rild-debug这个unix文件listen客户client链接
如上这2个unix套接字由init进程根据init.xxxx.rc对service的描述由service_start调用create_socket创建

其中/dev/socket/rild端口监听为non-persistent,也就是有任何一个client链接到该端口之后ril_event_loop就会在
processReadReadies时将该unix文件的监听句柄从select中拿掉,进而后续的client只能等着该client命令全部执行完毕,
并主动退出,即函数processCommandsCallback退出时,再次调用

rilEventAddWakeup(&s_listen_event);
才能让ril_event_loop再次接受新的client的connect连接.
而debugCallback被注册为监听/dev/socket/rild-debug的persistent类型,所以可以有多个client同时与他保持连接,
但是因为ril_event_loop对所有handler的处理都线性的统一到firePending函数中完成,所以任何一个client的交互
也都是线性顺序完成的.

转自http://wenku.baidu.com/view/fefb22d149649b6648d7477a.html
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命令都是以\r\n或\n\r的换行符来作为分隔符的,所以 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;       
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
完成最终的响应传递

你可能感兴趣的:(Android,系列)