首页 > Android, Telephony > Android源码分析:RIL代码分析

Android源码分析:RIL代码分析

2009-11-04 00:29 星期三    浏览: 3,418    绿 发表评论 阅读评论

 

源码分布

ril模块放置在源码包中的hardware/ril目录下,包含以下几个目录:

hardware/ril/rild/rild: 守护进程rild,利用socketAndroid Framework中的telephony模块进行通讯。
在该目录下还有一个radiooptions.c,它是个二进制工具程序,用于切换Radio的工作状态。

hardware/ril/reference-rilGSM的参考实现。客户定制时,需移植的部分,要给出自己的实现。

hardware/ril/reference-cdma-smsCDMA部分针对SMS的参考实现。定制部分。

hardware/ril/libril:为参考实现提供支撑的功用库。守护进程rildreference-ril会使用它。

hardware/ril/include:包含的头文件

总述

Android Framework会为上层App提供telephony服务,而这些telephony服务会通过socket机制与守护进程rild交互。telephony向本地socket中写入相应的requestrild守护进程监听来自telephony一侧发来的请求,然后将请求转化为ril_event放到队列里。

守护进程这一侧启动一个线程(dispatch线程)去处理这个队列,将请求转换为不同的AT命令写入到Modem串口设备中,若需要等待Modem回送Response,则睡眠,等待另一个线程readerModem设备文件中读取数据,将回送结果填充到发送AT命令时所分配的一个结果缓冲区后,然后唤醒dispatch线程。这时发送命令at_send_command函数簇可以返回,或者是等待超时后返回,这个超时时间可以指定。返回结果将被回送给Telephony Service。这种结果的回送当是内建数据类型时可通过parcel直接传递,若是raw data,则需向socket里写入raw数据。

守护进程还启动一个reader线程不断读取来自Modem侧的Response字符串。每次读取一行,然后对该行进行解析。当读取的行是最后一行时,表示这个response结束,可以得知此次执行的AT命令是否成功。若不是最后一行,则将结果添加到字符串链表中,再去读取下一行,直至一个完整的response结束。当response结束时,就要唤醒正在发送这个AT请求的dispatch线程,让其将返回结果传递给上层的telephony service

Modem侧上报的有些信息属于它主动上报的,不属于AT执行结果,如来电话的响铃RING或有新SMS等。这些属于unsolicited消息。这也是由reader线程进行处理,它也是解析完结果字符串后将它们送给上层的telephony service

针对AT命令的处理,以及往Modem串口设备中读写数据,都是在reference-ril库中实现,每一个baseband厂家都可以给出针对自己ModemAT命令集实现。该实现将被编译成单独的动态连接库。AT命令通过UART口与Modem进行交互。在某些未提供UART口的硬件系统中,模拟出一个UART口与Modem进行通讯。

 

 

hardware/ril/rild/代码中有两块,一是rild守护进程的代码;二是radiooptions,一个切换Modem的小程序的代码。

rild守护进程

hardware/ril/rild/rild.c是守护进程rild的代码

hardware/ril/rild/radiooptions.c二进制工具程序radiooptions的代码,用于切换radio的状态。

rild中主要完成下面这些功能:

选定reference-ril参考实现库路径

rild守护进程的main函数中,首先要指定参考实现库.so文件的路径。可以在启动rild守护进程时,在命令行参数中使用“-l”选项进行指定;另一个参数用于指定库的参数,比如指定所使用的Modem串口设备文件。用法如下:

Usage: rild -l <ril impl library> [-- <args for impl library>]

egrild -l /system/lib/your_libreference-ril.so --d /dev/ttyU0

若未在命令行参数中指定,则系统将从下面两个宏定义的属性进行中获取:

#define LIB_PATH_PROPERTY “rild.libpath”

#define LIB_ARGS_PROPERTY “rild.libargs”

而这个 两个属性值是通过文件build/ target/board/generic/system.prop中指定的:

rild.libpath=/system/lib/libreference-ril.so

rild.libargs=-d /dev/ttyU0

如果通过上述两个过程还没找到有效的参考实现库,将使用下面的宏定义的库(见行141):

#define REFERENCE_RIL_PATH “/system/lib/libreference-ril.so”

用户权限切换

这部分属于Linux的权限管理。切换用户,具体参见文件与进程能力:

参见附录文档:
1. POSIX 文件能力:分配根用户的能http://www.ibm.com/developerworks/cn/linux/l-posixcap.html

2.setuid函数的用法简介http://hi.baidu.com/%F2%DF%F2%D1%B7%C9%B9%FD%BC%D0%D6%F1%CC%D2/blog/item/d546fdc32c4f123ce5dd3b2b.html

 

启动Request事件循环

在启动Request事件循环之前,会打开该参考实现库libreference-ril.so

dlHandle = dlopen(rilLibPath, RTLD_NOW);

