RIL

在上一篇博文中我讲了如何在Android中不启动界面连接wifi,在这篇博文中我将讲如何不启动界面连接手机网络。

Android的RIL(Radio Interface Layer)模块提供Android telephony服务和radio硬件之间的一个抽象层。RIL模块的架构如下图所示

Android RIL模块非启动界面联网实战(一) - angelbear - angelbear的博客

最上面的是Android的应用程序,比如拨号、短信息等程序,这些程序调用Application Framework层的telephony API,即可以完成打电话、发短信等操作。

以上都是虚拟机以上层面的,rild和vender ril是linux层的用户程序(库),rild是一个daemon程序,在init.rc中可以看到rild程序的启动

service ril-daemon /system/bin/rild
    socket rild stream 660 root radio
    socket rild-debug stream 660 radio system
    user root
    group radio cache inet misc audio
同时注意到rild在启动的时候,还开启了两个以名称做标识的socket,rild和rild-debug,rild socket就是上层的framework和rild程序通信的手段,而rild-debug socket是留给radiooptions等程序调试使用的,后来这个rild-debug的socket起到了很重要的作用。

这次我没有从java端的代码入手,而是直接先开始看rild的代码。rild的代码只有rild.c一个,本身并不长,下面是rild的main函数

  
  
  1. int main ( int argc , char **argv )
  2. {
  3. const char * rilLibPath = NULL ;
  4. char **rilArgv ;
  5. void *dlHandle ;
  6. const RIL_RadioFunctions * ( *rilInit ) ( const struct RIL_Env *, int , char ** ) ;
  7. const RIL_RadioFunctions *funcs ;
  8. char libPath [PROPERTY_VALUE_MAX ] ;
  9. unsigned char hasLibArgs = 0 ;
  10. int i ;
  11. for (i = 1 ; i < argc ; ) {
  12. if ( 0 == strcmp (argv [i ] , "-l" ) && (argc - i > 1 ) ) {
  13. rilLibPath = argv [i + 1 ] ;
  14. i += 2 ;
  15. } else if ( 0 == strcmp (argv [i ] , "--" ) ) {
  16. i ++;
  17. hasLibArgs = 1 ;
  18. break ;
  19. } else {
  20. usage (argv [ 0 ] ) ;
  21. }
  22. }
  23. if (rilLibPath == NULL ) {
  24. if ( 0 == property_get (LIB_PATH_PROPERTY , libPath , NULL ) ) {
  25. // No lib sepcified on the command line, and nothing set in props.
  26. // Assume "no-ril" case.
  27. goto done ;
  28. } else {
  29. rilLibPath = libPath ;
  30. }
  31. }
  32. /* special override when in the emulator */
  33. #if 1
  34. {
  35. static char * arg_overrides [ 3 ] ;
  36. static char arg_device [ 32 ] ;
  37. int done = 0 ;
  38. #define REFERENCE_RIL_PATH "/system/lib/libreference-ril.so"
  39. /* first, read /proc/cmdline into memory */
  40. char buffer [ 1024 ] , *p , *q ;
  41. int len ;
  42. int fd = open ( "/proc/cmdline" ,O_RDONLY ) ;
  43. if (fd < 0 ) {
  44. LOGD ( "could not open /proc/cmdline:%s" , strerror (errno ) ) ;
  45. goto OpenLib ;
  46. }
  47. do {
  48. len = read (fd ,buffer , sizeof (buffer ) ) ; }
  49. while (len == - 1 && errno == EINTR ) ;
  50. if (len < 0 ) {
  51. LOGD ( "could not read /proc/cmdline:%s" , strerror (errno ) ) ;
  52. close (fd ) ;
  53. goto OpenLib ;
  54. }
  55. close (fd ) ;
  56. if (strstr (buffer , "android.qemud=" ) != NULL )
  57. {
  58. /* the qemud daemon is launched after rild, so
  59. * give it some time to create its GSM socket
  60. */
  61. int tries = 5 ;
  62. #define QEMUD_SOCKET_NAME "qemud"
  63. while ( 1 ) {
  64. int fd ;
  65. sleep ( 1 ) ;
  66. fd = socket_local_client (
  67. QEMUD_SOCKET_NAME ,
  68. ANDROID_SOCKET_NAMESPACE_RESERVED ,
  69. SOCK_STREAM ) ;
  70. if (fd >= 0 ) {
  71. close (fd ) ;
  72. snprintf ( arg_device , sizeof (arg_device ) , "%s/%s" ,
  73. ANDROID_SOCKET_DIR , QEMUD_SOCKET_NAME ) ;
  74. arg_overrides [ 1 ] = "-s" ;
  75. arg_overrides [ 2 ] = arg_device ;
  76. done = 1 ;
  77. break ;
  78. }
  79. LOGD ( "could not connect to %s socket: %s" ,
  80. QEMUD_SOCKET_NAME , strerror (errno ) ) ;
  81. if ( --tries == 0 )
  82. break ;
  83. }
  84. if ( !done ) {
  85. LOGE ( "could not connect to %s socket (giving up): %s" ,
  86. QEMUD_SOCKET_NAME , strerror (errno ) ) ;
  87. while ( 1 )
  88. sleep ( 0x00ffffff ) ;
  89. }
  90. }
  91. /* otherwise, try to see if we passed a device name from the kernel */
  92. if ( !done ) do {
  93. #define KERNEL_OPTION "android.ril="
  94. #define DEV_PREFIX "/dev/"
  95. p = strstr ( buffer , KERNEL_OPTION ) ;
  96. if (p == NULL )
  97. break ;
  98. p += sizeof (KERNEL_OPTION ) - 1 ;
  99. q = strpbrk ( p , " \t\n\r" ) ;
  100. if (q != NULL )
  101. *q = 0 ;
  102. snprintf ( arg_device , sizeof (arg_device ) , DEV_PREFIX "%s" , p ) ;
  103. arg_device [ sizeof (arg_device ) - 1 ] = 0 ;
  104. arg_overrides [ 1 ] = "-d" ;
  105. arg_overrides [ 2 ] = arg_device ;
  106. done = 1 ;
  107. } while ( 0 ) ;
  108. if (done ) {
  109. argv = arg_overrides ;
  110. argc = 3 ;
  111. i = 1 ;
  112. hasLibArgs = 1 ;
  113. rilLibPath = REFERENCE_RIL_PATH ;
  114. LOGD ( "overriding with %s %s" , arg_overrides [ 1 ] , arg_overrides [ 2 ] ) ;
  115. }
  116. }
  117. OpenLib :
  118. #endif
  119. switchUser ( ) ;
  120. dlHandle = dlopen (rilLibPath , RTLD_NOW ) ;
  121. if (dlHandle == NULL ) {
  122. fprintf (stderr , "dlopen failed: %s\n" , dlerror ( ) ) ;
  123. exit ( - 1 ) ;
  124. RIL_startEventLoop ( ) ;
  125. rilInit = ( const RIL_RadioFunctions * ( * ) ( const struct RIL_Env *, int , char ** ) )dlsym (dlHandle , "RIL_Init" ) ;
  126. if (rilInit == NULL ) {
  127. fprintf (stderr , "RIL_Init not defined or exported in %s\n" , rilLibPath ) ;
  128. exit ( - 1 ) ;
  129. }
  130. if (hasLibArgs ) {
  131. rilArgv = argv + i - 1 ;
  132. argc = argc -i + 1 ;
  133. } else {
  134. static char * newArgv [MAX_LIB_ARGS ] ;
  135. static char args [PROPERTY_VALUE_MAX ] ;
  136. rilArgv = newArgv ;
  137. property_get (LIB_ARGS_PROPERTY , args , "" ) ;
  138. argc = make_argv (args , rilArgv ) ;
  139. }
  140. // Make sure there's a reasonable argv[0]
  141. rilArgv [ 0 ] = argv [ 0 ] ;
  142. funcs = rilInit ( &s_rilEnv , argc , rilArgv ) ;
  143. RIL_register (funcs ) ;
  144. done :
  145. while ( 1 ) {
  146. // sleep(UINT32_MAX) seems to return immediately on bionic
  147. sleep ( 0x00ffffff ) ;
  148. }
  149. }

RIL_开头的结构和函数都是libril中的,我们先不管这些,从上面的main函数看来,rild并不干活,它只是把ril-lib-path的so文件载入,然后调用一下其中的RIL_Init函数,传入一下参数,然后再将RIL_Init得到的结果调用libril中的RIL_register,就进入死循环了。

我们在去看一下RIL_Init和RIL_register都是干什么的呢?

  
  
  1. /*include/telephony/ril.h*/
  2. typedef struct {
  3. int version ; /* set to RIL_VERSION */
  4. RIL_RequestFunc onRequest ;
  5. RIL_RadioStateRequest onStateRequest ;
  6. RIL_Supports supports ;
  7. RIL_Cancel onCancel ;
  8. RIL_GetVersion getVersion ;
  9. } RIL_RadioFunctions ;
  10. /*reference-ril/reference-ril.c*/
  11. static void onRequest ( int request , void *data , size_t datalen , RIL_Token t ) ;
  12. static RIL_RadioState currentState ( ) ;
  13. static int onSupports ( int requestCode ) ;
  14. static void onCancel (RIL_Token t ) ;
  15. static const char *getVersion ( ) ;
  16. /*** Static Variables ***/
  17. static const RIL_RadioFunctions s_callbacks = {
  18. RIL_VERSION ,
  19. onRequest ,
  20. currentState ,
  21. onSupports ,
  22. onCancel ,
  23. getVersion
  24. } ;
  25. const RIL_RadioFunctions *RIL_Init ( const struct RIL_Env *env , int argc , char **argv )
  26. {
  27. //blabla...
  28. return &s_callbacks ;
  29. }
  30. /*libril/ril.cpp*/
  31. extern "C" void
  32. RIL_register ( const RIL_RadioFunctions *callbacks ) {
  33. int ret ;
  34. int flags ;
  35. if (callbacks == NULL || ( (callbacks ->version != RIL_VERSION )
  36. && (callbacks ->version != 2 ) ) ) { // Remove when partners upgrade to version 3
  37. LOGE (
  38. "RIL_register: RIL_RadioFunctions * null or invalid version"
  39. " (expected %d)" , RIL_VERSION ) ;
  40. return ;
  41. }
  42. if (callbacks ->version < 3 ) {
  43. LOGE ( "RIL_register: upgrade RIL to version 3 current version=%d" , callbacks ->version ) ;
  44. }
  45. if (s_registerCalled > 0 ) {
  46. LOGE ( "RIL_register has been called more than once. "
  47. "Subsequent call ignored" ) ;
  48. return ;
  49. }
  50. memcpy ( &s_callbacks , callbacks , sizeof (RIL_RadioFunctions ) ) ;
  51. s_registerCalled = 1 ;
  52. // Little self-check
  53. for ( int i = 0 ; i < ( int )NUM_ELEMS (s_commands ) ; i ++ ) {
  54. assert (i == s_commands [i ]. requestNumber ) ;
  55. }
  56. for ( int i = 0 ; i < ( int )NUM_ELEMS (s_unsolResponses ) ; i ++ ) {
  57. assert (i + RIL_UNSOL_RESPONSE_BASE
  58. == s_unsolResponses [i ]. requestNumber ) ;
  59. }
  60. // New rild impl calls RIL_startEventLoop() first
  61. // old standalone impl wants it here.
  62. if (s_started == 0 ) {
  63. RIL_startEventLoop ( ) ;
  64. }
  65. // start listen socket
  66. #if 0
  67. ret = socket_local_server (SOCKET_NAME_RIL ,
  68. ANDROID_SOCKET_NAMESPACE_ABSTRACT , SOCK_STREAM ) ;
  69. if (ret < 0 ) {
  70. LOGE ( "Unable to bind socket errno:%d" , errno ) ;
  71. exit ( - 1 ) ;
  72. }
  73. s_fdListen = ret ;
  74. #else
  75. s_fdListen = android_get_control_socket (SOCKET_NAME_RIL ) ;
  76. if (s_fdListen < 0 ) {
  77. LOGE ( "Failed to get socket '" SOCKET_NAME_RIL "'" ) ;
  78. exit ( - 1 ) ;
  79. }
  80. ret = listen (s_fdListen , 4 ) ;
  81. if (ret < 0 ) {
  82. LOGE ( "Failed to listen on control socket '%d': %s" ,
  83. s_fdListen , strerror (errno ) ) ;
  84. exit ( - 1 ) ;
  85. }
  86. #endif
  87. /* note: non-persistent so we can accept only one connection at a time */
  88. ril_event_set ( &s_listen_event , s_fdListen , false ,
  89. listenCallback , NULL ) ;
  90. rilEventAddWakeup ( &s_listen_event ) ;
  91. #if 1
  92. // start debug interface socket
  93. s_fdDebug = android_get_control_socket (SOCKET_NAME_RIL_DEBUG ) ;
  94. if (s_fdDebug < 0 ) {
  95. LOGE ( "Failed to get socket '" SOCKET_NAME_RIL_DEBUG "' errno:%d" , errno ) ;
  96. exit ( - 1 ) ;
  97. }
  98. ret = listen (s_fdDebug , 4 ) ;
  99. if (ret < 0 ) {
  100. LOGE ( "Failed to listen on ril debug socket '%d': %s" ,
  101. s_fdDebug , strerror (errno ) ) ;
  102. exit ( - 1 ) ;
  103. }
  104. ril_event_set ( &s_debug_event , s_fdDebug , true ,
  105. debugCallback , NULL ) ;
  106. rilEventAddWakeup ( &s_debug_event ) ;
  107. #endif
  108. }

看到上面三个文件的代码片段,相信大家有个大概的认识了把,vendor ril的RIL_Init函数返回的是真正干活的函数数组的指针,而libril中的RIL_register将这些函数指针存着供自己用,至于什么时候用?在RIL_register中可以看到其尝试获得rild以及rild-debug两个socket的控制socket,于是便可以得知,libril是监听这些socket的信息,然后调用传入的函数指针来处理这些信息,在ril.cpp中随处可见s_callbacks.onRequest的调用,s_callbacks就是callbacks的拷贝。

在ril.cpp的main函数的109行,我们看见这里注册了一个rild socket的listener,是函数listenCallback,这个函数就用来处理向rild发送的命令,

  
  
  1. static void listenCallback ( int fd , short flags , void *param ) {
  2. int ret ;
  3. int err ;
  4. int is_phone_socket ;
  5. RecordStream *p_rs ;
  6. struct sockaddr_un peeraddr ;
  7. socklen_t socklen = sizeof (peeraddr ) ;
  8. struct ucred creds ;
  9. socklen_t szCreds = sizeof (creds ) ;
  10. struct passwd *pwd = NULL ;
  11. assert (s_fdCommand < 0 ) ;
  12. assert (fd == s_fdListen ) ;
  13. s_fdCommand = accept (s_fdListen , (sockaddr * ) &peeraddr , &socklen ) ;
  14. if (s_fdCommand < 0 ) {
  15. LOGE ( "Error on accept() errno:%d" , errno ) ;
  16. /* start listening for new connections again */
  17. rilEventAddWakeup ( &s_listen_event ) ;
  18. return ;
  19. }
  20. /* check the credential of the other side and only accept socket from
  21. * phone process
  22. */
  23. errno = 0 ;
  24. is_phone_socket = 0 ;
  25. err = getsockopt (s_fdCommand , SOL_SOCKET , SO_PEERCRED , &creds , &szCreds ) ;
  26. if (err == 0 && szCreds > 0 ) {
  27. errno = 0 ;
  28. pwd = getpwuid (creds. uid ) ;
  29. if (pwd != NULL ) {
  30. if (strcmp (pwd ->pw_name , PHONE_PROCESS ) == 0 ) {
  31. is_phone_socket = 1 ;
  32. } else {
  33. LOGE ( "RILD can't accept socket from process %s" , pwd ->pw_name ) ;
  34. }
  35. } else {
  36. LOGE ( "Error on getpwuid() errno: %d" , errno ) ;
  37. }
  38. } else {
  39. LOGD ( "Error on getsockopt() errno: %d" , errno ) ;
  40. }
  41. if ( !is_phone_socket ) {
  42. LOGE ( "RILD must accept socket from %s" , PHONE_PROCESS ) ;
  43. close (s_fdCommand ) ;
  44. s_fdCommand = - 1 ;
  45. onCommandsSocketClosed ( ) ;
  46. /* start listening for new connections again */
  47. rilEventAddWakeup ( &s_listen_event ) ;
  48. return ;
  49. }
  50. ret = fcntl (s_fdCommand , F_SETFL , O_NONBLOCK ) ;
  51. if (ret < 0 ) {
  52. LOGE ( "Error setting O_NONBLOCK errno:%d" , errno ) ;
  53. }
  54. LOGI ( "libril: new connection" ) ;
  55. p_rs = record_stream_new (s_fdCommand , MAX_COMMAND_BYTES ) ;
  56. ril_event_set ( &s_commands_event , s_fdCommand , 1 ,
  57. processCommandsCallback , p_rs ) ;
  58. rilEventAddWakeup ( &s_commands_event ) ;
  59. onNewCommandConnect ( ) ;
  60. }

但是仔细看这个这个函数的35行到48行,发现rild的socket并不是谁都可以连的,必须是PHONE_PROCESS才可以连接,也就是说如果是其他process通过rild socket发命令libril是不会处理,直接报错的,其实这里应该可以直接注释掉,但是为了尽量不破坏原来的代码,我就没有走这条路。向下稍微看几行就发现了debugCallback函数,在RIL_register中通过ril_event_set (&s_debug_event, s_fdDebug, true,  debugCallback, NULL);将debug socket的处理函数设定成debugCallback,观察debugCallback中的几个处理函数,发下下面几个可以用来联网的函数。

  
  
  1. case 5 :
  2. LOGI ( "Debug port: Radio On" ) ;
  3. data = 1 ;
  4. issueLocalRequest (RIL_REQUEST_RADIO_POWER , &data , sizeof ( int ) ) ;
  5. sleep ( 2 ) ;
  6. // Set network selection automatic.
  7. issueLocalRequest (RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC , NULL , 0 ) ;
  8. break ;
  9. case 6 :
  10. LOGI ( "Debug port: Setup Data Call, Apn :%s\n" , args [ 1 ] ) ;
  11. actData [ 0 ] = args [ 1 ] ;
  12. issueLocalRequest (RIL_REQUEST_SETUP_DATA_CALL , &actData ,
  13. sizeof (actData ) ) ;
  14. break ;
  15. case 7 :
  16. LOGI ( "Debug port: Deactivate Data Call" ) ;
  17. issueLocalRequest (RIL_REQUEST_DEACTIVATE_DATA_CALL , &deactData ,
  18. sizeof (deactData ) ) ;
  19. break ;

Radio on是将radio模块激活,Setup Data Call就是建立数据连接(gprs,td,wcdma之类的),有这两个功能的话就可以保证连上网了。

其实看到debugCallback的处理函数,我感到很眼熟,为什么呢?因为在rild的文件夹中有一个radiooptions.c的程序,这个程序的用法是这样的

  
  
  1. static void print_usage ( ) {
  2. perror ( "Usage: radiooptions [option] [extra_socket_args]\n\
  3. 0 - RADIO_RESET, \n \
  4. 1 - RADIO_OFF, \n \
  5. 2 - UNSOL_NETWORK_STATE_CHANGE, \n \
  6. 3 - QXDM_ENABLE, \n \
  7. 4 - QXDM_DISABLE, \n \
  8. 5 - RADIO_ON, \n \
  9. 6 apn- SETUP_PDP apn, \n \
  10. 7 - DEACTIVE_PDP, \n \
  11. 8 number - DIAL_CALL number, \n \
  12. 9 - ANSWER_CALL, \n \
  13. 10 - END_CALL \n" ) ;
  14. }

这样就正好对应上了,radiooptions程序就是官方用来调试ril模块的一个小程序,debugCallback就是在libril中给这个小程序开的“后门”。

于是事情就好办了,前面的一切工作似乎都是白费的,只要调用几下radiooptions不久好了么?

  Rild是Init进程启动的一个本地服务,这个本地服务并没有使用Binder之类的通讯手段,而是采用了socket通讯这种方式。

    Android给出了一个RIL实现框架。由于Android开发者使用的Modem是不一样的,各种指令格式,初始化序列都可能不一样,

GSM和CDMA就差别更大了,所以为了消除这些差别,Android设计者将RIL做了一个抽象,使用一个虚拟电话的概念。这个虚拟电

话对象就是GSMPhone(CDMAPhone),虚拟电话对象所提供的功能协议,以及要求下层的支撑环境都有一个统一的描述,这个底

层描述的实现就是靠RIL来完成适配。

    Android将RIL层分为两个代码空间:RILD管理框架,AT相关的xxxril.so动态链接库。将RIL独立成一个动态链接库的好处就是

Android系统适应不同的Modem,不同的Modem可以有一个独立的RIL与之对应。从这个层面上看,Rild更多是一个管理框架。

image

   

    而RIL是具体的AT指令合成者和应答解析者。从最基本的功能来讲,RIL建立了一个侦听Socket,等待客户端的连接,然后从该连

接上读取RIL-Java成传递来的命令并转化成AT指令发送到Modem。并等待Modem的回应,然后将结果通过套接口传回到RIL-Java

层。下图是RILD的基本框架:

image

    下面的数据流传递描述图表描述了RIL-JAVA层发出一个电话指令的5 步曲。

image

    在AT通讯的过程中有两类响应:一种是请求后给出应答,一种是通知类,即为不请自来的,例如短信通知达到,我们称该类通知

为URC。在Rild中URC和一般的Response是分开处理的,概念上URC由handleUnsolicited@Atchannel.c处理,而Response由

handleFinalResponse来处理。

1 Event Loop

    Rild管理的真正精髓在ril.cpp,ril_event.cpp中,在研究的过程中,可以看到设计者在抽象上所下的功夫,设计得很优美。EventLoop

的基本工作就是等待在事件端口(串口,Socket),一旦有数据到达就根据登记的Event回调函数进行处理。现在来看Ril设计者是如

何建立一套管理框架来完成这些工作的?

1.1 Event对象

    Event对象构成:(fd,index,persist,func,param)

        fd 事件相关设备句柄。例如对于串口数据事件,fd就是相关串口的设备句柄

        index

        persist 如果是保持的,则不从watch_list中删除。

        func 回调事件处理函数

        param 回调时参数

    为了统一管理事件,Android使用了三个队列:watch_list,timer_list,pending_list,并使用了一个设备句柄池readFDS。

        readFDS:是Linux的fd_set,readFDS保存了Rild中所有的设备文件句柄,以便利用select函数统一的完成事件的侦听。

        watch_list:监测时间队列。需要检测的事件都放入到该队列中。

        timer_list:timer队列

        pending_list:待处理事件队列,事件已经触发,需要所回调处理的事件。

    事件队列队列的操作:ril_event_add,ril_event_del, ril_timer_add

image

    在添加操作中,有两个动作:

        (1) 加入到watch_list

        (2) 将句柄加入到readFDS事件句柄池。

1.2 ril_event_loop()

    我们知道对于Linux设备来讲,我们可以使用select函数等待在FDS上,只要FDS中记录的设备有数据到来,select就会设置相应的标

志位并返回。readFDS记录了所有的事件相关设备句柄。readFDS中句柄是在在AddEvent加入的。所有的事件侦听都是建立在linux

的select readFDS基础上。

image

    ril_event_loop 利用select等待在readFDS(fd_set)上,当select设备有数据时,ril_event_loop会从select返回,在watch_list中相

应的Event放置到pend_list,如果Event是持久性的则不从watch_list中删除。然后ril_event_loop遍历pengding_list处理Event事件,发

起事件回调函数。

1.3 几个重要的Event

    上面分析了ril-d的框架,在该框架上跑的事件有什么

    (1)s_listen_event- (s_fdListen,listenCallback)

        listenCallback处理函数,

        接收客户端连接:s_fdCommand=accepte(..)

        添加s_commands_event()

        重新建立s_listen_event,等待下一次连接

    (2) s_command_event(s_fdCommand,ProcessCommandsCallback)

        从fdCommand  Socket连接中读取StreamRecord

        使用ProcessCommandBufer处理数据

    s_listen_event在大的功能上处理客户端连接(Ril-JAVA层发起的connect),并建立s_commands_event去处理Socket连接发来的Ril命令。ProcessCommandBufer实际上包含了Ril指令的下行过程。

1.4 下行命令翻译及其组织@ProcessCommandBuffer

    RIL_JAVA传递的命令格式:Parcel ,由命令号,令牌,内容组成。RIL_JAVA到达RIL_C时转为构建本地RequestInfo,并将被翻

译成具体的AT指令。由于每条AT命令的参数是不同的,所以对不同的AT指令,有不同的转换函数,在此Android设计在这里做了一

个抽象,做了一个分发框架,通过命令号,利用sCommand数组,获得该命令的处理函数。

    sComand[]={<...>}

    sComand 存在于Ril_command.h中。

    &sComand[]=<{RIL_REQUEST_GET_IMEI, dispatchVoid, responseString},

                              {RIL_REQUEST_DIAL, dispatchDial, responseVoid},

                              {….}>

    dispatchXxx函数一般都放在在Reference-ril.c中,Reference-ril.c这个就是我们需要根据不同的Modem来修改的文件。

1.5 send_at_command框架

    send_at_command是同步的,命令发送后,send_at_command将等待在s_commandcond,直到有sp_response>finalResponse。

    2 read loop@Atchannel.c

    Read loop是解决的问题是:解析从Modem发过来的回应。如果遇到URC则通过handleUnsolicited上报的RIL_JAVA。如果是命令

的应答,则通过handleFinalResponse通知send_at_command有应答结果。

image

    对于URC,Rild同样使用一个抽象数组@Ril.CPP.static UnsolResponseInfo s_unsolResponses[] = {#include "ril_unsol_commands.h"};并利用RIL_onUnsolicitedResponse将URC向上层发送。


3 Ril-d的整体数据流及其控制流示意图

image

   
换一张
 Android GSM驱动模块(rild)详细分析(一)基本架构及初始化
熊猫哥哥 发表于IT168和Opendroid 转载请注明

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协议等在这里并没有包含,也暂不作介绍。)

以下是详细分析,因为篇幅原因,会以连载形式发布出来(大概3篇),本文主要涉及基本架构和初始化的内容:

首先介绍一下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;        /* 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机制便能响应处理了。
到这里启动流程已经分析完毕。
 
Android GSM驱动模块(rild)详细分析(二)request流程
熊猫哥哥 发表于IT168和Opendroid 转载请注明

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的详细流程,就分析完毕了.
 
Android GSM驱动模块(rild)详细分析(三)response流程
熊猫哥哥 发表于IT168和Opendroid 转载请注明

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

到这里,我们分析了自动上报与命令响应,其实response部分,也就告一段落了。
三篇分析RIL的文章也到此结束
上一页 1... -1-1-1-1-1-1-1 ... -1 下一页
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值