RILD及qcril初始化流程

SLIM0201發表於2017-05-25

之前看了許多其他同行寫的這方面的好文章了,最近重溫這一塊,覺得應該自己寫一篇,以後再看也好上手。

Rild是Android提供的框架,位置處於AP側,對接Phone程式和BP側Modem。

架構

雙卡手機在phone程式會有2個Phone的例項對應各個卡槽,相應的Rild程式也會有2個,Phone和Rild之間通過Socket保持連線,各卡槽之間的操作互相獨立。

但是由於基帶需要由第三方的晶片廠商來提供,因此Google只提供了Rild的框架和介面,剩下的事情需要由晶片供應商自行來實現,這裡說的就是vendor RIL。
關於Vendor RIL的實現方法各有不同,如高通用qcril + qmi機制實現,而MTK則直接使用AT指令與Modem通訊,在RIL層不做過多的處理。
具體的實現以後有時間再來整理。接下來的內容以一個高通的工程為範本來分析。


1. RILD程式的啟動

用手上的一臺雙卡手機驗證了rild程式確實存在2個:

root@s2:/ # ps |grep rild
radio     464   1     107284 14088 hrtimer_na 7fa7f60604 S /system/bin/rild
radio     759   1     106252 13920 hrtimer_na 7f95db4604 S /system/bin/rild

Rild為系統服務,很明顯會在init.rc中能找到啟動的描述(Android7.0被定義在hardware\ril\rild\Rild.rc):

service ril-daemon /system/bin/rild
    class main
    socket rild stream 660 root radio
    socket sap_uim_socket1 stream 660 bluetooth bluetooth
    socket rild-debug stream 660 radio system
    user root

這裡啟動了一個rild,那麼第二個rild在哪裡呢?通過OpenGrok的幫忙,在工程中搜尋到device/qcom/common/rootdir/etc/init.qcom.rc有相關定義:

service ril-daemon2 /system/bin/rild -c 2
    class main
    socket rild2 stream 660 root radio
    socket rild-debug2 stream 660 radio system
    user root
    disabled
    group radio cache inet misc audio sdcard_r sdcard_rw qcom_diag diag log

service ril-daemon3 /system/bin/rild -c 3
    class main
    socket rild3 stream 660 root radio
    socket rild-debug3 stream 660 radio system
    user root
    disabled
    group radio cache inet misc audio sdcard_r sdcard_rw qcom_diag diag log

這裡有rild2和rild3的描述,猜想在工程編譯期間指令碼會通過編譯引數判斷工程支援的SIM卡數,然後對應地將這些啟動描述整合到最終的init.rc中。(以後再來驗證。)


2. RILD main函式

根據init.rc中描述可知,rild的入口函式名字為main。
下面過濾出main函式主要的處理,先大致看一下:

int main(int argc, char **argv) {
    const char * rilLibPath = NULL;
    char **rilArgv;
    void *dlHandle;
    const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **);
    const RIL_RadioFunctions *funcs;
    unsigned char hasLibArgs = 0;

    int i;
    const char *clientId = NULL;

    ...
    //1.從啟動引數中獲取clientId,代表是第幾個RILD
    for (i = 1; i < argc ;) {
        ...
    if (0 == strcmp(argv[i], "-c") &&  (argc - i > 1)) {
            clientId = argv[i+1];
            i += 2;
        } ...
    }

    //2.使用clientId設定RILD的socket名字
    if (strncmp(clientId, "0", MAX_CLIENT_ID_LENGTH)) {
        strlcat(rild, clientId, MAX_SOCKET_NAME_LENGTH);
        RIL_setRilSocketName(rild);
    }

    //3.獲取vendor ril庫的路徑
    //#define LIB_PATH_PROPERTY   "rild.libpath"
    if (rilLibPath == NULL) {
        if ( 0 == property_get(LIB_PATH_PROPERTY, libPath, NULL)) {
            // No lib sepcified on the command line, and nothing set in props.
            // Assume "no-ril" case.
            goto done;
        } else {
            rilLibPath = libPath;
        }
    }
    ...
    //4.開啟vendor ril庫
    dlHandle = dlopen(rilLibPath, RTLD_NOW);
    //5.啟動訊息迴圈
    RIL_startEventLoop();
    ...
    //6.獲取vendor RIL的初始化入口方法
    rilInit =
        (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))
        dlsym(dlHandle, "RIL_Init");
        ...
    //7.呼叫vendor RIL的RIL_Init方法,此方法會返回RIL_RadioFunctions
    rilArgv[argc++] = "-c";
    rilArgv[argc++] = clientId;
    rilArgv[0] = argv[0];
    funcs = rilInit(&s_rilEnv, argc, rilArgv);

    //8.註冊vendor RIL的處理方法
    RIL_register(funcs);
    ...
}

