RIL層源碼分析

寫在前面

    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的入口開始分析。

  1. @rild.c  
  2. int main(int argc, char **argv){  
  3.     //連接庫地址:/system/lib/libreference-ril.so  
  4.     #define  REFERENCE_RIL_PATH  "/system/lib/libreference-ril.so"  
  5.     rilLibPath = REFERENCE_RIL_PATH;  
  6.     //切換UID爲AID_RADIO  
  7.     switchUser();  
  8.     //打開鏈接庫  
  9.     dlHandle = dlopen(rilLibPath, RTLD_NOW);  
  10.     //開啓EventLoop循環  
  11.     RIL_startEventLoop();  
  12.     //從鏈接庫中(也就是reference-ril.c)尋找RIL_Init函數地址  
  13.     rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, intchar **))dlsym(dlHandle, "RIL_Init");  
  14.     //調用reference-ril.c中的RIL_Init函數進行初始化INIT,同時得到reference-ril的回調函數  
  15.     funcs = rilInit(&s_rilEnv, argc, rilArgv);  
  16.     //註冊得到的reference的回調函數  
  17.     RIL_register(funcs);  
  18. }  

    從上面可以看出,入口函數主要完成了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的構成:    

  1. struct ril_event {  
  2.     struct ril_event *next;  
  3.     struct ril_event *prev;  
  4.     int fd;  
  5.     int index;  
  6.     bool persist;  
  7.     struct timeval timeout;  
  8.     ril_event_cb func;  
  9.     void *param;  
  10. };  

    從上面的結構體可以看出,Event的管理是通過鏈表實現的,一些重要的成員變量的意義如下:

        fd:事件相關設備句柄。最重要的就是RILJ與RILC之間的Socket文件句柄
        persist:說明當前的Event需要保持,不能從watch_table中刪除
        func:當前Event的處理函數
        param:調用當前Event處理函數時的參數
    接下來,我們在看具體的處理流程之前,先來看一下處理Event需要哪些函數:    

  1. //Event的初始化,其實就是對3個主要鏈表的初始化  
  2. static void init_list(struct ril_event * list)  
  3. //添加Event到鏈表  
  4. static void addToList(struct ril_event * ev, struct ril_event * list)  
  5. //從鏈表刪除Event  
  6. static void removeFromList(struct ril_event * ev)  
  7. //從watch連表中刪除某個Event  
  8. static void removeWatch(struct ril_event * ev, int index)  
  9. //處理超時的Event  
  10. static void processTimeouts()  
  11. //初始化Event鏈表  
  12. void ril_event_init()  
  13. //初始化一個Event  
  14. void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param)  
  15. //把一個Event添加到watch_table中  
  16. void ril_event_add(struct ril_event * ev)  
  17. //把一個Event添加到timer_list中  
  18. void ril_timer_add(struct ril_event * ev, struct timeval * tv)  
  19. //把一個Event從watch_table中刪除  
  20. void ril_event_del(struct ril_event * ev)  
  21. //主循環  
  22. 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的創建入口開始:

  1. @ril.cpp  
  2. RIL_startEventLoop(void) {  
  3.     //打開Event線程,並調用eventLoop進入循環  
  4.     ret = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);  
  5. }  

    然後我們進入到線程的入口函數中查看:

  1. eventLoop(void *param) {  
  2.     //Event的初始化  
  3.     ril_event_init();  
  4.     //創建一個Event  
  5.     ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true,processWakeupCallback, NULL);  
  6.     //將上面創建的Event加入到watch_table中,這裏起到喚醒LoopEvent的作用,後面詳細說明  
  7.     rilEventAddWakeup (&s_wakeupfd_event);  
  8.     //進入loop循環  
  9.     ril_event_loop();  
  10. }  

    可以看出,Event的搭建由兩個步驟:1、初始化鏈表;2、進入loop循環。我們分別看一下兩個過程:

1.2、EventLoop的搭建過程


1.2.1、Event初始化過程。其實就是ril_event_init過程。

  1. @ril_event.cpp  
  2. void ril_event_init()  
  3. {  
  4.     FD_ZERO(&readFds);  
  5.     init_list(&timer_list);  
  6.     init_list(&pending_list);  
  7.     memset(watch_table, 0, sizeof(watch_table));  
  8. }  

    初始化的過程很簡單,就是對loop框架中涉及到的文件句柄集和3個重要鏈表的初始化。