RIL_startEventLoop();

打开它是为了完成后面的RIL_Init函数符号的解析。在该事件循环中,会启动一个事件分发dispatch线程,该线程调用processCommandBuffer解析来自Android Framework Telephony侧的request请求(根据request号查询数组s_commands,调用对应的分发函数。数组成员则是在头文件ril_commands.h中定义

然后,启动事件循环,见RIL_startEventLoop函数(详见后续章节),再解析参考实现库libreference-ril.so里的函数符号“RIL_Init”,并从属性“rild.libargs”里获取调用它的参数列表,最后执行该参考实现库里的RIL_Init,返回新的回调函数并注册之。主要代码如下:

rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, “RIL_Init”);
//…此处省略部分代码
property_get(LIB_ARGS_PROPERTY, args, “”);//
属性键是“rild.libargs”
argc = make_argv(args, rilArgv);

//…此处省略部分代码

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

RIL_register(funcs);

rild守护进程先是找到reference-ril库,ril event事件循环,并也创建一个dispatch线程,分发处理各种ril event。接着解析RIL_Init函数,在其中创建一个mainloop线程。在 mainLoop线程中又会创建一个reader线程,用于读取来自Modem串口设备的response

 

RIL_startEventLoop函数的代码在libril库中

radiooptions工具程序

可以在Linux命令行下,使用radiooptions来切换Radio状态,具体用法如下:

# /system/bin/radiooptions

Usage: radiooptions [option ] [extra_socket_args]

0 – RADIO_RESET,

1 – RADIO_OFF,

2 – UNSOL_NETWOR

3 – QXDM_ENABLE,

4 – QXDM_DISABLE

5 – RADIO_ON,

6 apn- SETUP_PDP

7 – DEACTIVE_PDP

8 number – DIAL_

9 – ANSWER_CALL,

10 – END_CALL

它把传递过来的参数写入“/dev/socket/rild-debug”标识的本地socket,不做其他操作。在rild守护进程会监听该socket,当有数据到达时,会调用相应的回调函数,解析发送过来的命令,并转换为相应的AT命令写入Modem

具体过程是:在radiooptions这一侧,先判断参数个数,然后逐个将它们写入socket。在rild守护进程侧,守护进程注册回调函数RIL_RadioFunctions时,会监听该套接字,当套接字上有数据到达时,就调用ril.cpp文件中的回调函数debugCallback,进行实际的向硬件BPModem发送AT命令。

libril

 

libril库中共有5个源代码文件,它们是:

  1. 定义ril_event数据结构及其上面的操作的ril_event.hril_event.cpp
  2. 定义主动请求的AT命令集的文件ril_commands.h和非主动请求的AT命令集文件ril_unsol_commands.h。它们构成了两个数组s_commandss_unsolResponses中的元素。主动请求的AT命令是AP侧主动向BP Modem发出查询设置的命令,而非主动则是BP侧主动发送来的状态报告。
  3. 定义分发和处理Request Eventril.cpp文件

先来看一下ril_event数据类型,及定义在该类型上的函数操作。

ril_event数据结构

头文件中定义了数据类型ril_event,声明了添加删除函数,部分代码如下:

 

struct ril_event {
struct ril_event *next;
struct ril_event *prev;

int fd;//用于通讯的文件描述符
int index;//
event在队列中的索引
bool persist;//
是否保存下来的标志
struct timeval timeout;//
等待多长时间后超时
ril_event_cb func;//
处理event的回调函数
void *param;//
传递给回调函数的参数指针
};

// Initialize internal data structs
void ril_event_init();

// Initialize an event
//
初始化一个ril_event,指定它的用于通讯的文件符、是否存储、回调函数以及回调函数的参数
void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param);

// Add event to watch list
//
添加到一个ril_eventwatch_table
void ril_event_add(struct ril_event * ev);

// Add timer event
//
添加一个ril_eventtimer_list
void ril_timer_add(struct ril_event * ev, struct timeval * tv);

// Remove event from watch list
//
watch_table中删除指定的ril_event
void ril_event_del(struct ril_event * ev);

// Event loop
void ril_event_loop();//
进入循环并阻塞,除非有数据到达或超时

上面定义了ril_event以及对几个ril_event列表的添加删除操作。因为多线程的缘故,这些对列表的操作,都采用了互斥锁同步机制。当进入添加删除操作之前,上锁,操作结束后,释放锁。

 

ril_event.cpp中声明的主要的全局变量有:

 

//rild守护进程调用RIL_register(funcs)将从libreference库中的 解析的RIL_Init函数符号赋值给它:
RIL_RadioFunctions s_callbacks = {0, NULL, NULL, NULL, NULL, NULL};

//……