我們看到,main函式主要的流程如下:
一)獲取vendor ril並開啟。(3和4步)
二)啟動訊息迴圈。(第5步)
三)獲得vendor ril入口方法,並取得vendor ril的訊息處理函式列表。(6和7)
四)註冊vendor ril回撥方法。(第8步)

用本文接下來的篇幅來分析這上面列出的4個步驟。


3. 深入分析main函式

3.1 獲取Vendor RIL庫檔案路徑

main函式中通過getprop獲取了vendor ril檔案的路徑,property名為:”rild.libpath”

#define LIB_PATH_PROPERTY   "rild.libpath"
property_get(LIB_PATH_PROPERTY, libPath, NULL)

在adb shell中輸入getprop命令,找到庫路徑:/vendor/lib64/libril-qc-qmi-1.so

root@s2:/ # getprop rild.libpath
/vendor/lib64/libril-qc-qmi-1.so

這個so檔案由qcril原始碼生成,mk檔案中有描述:
/vendor/qcom/proprietary/qcril/qcril_qmi/qcril_qmi.mk

ifndef QCRIL_DSDA_INSTANCE
    LOCAL_MODULE:= libril-qc-qmi-1

在獲得了vendor ril庫檔案路徑之後,開啟庫並取得檔案控制程式碼,流程似乎即將進入vendor RIL。但在此之前,Rild會先啟動一個子執行緒訊息迴圈,然後再開始qcril的流程。


3.2 RIL_startEventLoop 啟動監聽訊息迴圈

RIL_startEventLoop建立了一個執行緒,用於執行eventLoop這個函式。

Ril.cpp
extern "C" void
RIL_startEventLoop(void) {
    /* spin up eventLoop thread and wait for it to get started */
    s_started = 0;
    ...
    //啟動執行緒,執行eventLoop函式
    int result = pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL);
    ...
    //s_started會在eventLoop被執行後置成1,保證了執行緒啟動後才離開這個函式。
    while (s_started == 0) {
        pthread_cond_wait(&s_startupCond, &s_startupMutex);
    }
    ...
}

在RIL_startEventLoop退出之前,還進入了一個while迴圈去,這裡意圖是通過判斷s_started 的值,保證執行緒已經啟動且eventLoop已經開始被執行,因為s_started在eventLoop開始時會被賦值成1。

eventLoop可以說是Rild中訊息獲取和分發的中心,其實現的關鍵字如下:
無限迴圈、select 、fd_set、ril_event、callback。

深入到eventLoop函式,具體主要做了3件事情:
a. 初始化(fd_set和3個ril_event結構體)
b. 向fd_set中新增了一個fd_read,並向寫管道fd_write寫入字元(將會喚醒select)
c. 啟動一個無限迴圈,等待select(fd_set)返回,隨後呼叫ril_event回撥函式。

Ril.cpp
static void *
eventLoop(void *param) {
    int ret;
    int filedes[2];

    //1.初始化fd_set(readFds)
    //2.初始化幾個訊息連結串列(watch_table/pending_list/timer_list)
    ril_event_init();
    ...
    //使RIL_startEventLoop退出
    s_started = 1;
    pthread_cond_broadcast(&s_startupCond);
    ...
    //獲得讀&寫管道
    ret = pipe(filedes);
    ...
    s_fdWakeupRead = filedes[0];
    s_fdWakeupWrite = filedes[1];

    fcntl(s_fdWakeupRead, F_SETFL, O_NONBLOCK);

    //初始化一個event,回撥函式為processWakeupCallback
    ril_event_set (&s_wakeupfd_event, s_fdWakeupRead, true, processWakeupCallback, NULL);
    //1.向watch_list加入這個event
    //2.向fd_set加入s_fdWakeupRead,由於persist為true,此fd和event會一直保留
    //3.呼叫triggerEvLoop,向s_fdWakeupWrite寫入字元,(將會喚醒select)
    rilEventAddWakeup (&s_wakeupfd_event);

    //無限迴圈的event loop,select fd_set(readFds)
    ril_event_loop();
    RLOGE ("error in event_loop_base errno:%d", errno);
    // kill self to restart on error
    kill(0, SIGKILL);

    return NULL;
}

接下來分3節解析上面提到的abc3個處理。


3.2.1. ril_event_init 為eventLoop初始化資料

此函式作用是初始化資料,具體來看程式碼:

void ril_event_init()
{
    FD_ZERO(&readFds); //重置fd_set
    //清空3個ril_event連結串列結構體
    init_list(&timer_list); 
    init_list(&pending_list);
    memset(watch_table, 0, sizeof(watch_table));
}