1.2.2、Event循環的過程

  1. @ril_event.cpp  
  2. void ril_event_loop()  
  3. {  
  4.     //死循環檢測Event消息  
  5.     for (;;) {  
  6.         //計算下一次超時時間  
  7.         if (-1 == calcNextTimeout(&tv)) {  
  8.             //NULL說明select是阻塞模式  
  9.             ptv = NULL;  
  10.         } else {  
  11.             //非空說明在超時時間內是阻塞模式  
  12.             ptv = &tv;  
  13.         }  
  14.         //用select去掃描readFds中的所有管道集合,檢測RILJ是否有新數據出現。  
  15.         n = select(nfds, &rfds, NULL, NULL, ptv);  
  16.         //檢查超時事件,如果超時,將Event放入pending_list中  
  17.         processTimeouts();  
  18.         //檢查watch_table,將Event加入pending_list中  
  19.         processReadReadies(&rfds, n);  
  20.         //執行pending_list中的Event  
  21.         firePending();  
  22.     }  
  23. }  

    上面的for循環可以清晰的看到,當RILJ發送數據後,在EventLoop中會依次被timer_list、watch_table、pending_list處理;接着來分別看一下3個表的處理流程:
    a、timer_list表

  1. static void processTimeouts()  
  2. {  
  3.     //如果timer_list中某個事件已經超時  
  4.     while ((tev != &timer_list) && (timercmp(&now, &tev->timeout, >))) {  
  5.         //從timer_list中刪除  
  6.         removeFromList(tev);  
  7.         //添加到pending_list中  
  8.         addToList(tev, &pending_list);  
  9.     }  
  10. }  

    b、watch_table表

  1. static void processReadReadies(fd_set * rfds, int n)  
  2. {  
  3.     for (int i = 0; (i < MAX_FD_EVENTS) && (n > 0); i++) {  
  4.         //添加到pending_list  
  5.         addToList(rev, &pending_list);  
  6.         if (rev->persist == false) {  
  7.             //如果persist爲false纔去刪除當前Event  
  8.             removeWatch(rev, i);  
  9.         }  
  10.     }  
  11. }  

    c、pending_list表

  1. static void firePending()  
  2. {  
  3.     while (ev != &pending_list) {  
  4.         //刪除當前節點  
  5.         removeFromList(ev);  
  6.         //執行其func並把參數傳遞進去  
  7.         ev->func(ev->fd, 0, ev->param);  
  8.     }  
  9. }  

    上面的循環過程說明,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循環。
  1. @reference-ril.c  
  2. const RIL_RadioFunctions *RIL_Init(const struct RIL_Env *env, int argc, char **argv)  
  3. {  
  4.     //開啓ril的線程,入口函數是mainLoop  
  5.     ret = pthread_create(&s_tid_mainloop, &attr, mainLoop, NULL);  
  6.     //把ril的回調函數返回出來  
  7.     return &s_callbacks;  
  8. }  

    我們來看入口函數:

  1. static void * mainLoop(void *param)  
  2. {  
  3.     //初始化AT通道的關閉方法和超時方法  
  4.     at_set_on_reader_closed(onATReaderClosed);  
  5.     at_set_on_timeout(onATTimeout);  
  6.     for (;;) {  
  7.         //打開AT並把處理URC消息的方法onUnsolicited傳進去  
  8.         ret = at_open(fd, onUnsolicited);  
  9.         RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0);  
  10.         waitForClose();  
  11.     }  
  12. }  

    上面可以看到,不僅打開了AT通道,而且還設置了超時方法。當前線程在打開AT通道後,在waitForClose中阻塞等待,如果AT通道在檢測超時後,將會主動的關閉當前的AT通道,此時將會激活waitForClose中的阻塞線程,然後waitForClose將會返回。而一旦waitForClose函數返回,將會再次進入for循環,重新打開AT通道。

    我們主要跟蹤AT通道打開的過程,以及事件的處理流程:

  1. @atchannel.c  
  2. int at_open(int fd, ATUnsolHandler h)  
  3. {  
  4.     //URC消息的處理方式:onUnsolicited()  @reference-ril.c  
  5.     s_fd = fd;  
  6.     //創建線程讀取AT命令並處理Modem發過來的信息  
  7.     ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr);  
  8. }  

    看一下read線程的入口函數readerLoop:

  1. static void *readerLoop(void *arg)  
  2. {  
  3.     for (;;) {  
  4.         //讀取命令  
  5.         line = readline();  
  6.         if(isSMSUnsolicited(line)) {  
  7.             if (s_unsolHandler != NULL) {  
  8.                 //URC消息可以直接發送給RILJ  
  9.                 s_unsolHandler (line1, line2);  
  10.             }  
  11.         } else {  
  12.             processLine(line);  
  13.         }  
  14.     }  
  15.     //關閉read  
  16.     onReaderClosed();  
  17.     return NULL;  
  18. }  

    上面的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中對於不同的消息有不同的處理流程:

  1. static void processLine(const char *line)  
  2. {  
  3.     if (sp_response == NULL) {  
  4.         //URC消息處理  
  5.         handleUnsolicited(line);  
  6.     } else if (isFinalResponseSuccess(line)) {  
  7.         //非URC消息處理  
  8.         sp_response->success = 1;  
  9.         //發送迴應消息給EventLoop  
  10.         handleFinalResponse(line);  
  11.     } else switch (s_type) {  
  12.         case NO_RESULT:  
  13.         case NUMERIC:  
  14.             //非URC消息處理  
  15.             if (sp_response->p_intermediates == NULL  
  16.                 && isdigit(line[0])  
  17.             ) {  
  18.                 addIntermediate(line);  
  19.             } else {  
  20.                 /* either we already have an intermediate response or 
  21.                    the line doesn't begin with a digit */  
  22.                 handleUnsolicited(line);  
  23.             }  
  24.             break;  
  25.     }  
  26. }  

    可以看到,URC消息是通過handleUnsolicited處理的,而非URC消息有兩個地方處理。下面分別介紹兩種處理方式:

2.2、URC消息處理流程

    我們看URC消息的處理函數:

  1. @atchannel.c  
  2. static void handleUnsolicited(const char *line)  
  3. {  
  4.     s_unsolHandler(line, NULL);  
  5. }  

    這裏的s_unsolHandler來自於at_open時的參數,也就是reference-ril.c中的onUnsolicited:

  1. @reference-ril.c  
  2. static void onUnsolicited (const char *s, const char *sms_pdu)  
  3. {  
  4.     if (strStartsWith(s, "%CTZV:")) {  
  5.         //時區改變  
  6.         RIL_onUnsolicitedResponse (RIL_UNSOL_NITZ_TIME_RECEIVED,response, strlen(response));  
  7.     } else if (strStartsWith(s,"+CRING:")  
  8.                 || strStartsWith(s,"RING")  
  9.                 || strStartsWith(s,"NO CARRIER")  
  10.                 || strStartsWith(s,"+CCWA")  
  11.     ) {  
  12.         //通話狀態改變  
  13.         RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, NULL, 0);  
  14.     } else if (strStartsWith(s,"+CREG:")|| strStartsWith(s,"+CGREG:")) {  
  15.         //網絡註冊狀態改變  
  16.         RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED,NULL, 0);  
  17.     } else if (strStartsWith(s, "+CMT:")) {  
  18.         //新短信通知  
  19.         RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_NEW_SMS,sms_pdu, strlen(sms_pdu));  
  20.     } else if (strStartsWith(s, "+CDS:")) {  
  21.         //短信報告  
  22.         RIL_onUnsolicitedResponse (RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT,sms_pdu, strlen(sms_pdu));  
  23.     } else if (strStartsWith(s, "+CGEV:")) {  
  24.         RIL_requestTimedCallback (onDataCallListChanged, NULL, NULL);  
  25.     }  
  26. }  

    可以看出,URC消息的處理流程基本上就是根據命令頭的不同將其轉化爲不同的命令索引,然後調用RIL_onUnsolicitedResponse函數,而RIL_onUnsolicitedResponse的實現:

  1. #define RIL_onUnsolicitedResponse(a,b,c) s_rilenv->OnUnsolicitedResponse(a,b,c)  

    說明這個函數調用的是s_rilenv變量的OnUnsolicitedResponse方法。那麼s_rilenv是哪裏初始化的呢?

    我們在rild.c中的main函數中對reference庫初始化時是這樣的形式:

  1. 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實現。

  1. @ril.cpp  
  2. void RIL_onUnsolicitedResponse(int unsolResponse, void *data,  
  3.                                size_t datalen, RILId id)  
  4. {  
  5.     //得到當前命令的請求碼  
  6.     unsolResponseIndex = unsolResponse - RIL_UNSOL_RESPONSE_BASE;  
  7.     //從ril_unsol_commands.h文件中得到命令的類型  
  8.     wakeType = s_unsolResponses[unsolResponseIndex].wakeType;  
  9.     //根據不同命令的不同類型進行解析   
  10.     switch (wakeType) {  
  11.         case WAKE_PARTIAL:  
  12.         case DONT_WAKE:  
  13.     }  
  14.     appendPrintBuf("[UNSL]< %s", requestToString(unsolResponse));  
  15.   
  16.     Parcel p;  
  17.     p.writeInt32 (RESPONSE_UNSOLICITED);  
  18.     p.writeInt32 (unsolResponse);  
  19.     //調用當前命令的打包函數進行數據打包  
  20.     ret = s_unsolResponses[unsolResponseIndex].responseFunction(p, data, datalen);  
  21.     //把數據發送到RILJ中  
  22.     ret = sendResponse(p,id);  
  23.   
  24.     if (shouldScheduleTimeout) {  
  25.     }  
  26.     return;  
  27. }  

    上面的處理過程分爲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:

  1. static int sendResponse (Parcel &p) {  
  2.     return sendResponseRaw(p.data(), p.dataSize());  
  3. }  

    發送數據:

  1. static int sendResponseRaw (const void *data, size_t dataSize) {  
  2.     //發送通道的Socket ID  
  3.     int fd = s_fdCommand;  
  4.     int ret;  
  5.     uint32_t header;  
  6.   
  7.     //將數據長度這個整數轉換爲適合Socket  傳輸的字節順序  
  8.     header = htonl(dataSize);  
  9.   
  10.     //先發送一個關於數據大小的字節  
  11.     ret = blockingWrite(fd, (void *)&header, sizeof(header));  
  12.   
  13.     //再發送數據本身  
  14.     ret = blockingWrite(fd, data, dataSize);  
  15.   
  16.     return 0;  
  17. }  

    繼續看發送過程:

  1. static int blockingWrite(int fd, const void *buffer, size_t len) {  
  2.     size_t writeOffset = 0;  
  3.     const uint8_t *toWrite;  
  4.     toWrite = (const uint8_t *)buffer;  
  5.   
  6.     while (writeOffset < len) {  
  7.         ssize_t written;  
  8.         do {  
  9.             //發送數據到fd指定的句柄中,也就是RILJ對應的Socket通道  
  10.             written = write (fd, toWrite + writeOffset,len - writeOffset);  
  11.         } while (written < 0 && ((errno == EINTR) || (errno == EAGAIN)));  
  12.     }  
  13.     return 0;  
  14. }  

    這裏注意到,發送的最終操作,就是把數據放到一個fd指向的句柄中,那麼這個句柄從哪裏來的呢?

  1. #define SOCKET_NAME_RIL "rild"  
  2. //得到"rild"的Socket連接  
  3. s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);  
  4. //accept的函數默認會阻塞進程,直到有一個客戶連接建立後把可用的連接套接字返回  
  5. 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開始的:

  1. @atchannel.c  
  2. static void processLine(const char *line)  
  3. {  
  4.     if (sp_response == NULL) {  
  5.     } else if (isFinalResponseSuccess(line)) {  
  6.         sp_response->success = 1;  
  7.         //發送迴應消息給eventLoop  
  8.         handleFinalResponse(line);  
  9.     } else if (isFinalResponseError(line)) {  
  10.     } else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) {  
  11.     } else switch (s_type) {  
  12.         case NO_RESULT:  
  13.         case NUMERIC:  
  14.             if (sp_response->p_intermediates == NULL && isdigit(line[0])  
  15.             ) {  
  16.                 addIntermediate(line);  
  17.             } else {  
  18.                 handleUnsolicited(line);  
  19.             }  
  20.             break;  
  21.         case SINGLELINE:  
  22.             if (sp_response->p_intermediates == NULL  
  23.                 && strStartsWith (line, s_responsePrefix)  
  24.             ) {  
  25.                 addIntermediate(line);  
  26.             } else {  
  27.                 handleUnsolicited(line);  
  28.             }  
  29.             break;  
  30.         case MULTILINE:  
  31.         break;  
  32.     }  
  33. }  

    這裏簡要介紹以下Modem對於非URC消息的回覆格式。消息一般大於2行,前幾行是返回值的有效數據,最後一行是作爲當前命令結束的標誌位。如果是有效數據,那麼當前數據就有一個s_type的類型與之相關聯(從EventLoop發送給reference時決定)。因此就會在processLine中的switch中進入addIntermediate函數,而這個函數的作用就是把當前的數據放入到反饋數據的sp_response->p_intermediates裏面。等到命令的最後,因爲是標誌位,就會走到processLine的handleFinalResponse中,將數據發送給Event側。

    下面貼出涉及到的重要函數:

    a、將數據放到反饋數據中:addIntermediate

  1. static void addIntermediate(const char *line)  
  2. {  
  3.     ATLine *p_new;  
  4.     p_new = (ATLine  *) malloc(sizeof(ATLine));  
  5.     p_new->line = strdup(line);  
  6.     p_new->p_next = sp_response->p_intermediates;  
  7.     //把有效的返回值放到sp_response->p_intermediates中  
  8.     sp_response->p_intermediates = p_new;  
  9. }  

    b、判斷是否已經把所有有效數據傳輸完畢:isFinalResponseSuccess

  1. //結束符  
  2. static const char * s_finalResponsesSuccess[] = {  
  3.     "OK",  
  4.     "CONNECT"  
  5. };  
  6. //判斷  
  7. static int isFinalResponseSuccess(const char *line)  
  8. {  
  9.     size_t i;  
  10.     for (i = 0 ; i < NUM_ELEMS(s_finalResponsesSuccess) ; i++) {  
  11.         if (strStartsWith(line, s_finalResponsesSuccess[i])) {  
  12.             return 1;  
  13.         }  
  14.     }  
  15.     return 0;  
  16. }  

    c、發送數據給Event側,取消Event側阻塞的線程:handleFinalResponse

  1. static void handleFinalResponse(const char *line)  
  2. {  
  3.     //把迴應消息返回給eventLoop  
  4.     sp_response->finalResponse = strdup(line);  
  5.     //發信號給s_commandcond線程,使其脫離阻塞狀態  
  6.     pthread_cond_signal(&s_commandcond);  
  7. }  