static struct ril_event s_commands_event;
static struct ril_event s_wakeupfd_event;
static struct ril_event s_listen_event;
static struct ril_event s_wake_timeout_event;
static struct ril_event s_debug_event;

//……

/*
上层发送来的AT请求经转换为RequestInfo后挂在它上面,因此将串行执行AT请求,见processCommandBuffer
*/
static RequestInfo *s_pendingRequests = NULL;


static RequestInfo *s_toDispatchHead = NULL;
static RequestInfo *s_toDispatchTail = NULL;

//……

static UserCallbackInfo *s_last_wake_timeout_info = NULL;

//……

static CommandInfo s_commands[] = {
#include “ril_commands.h”
};

static UnsolResponseInfo s_unsolResponses[] = {
#include “ril_unsol_commands.h”
};

 

数组s_commands s_unsolResponses用来查询对应的分发 函数。

再来看一下ril_event_init()函数:

void ril_event_init()
{
MUTEX_INIT();
FD_ZERO(&readFds);
init_list(&timer_list);
init_list(&pending_list);
memset(watch_table, 0, sizeof(watch_table));
}

它初始化一个读文件集和三个列表:

static fd_set readFds;
static struct ril_event * watch_table[MAX_FD_EVENTS];//
目前定义的最多为8
static struct ril_event timer_list;
static struct ril_event pending_list;

watch_table列表中的各个ril_event对应的文件符将被设置在文件描述符集readFds

中。在往watch_table添加event时设置了文件描述符至描述符集readFds,以供监视,在processReadReadies中处理watch_table时则用FD_ISSET(rev->fd, rfds)来判断是否被设置可以有数据以供读取了。

 

 

 

文件ril.cpp

 

RIL_startEventLoopeventLoop函数

RIL_startEvent函数功能是创建一个新分发eventdispatch线程,线程id赋给ril.cpp中的本地全局 变量s_tid_dispatch,线程执行入口点函数是eventLoop

RIL_startEventLoop在主进程中执行,在其创建的dispatch线程执行eventLoop函数后,就在s_startupCond上等待dispatch线程初始化各个ril_event列表。当eventLoop完成各个列表的初始化后,RIL_startEventLoop才会继续往下执行直至返回到rild.c中,执行后面的代码,否则它在同步条件s_startupCond上一直等待下去。

下面的代码是RIL_startEventLoop创建dispatch线程并一直等待它完成初始化各个ril_event列表:

/* spin up eventLoop thread and wait for it to get started */
s_started = 0;
pthread_mutex_lock(&s_startupMutex);
pthread_attr_init (&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);
while (s_started == 0) {
pthread_cond_wait(&s_startupCond, &s_startupMutex);
}
pthread_mutex_unlock(&s_startupMutex);

eventLoop函数

它调用ril_event_init完成各个ril_event列表的初始化,包括指定ril_event的回调处理函数。eventLoop函数主要代码如下:

ril_event_init();
pthread_mutex_lock(&s_startupMutex);
s_started = 1;
pthread_cond_broadcast(&s_startupCond);
pthread_mutex_unlock(&s_startupMutex);//
唤醒在s_startupMutex上等待的线程
ret = pipe(filedes);//
创建管道,第一个用来读,第二个用来写
//…
此处删除了错误检查代码
s_fdWakeupRead = filedes[0];
s_fdWakeupWrite = filedes[1];
fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK);//
设置为非阻塞模式
ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true, processWakeupCallback, NULL);//
给本地全局变量赋值,接着会将其添加到列表中
rilEventAddWakeup (&s_wakeupfd_event);//
添加一个event中至watch列表中,然后写一个空格字符到管道的写端,唤醒该线程

ril_event_loop();

它在完成三个ril event列表的初始化之后,就唤醒主进程,让其执行RIL_startEventLoop的剩下部分。接着它创建一个管道,管道的读描述符被绑定到本地全局的s_wakeupfd_event上,然后将该本地全局的event添加到watch列表中,并写入一个空字符唤醒它。唤醒后执行的回调函数为processWakeupCallback

最后,它进入ril_event.cpp中的ril_event_loop()函数,处理各个列表上的ril_event:调用它们的回调函数(processWakeupCallback),处理它们。详见下面:

 

ril_event_loop()

它启动一个无限循环,不断进行如下内容:

首先不断检查timer list上是否有超时的ril_event,若有就计算超时时间,并传递给下面的select调用,让它检查文件读选择符集readFds是否有文件(或Socket)中有数据变得可读。若有可读或超时,则select返回,否则一直阻塞在该调用上。

 