//watch_table陣列設定了上限大小為8
#define MAX_FD_EVENTS 8
static struct ril_event * watch_table[MAX_FD_EVENTS];
static struct ril_event timer_list;
static struct ril_event pending_list;

//ril_event是連結串列結構,
struct ril_event {
    struct ril_event *next;
    struct ril_event *prev;

    int fd;//會加入到readFds
    int index;
    bool persist;//決定ril_event及fd是否一直保留在watch_table和readFds中
    struct timeval timeout; //超時
    ril_event_cb func;//回撥函式
    void *param;
};

readFds是一個fd_set,後續連線客戶端後,socket fd會加入到這個set去,監聽輸入。

ril_event是連結串列結構的結構體,儲存了前後2個ril_event的指標,元素中還有fd、persist值、回撥函式,和空指標型別的param。
而看func在被具體呼叫時,引數就是ril_event自身的fd和param:
ev->func(ev->fd, 0, ev->param);

watch_table是ril_event陣列指標,顧名思義,就是儲存這需要觀察的ril_event。
使用ril_event_add(struct ril_event)函式,將一個ril_event加入到watch_table,event->fd也會加入到readFds中。

timer_list 是ril_event結構體,帶有超時屬性的event,使用到ev->timeout值。通過ril_timer_add函式加入,在eventLoop中判斷超時後處理。

pending_list 同樣是ril_event結構體,存放需要馬上被回撥的event。eventLoop中select返回後,watch_table和timer_list中需要進行回撥的event都會加入到這裡,在回撥完成後,便會移除。

看完後面的流程再返回這裡看這幾個struct會更清晰。


3.2.2 建立pipe

上一步中準備好基礎資料後,接下來向fd_set加入讀管道,同時加入一個ril_event到watch_table。具體作用還不明確,先看流程。

執行pipe函式獲得讀寫fd:s_fdWakeupRead 及 s_fdWakeupWrite

    ret = pipe(filedes);
    ...
    s_fdWakeupRead = filedes[0];
    s_fdWakeupWrite = filedes[1];

執行ril_event_set函式:初始化一個ril_event,fd為s_fdWakeupRead ,回撥函式為processWakeupCallback,persist值為true(決定了event的fd不會從fd_set移除,獲得持續監聽和觸發回撥的能力)

//初始化一個event,引數傳入fd、persist及回撥函式
void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param)
{
    memset(ev, 0, sizeof(struct ril_event));
    ev->fd = fd;
    ev->index = -1;
    ev->persist = persist;
    ev->func = func;
    ev->param = param;
    fcntl(fd, F_SETFL, O_NONBLOCK);
}

原來processWakeupCallback做的操作是從s_fdWakeupRead中讀出資料,相當於清空管道。這樣來看這個ril_event存在的作用就是清道夫了。

static void processWakeupCallback(int fd, short flags, void *param) {
    char buff[16];
    int ret;
    /* empty our wakeup socket out */
    do {
        ret = read(s_fdWakeupRead, &buff, sizeof(buff));
    } while (ret > 0 || (ret < 0 && errno == EINTR));
}

event建立好後,接下來執行rilEventAddWakeup:將s_fdWakeupRead加入readFds集合,event加入到watch_table,然後向s_fdWakeupWrite 寫入資料。

//顧名思義rilEventAddWakeup做了add和wakeup兩件事情
static void rilEventAddWakeup(struct ril_event *ev) {
    ril_event_add(ev);
    triggerEvLoop();
}

//將event新增如watch_table,並將event->fd加入readFds集合
void ril_event_add(struct ril_event * ev)
{
    ...
    for (int i = 0; i < MAX_FD_EVENTS; i++) {
        if (watch_table[i] == NULL) {
            watch_table[i] = ev;
            ev->index = i;
            dump_event(ev);
            FD_SET(ev->fd, &readFds);
            if (ev->fd >= nfds) nfds = ev->fd+1;
            break;
        }
    }
    ...
}

//向s_fdWakeupWrite寫入空字元喚醒
static void triggerEvLoop() {
    int ret;
    if (!pthread_equal(pthread_self(), s_tid_dispatch)) {
         do {
            ret = write (s_fdWakeupWrite, " ", 1);
         } while (ret < 0 && errno == EINTR);
    }
}

現在只等待有人來select和處理這個event了。


3.2.3 ril_event_loop 監聽socket訊息的迴圈

ril_event_loop函式進入for迴圈後,將等待select函式返回。(因為在3.2.2中已經加入了一個event,這裡會馬上返回。)
待select返回後,將timer_list中超時的event和watch_table中event加入到pending_list中待處理。最後遍歷pending list,執行所有ril event的回撥函式。
然後,迴圈會進入下一輪。

這裡將會具體看到event中fd、persist、func等成員如何被使用。