三、一個完整的過程

    經過上面的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的入口:
  1. @rild.c  
  2. int main(int argc, char **argv)  
  3. {  
  4.     //搭建EventLoop循環  
  5.     RIL_startEventLoop();  
  6.   
  7.     //對reference動態庫進行初始化  
  8.     funcs = rilInit(&s_rilEnv, argc, rilArgv);  
  9.   
  10.     //註冊reference  的回調函數  
  11.     RIL_register(funcs);  
  12. }  

    上面看到,當我們調用reference的初始化函數(也就是RIL_Init)後,將會得到一個RIL_RadioFunctions類型的返回值:

  1. @reference-ril.c  
  2. static const RIL_RadioFunctions s_callbacks = {  
  3.     RIL_VERSION,  
  4.     onRequest,  
  5.     currentState,  
  6.     onSupports,  
  7.     onCancel,  
  8.     getVersion  
  9. };  

    這個變量的類型爲:

  1. typedef struct {  
  2.     int version;        //當前鏈接庫的版本信息  
  3.     RIL_RequestFunc onRequest;  //用於Event側向動態庫發起請求  
  4.     RIL_RadioStateRequest onStateRequest;   //得到當前庫的狀態  
  5.     RIL_Supports supports;  //查詢是否支持某個命令  
  6.     RIL_Cancel onCancel;       //取消一個Event的處理  
  7.     RIL_GetVersion getVersion;  //得到版本號  
  8. } RIL_RadioFunctions;  

    這些成員函數中最重要的是onRequest,當我們在Event側向reference庫發起請求時,就是用的這個入口函數。而我們將用這個對象去完成註冊的過程。

  1. @ril.cpp  
  2. //將reference中的回調函數註冊給RIL的框架  
  3. void RIL_register (const RIL_RadioFunctions *callbacks) {  
  4.     //把返回值傳給s_callbacks(全局變量)  
  5.     memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions));  
  6.   
  7.     //#define SOCKET_NAME_RIL "rild"  打開RILC與RILJ之間的Socket通道  
  8.     s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);  
  9.     ret = listen(s_fdListen, 4);  
  10.   
  11.     //用這個Socket通道句柄創建一個Event    
  12.     ril_event_set (&s_listen_event, s_fdListen, false, listenCallback, NULL);  
  13.   
  14.     //添加到Eventloop中  
  15.     rilEventAddWakeup (&s_listen_event);  
  16. }  

    在上面的註冊函數中主要完成了兩個任務: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的回調函數:

  1. @ril_event.cpp  
  2. static void firePending()  
  3. {  
  4.     struct ril_event * ev = pending_list.next;  
  5.     while (ev != &pending_list) {  
  6.         struct ril_event * next = ev->next;  
  7.         removeFromList(ev);  
  8.         //這裏的func就是listenCallback  
  9.         ev->func(ev->fd, 0, ev->param);  
  10.         ev = next;  
  11.     }  
  12. }  

    對於RILJ中過來的Event,我們註冊的回調函數是listenCallback,當RILJ向rilc發送數據時,首先會調用此函數:

  1. @ril.cpp  
  2. static void listenCallback (int fd, short flags, void *param) {  
  3.     //從s_fdListen偵聽套接字得到s_fdCommand(RILJ與RILC之間的流套接字)  
  4.     s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen);  
  5.   
  6.     //得到當前命令的選項值  
  7.     err = getsockopt(s_fdCommand, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);  
  8.   
  9.     //對文件描述符s_fdCommand進行控制,當前是設置其非阻塞模式  
  10.     ret = fcntl(s_fdCommand, F_SETFL, O_NONBLOCK);  
  11.   
  12.     //再次把當前命令放入Eventloop  
  13.     ril_event_set (&s_commands_event, s_fdCommand, 1, processCommandsCallback, p_rs);  
  14.     rilEventAddWakeup (&s_commands_event);  
  15.   
  16.     //發送URC消息,通知RIL狀態發生改變  
  17.     onNewCommandConnect();  
  18. }  

    上面的過程分爲2步:1、把當前消息重新發送到Eventloop;2、發送URC消息,通知RILJ(RIL狀態改變了)。我們先來看一下第二個過程:

  1. static void onNewCommandConnect() {  
  2.     //給RILJ發送URC消息,RIL連接成功  
  3.     RIL_onUnsolicitedResponse(RIL_UNSOL_RIL_CONNECTED, &rilVer, sizeof(rilVer));  
  4.   
  5.     //給RILJ發送URC消息,告訴RILJ,Radio狀態改變了  
  6.     RIL_onUnsolicitedResponse(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, NULL, 0);  
  7. }  

    我們再分析上面的第一個步驟,也就是把消息發送到Eventloop中的過程。當發送到Eventloop後,Eventloop就會調用當前Event的回調函數,現在的回調函數是processCommandsCallback:

  1. static void processCommandsCallback(int fd, short flags, void *param) {  
  2.     for (;;) { 
  3.         //真正讀取RILJ發過來的數據流就是在此處,此方法最終通過
  4.         //read (p_rs->fd, p_rs->read_end, p_rs->buffer_end - p_rs->read_end)方法讀取綁定的文件描述符內容
  5.         ret = record_stream_get_next(p_rs, &p_record, &recordlen);  
  6.   
  7.         if (ret == 0 && p_record == NULL) {  
  8.             break;  
  9.         } else if (ret < 0) {  
  10.             break;  
  11.         } else if (ret == 0) { /* && p_record != NULL */  
  12.             //把RILJ層數據通過AT發送到Modem  
  13.             processCommandBuffer(p_record, recordlen);  
  14.         }  
  15.     }  
  16.   
  17.     if (ret == 0 || !(errno == EAGAIN || errno == EINTR)) {  
  18.         //命令已經發送完成,關閉當前命令的流套接字  
  19.         close(s_fdCommand);  
  20.         s_fdCommand = -1;  
  21.         //刪掉當前Event  
  22.         ril_event_del(&s_commands_event);  
  23.         record_stream_free(p_rs);  
  24.         //重新添加RILJ與RILC之間的Socket  Event  
  25.         rilEventAddWakeup(&s_listen_event);  
  26.         onCommandsSocketClosed();  
  27.     }  

         
    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實現的:
  1. static int processCommandBuffer(void *buffer, size_t buflen) {  
  2.     Parcel p;  
  3.     int32_t request;  
  4.     int32_t token;  
  5.     RequestInfo *pRI;  
  6.   
  7.     p.setData((uint8_t *) buffer, buflen);  
  8.     //得到請求碼和令牌  
  9.     status = p.readInt32(&request);  
  10.     status = p.readInt32 (&token);  
  11.   
  12.     pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo));  
  13.     //設置當前請求的令牌  
  14.     pRI->token = token;  
  15.   
  16.     //s_commands中針對不同的命令對應不同的處理函數  
  17.     pRI->pCI = &(s_commands[request]);  
  18.   
  19.     //鏈表結構  
  20.     pRI->p_next = s_pendingRequests;  
  21.     s_pendingRequests = pRI;  
  22.   
  23.     //調用reference  中的  
  24.     pRI->pCI->dispatchFunction(p, pRI);  
  25.   
  26.     return 0;  
  27. }  

    首先說明一個很重要的標誌位:token令牌;這個令牌可以看作當前請求的ID,當我們從Modem得到數據後需要根據不同的令牌找到當初的請求命令,然後做出相應的答覆。

    這裏還涉及到一個特殊的數組s_commands。他的作用和s_unsolResponses類似,詳細說明在本文的最後一章,這裏還是簡單介紹一下他的作用:

    s_commands是一個數組,每個RILJ發送過來的命令在s_commands中都對應一個元素,而每個元素包含3個數據:

  1. typedef struct {  
  2.     int requestNumber;  
  3.     void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);  
  4.     int(*responseFunction) (Parcel &p, void *response, size_t responselen);  
  5. } 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中對於這條命令的描述爲:
  1. {RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus},  
    因此,它對應的發送函數就是dispatchVoid:
  1. @ril.cpp  
  2. static void dispatchVoid (Parcel& p, RequestInfo *pRI) {  
  3.     //發送數據到Modem    
  4.     clearPrintBuf;  
  5.     //打印Log信息RLOGD("[%04d]> %s %s", token, requestToString(req), printBuf)  
  6.     printRequest(pRI->token, pRI->pCI->requestNumber);  
  7.     //s_callbacks是從reference註冊過來的  
  8.     s_callbacks.onRequest(pRI->pCI->requestNumber, NULL, 0, pRI);  
  9. }  
    這裏看到,將命令發送到reference庫是通過s_callbacks的onRequest方法實現的,而且發送時會將命令號和ril_commands.h中對應的當前命令的信息一同發送。
    而關於s_callbacks這個全局變量,我們在這一節的最初就講到,當我們調用reference中的RIL_Init完成初始化時,就會得到reference返回當前鏈接庫提供的接口函數,而s_callbacks正是來自於這些接口:
  1. @reference-ril.c  
  2. static const RIL_RadioFunctions s_callbacks = {  
  3.     RIL_VERSION,  
  4.     onRequest,  
  5.     currentState,  
  6.     onSupports,  
  7.     onCancel,  
  8.     getVersion  
  9. };  

    因此,當上面的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側。

  1. @reference-ril.c  
  2. static void onRequest (int request, void *data, size_t datalen, RIL_Token t)  
  3. {  
  4.     ATResponse *p_response;  
  5.     RLOGD("onRequest: %s", requestToString(request));  
  6.   
  7.     //我們重點看RIL_REQUEST_GET_SIM_STATUS  
  8.     switch (request) {  
  9.         case RIL_REQUEST_GET_SIM_STATUS: {  
  10.             RIL_CardStatus_v6 *p_card_status;  
  11.             char *p_buffer;  
  12.             int buffer_size;  
  13.             //與Modem交互,發送命令並得到迴應  
  14.             int result = getCardStatus(&p_card_status);  
  15.             //把迴應傳回給Eventloop  
  16.             RIL_onRequestComplete(t, result, p_buffer, buffer_size);  
  17.             freeCardStatus(p_card_status);  
  18.             break;  
  19.         }  
  20.         case RIL_REQUEST_GET_CURRENT_CALLS:  
  21.             //得到當前通話  
  22.             requestGetCurrentCalls(data, datalen, t);  
  23.             break;  
  24.         case RIL_REQUEST_DIAL:  
  25.             //撥號  
  26.             requestDial(data, datalen, t);  
  27.             break;  
  28.         case RIL_REQUEST_HANGUP:  
  29.             //掛起  
  30.             requestHangup(data, datalen, t);  
  31.     }  
  32. }  
    從onRequest可以看出,reference中對所有的命令請求進行判別,然後選擇不同的處理方式。對於嘗試得到SIM狀態這個請求來說,不僅需要通過getCardStatus得到SIM卡狀態,而且還需要調用RIL_onRequestComplete將得到的狀態通過Eventloop返回給RILJ。我們先看給Modem發送數據的過程
  1. static int getCardStatus(RIL_CardStatus_v6 **pp_card_status) {  
  2.     //SIM卡所有可能的狀態  
  3.     static RIL_AppStatus app_status_array[] = {  
  4.         //SIM_ABSENT = 0        SIM不存在  
  5.         { RIL_APPTYPE_UNKNOWN, RIL_APPSTATE_UNKNOWN, RIL_PERSOSUBSTATE_UNKNOWN,  
  6.           NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },  
  7.         // SIM_NOT_READY = 1    SIM未就緒  
  8.         { RIL_APPTYPE_SIM, RIL_APPSTATE_DETECTED, RIL_PERSOSUBSTATE_UNKNOWN,  
  9.           NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },  
  10.         // SIM_READY = 2        SIM就緒  
  11.         { RIL_APPTYPE_SIM, RIL_APPSTATE_READY, RIL_PERSOSUBSTATE_READY,  
  12.           NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },  
  13.         ....  
  14.   
  15.     };  
  16.     //查詢SIM狀態,將AT命令發送到Modem,然後得到Modem的數據  
  17.     int sim_status = getSIMStatus();  
  18.   
  19.     RIL_CardStatus_v6 *p_card_status = malloc(sizeof(RIL_CardStatus_v6));  
  20.   
  21.     if (num_apps != 0) {  
  22.         // Only support one app, gsm  
  23.         p_card_status->num_applications = 2;  
  24.         p_card_status->gsm_umts_subscription_app_index = 0;  
  25.         p_card_status->cdma_subscription_app_index = 1;  
  26.   
  27.         // Get the correct app status  
  28.         p_card_status->applications[0] = app_status_array[sim_status];  
  29.         p_card_status->applications[1] = app_status_array[sim_status + RUIM_ABSENT];  
  30.     }  
  31.   
  32.     *pp_card_status = p_card_status;  
  33.     //將狀態返回給reference  
  34.     return RIL_E_SUCCESS;  
  35. }  
    繼續看發送過程
  1. static SIM_Status getSIMStatus()  
  2. {  
  3.     //將命令轉換爲AT命令發送到Modem,而modem的迴應放到p_response中  
  4.     err = at_send_command_singleline("AT+CPIN?""+CPIN:", &p_response);  
  5.   
  6.     //取得返回值  
  7.     cpinLine = p_response->p_intermediates->line;  
  8.     err = at_tok_start (&cpinLine);  
  9.     err = at_tok_nextstr(&cpinLine, &cpinResult);  
  10.   
  11.     //根據返回值得到當前SIM卡狀態  
  12.     if (0 == strcmp (cpinResult, "SIM PIN")) {  
  13.         //PIN鎖  
  14.         ret = SIM_PIN;  
  15.         goto done;  
  16.     } else if (0 == strcmp (cpinResult, "SIM PUK")) {  
  17.         //PUK鎖  
  18.         ret = SIM_PUK;  
  19.         goto done;  
  20.     } else if (0 == strcmp (cpinResult, "PH-NET PIN")) {  
  21.         return SIM_NETWORK_PERSONALIZATION;  
  22.     } else if (0 != strcmp (cpinResult, "READY"))  {  
  23.         //SIM卡不存在  
  24.         ret = SIM_ABSENT;  
  25.         goto done;  
  26.     }  
  27. //返回結果  
  28. done:  
  29.     at_response_free(p_response);  
  30.     return ret;  
  31. }  
    繼續看at_send_command_singleline的發送過程:
  1. @atchannel.c  
  2. int at_send_command_singleline (const char *command,  
  3.                                 const char *responsePrefix,  
  4.                                  ATResponse **pp_outResponse)  
  5. {  
  6.     err = at_send_command_full (command, SINGLELINE, responsePrefix, NULL, 0, pp_outResponse);  
  7.   
  8.     return err;  
  9. }  
  10. static int at_send_command_full (const char *command, ATCommandType type,  
  11.                     const char *responsePrefix, const char *smspdu,  
  12.                     long long timeoutMsec, ATResponse **pp_outResponse)  
  13. {  
  14.     //發送  
  15.     err = at_send_command_full_nolock(command, type,  
  16.                     responsePrefix, smspdu,  
  17.                     timeoutMsec, pp_outResponse);  
  18.     return err;  
  19. }  
  20. 繼續看   
  21. static int at_send_command_full_nolock (const char *command, ATCommandType type,  
  22.                     const char *responsePrefix, const char *smspdu,  
  23.                     long long timeoutMsec, ATResponse **pp_outResponse)  
  24. {  
  25.     //給Modem發消息AT  
  26.     err = writeline (command);  
  27.       
  28.     //創建新的sp_response作爲迴應  
  29.     sp_response = at_response_new();  
  30.   
  31.     //上面發送完消息後需要阻塞等待Modem迴應  
  32.     while (sp_response->finalResponse == NULL && s_readerClosed == 0) {  
  33.         if (timeoutMsec != 0) {  
  34.             //線程進入阻塞狀態,等待另一線程滿足s_commandcond  後解除  
  35.             err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);  
  36.         }  
  37.     }  
  38.   
  39.     if (pp_outResponse == NULL) {  
  40.         //釋放sp_response  
  41.         at_response_free(sp_response);  
  42.     } else {  
  43.         reverseIntermediates(sp_response);  
  44.         //將回應發送給當初請求AT的線程  
  45.         *pp_outResponse = sp_response;  
  46.     }  
  47.     return err;  
  48. }  
    在上面的函數中,完成了兩個重要動作:1、通過writeline發送數據到Modem;2、阻塞當前線程,等待Modem迴應。我們先來看writeline的過程:
  1. static int writeline (const char *s)  
  2. {  
  3.     size_t len = strlen(s);  
  4.     ssize_t written;  
  5.   
  6.     //Log信息  
  7.     RLOGD("AT> %s\n", s);  
  8.     while (cur < len) {  
  9.         do {  
  10.             //s_fd就是Modem與RILC之間的串口  
  11.             written = write (s_fd, s + cur, len - cur);  
  12.         } while (written < 0 && errno == EINTR);  
  13.   
  14.         cur += written;  
  15.     }  
  16.     do {  
  17.         //以r結尾  
  18.         written = write (s_fd, "\r" , 1);  
  19.     } while ((written < 0 && errno == EINTR) || (written == 0));  
  20.   
  21.     return 0;  
  22. }  
    經過上面的操作,就將一條命令傳輸到了Modem側。
    上面說道,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:
  1. static int at_send_command_full_nolock (const char *command, ATCommandType type,  
  2.                     const char *responsePrefix, const char *smspdu,  
  3.                     long long timeoutMsec, ATResponse **pp_outResponse)  
  4. {  
  5.     //給Modem發消息AT  
  6.     err = writeline (command);  
  7.       
  8.     //創建新的sp_response作爲迴應  
  9.     sp_response = at_response_new();  
  10.   
  11.     //上面發送完消息後需要阻塞等待Modem迴應  
  12.     while (sp_response->finalResponse == NULL && s_readerClosed == 0) {  
  13.         if (timeoutMsec != 0) {  
  14.             //線程進入阻塞狀態,等待另一線程滿足s_commandcond  後解除  
  15.             err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);  
  16.         }  
  17.     }  
  18.   
  19.     if (pp_outResponse == NULL) {  
  20.         //釋放sp_response  
  21.         at_response_free(sp_response);  
  22.     } else {  
  23.         reverseIntermediates(sp_response);  
  24.         //將回應發送給當初請求AT的線程  
  25.         *pp_outResponse = sp_response;  
  26.     }  
  27.     return err;  
  28. }  
    上面的sp_response是返回給RILC的數據。而while循環中就是在阻塞狀態下等待Modem的迴應。這裏需要簡要說明一下pthread_cond_timeout_np的作用,pthread_cond_timeout_np的第一個參數s_commandcond是一種條件變量,而條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。爲了防止競爭,條件變量的使用總是和一個互斥鎖結合在一起。  
    在這裏,爲了等待s_commandcond條件變量而自動阻塞了當前線程。那麼,只有當另一個線程通過pthread_cond_signal接口是s_commandcond條件滿足時,當前阻塞的線程纔會被喚醒。
    那麼是在哪裏去喚醒當前線程的呢?
    我們分析一下,當我們發送數據或命令給Modem的時候,阻塞了當前的線程,阻塞的目的就是等待Modem的迴應,而如果Modem有數據上來,那麼肯定是先被reference的ReaderLoop檢測到並處理,因此,也應該是在ReaderLoop的消息處理中去喚醒當前阻塞的線程,而且應該把Modem的反饋傳輸給阻塞線程。
    我們直接來看ReaderLoop的處理函數,因爲作爲Modem的迴應,當前消息一定不是URC消息,因此處理函數就是processLine:
  1. @atchannel.c  
  2. static void processLine(const char *line)  
  3. {  
  4.     if (sp_response == NULL) {  
  5.     } else if (isFinalResponseSuccess(line)) {  
  6.         sp_response->success = 1;  
  7.         //發送迴應消息給EventLoop  
  8.         handleFinalResponse(line);  
  9.     } else if (isFinalResponseError(line)) {  
  10.     } else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) {  
  11.     } else switch (s_type) {  
  12.         case NO_RESULT:  
  13.         case NUMERIC:  
  14.         case SINGLELINE:  
  15.             if (sp_response->p_intermediates == NULL  
  16.                 && strStartsWith (line, s_responsePrefix)  
  17.             ) {  
  18.                 addIntermediate(line);  
  19.             } else {  
  20.                 /* we already have an intermediate response */  
  21.                 handleUnsolicited(line);  
  22.             }  
  23.             break;  
  24.         case MULTILINE:  
  25.         break;  
  26.     }  
  27. }  
    上面在分析processLine時就說過,Modem給的數據可能有很多行,組後一行是OK的標誌位,而之前的行都是有效數據。我們還知道,對於得到SIM卡狀態這個請求,他的s_type是SINGLELINE(傳遞給reference時決定的)。
    因此,我們接受的Modem數據應該先被switch中的SINGLELINE處理,而這一步的處理就是把Modem的有效數據放到sp_response->p_intermediates中:
  1. static void addIntermediate(const char *line)  
  2. {  
  3.     ATLine *p_new;  
  4.     p_new = (ATLine  *) malloc(sizeof(ATLine));  
  5.     p_new->line = strdup(line);  
  6.     p_new->p_next = sp_response->p_intermediates;  
  7.     sp_response->p_intermediates = p_new;  
  8. }  
    等有效數據處理完後,再次進入processLine時,遇到了標誌位行,因此isFinalResponseSuccess判斷成立,去喚醒另一端阻塞的線程:
  1. static void handleFinalResponse(const char *line)  
  2. {  
  3.     //把迴應消息返回給RIL  
  4.     sp_response->finalResponse = strdup(line);  
  5.     //s_commandcond條件變量的條件得到滿足,將會喚醒相應的阻塞線程  
  6.     pthread_cond_signal(&s_commandcond);  
  7. }  
    到這裏,完全符合我們的預期,也就是在ReaderLoop的處理過程中,滿足條件變量,從而使等待在pthread_cond_timeout_np的線程脫離阻塞狀態,同時,把Modem的迴應(line)傳遞給sp_response->p_intermediates。
    此時,我們可以再次回到被阻塞的線程,看看接下來的數據傳輸過程:
  1. @atchannel.c  
  2. static int at_send_command_full_nolock (const char *command, ATCommandType type,  
  3.                     const char *responsePrefix, const char *smspdu,  
  4.                     long long timeoutMsec, ATResponse **pp_outResponse)  
  5. {  
  6.     //給Modem發消息AT  
  7.     err = writeline (command);  
  8.       
  9.     //創建新的sp_response作爲迴應  
  10.     sp_response = at_response_new();  
  11.   
  12.     //上面發送完消息後需要阻塞等待Modem迴應  
  13.     while (sp_response->finalResponse == NULL && s_readerClosed == 0) {  
  14.         if (timeoutMsec != 0) {  
  15.             //線程進入阻塞狀態,等待另一線程滿足s_commandcond後解除  
  16.             err = pthread_cond_timeout_np(&s_commandcond, &s_commandmutex, timeoutMsec);  
  17.         }  
  18.     }  
  19.   
  20.     if (pp_outResponse == NULL) {  
  21.         //釋放sp_response  
  22.         at_response_free(sp_response);  
  23.     } else {  
  24.         reverseIntermediates(sp_response);  
  25.         //將回應發送給當初請求AT的線程  
  26.         *pp_outResponse = sp_response;  
  27.     }  
  28.     return err;  
  29. }  
    當這個函數從pthread_cond_timeout_np中喚醒後,如果當前的命令確實要求Modem給出相應的迴應,就會把迴應的數據放到pp_outResponse中,返回給上層使用。我們知道,當前的sp_response->finalResponse就是Modem給出的迴應。接着,at_send_command_full_nolock就返回了。
    我們再次回顧一下當初調用到pthread_cond_timeout_np的路徑:
    getSIMStatus()-->at_send_command_singleline()->at_send_command_full()->at_send_command_full_nolock();
    因此,我們再次回到getSIMStatus中查看:
  1. static SIM_Status getSIMStatus()  
  2. {  
  3.     //將命令轉換爲AT命令發送到Modem,而modem的迴應放到p_response中  
  4.     err = at_send_command_singleline("AT+CPIN?""+CPIN:", &p_response);  
  5.   
  6.     //取得返回值  
  7.     cpinLine = p_response->p_intermediates->line;  
  8.     err = at_tok_start (&cpinLine);  
  9.     err = at_tok_nextstr(&cpinLine, &cpinResult);  
  10.   
  11.     //根據返回值得到當前SIM卡狀態  
  12.     if (0 == strcmp (cpinResult, "SIM PIN")) {  
  13.         //PIN鎖  
  14.         ret = SIM_PIN;  
  15.         goto done;  
  16.     } else if (0 == strcmp (cpinResult, "SIM PUK")) {  
  17.         //PUK鎖  
  18.         ret = SIM_PUK;  
  19.         goto done;  
  20.     } else if (0 == strcmp (cpinResult, "PH-NET PIN")) {  
  21.         return SIM_NETWORK_PERSONALIZATION;  
  22.     } else if (0 != strcmp (cpinResult, "READY"))  {  
  23.         //SIM卡不存在  
  24.         ret = SIM_ABSENT;  
  25.         goto done;  
  26.     }  
  27. }  
    在getSIMStatus中,當我們從at_send_command_singleline中返回後,用Modem給的數據p_response->p_intermediates得到了當前SIM的狀態。
    我們在往上層看,當初是在getCardStatus中調用getSIMStatus的:
  1. static int getCardStatus(RIL_CardStatus_v6 **pp_card_status) {  
  2.     //SIM卡所有可能的狀態  
  3.     static RIL_AppStatus app_status_array[] = {  
  4.         { RIL_APPTYPE_UNKNOWN, RIL_APPSTATE_UNKNOWN, RIL_PERSOSUBSTATE_UNKNOWN,  
  5.           NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },  
  6.         { RIL_APPTYPE_SIM, RIL_APPSTATE_DETECTED, RIL_PERSOSUBSTATE_UNKNOWN,  
  7.           NULL, NULL, 0, RIL_PINSTATE_UNKNOWN, RIL_PINSTATE_UNKNOWN },  
  8.     };  
  9.   
  10.     //查詢SIM狀態,將AT命令發送到Modem,然後得到Modem的數據  
  11.     int sim_status = getSIMStatus();  
  12.     //得到返回值  
  13.     p_card_status->applications[0] = app_status_array[sim_status];  
  14.     p_card_status->applications[1] = app_status_array[sim_status + RUIM_ABSENT];  
  15.     //把返回值帶回到上一級中  
  16.     *pp_card_status = p_card_status;  
  17.     return RIL_E_SUCCESS;  
  18. }  
    在這個函數中,我們通過getSIMStatus得到的int型數據,由app_status_array數組轉換爲各個更容易理解的狀態,然後放入pp_card_status返回值中,返回給父一級。
  1. @reference-ril.c  
  2. static void onRequest (int request, void *data, size_t datalen, RIL_Token t)  
  3. {  
  4.     switch (request) {  
  5.         case RIL_REQUEST_GET_SIM_STATUS: {  
  6.             RIL_CardStatus_v6 *p_card_status;  
  7.             char *p_buffer;  
  8.             int buffer_size;  
  9.             //與Modem交互,發送命令並得到迴應  
  10.             int result = getCardStatus(&p_card_status);  
  11.             p_buffer = (char *)p_card_status;  
  12.             buffer_size = sizeof(*p_card_status);  
  13.             //把迴應傳回給Eventloop  
  14.             RIL_onRequestComplete(t, result, p_buffer, buffer_size);  
  15.             freeCardStatus(p_card_status);  
  16.             break;  
  17.         }  
  18.     }  
  19. }  
    這個函數就是當Eventloop中有請求時,我們把請求發送到註冊的reference庫的回調函數中,而reference的回調函數就是onRequest。在這裏,當我們從getCardStatus返回後,就把數據轉換爲buffer,然後通過RIL_onRequestComplete重新返回給Eventloop,當我們去觀察這個函數參數時,除了表示成功或失敗的result和表示數據的buffer外,還有一個t,而這個t就是我們前面一再提到的令牌。當我們從RILJ發送數據時,就會把一個表示當前這個請求的的令牌傳遞下去,而當Modem返回數據後,我們再通過這個令牌找到當初發送的請求,然後把數據傳遞給他。接下來我們看這個函數的具體實現:
  1. #define RIL_onRequestComplete(t, e, response, responselen) s_rilenv->OnRequestComplete(t,e, response, responselen)  
    我們看到,RIL_onRequestComplete就是s_rilenv的OnRequestComplete函數,而s_rilenv的來歷我們在前面已經介紹過,他的各個處理函數都在ril.cpp中。
    因此,到這裏,我們的流程就又走到了ril.cpp中的RIL_onRequestComplete裏面。而從流程上來講,我們就將從Modem得到的迴應,又由reference庫交接給了Event端。
    接下來要做的就是在Event側把數據重新返回給RILJ。