for (;;) {
// make local copy of read fd_set
memcpy(&rfds, &readFds, sizeof(fd_set));
if (-1 == calcNextTimeout(&tv)) {//
根据下一个timerlist中最近一个要到//来的event,计算最快的即将到来的超时时间
// no pending timers; block indefinitely
dlog(“~~~~ no timers; blocking indefinitely ~~~~”);
ptv = NULL;//
无需等待,则下面的select超时参数为空,将会无限等待下
//
去,除非有可读的数据就绪
} else {
dlog(“~~~~ blocking for %ds + %dus ~~~~”, (int)tv.tv_sec, (int)tv.tv_usec);
ptv = &tv;//
超时等待时间,select可以立即返回,对其进行处理
}
printReadies(&rfds);
n = select(nfds, &rfds, NULL, NULL, ptv);//
检查读文件符集,有数据可读//时则文件描述符标志被修改,select返回,否则一直阻塞等待下去,除非超时;若超时参数为//0,则一直等待下去,除非所监控的文件集中有数据到达变得可读。

// Check for timeouts
processTimeouts();

// Check for read-ready
processReadReadies(&rfds, n);

// Fire away
firePending();

}

接着系统开始处理超时: processTimeouts将超时的ril_eventtimer_list中移除,添加到pending_list中,等待处理。

processReadReadies 则是监控watch_table上的所有event,将它们从watch_table移除,填加到pending_list中。

最后一个firePending则是调用pending_list中的各个回调函数并将它们从列表中移除。

timeout超时的回调函数是ril.cpp文件userTimerCallback

 

RIL_register (const RIL_RadioFunctions *callbacks)函数

主要功能是将reference-ril库中的本地s_callbacks赋给libril中的全局的s_callbacks,这样,在Android Framework中通过IPC Parcel协议传递的Request及其数据通过dispatch线程分发给reference-ril中的onRequest进行处理。最后它开始,监听两个套接字上是否有数据到来。

首先,它对传进来的实参callbacks进行正确性检查后,通过内存拷贝的方式赋给libril中全局变量s_callbacks。该实参保存了reference-ril库中的几个回调函数指针及其它信息:

/*** Static Variables ***/
static const RIL_RadioFunctions s_callbacks = {
RIL_VERSION,
onRequest,
currentState,
onSupports,
onCancel,
getVersion
};

内存拷贝的方式赋值:

memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions));

接着,它监听在“/dev/socket/rild”上的sockets_fdListen,它监听来自Android Telephony ServiceRequest

ril_event_set (&s_listen_event, s_fdListen, false, listenCallback, NULL);

rilEventAddWakeup (&s_listen_event);

它设置本地全局的s_listen_event,该events_fdListen绑定,然后将该event添加到watch列表中。当有数据到达时,会在dispatch线程中的eventLoop中调用listenCallback

Request数据通过Parcel协议写入socket,在dispatch线程中的eventLoop循环中(watcher列表)监控这些socket是否有数据写入(Telephony去写入请求),当有数据到达时,文件描述符被设置,于是event的回调函数被调用,

 

另外一个“/dev/socket/rild-debug”上的sockets_fdDebug,监听来自radiooptions工具程序的请求操作,处理请求的回调函数是debugCallback

ril_event_set (&s_debug_event, s_fdDebug, true, debugCallback, NULL);

rilEventAddWakeup (&s_debug_event);

该回调函数的也是在dispatch线程中的eventLoop中被调用的。

listenCallback

它接受远端的一个套接字连接,然后返回队列上一个新的套接字描述符。接着在下面还要进行权限检查,如果不是phone process中发起的连接,将关闭s_fdCommand,并返回不再往下执行。

s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen);

接着,为它分配一个记录流的缓冲区,设置一个ril_event,将其添加到watch列表中,监视新数据的到来。当有数据到来时,调用processCommandsCallback进行处理。

p_rs = record_stream_new(s_fdCommand, MAX_COMMAND_BYTES);

ril_event_set (&s_commands_event, s_fdCommand, 1, processCommandsCallback, p_rs);

rilEventAddWakeup (&s_commands_event);

onNewCommandConnect();

 

processCommandsCallback

当读socket出错时则关闭socket,将其从watch列表中删除,释放前面分配的缓冲区,重新将s_fdListen加入到watch列表中(因为在调用回调函数后,event将被从列表中清除)。正常情况下则调用processCommandBuffer进行处理来自Parcel里的数据。

 

processCommandBuffer

它首先从Parcel里读取request请求号和token,然后分配一个RequestInfo缓冲区,查询数组得到对应s_commands[request]

status = p.readInt32(&request);//读取数据
status = p.readInt32 (&token);

//…

pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo));
pRI->token = token;
pRI->pCI = &(s_commands[request]);//
将全局数组项的地址赋给它

ret = pthread_mutex_lock(&s_pendingRequestsMutex);
assert (ret == 0);
pRI->p_next = s_pendingRequests;//
这两行,将request添加到队列头部
s_pendingRequests = pRI;
ret = pthread_mutex_unlock(&s_pendingRequestsMutex);
assert (ret == 0);
/* sLastDispatchedToken = token; */
pRI->pCI->dispatchFunction(p, pRI);//
调用request对应的dispatch函数

 