void ril_event_loop()
{
    int n;
    fd_set rfds;
    struct timeval tv;
    struct timeval * ptv;

    for (;;) {
        memcpy(&rfds, &readFds, sizeof(fd_set));
        if (-1 == calcNextTimeout(&tv)) {
            ptv = NULL;
        } else {
            ptv = &tv;
        }
        //select等待返回
        n = select(nfds, &rfds, NULL, NULL, ptv);
        if (n < 0) {
            if (errno == EINTR) continue;
            return;
        }

        // Check for timeouts
        processTimeouts();
        // Check for read-ready
        processReadReadies(&rfds, n);
        // Fire away
        firePending();
    }
}

接下來看後面3個函式如何處理timer_list, watch_table和pending_list中的event。

static void processTimeouts()
{
    MUTEX_ACQUIRE();
    struct timeval now;
    struct ril_event * tev = timer_list.next;
    struct ril_event * next;

    getNow(&now);
    // walk list, see if now >= ev->timeout for any events
    while ((tev != &timer_list) && (timercmp(&now, &tev->timeout, >))) {//判斷是否已經timeout
        // Timer expired
        next = tev->next;
        //從timer_list移除該event
        removeFromList(tev);
        //將event加到pending_list待處理
        addToList(tev, &pending_list);
        tev = next;
    }
    MUTEX_RELEASE();
}

static void processReadReadies(fd_set * rfds, int n)
{
    MUTEX_ACQUIRE();
    for (int i = 0; (i < MAX_FD_EVENTS) && (n > 0); i++) {
        struct ril_event * rev = watch_table[i];
        if (rev != NULL && FD_ISSET(rev->fd, rfds)) {
            //event加入pending_list
            addToList(rev, &pending_list);
            if (rev->persist == false) {//判斷persist
                //從fd_set移除fd
                //並從watch_table移除該event
                removeWatch(rev, i);
            }
            n--;
        }
    }
    MUTEX_RELEASE();
}

static void firePending()
{
    struct ril_event * ev = pending_list.next;
    while (ev != &pending_list) {
        struct ril_event * next = ev->next;
        //從pending_list中移除該event
        removeFromList(ev);
        //觸發event的回撥方法
        ev->func(ev->fd, 0, ev->param);
        //繼續處理下一個
        ev = next;
    }
}

此圖大致描繪了這個loop的工作流程。
這裡寫圖片描述

eventloop已經準備妥當,回到rild mian函式中,接下來就是關聯vendor ril的處理了。


3.3 初始化Qcril並獲取回撥函式

rild和qcril之間通過確定的幾個介面函式來互相交流,現在rild需要和qcril互相交換各自的介面列表。
rild.c呼叫qcril RIL_Init完成了這個工作。

rilInit =
        (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))
        dlsym(dlHandle, "RIL_Init");
funcs = rilInit(&s_rilEnv, argc, rilArgv);

Ril_Init的第一個引數為s_rilEnv,就是作為rild執行response處理的函式表,qcril需要上報訊息給rild時,就可以通過這些函式實現:

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

RIL_Env 定義如下:

ril.h
struct RIL_Env {
    void (*OnRequestComplete)(RIL_Token t, RIL_Errno e,void *response, size_t responselen);

    void (*OnUnsolicitedResponse)(int unsolResponse, const void *data, size_t datalen, RIL_SOCKET_ID socket_id);

    void (*RequestTimedCallback) (RIL_TimedCallback callback,void *param, const struct timeval *relativeTime);

    void (*OnRequestAck) (RIL_Token t);
};

接著進去qcril,來看RIL_Init的流程:
1. 儲存rild傳入的回撥函式。
2. 初始化qcril環境。
3. 啟動子執行緒執行迴圈接收從Modem上報的訊息,並處理(類似rild的eventloop)。
4. 返回qcril的回撥函式給rild,作為rild下發request的入口。

qcril.c

const RIL_RadioFunctions *RIL_Init
(
  const struct RIL_Env *env,
  int argc,
  char **argv
)
{
   ...
  //RIL_Env是rild.c傳入的引數,是rild側的回撥函式表
  //這裡qcril_response_api陣列下標可以理解成對應著卡槽
  qcril_response_api[ QCRIL_DEFAULT_INSTANCE_ID ] = (struct RIL_Env *) env;
  qcril_response_api[ QCRIL_SECOND_INSTANCE_ID ] = (struct RIL_Env *) env;

  // flow control
  //內部是一些設定及其取值範圍,此處具體作用還不太明確
  qmi_ril_fw_android_request_flow_control_init();

  //初始化pending_unsol_resp_list並分配空間
  qmi_ril_init_android_unsol_resp_pending_list();

  //建立子執行緒並啟動訊息迴圈處理來自Modem的訊息,實現類似於Rild的eventloop
  //訊息迴圈還沒有實際開始工作。
  qcril_event_init();

  //初始化qcril中所有模組及引數,包括資料庫、ims、voice等模組的引數狀態等
  qcril_init(c_argc, c_argv);

  //使event迴圈開始工作
  qcril_event_start();
  ...
  //返回qcril的回撥函式處理入口
  return &qcril_request_api[ QCRIL_DEFAULT_INSTANCE_ID ];

} /* RIL_Init() */

