寫在前面
1、本文從宏觀的角度去分析問題,因此忽略了一些非主線的函數。
2、同理,對於函數內部非主要的邏輯部分,也採取了省略。
3、受限於知識的積累和理解能力,文中描述如有分析不妥之處,希望能夠得到大家更正。
從Main函數開始的故事
Android的智能機架構是應用處理器+基帶芯片,也就是AP+Modem的模式,AP部分相當於CPU,Modem相當於網卡,而且每個廠商使用的Modem都有可能不一樣。每種通訊協議如GSM/CDMA又有很大的不同,爲了將所有的Modem抽象爲統一的模式,因此Android搭建了RIL(Radio Interface Layer)層。在這個層的下面,每個廠商可以有自己不同的實現方式,但是經過RIL層協議的轉換,就將所有的Modem抽象爲統一的對象向上層負責。
RILC與上層的RILJ溝通方式是通過Socket傳輸數據與命令,而與底層Modem的信號傳輸是通過串口用AT命令來實現。
我們從RIL的入口開始分析。
- @rild.c
- int main(int argc, char **argv){
- //連接庫地址:/system/lib/libreference-ril.so
- #define REFERENCE_RIL_PATH "/system/lib/libreference-ril.so"
- rilLibPath = REFERENCE_RIL_PATH;
- //切換UID爲AID_RADIO
- switchUser();
- //打開鏈接庫
- dlHandle = dlopen(rilLibPath, RTLD_NOW);
- //開啓EventLoop循環
- RIL_startEventLoop();
- //從鏈接庫中(也就是reference-ril.c)尋找RIL_Init函數地址
- rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");
- //調用reference-ril.c中的RIL_Init函數進行初始化INIT,同時得到reference-ril的回調函數
- funcs = rilInit(&s_rilEnv, argc, rilArgv);
- //註冊得到的reference的回調函數
- RIL_register(funcs);
- }
從上面可以看出,入口函數主要完成了3個作用:
1、開啓EventLoop循環,完成RIL與RILJ層數據交互(通過Socket)
2、打開動態庫reference並構建ReaderLoop循環,完成RIL與Modem層數據交互(通過AT)
3、註冊reference的回調函數
下面我們詳細介紹具體流程。而在介紹之前,先把整個RIL層的數據流向用一張圖片展示:
一、Event機制
1.1、Event框架
Event要做的就是循環檢測EventLoop中添加的句柄池,如果在當前的句柄池中有任意一個句柄所代表的通道中有新的數據進來,就去處理當前的新數據。而在句柄池中最重要的句柄就是RILJ與RILC之間的Socket通道。
Event的實現主要在ril_event.cpp文件中。我們先來看一下一個標準的Event的構成:
- 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;
- };
從上面的結構體可以看出,Event的管理是通過鏈表實現的,一些重要的成員變量的意義如下:
fd:事件相關設備句柄。最重要的就是RILJ與RILC之間的Socket文件句柄
persist:說明當前的Event需要保持,不能從watch_table中刪除
func:當前Event的處理函數
param:調用當前Event處理函數時的參數
接下來,我們在看具體的處理流程之前,先來看一下處理Event需要哪些函數:
- //Event的初始化,其實就是對3個主要鏈表的初始化
- static void init_list(struct ril_event * list)
- //添加Event到鏈表
- static void addToList(struct ril_event * ev, struct ril_event * list)
- //從鏈表刪除Event
- static void removeFromList(struct ril_event * ev)
- //從watch連表中刪除某個Event
- static void removeWatch(struct ril_event * ev, int index)
- //處理超時的Event
- static void processTimeouts()
- //初始化Event鏈表
- void ril_event_init()
- //初始化一個Event
- void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param)
- //把一個Event添加到watch_table中
- void ril_event_add(struct ril_event * ev)
- //把一個Event添加到timer_list中
- void ril_timer_add(struct ril_event * ev, struct timeval * tv)
- //把一個Event從watch_table中刪除
- void ril_event_del(struct ril_event * ev)
- //主循環
- void ril_event_loop()
通過上面的主要函數我們可以大致推測出,管理Event的過程應該是生成相應的Event節點,然後將Event添加到鏈表,處理Event之後就需要把當前的Event從鏈表中刪除。而且我們看出,Event管理中應該存在多個鏈表,那麼究竟有哪些鏈表在運行呢?
RIL的Event管理體系中存在3個鏈表結構:watch_table,timer_list,pending_list,並使用了一個設備句柄池readFDS,把所有的Socket管道的文件句柄保存起來。而管理的過程可以歸納爲以下6點:
1、可以將一個Event添加到watch_table或者timer_list中;
2、如果Event是添加到watch_table中,需要把當前Event的fd(事件設備句柄)添加到readFDS中;
3、如果Event是添加到timer_list中,不需要把當前Event的fd(事件設備句柄)添加到readFDS中,而且當前Event的fd值是無效的;
4、在循環檢測過程中,如果發現watch_table中有Event就會把當前Event添加到pending_list中,如果當前Event的persist屬性爲false,說明不需要保留當前節點,就把當前的Event從watch_table中刪除;如果persist爲true,說明需要保留,就不需要從watch_table中刪除當前節點。
5、在循環檢測過程中,如果發現timer_list中的Event超時時,把當前Event移動到pending_list中,同時刪除timer_list中的節點。
6、在循環檢測的過程中,等watch_table和timer_list處理完畢後,就去pending_list中執行裏面的Event所指向的func。
有了以上的認識,我們來看一下具體的流程。從Event的創建入口開始:
- @ril.cpp
- RIL_startEventLoop(void) {
- //打開Event線程,並調用eventLoop進入循環
- ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);
- }
然後我們進入到線程的入口函數中查看:
- eventLoop(void *param) {
- //Event的初始化
- ril_event_init();
- //創建一個Event
- ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,processWakeupCallback, NULL);
- //將上面創建的Event加入到watch_table中,這裏起到喚醒LoopEvent的作用,後面詳細說明
- rilEventAddWakeup (&s_wakeupfd_event);
- //進入loop循環
- ril_event_loop();
- }
可以看出,Event的搭建由兩個步驟:1、初始化鏈表;2、進入loop循環。我們分別看一下兩個過程:
1.2、EventLoop的搭建過程
1.2.1、Event初始化過程。其實就是ril_event_init過程。
- @ril_event.cpp
- void ril_event_init()
- {
- FD_ZERO(&readFds);
- init_list(&timer_list);
- init_list(&pending_list);
- memset(watch_table, 0, sizeof(watch_table));
- }
初始化的過程很簡單,就是對loop框架中涉及到的文件句柄集和3個重要鏈表的初始化。
1.2.2、Event循環的過程
- @ril_event.cpp
- void ril_event_loop()
- {
- //死循環檢測Event消息
- for (;;) {
- //計算下一次超時時間
- if (-1 == calcNextTimeout(&tv)) {
- //NULL說明select是阻塞模式
- ptv = NULL;
- } else {
- //非空說明在超時時間內是阻塞模式
- ptv = &tv;
- }
- //用select去掃描readFds中的所有管道集合,檢測RILJ是否有新數據出現。
- n = select(nfds, &rfds, NULL, NULL, ptv);
- //檢查超時事件,如果超時,將Event放入pending_list中
- processTimeouts();
- //檢查watch_table,將Event加入pending_list中
- processReadReadies(&rfds, n);
- //執行pending_list中的Event
- firePending();
- }
- }
上面的for循環可以清晰的看到,當RILJ發送數據後,在EventLoop中會依次被timer_list、watch_table、pending_list處理;接着來分別看一下3個表的處理流程:
a、timer_list表
- static void processTimeouts()
- {
- //如果timer_list中某個事件已經超時
- while ((tev != &timer_list) && (timercmp(&now, &tev->timeout, >))) {
- //從timer_list中刪除
- removeFromList(tev);
- //添加到pending_list中
- addToList(tev, &pending_list);
- }
- }
b、watch_table表
- static void processReadReadies(fd_set * rfds, int n)
- {
- for (int i = 0; (i < MAX_FD_EVENTS) && (n > 0); i++) {
- //添加到pending_list
- addToList(rev, &pending_list);
- if (rev->persist == false) {
- //如果persist爲false纔去刪除當前Event
- removeWatch(rev, i);
- }
- }
- }
c、pending_list表
- static void firePending()
- {
- while (ev != &pending_list) {
- //刪除當前節點
- removeFromList(ev);
- //執行其func並把參數傳遞進去
- ev->func(ev->fd, 0, ev->param);
- }
- }
上面的循環過程說明,eventLoop的是通過在內部循環中用Linux中的select方法檢測readFds中所有的文件句柄(或者說管道),如果發現有新的數據進來,就去遍歷watch_table和timer_list表,把需要處理的eventLoop加入到pending_list中,然後進入pending_list中去執行每個Event的func。
上面提到了循環接收數據的過程,那麼具體的處理這些命令的過程是怎樣的呢?這個過程等我們瞭解了reference的過程後再去講解。
再次提醒一下,這裏偵測到的數據主要是RILJ發送下來的命令(而不是Modem側上來的AT命令)。
二、reference庫的加載
在這一步中,RIL需要加載一個AT相關的***ril.so的動態鏈接庫。之所以使用庫的形式,就是考慮到每個廠商使用的Modem不同,我們沒法用統一的接口去向底層負責,因此使用庫的形式。這樣一來,不同的Modem廠商提供不同的鏈接庫,只要符合RIL層的框架即可。而當前的鏈接庫中最主要的就是就是reference-ril.c和atchannel.c文件。
而reference庫需要完成兩個任務:
1、將eventLoop中的命令通過AT發送給Modem;
2、構建一個readerLoop循環,接受Modem消息,並根據消息的不同(URC和非URC)將消息返回給eventLoop(非URC消息)或者直接發送給RILJ(URC消息)。
我們先看readerLoop構建過程(發送AT的過程在文檔的最後一章介紹):
2.1、reference中readerLoop建立和循環過程
在這一步中,需要完成reference的初始化,並且打開的ReaderLoop循環。- @reference-ril.c
- const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv)
- {
- //開啓ril的線程,入口函數是mainLoop
- ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);
- //把ril的回調函數返回出來
- return &s_callbacks;
- }
我們來看入口函數:
- static void * mainLoop(void *param)
- {
- //初始化AT通道的關閉方法和超時方法
- at_set_on_reader_closed(onATReaderClosed);
- at_set_on_timeout(onATTimeout);
- for (;;) {
- //打開AT並把處理URC消息的方法onUnsolicited傳進去
- ret = at_open(fd, onUnsolicited);
- RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0);
- waitForClose();
- }
- }
上面可以看到,不僅打開了AT通道,而且還設置了超時方法。當前線程在打開AT通道後,在waitForClose中阻塞等待,如果AT通道在檢測超時後,將會主動的關閉當前的AT通道,此時將會激活waitForClose中的阻塞線程,然後waitForClose將會返回。而一旦waitForClose函數返回,將會再次進入for循環,重新打開AT通道。
我們主要跟蹤AT通道打開的過程,以及事件的處理流程:
- @atchannel.c
- int at_open(int fd, ATUnsolHandler h)
- {
- //URC消息的處理方式:onUnsolicited() @reference-ril.c
- s_fd = fd;
- //創建線程讀取AT命令並處理Modem發過來的信息
- ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr);
- }
看一下read線程的入口函數readerLoop:
- static void *readerLoop(void *arg)
- {
- for (;;) {
- //讀取命令
- line = readline();
- if(isSMSUnsolicited(line)) {
- if (s_unsolHandler != NULL) {
- //URC消息可以直接發送給RILJ
- s_unsolHandler (line1, line2);
- }
- } else {
- processLine(line);
- }
- }
- //關閉read
- onReaderClosed();
- return NULL;
- }
上面的readerLoop就是在不斷偵測Modem上報的消息,然後根據是否是URC消息來採用不同的處理方式。至於具體的判斷依據,在兩個地方可以體現:1、通過isSMSUnsolicited判斷(如果以CMT/CDS/CBM開頭則判斷成立);2、也可以在processLine中判斷(這是主要的判斷依據)。
我們簡要說一下processLine判斷URC消息的依據。我們知道,如果不是URC消息,那麼就是我們主動發送的請求,Modem是作爲迴應給我們發的消息,而在我們給Modem發送消息時,會註冊各種的回調函數和用於放置Modem返回值的指針sp_response。而如果是URC消息,那麼就沒有回調函數,而且sp_response是爲空,reference正是通過判斷sp_response的內容來達到區分URC消息的目的。
在processLine中對於不同的消息有不同的處理流程:
- static void processLine(const char *line)
- {
- if (sp_response == NULL) {
- //URC消息處理
- handleUnsolicited(line);
- } else if (isFinalResponseSuccess(line)) {
- //非URC消息處理
- sp_response->success = 1;
- //發送迴應消息給EventLoop
- handleFinalResponse(line);
- } else switch (s_type) {
- case NO_RESULT:
- case NUMERIC:
- //非URC消息處理
- 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;
- }
- }
可以看到,URC消息是通過handleUnsolicited處理的,而非URC消息有兩個地方處理。下面分別介紹兩種處理方式:
2.2、URC消息處理流程
我們看URC消息的處理函數:
- @atchannel.c
- static void handleUnsolicited(const char *line)
- {
- s_unsolHandler(line, NULL);
- }
這裏的s_unsolHandler來自於at_open時的參數,也就是reference-ril.c中的onUnsolicited:
- @reference-ril.c
- static void onUnsolicited (const char *s, const char *sms_pdu)
- {
- if (strStartsWith(s, "%CTZV:")) {
- //時區改變
- RIL_onUnsolicitedResponse (RIL_UNSOL_NITZ_TIME_RECEIVED,response, strlen(response));
- } else if (strStartsWith(s,"+CRING:")
- || strStartsWith(s,"RING")
- || strStartsWith(s,"NO CARRIER")
- || strStartsWith(s,"+CCWA")
- ) {
- //通話狀態改變
- RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, NULL, 0);
- } else if (strStartsWith(s,"+CREG:")|| strStartsWith(s,"+CGREG:")) {
- //網絡註冊狀態改變
- RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED,NULL, 0);
- } else if (strStartsWith(s, "+CMT:")) {
- //新短信通知
- RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_NEW_SMS,sms_pdu, strlen(sms_pdu));
- } else if (strStartsWith(s, "+CDS:")) {
- //短信報告
- RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT,sms_pdu, strlen(sms_pdu));
- } else if (strStartsWith(s, "+CGEV:")) {
- RIL_requestTimedCallback (onDataCallListChanged, NULL, NULL);
- }
- }
可以看出,URC消息的處理流程基本上就是根據命令頭的不同將其轉化爲不同的命令索引,然後調用RIL_onUnsolicitedResponse函數,而RIL_onUnsolicitedResponse的實現:
- #define RIL_onUnsolicitedResponse(a,b,c) s_rilenv->OnUnsolicitedResponse(a,b,c)
說明這個函數調用的是s_rilenv變量的OnUnsolicitedResponse方法。那麼s_rilenv是哪裏初始化的呢?
我們在rild.c中的main函數中對reference庫初始化時是這樣的形式:
- funcs = rilInit(&s_rilEnv, argc, rilArgv);
上面的初始化過程將s_rilEnv全局變量傳遞給了reference,然後在reference-ril.c內部將這個值傳給了s_rilenv,而s_rilEnv的各個處理函數是在ril.cpp中實現的。
上面繞了一圈,還是把對消息的處理從動態庫(也就是reference-ril.c文件)饒回到了ril.c文件中。這也符合整個RIL架構的設計理念:框架和處理方式由ril.c管理,差異化的AT命令由reference實現。
- @ril.cpp
- void RIL_onUnsolicitedResponse(int unsolResponse, void *data,
- size_t datalen, RILId id)
- {
- //得到當前命令的請求碼
- unsolResponseIndex = unsolResponse - RIL_UNSOL_RESPONSE_BASE;
- //從ril_unsol_commands.h文件中得到命令的類型
- wakeType = s_unsolResponses[unsolResponseIndex].wakeType;
- //根據不同命令的不同類型進行解析
- switch (wakeType) {
- case WAKE_PARTIAL:
- case DONT_WAKE:
- }
- appendPrintBuf("[UNSL]< %s", requestToString(unsolResponse));
- Parcel p;
- p.writeInt32 (RESPONSE_UNSOLICITED);
- p.writeInt32 (unsolResponse);
- //調用當前命令的打包函數進行數據打包
- ret = s_unsolResponses[unsolResponseIndex].responseFunction(p, data, datalen);
- //把數據發送到RILJ中
- ret = sendResponse(p,id);
- if (shouldScheduleTimeout) {
- }
- return;
- }
上面的處理過程分爲2步:1、調用當前命令對應的打包函數進行數據打包;2、將數據發送給RILJ。
數據打包的過程涉及到一個數組s_unsolResponses,他的詳細作用在本文的最後一張有說明,這裏簡要介紹一下。s_unsolResponses是一個文件,文件中對所有的URC消息都有一個對應的數組相對應,每個數組分3部分:1、命令的請求碼;2、命令的打包函數;3、命令的類型;我們要做的就是用當前Modem給出的命令,找到對應的請求碼,然後得到相應的打包函數進行數據打包。
而發送的過程就是調用sendResponse把Parcel數據發送給RILJ。
在上面的過程中,用reference中通過AT頭轉換的命令值與RIL_UNSOL_RESPONSE_BASE相減得到在s_unsolResponses表中對應的命令索引。查找相應的wakeType類型去決定是否計算超時(shouldScheduleTimeout),之後就用s_unsolResponses中的responseFunction去解析命令,最後通過sendResponse將數據發送給RILJ:
- static int sendResponse (Parcel &p) {
- return sendResponseRaw(p.data(), p.dataSize());
- }
發送數據:
- static int sendResponseRaw (const void *data, size_t dataSize) {
- //發送通道的Socket ID
- int fd = s_fdCommand;
- int ret;
- uint32_t header;
- //將數據長度這個整數轉換爲適合Socket 傳輸的字節順序
- header = htonl(dataSize);
- //先發送一個關於數據大小的字節
- ret = blockingWrite(fd, (void *)&header, sizeof(header));
- //再發送數據本身
- ret = blockingWrite(fd, data, dataSize);
- return 0;
- }
繼續看發送過程:
- static int blockingWrite(int fd, const void *buffer, size_t len) {
- size_t writeOffset = 0;
- const uint8_t *toWrite;
- toWrite = (const uint8_t *)buffer;
- while (writeOffset < len) {
- ssize_t written;
- do {
- //發送數據到fd指定的句柄中,也就是RILJ對應的Socket通道
- written = write (fd, toWrite + writeOffset,len - writeOffset);
- } while (written < 0 && ((errno == EINTR) || (errno == EAGAIN)));
- }
- return 0;
- }
這裏注意到,發送的最終操作,就是把數據放到一個fd指向的句柄中,那麼這個句柄從哪裏來的呢?
- #define SOCKET_NAME_RIL "rild"
- //得到"rild"的Socket連接
- s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);
- //accept的函數默認會阻塞進程,直到有一個客戶連接建立後把可用的連接套接字返回
- s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen);
上面的遞歸關係我們可以看出,s_fdCommand就是RILJ與RILC之間建立的Socket通道的套接字!因此我們可以通過這個通道發送數據給RILJ。
2.3、非URC消息處理流程
上面介紹了URC消息的流程,下面分析一下更普遍的非URC消息的處理流程。
前面說道,非URC消息就是一種迴應。當上層通過AT向Modem發送請求後,會一直處於阻塞狀態等待迴應,一旦readerLoop得到了非URC的消息,就會去喚醒Event端等待的進程。
我們在此主要介紹reference如何喚醒eventLoop端的線程。
非URC消息的上報流程也是從processLine開始的:
- @atchannel.c
- static void processLine(const char *line)
- {
- if (sp_response == NULL) {
- } else if (isFinalResponseSuccess(line)) {
- sp_response->success = 1;
- //發送迴應消息給eventLoop
- handleFinalResponse(line);
- } else if (isFinalResponseError(line)) {
- } else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) {
- } else switch (s_type) {
- case NO_RESULT:
- case NUMERIC:
- if (sp_response->p_intermediates == NULL && isdigit(line[0])
- ) {
- addIntermediate(line);
- } else {
- handleUnsolicited(line);
- }
- break;
- case SINGLELINE:
- if (sp_response->p_intermediates == NULL
- && strStartsWith (line, s_responsePrefix)
- ) {
- addIntermediate(line);
- } else {
- handleUnsolicited(line);
- }
- break;
- case MULTILINE:
- break;
- }
- }
這裏簡要介紹以下Modem對於非URC消息的回覆格式。消息一般大於2行,前幾行是返回值的有效數據,最後一行是作爲當前命令結束的標誌位。如果是有效數據,那麼當前數據就有一個s_type的類型與之相關聯(從EventLoop發送給reference時決定)。因此就會在processLine中的switch中進入addIntermediate函數,而這個函數的作用就是把當前的數據放入到反饋數據的sp_response->p_intermediates裏面。等到命令的最後,因爲是標誌位,就會走到processLine的handleFinalResponse中,將數據發送給Event側。
下面貼出涉及到的重要函數:
a、將數據放到反饋數據中:addIntermediate
- static void addIntermediate(const char *line)
- {
- ATLine *p_new;
- p_new = (ATLine *) malloc(sizeof(ATLine));
- p_new->line = strdup(line);
- p_new->p_next = sp_response->p_intermediates;
- //把有效的返回值放到sp_response->p_intermediates中
- sp_response->p_intermediates = p_new;
- }
b、判斷是否已經把所有有效數據傳輸完畢:isFinalResponseSuccess
- //結束符
- static const char * s_finalResponsesSuccess[] = {
- "OK",
- "CONNECT"
- };
- //判斷
- static int isFinalResponseSuccess(const char *line)
- {
- size_t i;
- for (i = 0 ; i < NUM_ELEMS(s_finalResponsesSuccess) ; i++) {
- if (strStartsWith(line, s_finalResponsesSuccess[i])) {
- return 1;
- }
- }
- return 0;
- }
c、發送數據給Event側,取消Event側阻塞的線程:handleFinalResponse
- static void handleFinalResponse(const char *line)
- {
- //把迴應消息返回給eventLoop
- sp_response->finalResponse = strdup(line);
- //發信號給s_commandcond線程,使其脫離阻塞狀態
- pthread_cond_signal(&s_commandcond);
- }
三、一個完整的過程
經過上面的eventLoop和readerLoop過程分析,我們分別對eventLoop的機制和readerLoop中兩種消息的處理有個大致的瞭解,但是還有一些問題我們沒有解決,比如:
1、eventLoop所構建的循環如何接收RILJ的消息?又如何通過reference將消息發送到Modem?
2、上面說道reference接收到非URC消息後需要通知eventLoop讀取消息,具體怎麼通知eventLoop的?
這些問題將在這一節中詳細說明。我們用一個完整的數據流來把兩個loop串起來。而一個完整的數據流應該包括以下四個步驟:
1、Eventloop接收RILJ的請求,並負責把請求發送給reference庫:Eventloop--->reference
2、reference負責把命令轉化爲AT命令,然後發送給Modem:reference--->Modem
3、reference通過readerLoop得到Modem迴應後把數據返回給Eventloop: Modem--->ReaderLoop
4、Eventloop再把數據返回給RILJ:ReaderLoop--->Eventloop
下面我們就分別介紹這4個步驟的詳細流程。
3.1、Eventloop把RILJ命令發送給reference庫。
我們再次回到RILC層的入口處,前面兩節介紹了Eventloop和reference,接下來就是RIL_register的入口:- @rild.c
- int main(int argc, char **argv)
- {
- //搭建EventLoop循環
- RIL_startEventLoop();
- //對reference動態庫進行初始化
- funcs = rilInit(&s_rilEnv, argc, rilArgv);
- //註冊reference 的回調函數
- RIL_register(funcs);
- }
上面看到,當我們調用reference的初始化函數(也就是RIL_Init)後,將會得到一個RIL_RadioFunctions類型的返回值:
- @reference-ril.c
- static const RIL_RadioFunctions s_callbacks = {
- RIL_VERSION,
- onRequest,
- currentState,
- onSupports,
- onCancel,
- getVersion
- };
這個變量的類型爲:
- typedef struct {
- int version; //當前鏈接庫的版本信息
- RIL_RequestFunc onRequest; //用於Event側向動態庫發起請求
- RIL_RadioStateRequest onStateRequest; //得到當前庫的狀態
- RIL_Supports supports; //查詢是否支持某個命令
- RIL_Cancel onCancel; //取消一個Event的處理
- RIL_GetVersion getVersion; //得到版本號
- } RIL_RadioFunctions;
這些成員函數中最重要的是onRequest,當我們在Event側向reference庫發起請求時,就是用的這個入口函數。而我們將用這個對象去完成註冊的過程。
- @ril.cpp
- //將reference中的回調函數註冊給RIL的框架
- void RIL_register (const RIL_RadioFunctions *callbacks) {
- //把返回值傳給s_callbacks(全局變量)
- memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions));
- //#define SOCKET_NAME_RIL "rild" 打開RILC與RILJ之間的Socket通道
- s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);
- ret = listen(s_fdListen, 4);
- //用這個Socket通道句柄創建一個Event
- ril_event_set (&s_listen_event, s_fdListen, false, listenCallback, NULL);
- //添加到Eventloop中
- rilEventAddWakeup (&s_listen_event);
- }
在上面的註冊函數中主要完成了兩個任務:1、將我們從reference得到的回調函數callbacks傳遞給一個全局變量s_callbacks;2、打開Socket通道並添加句柄到Eventloop中。對於第二個任務,我們看到在註冊的過程中,通過android_get_control_socket的方法打開了RILJ與RILC之間的Socket通道,而且這個通道的文件句柄爲s_fdListen(全局變量),並用這個通道的句柄構建一個Event,然後添加到Eventloop中。並且爲這個Event註冊了的回調函數listenCallback。
經過上面的過程,就在RILJ與EventLoop之間建立了溝通的渠道。
還記得我們在Eventloop中提到如果檢測到句柄池中的某個句柄有新數據的話,就會調用將timer_list和watch_table中的Event放入pending_list中,然後調用當前Event的回調函數:
- @ril_event.cpp
- static void firePending()
- {
- struct ril_event * ev = pending_list.next;
- while (ev != &pending_list) {
- struct ril_event * next = ev->next;
- removeFromList(ev);
- //這裏的func就是listenCallback
- ev->func(ev->fd, 0, ev->param);
- ev = next;
- }
- }
對於RILJ中過來的Event,我們註冊的回調函數是listenCallback,當RILJ向rilc發送數據時,首先會調用此函數:
- @ril.cpp
- static void listenCallback (int fd, short flags, void *param) {
- //從s_fdListen偵聽套接字得到s_fdCommand(RILJ與RILC之間的流套接字)
- s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen);
- //得到當前命令的選項值
- err = getsockopt(s_fdCommand, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
- //對文件描述符s_fdCommand進行控制,當前是設置其非阻塞模式
- ret = fcntl(s_fdCommand, F_SETFL, O_NONBLOCK);
- //再次把當前命令放入Eventloop
- ril_event_set (&s_commands_event, s_fdCommand, 1, processCommandsCallback, p_rs);
- rilEventAddWakeup (&s_commands_event);
- //發送URC消息,通知RIL狀態發生改變
- onNewCommandConnect();
- }
上面的過程分爲2步:1、把當前消息重新發送到Eventloop;2、發送URC消息,通知RILJ(RIL狀態改變了)。我們先來看一下第二個過程:
- static void onNewCommandConnect() {
- //給RILJ發送URC消息,RIL連接成功
- RIL_onUnsolicitedResponse(RIL_UNSOL_RIL_CONNECTED, &rilVer, sizeof(rilVer));
- //給RILJ發送URC消息,告訴RILJ,Radio狀態改變了
- RIL_onUnsolicitedResponse(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, NULL, 0);
- }
我們再分析上面的第一個步驟,也就是把消息發送到Eventloop中的過程。當發送到Eventloop後,Eventloop就會調用當前Event的回調函數,現在的回調函數是processCommandsCallback:
- static void processCommandsCallback(int fd, short flags, void *param) {
- for (;;) {
- //真正讀取RILJ發過來的數據流就是在此處,此方法最終通過
- //read (p_rs->fd, p_rs->read_end, p_rs->buffer_end - p_rs->read_end)方法讀取綁定的文件描述符內容
- ret = record_stream_get_next(p_rs, &p_record, &recordlen);
- if (ret == 0 && p_record == NULL) {
- break;
- } else if (ret < 0) {
- break;
- } else if (ret == 0) { /* && p_record != NULL */
- //把RILJ層數據通過AT發送到Modem
- processCommandBuffer(p_record, recordlen);
- }
- }
- if (ret == 0 || !(errno == EAGAIN || errno == EINTR)) {
- //命令已經發送完成,關閉當前命令的流套接字
- close(s_fdCommand);
- s_fdCommand = -1;
- //刪掉當前Event
- ril_event_del(&s_commands_event);
- record_stream_free(p_rs);
- //重新添加RILJ與RILC之間的Socket Event
- rilEventAddWakeup(&s_listen_event);
- onCommandsSocketClosed();
- }
- }
status = p.readInt32(&request);//request 代表請求類型:VOICE_REGISTRATION_STATE
status = p.readInt32 (&token);//token代表請求編號:100
05-08 16:26:41.504 D/use-Rlog/RLOG-RILC( 202): [w] PCB request code 20 token 100
05-08 16:26:41.504 D/use-Rlog/RLOG-RILC( 202): [w] [0100]> VOICE_REGISTRATION_STATE
05-08 16:26:41.504 D/use-Rlog/RLOG-RIL( 202): [w] onRequest: VOICE_REGISTRATION_STATE sState=4
上面看到,發送命令給Modem是通過processCommandBuffer實現的:
- static int processCommandBuffer(void *buffer, size_t buflen) {
- Parcel p;
- int32_t request;
- int32_t token;
- RequestInfo *pRI;
- p.setData((uint8_t *) buffer, buflen);
- //得到請求碼和令牌
- status = p.readInt32(&request);
- status = p.readInt32 (&token);
- pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo));
- //設置當前請求的令牌
- pRI->token = token;
- //s_commands中針對不同的命令對應不同的處理函數
- pRI->pCI = &(s_commands[request]);
- //鏈表結構
- pRI->p_next = s_pendingRequests;
- s_pendingRequests = pRI;
- //調用reference 中的
- pRI->pCI->dispatchFunction(p, pRI);
- return 0;
- }
首先說明一個很重要的標誌位:token令牌;這個令牌可以看作當前請求的ID,當我們從Modem得到數據後需要根據不同的令牌找到當初的請求命令,然後做出相應的答覆。
這裏還涉及到一個特殊的數組s_commands。他的作用和s_unsolResponses類似,詳細說明在本文的最後一章,這裏還是簡單介紹一下他的作用:
s_commands是一個數組,每個RILJ發送過來的命令在s_commands中都對應一個元素,而每個元素包含3個數據:
- typedef struct {
- int requestNumber;
- void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
- int(*responseFunction) (Parcel &p, void *response, size_t responselen);
- } CommandInfo;
其中requestNumber表示當前命令的編號;dispatchFunction的作用是,當前命令可以通過這個接口把數據發送到reference庫;responseFunction的作用是:當Modem返回數據後reference側可以用這個函數把數據進行打包,然後傳遞給Event側。
而在processCommandBuffer中要做的就是通過當前的命令號,找到對應的發送函數(dispatchFunction)和打包函數(responseFunction)。然後把這三個數據連同當前命令的令牌(當前命令的ID)構建一個在Event和reference側通用的數據類型(RequestInfo)並把它發送給reference側。
假如我們從RILJ得到的命令號爲RIL_REQUEST_GET_SIM_STATUS(得到當前SIM卡的狀態),那麼對應的,就需要調用當前命令的發送函數,而在s_commands中對於這條命令的描述爲:
- {RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus},
- @ril.cpp
- static void dispatchVoid (Parcel& p, RequestInfo *pRI) {
- //發送數據到Modem
- clearPrintBuf;
- //打印Log信息RLOGD("[%04d]> %s %s", token, requestToString(req), printBuf)
- printRequest(pRI->token, pRI->pCI->requestNumber);
- //s_callbacks是從reference註冊過來的
- s_callbacks.onRequest(pRI->pCI->requestNumber, NULL, 0, pRI);
- }
而關於s_callbacks這個全局變量,我們在這一節的最初就講到,當我們調用reference中的RIL_Init完成初始化時,就會得到reference返回當前鏈接庫提供的接口函數,而s_callbacks正是來自於這些接口:
- @reference-ril.c
- static const RIL_RadioFunctions s_callbacks = {
- RIL_VERSION,
- onRequest,
- currentState,
- onSupports,
- onCancel,
- getVersion
- };
因此,當上面的Eventloop將數據通過s_callbacks.onRequest發送給reference的過程就是調用reference-ril.c中的onRequest的過程。
之後,Eventloop就完成了下發命令的任務,接下來需要reference完成把命令發送給Modem的任務。
3.2、reference將Eventloop的數據發送到Modem
上面說道,s_callbacks.onRequest其實就是reference-ril.c中的onRequest,經過這次調用,就將數據由Eventloop側傳到了reference側。
- @reference-ril.c
- static void onRequest (int request, void *data, size_t datalen, RIL_Token t)
- {
- ATResponse *p_response;
- RLOGD("onRequest: %s", requestToString(request));
- //我們重點看RIL_REQUEST_GET_SIM_STATUS
- switch (request) {
- case RIL_REQUEST_GET_SIM_STATUS: {
- RIL_CardStatus_v6 *p_card_status;
- char *p_buffer;
- int buffer_size;
- //與Modem交互,發送命令並得到迴應
- int result = getCardStatus(&p_card_status);
- //把迴應傳回給Eventloop
- RIL_onRequestComplete(t, result, p_buffer, buffer_size);
- freeCardStatus(p_card_status);
- break;
- }
- case RIL_REQUEST_GET_CURRENT_CALLS:
- //得到當前通話
- requestGetCurrentCalls(data, datalen, t);
- break;
- case RIL_REQUEST_DIAL:
- //撥號
- requestDial(data, datalen, t);
- break;
- case RIL_REQUEST_HANGUP:
- //掛起
- requestHangup(data, datalen, t);
- }
- }
- static int getCardStatus(RIL_CardStatus_v6 **pp_card_status) {
- //SIM卡所有可能的狀態
- static RIL_AppStatus app_status_array[] = {
- //SIM_ABSENT = 0 SIM不存在
- { RIL_APPTYPE_UNKNOWN, RIL_APPSTATE_UNKNOWN, RIL_PERSOSUBSTATE_UNKNOWN,
- NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
- // SIM_NOT_READY = 1 SIM未就緒
- { RIL_APPTYPE_SIM, RIL_APPSTATE_DETECTED, RIL_PERSOSUBSTATE_UNKNOWN,
- NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
- // SIM_READY = 2 SIM就緒
- { RIL_APPTYPE_SIM, RIL_APPSTATE_READY, RIL_PERSOSUBSTATE_READY,
- NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
- ....
- };
- //查詢SIM狀態,將AT命令發送到Modem,然後得到Modem的數據
- int sim_status = getSIMStatus();
- RIL_CardStatus_v6 *p_card_status = malloc(sizeof(RIL_CardStatus_v6));
- if (num_apps != 0) {
- // Only support one app, gsm
- p_card_status->num_applications = 2;
- p_card_status->gsm_umts_subscription_app_index = 0;
- p_card_status->cdma_subscription_app_index = 1;
- // Get the correct app status
- p_card_status->applications[0] = app_status_array[sim_status];
- p_card_status->applications[1] = app_status_array[sim_status + RUIM_ABSENT];
- }
- *pp_card_status = p_card_status;
- //將狀態返回給reference
- return RIL_E_SUCCESS;
- }
- static SIM_Status getSIMStatus()
- {
- //將命令轉換爲AT命令發送到Modem,而modem的迴應放到p_response中
- err = at_send_command_singleline("AT+CPIN?", "+CPIN:", &p_response);
- //取得返回值
- cpinLine = p_response->p_intermediates->line;
- err = at_tok_start (&cpinLine);
- err = at_tok_nextstr(&cpinLine, &cpinResult);
- //根據返回值得到當前SIM卡狀態
- if (0 == strcmp (cpinResult, "SIM PIN")) {
- //PIN鎖
- ret = SIM_PIN;
- goto done;
- } else if (0 == strcmp (cpinResult, "SIM PUK")) {
- //PUK鎖
- ret = SIM_PUK;
- goto done;
- } else if (0 == strcmp (cpinResult, "PH-NET PIN")) {
- return SIM_NETWORK_PERSONALIZATION;
- } else if (0 != strcmp (cpinResult, "READY")) {
- //SIM卡不存在
- ret = SIM_ABSENT;
- goto done;
- }
- //返回結果
- done:
- at_response_free(p_response);
- return ret;
- }
- @atchannel.c
- int at_send_command_singleline (const char *command,
- const char *responsePrefix,
- ATResponse **pp_outResponse)
- {
- err = at_send_command_full (command, SINGLELINE, responsePrefix, NULL, 0, pp_outResponse);
- return err;
- }
- static int at_send_command_full (const char *command, ATCommandType type,
- const char *responsePrefix, const char *smspdu,
- long long timeoutMsec, ATResponse **pp_outResponse)
- {
- //發送
- err = at_send_command_full_nolock(command, type,
- responsePrefix, smspdu,
- timeoutMsec, pp_outResponse);
- return err;
- }
- 繼續看
- static int at_send_command_full_nolock (const char *command, ATCommandType type,
- const char *responsePrefix, const char *smspdu,
- long long timeoutMsec, ATResponse **pp_outResponse)
- {
- //給Modem發消息AT
- err = writeline (command);
- //創建新的sp_response作爲迴應
- sp_response = at_response_new();
- //上面發送完消息後需要阻塞等待Modem迴應
- while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
- if (timeoutMsec != 0) {
- //線程進入阻塞狀態,等待另一線程滿足s_commandcond 後解除
- err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);
- }
- }
- if (pp_outResponse == NULL) {
- //釋放sp_response
- at_response_free(sp_response);
- } else {
- reverseIntermediates(sp_response);
- //將回應發送給當初請求AT的線程
- *pp_outResponse = sp_response;
- }
- return err;
- }
- static int writeline (const char *s)
- {
- size_t len = strlen(s);
- ssize_t written;
- //Log信息
- RLOGD("AT> %s\n", s);
- while (cur < len) {
- do {
- //s_fd就是Modem與RILC之間的串口
- written = write (s_fd, s + cur, len - cur);
- } while (written < 0 && errno == EINTR);
- cur += written;
- }
- do {
- //以r結尾
- written = write (s_fd, "\r" , 1);
- } while ((written < 0 && errno == EINTR) || (written == 0));
- return 0;
- }
上面說道,at_send_command_full_nolock函數的另一個作用是阻塞當前線程,等待Modem迴應,那麼,如何實現阻塞的過程?又需要在什麼樣的情況下解除阻塞狀態?又是如何在解除阻塞時把Modem的數據讀取出來呢?這一串的疑問我們在接下來的小節中講解。
3.3、reference通過readerLoop得到Modem迴應後把數據返回給Eventloop
我們在上面的at_send_command_full_nolock函數中,調用writeline將命令寫入Modem後,還做了一個很重要的動作,就是阻塞當前線程,等待Modem迴應。我們再次回到at_send_command_full_nolock:- static int at_send_command_full_nolock (const char *command, ATCommandType type,
- const char *responsePrefix, const char *smspdu,
- long long timeoutMsec, ATResponse **pp_outResponse)
- {
- //給Modem發消息AT
- err = writeline (command);
- //創建新的sp_response作爲迴應
- sp_response = at_response_new();
- //上面發送完消息後需要阻塞等待Modem迴應
- while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
- if (timeoutMsec != 0) {
- //線程進入阻塞狀態,等待另一線程滿足s_commandcond 後解除
- err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);
- }
- }
- if (pp_outResponse == NULL) {
- //釋放sp_response
- at_response_free(sp_response);
- } else {
- reverseIntermediates(sp_response);
- //將回應發送給當初請求AT的線程
- *pp_outResponse = sp_response;
- }
- return err;
- }
在這裏,爲了等待s_commandcond條件變量而自動阻塞了當前線程。那麼,只有當另一個線程通過pthread_cond_signal接口是s_commandcond條件滿足時,當前阻塞的線程纔會被喚醒。
那麼是在哪裏去喚醒當前線程的呢?
我們分析一下,當我們發送數據或命令給Modem的時候,阻塞了當前的線程,阻塞的目的就是等待Modem的迴應,而如果Modem有數據上來,那麼肯定是先被reference的ReaderLoop檢測到並處理,因此,也應該是在ReaderLoop的消息處理中去喚醒當前阻塞的線程,而且應該把Modem的反饋傳輸給阻塞線程。
我們直接來看ReaderLoop的處理函數,因爲作爲Modem的迴應,當前消息一定不是URC消息,因此處理函數就是processLine:
- @atchannel.c
- static void processLine(const char *line)
- {
- if (sp_response == NULL) {
- } else if (isFinalResponseSuccess(line)) {
- sp_response->success = 1;
- //發送迴應消息給EventLoop
- handleFinalResponse(line);
- } else if (isFinalResponseError(line)) {
- } else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) {
- } else switch (s_type) {
- case NO_RESULT:
- case NUMERIC:
- 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:
- break;
- }
- }
因此,我們接受的Modem數據應該先被switch中的SINGLELINE處理,而這一步的處理就是把Modem的有效數據放到sp_response->p_intermediates中:
- static void addIntermediate(const char *line)
- {
- ATLine *p_new;
- p_new = (ATLine *) malloc(sizeof(ATLine));
- p_new->line = strdup(line);
- p_new->p_next = sp_response->p_intermediates;
- sp_response->p_intermediates = p_new;
- }
- static void handleFinalResponse(const char *line)
- {
- //把迴應消息返回給RIL
- sp_response->finalResponse = strdup(line);
- //s_commandcond條件變量的條件得到滿足,將會喚醒相應的阻塞線程
- pthread_cond_signal(&s_commandcond);
- }
此時,我們可以再次回到被阻塞的線程,看看接下來的數據傳輸過程:
- @atchannel.c
- static int at_send_command_full_nolock (const char *command, ATCommandType type,
- const char *responsePrefix, const char *smspdu,
- long long timeoutMsec, ATResponse **pp_outResponse)
- {
- //給Modem發消息AT
- err = writeline (command);
- //創建新的sp_response作爲迴應
- sp_response = at_response_new();
- //上面發送完消息後需要阻塞等待Modem迴應
- while (sp_response->finalResponse == NULL && s_readerClosed == 0) {
- if (timeoutMsec != 0) {
- //線程進入阻塞狀態,等待另一線程滿足s_commandcond後解除
- err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);
- }
- }
- if (pp_outResponse == NULL) {
- //釋放sp_response
- at_response_free(sp_response);
- } else {
- reverseIntermediates(sp_response);
- //將回應發送給當初請求AT的線程
- *pp_outResponse = sp_response;
- }
- return err;
- }
我們再次回顧一下當初調用到pthread_cond_timeout_np的路徑:
getSIMStatus()-->at_send_command_singleline()->at_send_command_full()->at_send_command_full_nolock();
因此,我們再次回到getSIMStatus中查看:
- static SIM_Status getSIMStatus()
- {
- //將命令轉換爲AT命令發送到Modem,而modem的迴應放到p_response中
- err = at_send_command_singleline("AT+CPIN?", "+CPIN:", &p_response);
- //取得返回值
- cpinLine = p_response->p_intermediates->line;
- err = at_tok_start (&cpinLine);
- err = at_tok_nextstr(&cpinLine, &cpinResult);
- //根據返回值得到當前SIM卡狀態
- if (0 == strcmp (cpinResult, "SIM PIN")) {
- //PIN鎖
- ret = SIM_PIN;
- goto done;
- } else if (0 == strcmp (cpinResult, "SIM PUK")) {
- //PUK鎖
- ret = SIM_PUK;
- goto done;
- } else if (0 == strcmp (cpinResult, "PH-NET PIN")) {
- return SIM_NETWORK_PERSONALIZATION;
- } else if (0 != strcmp (cpinResult, "READY")) {
- //SIM卡不存在
- ret = SIM_ABSENT;
- goto done;
- }
- }
我們在往上層看,當初是在getCardStatus中調用getSIMStatus的:
- static int getCardStatus(RIL_CardStatus_v6 **pp_card_status) {
- //SIM卡所有可能的狀態
- static RIL_AppStatus app_status_array[] = {
- { RIL_APPTYPE_UNKNOWN, RIL_APPSTATE_UNKNOWN, RIL_PERSOSUBSTATE_UNKNOWN,
- NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
- { RIL_APPTYPE_SIM, RIL_APPSTATE_DETECTED, RIL_PERSOSUBSTATE_UNKNOWN,
- NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },
- };
- //查詢SIM狀態,將AT命令發送到Modem,然後得到Modem的數據
- int sim_status = getSIMStatus();
- //得到返回值
- p_card_status->applications[0] = app_status_array[sim_status];
- p_card_status->applications[1] = app_status_array[sim_status + RUIM_ABSENT];
- //把返回值帶回到上一級中
- *pp_card_status = p_card_status;
- return RIL_E_SUCCESS;
- }
- @reference-ril.c
- static void onRequest (int request, void *data, size_t datalen, RIL_Token t)
- {
- switch (request) {
- case RIL_REQUEST_GET_SIM_STATUS: {
- RIL_CardStatus_v6 *p_card_status;
- char *p_buffer;
- int buffer_size;
- //與Modem交互,發送命令並得到迴應
- int result = getCardStatus(&p_card_status);
- p_buffer = (char *)p_card_status;
- buffer_size = sizeof(*p_card_status);
- //把迴應傳回給Eventloop
- RIL_onRequestComplete(t, result, p_buffer, buffer_size);
- freeCardStatus(p_card_status);
- break;
- }
- }
- }
- #define RIL_onRequestComplete(t, e, response, responselen) s_rilenv->OnRequestComplete(t,e, response, responselen)
因此,到這裏,我們的流程就又走到了ril.cpp中的RIL_onRequestComplete裏面。而從流程上來講,我們就將從Modem得到的迴應,又由reference庫交接給了Event端。
接下來要做的就是在Event側把數據重新返回給RILJ。
3.4、Eventloop把數據返回給RILJ
我們直接來看此時的接收函數:- @ril.cpp
- void RIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen) {
- RequestInfo *pRI;
- pRI = (RequestInfo *)t;
- Parcel p;
- p.writeInt32 (RESPONSE_SOLICITED);
- p.writeInt32 (pRI->token);
- errorOffset = p.dataPosition();
- p.writeInt32 (e);
- if (response != NULL) {
- //先調用當前請求的回調函數
- ret = pRI->pCI->responseFunction(p, response, responselen);
- if (ret != 0) {
- p.setDataPosition(errorOffset);
- p.writeInt32 (ret);
- }
- }
- 發送數據給RILJ
- sendResponse(p);
- }
那麼responseFunction函數的具體形式是什麼呢?
我們回顧一下,我們當初在接到RILJ的命令後,由Eventloop把命令交給reference之前,在processCommandBuffer中構建了用於在RILC層傳輸的RequestInfo數據類型,而構建這個數據的原理就是在ril_commands.h中查找當前命令所對應的兩個回調函數,一個用於向reference傳輸時使用,另一個用於reference給Eventloop迴應時使用。responseFunction指的就是第二個回調函數。
{RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus},
也就是說,發送到reference時我們需要調用dispatchVoid,而從reference接受Modem的迴應時,我們就要從responseSimStatus進入了:
- @ril.cpp
- static int responseSimStatus(Parcel &p, void *response, size_t responselen) {
- RIL_CardStatus_v6 *p_cur = ((RIL_CardStatus_v6 *) response);
- p.writeInt32(p_cur->card_state);
- p.writeInt32(p_cur->universal_pin_state);
- p.writeInt32(p_cur->gsm_umts_subscription_app_index);
- p.writeInt32(p_cur->cdma_subscription_app_index);
- p.writeInt32(p_cur->ims_subscription_app_index);
- sendSimStatusAppInfo(p, p_cur->num_applications, p_cur->applications);
- }
- static void sendSimStatusAppInfo(Parcel &p, int num_apps, RIL_AppStatus appStatus[]) {
- p.writeInt32(num_apps);
- startResponse;
- for (int i = 0; i < num_apps; i++) {
- p.writeInt32(appStatus[i].app_type);
- p.writeInt32(appStatus[i].app_state);
- p.writeInt32(appStatus[i].perso_substate);
- writeStringToParcel(p, (const char*)(appStatus[i].aid_ptr));
- writeStringToParcel(p, (const char*)(appStatus[i].app_label_ptr));
- p.writeInt32(appStatus[i].pin1_replaced);
- p.writeInt32(appStatus[i].pin1);
- p.writeInt32(appStatus[i].pin2);
- }
- closeResponse;
- }
其實這裏所謂的打包處理,不過是根據不同的命令,把相應的迴應寫入到給RILJ的返回數據包中。
然後在RIL_onRequestComplete中調用sendResponse把數據發送到RILJ層。
以上就是RIL數據傳輸的整個過程。
現在,我們再來回顧一下在本文開始處展示的RIL層數據流向圖,希望這次看到他時不會那麼的陌生:
四、一些重要概念
4.1、s_commandcond
我們知道,RIL是一個框架,並不會在意每個命令之間的差異,但是每個命令所發送的數據不同,要求的迴應更是不同。而這個數組就是完成了從標準化到差異化的轉換過程。先來看一下定義:
- static CommandInfo s_commands[] = {
- #include "ril_commands.h"
- };
- typedef struct {
- int requestNumber;
- void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
- int(*responseFunction) (Parcel &p, void *response, size_t responselen);
- } CommandInfo;
主要內容如下:
- {RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus},
- {RIL_REQUEST_ENTER_SIM_PIN, dispatchStrings, responseInts},
- {RIL_REQUEST_ENTER_SIM_PUK, dispatchStrings, responseInts},
而當reference接收到Modem數據後,根據當前命令的RequestInfo信息,查找當前命令的應答回調(responseFunction)對迴應數據進行封裝,封裝後調用統一的reference到Event側的接口函數(RIL_onRequestComplete)把數據發送給Event側,再由Event側發送到RILJ。
4.2、s_unsolResponses
對比上面的s_commands來看,這個變量就顯得比較簡單了。我們先來看一下他的定義:- static UnsolResponseInfo s_unsolResponses[] = {
- #include "ril_unsol_commands.h"
- };
- {RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, responseVoid, WAKE_PARTIAL},
- {RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, responseVoid, WAKE_PARTIAL},
- {RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, responseVoid, WAKE_PARTIAL},
- typedef struct {
- int requestNumber;
- int (*responseFunction) (Parcel &p, void *response, size_t responselen);
- WakeType wakeType;
- } UnsolResponseInfo;
上面我們講到,非URC消息是由RILJ主動發起的並且需要接受Modem的數據;而URC消息是Modem主動上報的(比如網絡狀態改變的消息),因此沒有發送的過程,只有接受消息的過程。因此不需要dispatchFunction。
再次結合流程來看一下,當我們在reference的ReaderLoop中檢測到Modem消息時,如果是URC消息,就在reference中根據AT命令頭的不同轉換爲相應的命令號(onUnsolicited中完成),然後把數據發送給Event側(RIL_onUnsolicitedResponse接收),在Event側根據命令號的不同找到s_unsolResponses中對應的responseFunction,對數據進行不同的封裝,然後發送給RILJ(仍在RIL_onUnsolicitedResponse中處理)。
4.3、RequestInfo
這個數據類型是Event側與reference側協定的統一命令格式。所有的命令,當從Event側發送到reference側時,都要把他標準化爲RequestInfo結構。數據結構:
- typedef struct RequestInfo {
- int32_t token;
- CommandInfo *pCI;
- struct RequestInfo *p_next;
- char cancelled;
- char local;
- } RequestInfo;
- typedef struct {
- int requestNumber;
- void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
- int(*responseFunction) (Parcel &p, void *response, size_t responselen);
- } CommandInfo;
將你自己的Vendor RIL實現編譯爲共享庫形式:libril-<companyname>-<RIL version>.so
比如:libril-techfaith-124.so
其中:libril:所有vendor RIL的開頭
<companyname>:公司縮寫
<RIL version>:RIL版本number
so:文件擴展
在init.rc文件中,將通過這種方式來進行Android RIL的加載。
service ril-daemon /system/bin/rild -l /system/lib/libreference-ril.so -- -d /dev/ttyS0
也可以手動加載:
/system/bin/rild -l /system/lib/libreference-ril.so -- -d /dev/ttyS0
這兩種方式,都將啓動rild守護進程,然後通過-l參數將libreference-ril.so共享庫鏈入,libreference-ril.so的參數-d是指加載一個串口設備,
/dev/ttyS0則是這個串口設備的具體設備文件,除了參數-d外,還有-s代表加載類型爲socket的設備,-p代表迴環接口。