在解析完成后查询数组s_commands构建的数据结构类型(见ril.cpp),它们用于调用底层的AT发送函数。

typedef struct {
int requestNumber;//
请求号
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
int(*responseFunction) (Parcel &p, void *response, size_t
responselen);
} CommandInfo;

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;

 

 

用于主动上报Modem发送来的信息结构体如下:

typedef struct {
int requestNumber;
int (*responseFunction) (Parcel &p, void *response, size_t responselen);
WakeType wakeType;
} UnsolResponseInfo;

typedef struct UserCallbackInfo {
RIL_TimedCallback p_callback;
void *userParam;
struct ril_event event;
struct UserCallbackInfo *p_next;
} UserCallbackInfo;

 

 

dispatchXXX函数簇

dispatchXXX函数簇,被上面的缓冲区处理函数调用。这些函数进一步从socket里读取数据,解析之后作为待处理的数据,转给reference-ril库中的onRequest函数进行处理。具体是从parcel里读取数据,解析后调用s_callbacks.onRequest函数进行处理。

 

Ril.cpp里提供了多个dispatch函数,用来处理不同类型的AT请求。

static void dispatchVoid (Parcel& p, RequestInfo *pRI);

static void dispatchString (Parcel& p, RequestInfo *pRI);

static void dispatchStrings (Parcel& p, RequestInfo *pRI);

static void dispatchInts (Parcel& p, RequestInfo *pRI);

static void dispatchDial (Parcel& p, RequestInfo *pRI);

static void dispatchSIM_IO (Parcel& p, RequestInfo *pRI);

static void dispatchCallForward(Parcel& p, RequestInfo *pRI);

static void dispatchRaw(Parcel& p, RequestInfo *pRI);

static void dispatchSmsWrite (Parcel &p, RequestInfo *pRI);

static void dispatchCdmaSms(Parcel &p, RequestInfo *pRI);

static void dispatchCdmaSmsAck(Parcel &p, RequestInfo *pRI);

static void dispatchGsmBrSmsCnf(Parcel &p, RequestInfo *pRI);

static void dispatchCdmaBrSmsCnf(Parcel &p, RequestInfo *pRI);

static void dispatchRilCdmaSmsWriteArgs(Parcel &p, RequestInfo *pRI);

AT请求执行完成后,用于发送Modem侧的回复的函数簇为:

static int responseInts(Parcel &p, void *response, size_t responselen);

static int responseStrings(Parcel &p, void *response, size_t responselen);

static int responseString(Parcel &p, void *response, size_t responselen);

static int responseVoid(Parcel &p, void *response, size_t responselen);

static int responseCallList(Parcel &p, void *response, size_t responselen);

static int responseSMS(Parcel &p, void *response, size_t responselen);

static int responseSIM_IO(Parcel &p, void *response, size_t responselen);

static int responseCallForwards(Parcel &p, void *response, size_t responselen);

static int responseDataCallList(Parcel &p, void *response, size_t responselen);

static int responseRaw(Parcel &p, void *response, size_t responselen);

static int responseSsn(Parcel &p, void *response, size_t responselen);

static int responseSimStatus(Parcel &p, void *response, size_t responselen);

static int responseGsmBrSmsCnf(Parcel &p, void *response, size_t responselen);

static int responseCdmaBrSmsCnf(Parcel &p, void *response, size_t responselen);

static int responseCdmaSms(Parcel &p, void *response, size_t responselen);

static int responseCellList(Parcel &p, void *response, size_t responselen);

static int responseCdmaInformationRecords(Parcel &p,void *response, size_t responselen);

static int responseRilSignalStrength(Parcel &p,void *response, size_t responselen);

static int responseCallRing(Parcel &p, void *response, size_t responselen);

static int responseCdmaSignalInfoRecord(Parcel &p,void *response, size_t responselen);

static int responseCdmaCallWaiting(Parcel &p,void *response, size_t responselen);

 

 

其它函数功能释义

 

strdupReadString(Parcel &p)

Parcel中读取UTF16编码的字符串,并把它转换为UTF8格式,然后返回之。

 

writeStringToParcel(Parcel &p, const char *s)

UTF8格式字符串转换为UTF16后,写入Parcel

 

 

processWakeupCallback,它用于清理管道中的数据。Watch列表的作用仅是将event的处理从select中唤醒过来?(TODO

todo

 

 

issueLocalRequest(int request, void *data, int len)

根据request号(即数组s_commands中的索引,即s_commands[request])构建一个RequestInfo对象pRItoken设置为0xfffffffflocal设置为1;然后将其添加到s_pendingRequests列表的头部(将其next指针指向原s_pendingRequests,将新的s_pendingRequests指向它);最后,调用s_callbacks.onRequest(request, data, len, pRI);

因为AT请求一般都来自于上层的Framework中的Telephony部分,然后rild解析后得到,此函数提供了一个直接声称AT请求的一个入口。接收到radiooptions工具程序发送来的请求后在debugcallback中会得到调用。

 

RIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen)