先看下返回的qcril_request_api,這是是一個RIL_RadioFunctions 結構體:

ril.h
typedef struct {
    int version;        /* set to RIL_VERSION */
    RIL_RequestFunc onRequest;
    RIL_RadioStateRequest onStateRequest;
    RIL_Supports supports;
    RIL_Cancel onCancel;
    RIL_GetVersion getVersion;
} RIL_RadioFunctions;

qcril_request_api就是提供給rild下發request的入口:

static const RIL_RadioFunctions qcril_request_api[] = {
  { QCRIL_RIL_VERSION, onRequest_rid, currentState_rid, onSupports_rid, onCancel_rid, getVersion_rid }
};

回頭看訊息迴圈的定義和啟動。
qcril_event_init中起了子執行緒,執行qcril_event_main函式進入迴圈。

void qcril_event_init( void )
{
  pthread_attr_t attr;
  int ret;
  ...
  pthread_attr_init (&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  //啟動子執行緒執行qcril_event_main
  ret = pthread_create(&qcril_event.tid, &attr, qcril_event_main, NULL);
  pthread_attr_destroy( &attr );
  ...
  //等待qcril_event_main被執行
  while (qcril_event.started == 0)
  {
    pthread_cond_wait(&qcril_event_startupCond, &qcril_event.startup_mutex);
  }

} /* qcril_event_init() */

qcril_event_main所執行的操作:
1. 初始化qcril_event.list,這是名為qcril_event_buf的結構體,以鏈的方式關聯訊息。
2. 初始化fd_set,建立讀寫管道。
3. 啟動訊息迴圈,使用select fd_set方式監聽訊息。
4. select返回後,清空讀管道,並迴圈處理所有訊息。

與rild的 eventLoop比較類似。

static void *qcril_event_main
(
  QCRIL_UNUSED(void *param)
)
{
  int ret;
  int filedes[2];
  int n;
  fd_set rfds;
  qcril_event_type *ev;
  char buff[16];
  ...
  //初始化qcril_event_type,這是個訊息結構體,同樣以連結串列方式關聯其他訊息
  qcril_event_init_list(&qcril_event.list);

  //重置fd_set
  FD_ZERO(&qcril_event.readFds); /* Needed to use select() system call */

  ...
  qcril_event.started = 1;

  //建立讀寫管道
  ret = pipe(filedes);
  ...
  qcril_event.fdWakeupRead = filedes[0];
  qcril_event.fdWakeupWrite = filedes[1];
  fcntl(qcril_event.fdWakeupRead, F_SETFL, O_NONBLOCK);

  //fdWakeupRead加入到readFds
  FD_SET(qcril_event.fdWakeupRead, &qcril_event.readFds);

  pthread_cond_broadcast(&qcril_event_startupCond);

  //此處block,等待qcril_event_start()呼叫後才能繼續執行
  while (qcril_event.started < 2)
  {
    pthread_cond_wait(&qcril_event_startupCond, &qcril_event.startup_mutex);
  }
  ...

  //訊息迴圈
  for (;;)
  {
    memcpy(&rfds, &qcril_event.readFds, sizeof(fd_set));

    /* Block waiting for a event to be put on the queue */
    n = select(qcril_event.fdWakeupRead + 1, &rfds, NULL, NULL, NULL);
    ...
    /* Empty the socket */
    do
    { 
      //清空管道
      ret = read(qcril_event.fdWakeupRead, &buff, sizeof(buff));
    } while (ret > 0 || (ret < 0 && errno == EINTR));
    ...
    //處理訊息
    do
    {
      if ( ( NULL != ( ev = qcril_event.list.next ) && ( ev != &qcril_event.list ) ) )
      {
        //從qcril_event.list中去掉本次的event
        qcril_event_remove_from_list( ev );
        ...
        //處理event
        err_no = qcril_process_event( ev->instance_id, ev->modem_id, ev->event_id, ev->data, ev->datalen, ev->t );
        ...
        boolean to_check_ev_data_free = TRUE;
        ...

        //釋放資源
        if ( to_check_ev_data_free && 
             ev->data_must_be_freed && ev->data )
        {
          qcril_free( ev->data );
        }
        qcril_free( ev );
      }
      go_on = ( ( 
      NULL != ( ev =qcril_event.list.next ) && 
      ( ev != &qcril_event.list ) ) );
     //迴圈處理完qcril_event.list中所有訊息
    } while ( go_on );
    ...
  }
  ...
  return NULL;
} /* qcril_event_main() */

看過rild eventLoop之後,其實qcril_event_main的流程比較明顯,這裡就不再畫圖了。

這裡的幾個細節需要記錄下:
1. 這裡用到的訊息列表qcril_event.list,是名為qcril_event_buf的結構體,以鏈的方式關聯訊息。

//訊息結構體同樣是以連結串列形式儲存訊息
typedef struct qcril_event_buf
{
  struct qcril_event_buf *next;
  struct qcril_event_buf *prev;
  qcril_instance_id_e_type instance_id;//訊息id
  qcril_modem_id_e_type modem_id;
  qcril_evt_e_type event_id;
  void *data;//訊息附帶的內容
  size_t datalen;
  RIL_Token t;
  boolean data_must_be_freed;
} qcril_event_type;

2.select返回後,先清空read管道,然後遍歷qcril_event.list,並分發給qcril_process_event函式處理。執行結束後進行下一輪迴圈,等待select返回。

現在qcril完成了初始化,訊息處理迴圈也已經啟動,RIL_Init函式最終返回了qcril_request_api。
接下來看rild.c最後一步如何處理。


3.4 RIL_register-註冊qcril回撥並與phone程式連線

RIL_register從命名可知,肯定是儲存了qcril RIL_Init中返回的回撥函式,但實際上還進行了建立socket和連線Phone程式的處理。

Ril.cpp
extern "C" void
RIL_register (const RIL_RadioFunctions *callbacks) {
    int ret;
    int flags;

    ...
    //儲存callbacks函式結構體到本地
    memcpy(&s_callbacks, callbacks, sizeof (RIL_RadioFunctions));

    //初始化socket的引數
    s_ril_param_socket = {
          RIL_SOCKET_1,             /* socket_id */
          -1,                       /* fdListen */
          -1,                       /* fdCommand */
          PHONE_PROCESS,            /* processName */
       &s_commands_event,        /* commands_event */
       &s_listen_event,          /* listen_event */
       processCommandsCallback,  
          NULL,                     /* p_rs */
          RIL_TELEPHONY_SOCKET      /* type */
     };


#if (SIM_COUNT >= 2)
    s_ril_param_socket2 = {
          RIL_SOCKET_2,               /* socket_id */
          -1,                         /* fdListen */
          -1,                         /* fdCommand */
        PHONE_PROCESS,              /* processName */
     &s_commands_event_socket2,  /* commands_event */
     &s_listen_event_socket2,    /* listen_event */
        processCommandsCallback,  
         NULL,                       /* p_rs */
         RIL_TELEPHONY_SOCKET        /* type */
    };
#endif
    ...

    //開始監聽socket
    startListen(RIL_SOCKET_1, &s_ril_param_socket);

#if (SIM_COUNT >= 2)
    // start listen socket2
    startListen(RIL_SOCKET_2, &s_ril_param_socket2);
#endif /* (SIM_COUNT == 2) */

    //啟動一個用於debug的socket,猜測是高通工具會使用到這個介面
#if 1
    char *inst = NULL;
    if (strlen(RIL_getRilSocketName()) >= strlen(SOCKET_NAME_RIL)) {
        inst = RIL_getRilSocketName() + strlen(SOCKET_NAME_RIL);
    }

    char rildebug[MAX_DEBUG_SOCKET_NAME_LENGTH] = SOCKET_NAME_RIL_DEBUG;
    if (inst != NULL) {
        strlcat(rildebug, inst, MAX_DEBUG_SOCKET_NAME_LENGTH);
    }

    s_fdDebug = android_get_control_socket(rildebug);
    ...
    ret = listen(s_fdDebug, 4);
    ...
    ril_event_set (&s_debug_event, s_fdDebug, true,
                debugCallback, NULL);


    rilEventAddWakeup (&s_debug_event);
#endif
}

開始Listen之前,初始化的s_ril_param_socket結構體是SocketListenParam類,具體結構及元素作用如下:

typedef struct SocketListenParam {
    RIL_SOCKET_ID socket_id;
    int fdListen; //用於監聽客戶端連線請求的fd
    int fdCommand; //客戶端連線後,接收訊息的fd
    const char* processName; //指定這個socket接受哪個程式的連線
    struct ril_event* commands_event; //fdCommand對應的event
    struct ril_event* listen_event; //fdListen對應的event
    void (*processCommandsCallback)(int fd, short flags, void *param); //處理客戶端請求的回撥函式
    RecordStream *p_rs; //客戶端請求的具體資料
    RIL_SOCKET_TYPE type;//指定socket是Telephony還是SAP(與藍芽相關)型別
} SocketListenParam;

接下來深入看startListen。這裡啟動了socket服務端,等待client端接入。但並沒有看到accept()函式,留意最後2句程式碼處設定了一個ril_event並喚醒了eventLoop,回撥函式是listenCallback,persist值是false,這個event只會執行一次,應該跟連線客戶端有關。
繼續看listenCallback。

static void startListen(RIL_SOCKET_ID socket_id, SocketListenParam* socket_listen_p) {
    int fdListen = -1;
    int ret;
    char socket_name[10];

    memset(socket_name, 0, sizeof(char)*10);

    switch(socket_id) {
        case RIL_SOCKET_1:
            strncpy(socket_name, RIL_getRilSocketName(), 9);
            break;
    #if (SIM_COUNT >= 2)
        case RIL_SOCKET_2:
            strncpy(socket_name, SOCKET2_NAME_RIL, 9);
            break;
    ...
    }

    //建立一個socket
    fdListen = android_get_control_socket(socket_name);
    ...
    //開始接受客戶端連線
    ret = listen(fdListen, 4);
    ..
    //監聽fd儲存到SocketListenParam 
    socket_listen_p->fdListen = fdListen;

    //將fd和listen event加入watch table,並喚醒eventLoop
    ril_event_set (socket_listen_p->listen_event, fdListen, false,
                listenCallback, socket_listen_p);
    rilEventAddWakeup (socket_listen_p->listen_event);
}

在listenCallback中成功accept()函式,流程進入最後階段:
1. 獲取到client端的fd(如果返回錯誤則重新執行一次listenCallback)
2. 初始化用於監聽和執行上層下發命令的ril_event,persist為true,回撥方法為processCommandsCallback,持續監聽來自客戶端的訊息。
3. 上報Radio初始狀態到客戶端

static void listenCallback (int fd, short flags, void *param) {
    int ret;
    int err;
    int is_phone_socket;
    int fdCommand = -1;
    const char* processName;
    RecordStream *p_rs;
    MySocketListenParam* listenParam;
    RilSocket *sapSocket = NULL;
    socketClient *sClient = NULL;


    SocketListenParam *p_info = (SocketListenParam *)param;

    if(RIL_SAP_SOCKET == p_info->type) {
        listenParam = (MySocketListenParam *)param;
        sapSocket = listenParam->socket;
    }

    struct sockaddr_un peeraddr;
    socklen_t socklen = sizeof (peeraddr);
    struct ucred creds;
    socklen_t szCreds = sizeof(creds);
    struct passwd *pwd = NULL;

    ...
    //獲取等待中的客戶端fd
    fdCommand = accept(fd, (sockaddr *) &peeraddr, &socklen);

    //返回錯誤,重新執行一次
    if (fdCommand < 0 ) {
        RLOGE("Error on accept() errno:%d", errno);
        /* start listening for new connections again */
        if(NULL == sapSocket) {
            rilEventAddWakeup(p_info->listen_event);
        } else {
            rilEventAddWakeup(sapSocket->getListenEvent());
        }
        return;
    }
    ...

    err = getsockopt(fdCommand, SOL_SOCKET, SO_PEERCRED, &creds, &szCreds);
    ...

    ret = fcntl(fdCommand, F_SETFL, O_NONBLOCK);
    ...
    //已經獲得客戶端連線,listen_event的使命暫時結束
    //接下來開始從fdCommand中獲取客戶端下發的命令,並執行處理
    if(NULL == sapSocket) {
        p_info->fdCommand = fdCommand;
        p_rs = record_stream_new(p_info->fdCommand, MAX_COMMAND_BYTES);
        p_info->p_rs = p_rs;
        //persist為1,持續監聽來自fdCommand的資料
    //回撥函式為processCommandsCallback
        ril_event_set (p_info->commands_event, p_info->fdCommand, 1,
        p_info->processCommandsCallback, p_info);
        rilEventAddWakeup (p_info->commands_event);
        //連線客戶端成功,上報Radio狀態給上層
        onNewCommandConnect(p_info->socket_id);
    }
    ...
}

最後的最後,看看成功連線Phone程式後,RIL向上層上報了什麼訊息:

//包括RIL_CONNECTED、RADIO_STATE_CHANGED、NITZ資料
//設定PROPERTY_RIL_IMPL
static void onNewCommandConnect(RIL_SOCKET_ID socket_id) {
    // Inform we are connected and the ril version
    int rilVer = s_callbacks.version;
    RIL_UNSOL_RESPONSE(RIL_UNSOL_RIL_CONNECTED,
                                    &rilVer, sizeof(rilVer), socket_id);

    // implicit radio state changed
    RIL_UNSOL_RESPONSE(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED,
                                    NULL, 0, socket_id);

    // Send last NITZ time data, in case it was missed
    if (s_lastNITZTimeData != NULL) {
        sendResponseRaw(s_lastNITZTimeData, s_lastNITZTimeDataSize, socket_id);

        free(s_lastNITZTimeData);
        s_lastNITZTimeData = NULL;
    }

    // Get version string
    if (s_callbacks.getVersion != NULL) {
        const char *version;
        version = s_callbacks.getVersion();
        property_set(PROPERTY_RIL_IMPL, version);
    } else {
        property_set(PROPERTY_RIL_IMPL, "unavailable");
    }

}

現在Phone程式已經可以通過RILJ來與RILD通訊了,RILJ與qcril也建立了連線,至於qcril的event迴圈如何獲得modem的訊息,這裡順便再引申分析一下。


3.5 qcril event迴圈從何訊息輸入?

從3.4的分析發現qcril_event_main函式中有讀寫管道,而fd read也被設定到了Fd_set中:

  qcril_event.fdWakeupRead = filedes[0];
  qcril_event.fdWakeupWrite = filedes[1];

  fcntl(qcril_event.fdWakeupRead, F_SETFL, O_NONBLOCK);
  FD_SET(qcril_event.fdWakeupRead, &qcril_event.readFds);

要知道訊息在哪裡輸入,這裡先找到哪裡喚醒了select,於是搜尋fdWakeupWrite尋找往管道寫入資料的地方, 定位到qcril_event.c檔案的qcril_event_queue函式:

qcril_event.c
IxErrnoType qcril_event_queue
(
  qcril_instance_id_e_type instance_id,
  qcril_modem_id_e_type modem_id,
  qcril_data_src_e_type data_src,
  qcril_evt_e_type event_id,
  void *data,
  size_t datalen,
  RIL_Token t
)
{
  int ret;
  qcril_event_type *ev;
  IxErrnoType result;

  do
  {
    result = E_SUCCESS;
    //初始化一個qcril_event並填充資料
    ev = (qcril_event_type *) qcril_malloc( sizeof(qcril_event_type) );
    ...
    ev->instance_id = instance_id;
    ev->modem_id = modem_id;
    ev->event_id = event_id;
    ev->t = t;
    ev->data_must_be_freed = data_src;

    if ( data == NULL )
    {
      ev->data = NULL;
      ev->datalen = 0;
    }
    ...
    else
    {
      ev->data = data;
      ev->datalen = datalen;
    }

    ...
    //向list加入event
    qcril_event_add_to_list( ev, &qcril_event.list );
    ...

    if (!pthread_equal(pthread_self(), qcril_event.tid))
    {
      do
      {
        //喚醒event迴圈
        ret = write (qcril_event.fdWakeupWrite, " ", 1);
      } while (ret < 0 && errno == EINTR);
    }

  } while(0);
  ...
  return result;
} /* qcril_event_queue */

尋根問底,繼續搜尋qcril_event_queue的呼叫,但返回的結果實在太多。
這裡寫圖片描述

事已至此總不能半途而棄,抱著不放棄的精神,再檢視了其中一些結果,並找到了一處比較典型的呼叫作為例子。
從呼叫者函式的命名來看應該是跟qmi有關聯的,深入調查後也證實了這個猜測,但超出這篇文章的範圍,更具體的分析留待後面來說。

Qcril_data_netctrl.c

void qcril_data_post_qmi_events(
  qcril_data_call_info_tbl_type * info_tbl_ptr,
  dsi_net_evt_t net_evt
)
{
...
if(E_SUCCESS != qcril_event_queue( instance_id,
                                       modem_id,
                                       QCRIL_DATA_NOT_ON_STACK,
                                       QCRIL_EVT_DATA_EVENT_CALLBACK,
                                       ( void * )evt,
                                       sizeof( qcril_data_event_data_t ),
                                       tok ))
    {
      /* free allocated memory for immediate failure from qcril_event_queue */
      free(evt);
      break;
    }
..
}

qcril的event main迴圈輸入及訊息處理過程:
這裡寫圖片描述


4.小結

rild main函式已經執行完畢,rild與phone程式通過socket連線,並建立了一個訊息迴圈來監聽處理phone下發的指令;而qcril也啟動了一個訊息迴圈,監聽處理modem上報的訊息。

至於目前想到的一些問題,有時間會再探究:
MTK的rild是怎樣工作的?
訊息具體怎麼在rild和qcril中傳遞和處理?
qcril如何與modem通訊(qmi)?
如何新增一個新的訊息?

相關文章