源码分布
ril模块放置在源码包中的hardware/ril目录下,包含以下几个目录:
hardware/ril/rild/rild: 守护进程rild,利用socket与Android Framework中的telephony模块进行通讯。
在该目录下还有一个radiooptions.c,它是个二进制工具程序,用于切换Radio的工作状态。
hardware/ril/reference-ril:GSM的参考实现。客户定制时,需移植的部分,要给出自己的实现。
hardware/ril/reference-cdma-sms:CDMA部分针对SMS的参考实现。定制部分。
hardware/ril/libril:为参考实现提供支撑的功用库。守护进程rild和reference-ril会使用它。
hardware/ril/include:包含的头文件
总述
Android Framework会为上层App提供telephony服务,而这些telephony服务会通过socket机制与守护进程rild交互。telephony向本地socket中写入相应的request,rild守护进程监听来自telephony一侧发来的请求,然后将请求转化为ril_event放到队列里。
守护进程这一侧启动一个线程(dispatch线程)去处理这个队列,将请求转换为不同的AT命令写入到Modem串口设备中,若需要等待Modem回送Response,则睡眠,等待另一个线程reader从Modem设备文件中读取数据,将回送结果填充到发送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厂家都可以给出针对自己Modem的AT命令集实现。该实现将被编译成单独的动态连接库。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>]
eg:rild -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,进行实际的向硬件BP侧Modem发送AT命令。
库libril
在libril库中共有5个源代码文件,它们是:
- 定义ril_event数据结构及其上面的操作的ril_event.h和ril_event.cpp
- 定义主动请求的AT命令集的文件ril_commands.h和非主动请求的AT命令集文件ril_unsol_commands.h。它们构成了两个数组s_commands和s_unsolResponses中的元素。主动请求的AT命令是AP侧主动向BP Modem发出查询设置的命令,而非主动则是BP侧主动发送来的状态报告。
- 定义分发和处理Request Event的ril.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_event到watch_table中
void ril_event_add(struct ril_event * ev);
// Add timer event
//添加一个ril_event至timer_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_startEventLoop与eventLoop函数
RIL_startEvent函数功能是创建一个新分发event的dispatch线程,线程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_event从timer_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”上的socket: s_fdListen,它监听来自Android Telephony Service的Request。
ril_event_set (&s_listen_event, s_fdListen, false, listenCallback, NULL);
rilEventAddWakeup (&s_listen_event);
它设置本地全局的s_listen_event,该event与s_fdListen绑定,然后将该event添加到watch列表中。当有数据到达时,会在dispatch线程中的eventLoop中调用listenCallback。
Request数据通过Parcel协议写入socket,在dispatch线程中的eventLoop循环中(watcher列表)监控这些socket是否有数据写入(Telephony去写入请求),当有数据到达时,文件描述符被设置,于是event的回调函数被调用,
另外一个“/dev/socket/rild-debug”上的socket:s_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对象pRI,token设置为0xffffffff,local设置为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_port、s_device_socket和s_device_path优先级顺序,当前者 空时,就检查后者,不为空就打开它,获取相应的文件描述符。
然后,调用libril库中ril.cpp的RIL_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_open与reader线程
主要功能是将处理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 Response。AT回送的结果,会有单行会多个行组成,最后一行往往标识了AT命令执行的结果是error还是ok。同样,有些Response是Modem主动上报的消息,不是对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添加到ATResponse的ATLine链表中。
附注:
检查是不是SMS消息的标准是:AT的Response是不是以返回下面的字符做为前缀:
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 250)ms后醒来,向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_onTimeout和s_onReaderClosed,以备超时后和reader进程关闭时被调用。
在mainLoop函数中,将onATTimeout和onATReaderClosed两个函数指针被赋值给s_onTimeout和s_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);
上述四个函数,均是发送各种类型的AT至Modem侧的函数,它们都是都调用下面的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。