它在响应request完成后,将response写入到parcel中。它将调用reponseXXX函数簇

todo

responseXXX函数簇

 

todo

RIL_requestTimedCallback

它将一个回调函数与一个timer ril_event绑定,然后添加到timer list中,timeout后,在dispatch的线程中得到执行。

todo

RIL_onUnsolicitedResponse

todo

reference-ril

 

mainLoop线程

reference-ril库中的RIL_Init函数主要完成两个功能,一是根据传递过来的参数,打开不同的设备,二是创建一个线程,将线程号指定给s_tid_mainloop。打开设备的用法如下:

reference-ril requires: -p <tcp port> or -d /dev/tty_device

实际上,还可以指定一个参数s,指定使用socket方式,该socket值为1

选项用来指定读写AT所要用的接口:用选项p指定端口号,优先级最高;用s指定socket优先级其次,用d指定AT设备,一般为/dev/tty_device,优先级最低,见mainLoop函数。

 

第二个任务是创建mailLoop线程,线程号s_tid_mainloop,函数入口点是mainLoop

 

ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);

 

mainLoop函数

 

先指定reader关闭和timeout时的回调函数;之后就进入for无限循环,打开AT交互设备,获取文件描述符,然后调用at_open打开at channel。当打开at channel时,创建一个reader线程,它读取Modem串口设备上的AT response

打开文件获取描述符时,依照s_ports_device_sockets_device_path优先级顺序,当前者 空时,就检查后者,不为空就打开它,获取相应的文件描述符。

 

然后,调用libril库中ril.cppRIL_requestTimedCallback(它调用的是本地的一个全局结构体变量s_rilenv中的一个指针,而s_rilenv来自于rild中的s_rilenv,后者这个结构体中的函数指针都在libril库中ril.cpp中)。

附注:rild中的s_rilenv

static struct RIL_Env s_rilEnv = {
RIL_onRequestComplete,
RIL_onUnsolicitedResponse,
RIL_requestTimedCallback
};

 

RIL_requestTimedCallback函数将initializeCallback与一个Timer ril_event绑定,让其在一定时间间隔后(此处为0,意味着立即执行)执行回调函数initializeCallback。详见RIL_requestTimedCallback注释。

 

mainLoop线程在完成上述操作后,进入睡眠,等待at channel被关闭,当被关闭时,它被唤醒再次进入循环,然后再次执行上述指定回调函数和打开at channel的操作。

 

at_set_on_reader_closed(onATReaderClosed);
at_set_on_timeout(onATTimeout);
//
进入无限循环
for(;;) {

/*此处省略代码:打开AT交互设备,获取文件描述符fd*/

ret = at_open(fd, onUnsolicited);

//…省略部分检查代码

RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0);

//…

waitForClose();

//…

}

onRequest

dispatch线程处理ril_event表上的event时,调用它们的回调函数,进而最终调用到onRequest函数。它首先检查Radio是不是不可用(unavailable),不可用时,只能查询SIM状态;当Radio关闭时,也只能查询SIM卡状态和Radio是否关闭。因此,遇到上述情况,只是调用ril.cpp中的RIL_onRequestComplete后直接返回:返回结果是一个空指针。

然后,onRequest根据请求号,要么调用request函数簇,或直接将来自Telephony的请求转为AT-command,调用atchannel.cpp中的发送函数将AT写到Modem串口设备中。完成后,调用

onUnsolicited

它会根据AT Response字符串行首的几个字符,判断是对哪个AT命令的响应,然后调用RIL_onUnsolicitedResponse函数告诉Telephony Service请求结果。

文件atchannel.c

部分数据结构释义:

 

typedef struct ATLine {
struct ATLine *p_next;
char *line;
} ATLine;//
该数据结构定义了一个字符串列表,它表示了AT Response里的一组连续的字符串

typedef struct {
int success; /* true if final response indicates success (eg “OK”) */
char *finalResponse; /* eg OK, ERROR */
ATLine *p_intermediates; /* any intermediate responses */
} ATResponse;//
一个完整的AT Response

函数void at_response_free(ATResponse *p_response)释放上述一个ATResponse对象的内存。对应的at_response_new()函数为内部的,只能在atchannel.c文件中使用。

 

 

 

at_openreader线程

主要功能是将处理unsolicited 信息的handler赋给s_unsolHandler(在上面的mainLoop函数中,传递过来的onUnsolicited将被赋值给s_unsolHandler),以备调用。第二个功能是创建一个新的读取Modem串口设备的线程reader,线程号为s_tid_reader,线程入口点为readerLoop,用于读取Modem设备送来的Response