3.4、Eventloop把數據返回給RILJ

    我們直接來看此時的接收函數:
  1. @ril.cpp  
  2. void RIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen) {  
  3.     RequestInfo *pRI;  
  4.     pRI = (RequestInfo *)t;  
  5.   
  6.     Parcel p;  
  7.     p.writeInt32 (RESPONSE_SOLICITED);  
  8.     p.writeInt32 (pRI->token);  
  9.     errorOffset = p.dataPosition();  
  10.     p.writeInt32 (e);  
  11.   
  12.     if (response != NULL) {  
  13.         //先調用當前請求的回調函數  
  14.         ret = pRI->pCI->responseFunction(p, response, responselen);  
  15.   
  16.         if (ret != 0) {  
  17.             p.setDataPosition(errorOffset);  
  18.             p.writeInt32 (ret);  
  19.         }  
  20.     }  
  21. 發送數據給RILJ  
  22.     sendResponse(p);  
  23. }  
    我們看到,在RILC發送數據給RILJ之前,會先調用當前命令的迴應處理函數,也就是responseFunction,對將要傳輸的數據進行差異化處理,然後再通過sendResponse發送給RILJ。

    那麼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進入了:

  1. @ril.cpp  
  2. static int responseSimStatus(Parcel &p, void *response, size_t responselen) {  
  3.         RIL_CardStatus_v6 *p_cur = ((RIL_CardStatus_v6 *) response);  
  4.         p.writeInt32(p_cur->card_state);  
  5.         p.writeInt32(p_cur->universal_pin_state);  
  6.         p.writeInt32(p_cur->gsm_umts_subscription_app_index);  
  7.         p.writeInt32(p_cur->cdma_subscription_app_index);  
  8.         p.writeInt32(p_cur->ims_subscription_app_index);  
  9.   
  10.         sendSimStatusAppInfo(p, p_cur->num_applications, p_cur->applications);  
  11. }  
    接下來:
  1. static void sendSimStatusAppInfo(Parcel &p, int num_apps, RIL_AppStatus appStatus[]) {  
  2.         p.writeInt32(num_apps);  
  3.         startResponse;  
  4.         for (int i = 0; i < num_apps; i++) {  
  5.             p.writeInt32(appStatus[i].app_type);  
  6.             p.writeInt32(appStatus[i].app_state);  
  7.             p.writeInt32(appStatus[i].perso_substate);  
  8.             writeStringToParcel(p, (const char*)(appStatus[i].aid_ptr));  
  9.             writeStringToParcel(p, (const char*)(appStatus[i].app_label_ptr));  
  10.             p.writeInt32(appStatus[i].pin1_replaced);  
  11.             p.writeInt32(appStatus[i].pin1);  
  12.             p.writeInt32(appStatus[i].pin2);  
  13.         }  
  14.         closeResponse;  
  15. }  

    其實這裏所謂的打包處理,不過是根據不同的命令,把相應的迴應寫入到給RILJ的返回數據包中。
    然後在RIL_onRequestComplete中調用sendResponse把數據發送到RILJ層。
    以上就是RIL數據傳輸的整個過程。
    現在,我們再來回顧一下在本文開始處展示的RIL層數據流向圖,希望這次看到他時不會那麼的陌生:



四、一些重要概念


4.1、s_commandcond

    我們知道,RIL是一個框架,並不會在意每個命令之間的差異,但是每個命令所發送的數據不同,要求的迴應更是不同。而這個數組就是完成了從標準化到差異化的轉換過程。
    先來看一下定義:
  1. static CommandInfo s_commands[] = {  
  2.     #include "ril_commands.h"  
  3. };  
    以上這個變量定義在是一個數組,類型如下:
  1. typedef struct {  
  2.     int requestNumber;  
  3.     void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);  
  4.     int(*responseFunction) (Parcel &p, void *response, size_t responselen);  
  5. } CommandInfo;  
    這個結構體中有3個數據:requestNumber表示一個命令的請求碼;dispatchFunction表示當前命令發送給reference庫時的入口函數;而responseFunction表示reference給出應答後需要用這個函數對應答數據進行封裝,然後傳遞給RILJ;
    主要內容如下:
  1. {RIL_REQUEST_GET_SIM_STATUS, dispatchVoid, responseSimStatus},  
  2. {RIL_REQUEST_ENTER_SIM_PIN, dispatchStrings, responseInts},  
  3. {RIL_REQUEST_ENTER_SIM_PUK, dispatchStrings, responseInts},  
    結合到流程來說,當我們從Eventloop中讀取一條RILJ發送的請求後,在Eventloop的發送最後(processCommandBuffer函數)根據當前命令去s_commands中查找相應的dispatchFunction和responseFunction,組成一個RequestInfo的數據。然後調用當前命令的dispatchFunction函數將RequestInfo數據發送給reference庫。
    而當reference接收到Modem數據後,根據當前命令的RequestInfo信息,查找當前命令的應答回調(responseFunction)對迴應數據進行封裝,封裝後調用統一的reference到Event側的接口函數(RIL_onRequestComplete)把數據發送給Event側,再由Event側發送到RILJ。