todo

reader线程中,readerLoop不断读取Modem串口设备文件,先读取一行,据此判断是否是SMS,若是还要再读一行,获取PDP文本,然后调用s_unsolHandler(见上面,在调用at_open函数时,它被指定为reference-ril.cpp中的onUnsolicited)进行处理。否则不为SMS类型消息时,调用processLine处理。

 

processLine

该函数处理来自AT ResponseAT回送的结果,会有单行会多个行组成,最后一行往往标识了AT命令执行的结果是error还是ok。同样,有些ResponseModem主动上报的消息,不是对AT命令执行的响应。因此,此函数是处理来自Modem设备的一行数据,在readerLoop里反复不断读取调用该行。

它先检查当前的处理是否是一个新的AT Response,即还没有正在处理的response,此时调用handleUnsolicited函数。

再就是判断是否是最后的AT执行结果标识行,根据该行,分别标识出一个AT执行结果是否成功。

再判断是否是执行发送SMS后的Response

最后,判断不同的命令类型,执行不同的处理。

 

if (sp_response == NULL) {//若是新开始处理一个response
/* no command pending */
handleUnsolicited(line);
} else if (isFinalResponseSuccess(line)) {//
如果行是最后的成功标识行
sp_response->success = 1;//
设置response结果标志
handleFinalResponse(line);//
} else if (isFinalResponseError(line)) {//
如果最后的结果行是错误标识行
sp_response->success = 0;//
设置response结果标志
handleFinalResponse(line);
} else if (s_smsPDU != NULL && 0 == strcmp(line, “> “)) {//
若是发送SMS
// See eg. TS 27.005 4.3
// Commands like AT+CMGS have a “> ” prompt
writeCtrlZ(s_smsPDU);
s_smsPDU = NULL;
} else switch (s_type) {//
根据类型不同的AT类型进行不同的结果处理
case NO_RESULT:
handleUnsolicited(line);
break;
case NUMERIC:
if (sp_response->p_intermediates == NULL
&& isdigit(line[0])
) {
addIntermediate(line);
} else {
/* either we already have an intermediate response or
the line doesn’t begin with a digit */
handleUnsolicited(line);
}
break;
case SINGLELINE:
if (sp_response->p_intermediates == NULL
&& strStartsWith (line, s_responsePrefix)
) {
addIntermediate(line);
} else {
/* we already have an intermediate response */
handleUnsolicited(line);
}
break;
case MULTILINE:
if (strStartsWith (line, s_responsePrefix)) {
addIntermediate(line);
} else {
handleUnsolicited(line);
}
break;
default: /* this should never be reached */
LOGE(“Unsupported AT command type %dn”, s_type);
handleUnsolicited(line);
break;
}

readerLoop不断循环调用processLine时,当为对AT命令响应结果时,最终会走到最后的结果标识行,调用到handleFinalResponse函数,标识一个完整的AT Response结束,唤醒正在该结果上的等待的sent_at_command_full_nolock函数。

 

addIntermediate(line)

reponse结果行保存到ATResponse中去:将字符串转为ATLine添加到ATResponseATLine链表中。

 

附注:

检查是不是SMS消息的标准是:ATResponse是不是以返回下面的字符做为前缀:

static const char * s_smsUnsoliciteds[] = {
“+CMT:”, //
Received SMS indication
“+CDS:”, //
+CDS Received SR indication
“+CBM:” //
+CBM Received CBM indication
};

 

isFinalResponseSuccess (const char *line)

AT命令的response行是标识AT执行成功的行,则返回1,否则返回0

原代码注释:returns 1 if line is a final response indicating success

它是根据下列字符进行判断:

static const char * s_finalResponsesSuccess[] = {
“OK”,
“CONNECT” /* some stacks start up data on another channel */
};

 

 

isFinalResponseError (const char *line)

AT命令的response行是标识AT执行失败的行,则返回1,否则返回0

原代码注释:returns 1 if line is a final response indicating error

它是根据下列字符进行判断:

static const char * s_finalResponsesError[] = {
“ERROR”,
“+CMS ERROR:”,
“+CME ERROR:”,
“NO CARRIER”, /* sometimes! */
“NO ANSWER”,
“NO DIALTONE”,
};

handleUnsolicited

 

at_close

关闭AT设备,即Modem。并将全局的s_readerClosed标志设为1

 

int at_handshake()

每隔250ms,即睡眠250#define HANDSHAKE_TIMEOUT_MSEC 250ms后醒来,向Modem写入字符“ATE0Q0V1″,用于启动和保持At channel,见代码注释:Used to ensure channel has start up and is active

 

void at_set_on_timeout(void (*onTimeout)(void))

void at_set_on_reader_closed(void (*onClose)(void))

这两个函数是设定回调函数的。设定的回调函数指针分别被赋给全局的s_onTimeouts_onReaderClosed,以备超时后和reader进程关闭时被调用。

mainLoop函数中,将onATTimeoutonATReaderClosed两个函数指针被赋值给s_onTimeouts_onReaderClosed,执行关闭操作,将radio设置为unavailable状态。

 

int at_send_command_singleline (const char *command, const char *responsePrefix, ATResponse **pp_outResponse)

 

int at_send_command_numeric (const char *command, ATResponse **pp_outResponse);

 

int at_send_command_multiline (const char *command, const char *responsePrefix, ATResponse **pp_outResponse)

 

int at_send_command_sms (const char *command, const char *pdu, const char *responsePrefix, ATResponse **pp_outResponse);

 

上述四个函数,均是发送各种类型的ATModem侧的函数,它们都是都调用下面的at_send_command_full函数:

 

int at_send_command_full (const char *command, ATCommandType type,

const char *responsePrefix, const char *smspdu,

long long timeoutMsec, ATResponse **pp_outResponse)

该函数首先检查调用者是不是在reader线程中,若是则返回一个错误号。(最终调用它的应该是在dispatch线程中)然后上锁同步,避免出现写AT出现竞争的情况,在上锁期间,调用at_send_command_full_nolock完成发送一个完整的AT命令。若出现发送超时,且超时回调函数被指定,则调用超时回调函数进行处理。

 

 

int at_send_command_full_nolock (const char *command, ATCommandType type,

const char *responsePrefix, const char *smspdu,

long long timeoutMsec, ATResponse **pp_outResponse)

该函数完成一个AT命令的发送。它首先检查当前是否有一个还未完成发送的AT命令正在发送:判断全局的sp_response是否为空,当为空时,说明没有正在发送的AT命令。这是因为:当有正在发送的AT命令时,首先在内存里创建一个ATResponse的对象,然后将指针赋给sp_response。当成功发送后再将sp_response指定为NULL

当它向Modem发送完AT命令后,将等待在互斥锁s_commandmutex上,等待Modem侧的response(由reader线程去读取response)被完整读取,否则reader线程不断去读取,若没有数据,将会等待阻塞调用线程,即dispatch线程。读取完被唤醒后(见handleFinalResponse函数和processLine注释),设置相关的response全局变量后,即可将sp_response指定为NULL后返回。

 

 

 

void reverseIntermediates(ATResponse *p_response)

反转字符串顺序列表

 

函数writeCtrlZ (const char *s)

辅助函数,非接口函数。将字符串发送到Modem中(写入到s_fd设备,即在at_open中打开的与AT交互设备)后,然后再写入个控制字符“^Z”。在代码中有如下注释:See eg. TS 27.005 4.3 Commands like AT+CMGS have a “> ” prompt

 

 

AT_CME_Error at_get_cme_error(const ATResponse *p_response)

返回错误码,见代码注释:Returns error code from response. Assumes AT+CMEE=1 (numeric) mode

typedef enum {
CME_ERROR_NON_CME = -1,
CME_SUCCESS = 0,
CME_SIM_NOT_INSERTED = 10
} AT_CME_Error;

 

参考:

pthread_mutex_lock

pthread_mutex_unlock

pthread_cond_signal

pthread_cond_timeout_np

pthread_cond_timedwait

pthread_cond_wait

 

 

 

 

at_tok.c at_tok.h

int at_tok_start(char **p_cur)

寻找token的位置,将其更新到*p_cur,成功返回0,失败返回-1。具体为:p_cur指向的字符串地址值被更新,新的值是是字符串中冒号(“:”)后面的一个字符串的首字符的地址。

 

 

int at_tok_start(char **p_cur);

 

int at_tok_nextint(char **p_cur, int *p_out);

int at_tok_nexthexint(char **p_cur, int *p_out);

int at_tok_nextbool(char **p_cur, char *p_out);

int at_tok_nextstr(char **p_cur, char **out);

 

上面的函数军事遍历AT Response字符串,并把得到的结果置于p_out中。同时更新*P_cur,让其为下面对字符地址。成功,返回0,失败返回-1

 

int at_tok_hasmore(char **p_cur);

检查是否还有新的token

 

这些主要是被at_get_cme_error函数使用获取错误号。

 

misc.h misc.c

这两个文件中只有一个函数int strStartsWith(const char *line, const char *prefix),用于返回字符串line中是否有前缀字符串prefix开头,是则返回1,否则返回0

 

本文链接地址: http://blog.redwolf-soft.com/?p=966

原创文章,版权©红狼博客所有, 转载随意,但请注明出处。

    分享到:

相关文章:

  • 无相关文章
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.
订阅评论
  欢迎参与讨论,请在这里发表您的看法、交流您的观点。