4.2、s_unsolResponses

    對比上面的s_commands來看,這個變量就顯得比較簡單了。我們先來看一下他的定義:
  1. static UnsolResponseInfo s_unsolResponses[] = {  
  2.     #include "ril_unsol_commands.h"  
  3. };  
    然後列出幾個重要的數組內容
  1. {RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, responseVoid, WAKE_PARTIAL},  
  2. {RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, responseVoid, WAKE_PARTIAL},  
  3. {RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, responseVoid, WAKE_PARTIAL},  
    再來看數據類型:
  1. typedef struct {  
  2.     int requestNumber;  
  3.     int (*responseFunction) (Parcel &p, void *response, size_t responselen);  
  4.     WakeType wakeType;  
  5. } UnsolResponseInfo;  
    這裏我們看到,他和s_commands的區別就在於他的裏面只有responseFunction而沒有dispatchFunction。爲什麼會這樣呢?原因就在於URC消息與非URC消息的區別上。
    上面我們講到,非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結構。
    數據結構:
  1. typedef struct RequestInfo {  
  2.     int32_t token;  
  3.     CommandInfo *pCI;  
  4.     struct RequestInfo *p_next;  
  5.     char cancelled;  
  6.     char local;  
  7. } RequestInfo;  
    其中token是當前命令的令牌,相當於命令的ID。pCI的數據結構:
  1. typedef struct {  
  2.     int requestNumber;  
  3.     void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);  
  4.     int(*responseFunction) (Parcel &p, void *response, size_t responselen);  
  5. } CommandInfo;  
    這個數據表明了當前命令的3個很重要屬性:1、當前命令的請求碼;2、當前命令發送到reference側的入口;3、當前命令從reference返回給Event側時要調用responseFunction進行數據封裝。

五、Android RIL的配置與加載
將你自己的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代表迴環接口。

發佈了18 篇原創文章 · 獲贊 12 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章