STK Telephony学习


1概述

前言

本文档作为一个学习输出,通过分析STK不同层级间命令的传递方式,总结STK模块在Telephony部分的知识。涉及framework与package目录下的代码。

背景

STK即SIM Tool Kit,它提供一系列用于移动设备与SIM卡间交互的机制。通过这些机制,支持STK的手机可以操作SIM卡里的应用。其Framework层负责消息的转化和传递,实现RIL层与应用层间的交互。

简介

图解

目前的手机硬件结构大多分AP端与BP端。AP端运行操作系统以及各种基于操作系统的应用,BP端负责无线通信基本能力,Telephony的业务跨越了AP与BP,是做协议开发主要涉及的内容,具体如下图。

Android Telephony 

从这个结构图可以看出:

- Telephony主要运行在Linux Kernel上的用户空间。

- 与Android系统分层对应

- BP端是处理无线通信部分,即modem部分。

组成

1. RIL层指的是工程目录下hardware/ril部分,又称RILC,由C代码组成,与上层java进行通信,具体的消息传递方式在2.3部分讲述。

2. Telephony Framework分为telecom和telephony,相关目录为:

.../frameworks/base/telephony/java/com/android/internal/telephony/

.../frameworks/base/telecomm/java/com/android/internal/telecom/

.../frameworks/opt/telephony/src/java/com/android/internal/telephony/

  Stk模块相关代码为第三个目录下的CatService.java以及RIL.java,其中CatService.java负责RIL层与应用层间的消息传递;RIL.java为RIL层的上层结构,又称RILJ,与Telephony做最直接的交互,作为RIL的消息入口。

3.Apps层存放在目录/packages/services/Telephony/与/packages/services/Telecom/,不涉及STK模块。

接下来根据Telephony的分层结构进行单独的分析。

Telephony Framework

这一部分实现了STK APP与RIL层的信息同步,在modem端上传SIM卡命令后,通过广播的方式将命令发送至应用层处理,同时将应用层下发的TR传至RILC,最终实现消息的转换与传递(下发的还有Envelope Command以及Terminal Profile)。负责处理STK业务逻辑的实类为CatService。

CatService

类图


CatService


CatSerice继承Handler,实现了AppInterface接口。

通过handler接收应用层消息

STK APP通过调用接口函数onCmdResponse()发送消息至CatService,具体代码实现:

    public synchronized void onCmdResponse(CatResponseMessage resMsg) {

        if (resMsg == null) {

            return;

        }

        // queue a response message.

        Message msg =obtainMessage(MSG_ID_RESPONSE, resMsg);

        msg.sendToTarget();

    }

可以看到最终通过sendToTarget将消息发至自身的消息队列。(这里用法与直接new Message 再调用sendMessage不同,跟进前者代码发现最终走的还是sendMessage,区别在于没有新构造一个消息对象,根据注释解释,前者更有效率)

通过Handler接收RILJ的消息

回到CatService的构建函数,可以看到:

     // Register ril events handling.

      mCmdIf.setOnCatSessionEnd(this,MSG_ID_SESSION_END, null);

      mCmdIf.setOnCatProactiveCmd(this,MSG_ID_PROACTIVE_COMMAND, null);

      mCmdIf.setOnCatEvent(this,MSG_ID_EVENT_NOTIFY, null);

      mCmdIf.setOnCatCallSetUp(this,MSG_ID_CALL_SETUP, null);

     //mCmdIf.setOnSimRefresh(this,MSG_ID_REFRESH, null);

这里调用了CommandInterface的函数去注册实现观察者模式,具体实现在RILJ的父类BaseCommands当中:

    public void setOnCatProactiveCmd(Handler h,int what, Object obj) {

        mCatProCmdRegistrant = new Registrant(h, what, obj);

    }

这里的Handler即为CatService自身,即将自身所需接收的消息注册给RILJ。

关键函数

1. getInstance::向外提供CatService的单例。

2. handlemessage:处理来自APP以及RILJ的消息。

3. sendStartDecodingMessageParams:接口函数,将RIL Message解码成CommandParams Objects。

4. handleRilMsg:处理解码器转换后的RIL层消息。

5. handleCommand:处理具体的命令。

6. handleCmdResponse:处理APP发来的Repsonse。

7. sendTerminalResponse:发送TR。、

消息处理

完整的CatService消息处理机制如下图:


CatService消息处理

RILJ

类图


RILJ类图

关键函数

BaseCommands: 实现CommandsInterface的大部分接口函数,涉及多个RegistrantList与Registrant,向外提供多个事件的观察者模式注册函数。查看Registrant文件,发现就是对Handler的封装,由注册的一方指定一个需要通知的事件,另一方只需要调用notifyRegistrant()来实现message的创建、填充、发送过程。RegistrantList实现多个注册对象的同时通知。

如: setOnCatProactiveCmd:主动式命令的监听注册,RILJ在接收到RILC的主动式命令消息后,由Registrant向注册时指定的Handler(这里指CatService)发送消息通知。

RILJ:将Telephony的业务封装成RILRequest并发至RILC。

1. sendTerminalResponse:将主动式命令的处理结果封装后发送至RILC。

2. getRadioProxy: 初始化IRadio相关服务。

3. obtainRequest:创建RILRequest对象。

4. processResponse: 处理RILC请求返回的结果。

5. processIndication: 处理RILC上报的消息。

完整流程

贴上完整的主动式命令处理时Telephony部分的传输过程:

Proactive Command
Terminal Response

RIL层

这一部分主要是参考《Android Telephony 原理解析与开发指南》一书进行学习。

进入RILC的目录,可以看到代码结构分为四部分,分别是include、libril、reference-ril、rild。

- Include:ril.h定义了RIL_Init、RIL_register等函数和其他结构体,以及RIL消息RIL_REQUEST_XXX和RIL_UNSOL_XXX。

- libril:ril_commands.h、ril_unsol_commands.h、ril.cpp、ril_service.cpp。编译输出libril.so。

- reference_ril:编译输出libreference-ril.so,实现了RIL AT命令的交互机制。

- rild:主要关注点,编译生成可执行文件rild。

RILC运行机制

消息分类

RIL中的消息类型,按照发起方分为Solicited以及UnSolicited。

1. Solicited是由AP端发起的消息,又根据传递方向分为Solicited Request和Solicited Response。

2. UnSolicited是由BP侧主动发起的消息。

这两种消息命名方式为RIL_REQUEST_ XXX、RIL_REQUEST_XXX,具体的消息定义查看frameworks/base/telephony/java/com/android/internal/telephony/RILConstants.java

通过Radio log可以查看具体的消息类型与传递方向,其中’<’,’>’表示发送的方向。:

Solicitesd:

RILJ: [4041]> RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING [SUB0]

RILJ: [4041]< RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING [SUB0]

UnSolicited:

RILJ: [UNSL]< UNSOL_STK_PROACTIVE_COMMAND [SUB0]

消息处理

1. Solicited消息

以2.2.3发送TR为例,RILJ最终调用的IRadio. sendTerminalResponseToSim(),该函数最终在

hardware/ril/libril/ril_service.cpp中实现:

Return<void> RadioImpl::sendTerminalResponseToSim(int32_t serial,

                    const hidl_string& commandResponse) {

#if VDBG

    RLOGD("sendTerminalResponseToSim: serial %d", serial);

#endif

    dispatchString(serial, mSlotId, RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE,

            commandResponse.c_str());

    return Void();

}

其中dispatchString函数:

bool dispatchString(int serial, int slotId, int request, const char * str) {

    RequestInfo *pRI = android::addRequestToList(serial, slotId, request);

    if (pRI == NULL) {

        return false;

    }

    char *pString;

    if (!copyHidlStringToRil(&pString, str, pRI)) {

        return false;

    }

    CALL_ONREQUEST(request, pString, sizeof(char *), pRI, slotId);

    memsetAndFreeStrings(1, pString);

    return true;

}

在RadioImpl中可看到所有的Solicited消息处理最终都调用了dispatchString()。

主要关注addRequestToList与CALL_ONREQUEST

addRequestToList:

RequestInfo *

addRequestToList(int serial, int slotId, int request) {

    RequestInfo *pRI;

    int ret;

    RIL_SOCKET_ID socket_id = (RIL_SOCKET_ID) slotId;

    /* Hook for current context */

    /* pendingRequestsMutextHook refer to &s_pendingRequestsMutex */

    pthread_mutex_t* pendingRequestsMutexHook = &s_pendingRequestsMutex;

    /* pendingRequestsHook refer to &s_pendingRequests */

    RequestInfo**    pendingRequestsHook = &s_pendingRequests;

    ......

    pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo));

    if (pRI == NULL) {

        RLOGE("Memory allocation failed for request %s", requestToString(request));

        return NULL;

    }

    pRI->token = serial;

    pRI->pCI = &(s_commands[request]);

    pRI->socket_id = socket_id;

    ret = pthread_mutex_lock(pendingRequestsMutexHook);

    assert (ret == 0);

    pRI->p_next = *pendingRequestsHook;

    *pendingRequestsHook = pRI;

    ret = pthread_mutex_unlock(pendingRequestsMutexHook);

    assert (ret == 0);

    return pRI;

}

其中s_commands[]定义为ril_commands.h,该头文件定义了RIL请求类型和回调函数,如{RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, radio::sendTerminalResponseToSimResponse}

即RILC会调用sendTerminalResponseToSimResponse作为Solicited Request消息的Response。

CALL_ONREQUEST:

ril_service.cpp的宏定义为:

#define CALL_ONREQUEST(a, b, c, d, e) s_vendorFunctions->onRequest((a), (b), (c), (d))

即CALL_ONREQUEST将调用s_vendorFunctions->onRequest, 进入该函数,发现没有对RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE做具体的处理,只调用RIL_onRequestComplete。

进入该函数的定义:

extern "C" void

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

    RequestInfo *pRI;

    int ret;

    RIL_SOCKET_ID socket_id = RIL_SOCKET_1;

    pRI = (RequestInfo *)t;

    if (!checkAndDequeueRequestInfoIfAck(pRI, false)) {

        RLOGE ("RIL_onRequestComplete: invalid RIL_Token");

        return;

    }

    socket_id = pRI->socket_id;

    appendPrintBuf("[%04d]< %s",

        pRI->token, requestToString(pRI->pCI->requestNumber));

    if (pRI->cancelled == 0) {

        int responseType;

        ......

        int rwlockRet = pthread_rwlock_rdlock(radioServiceRwlockPtr);

        assert(rwlockRet == 0);

        ret = pRI->pCI->responseFunction((int) socket_id,

                responseType, pRI->token, e, response, responselen);

        rwlockRet = pthread_rwlock_unlock(radioServiceRwlockPtr);

        assert(rwlockRet == 0);

    }

    free(pRI);

}

以上的主要处理流程是:调用checkAndDequeueRequestInfoIfAck找到RIL请求对应的RequestInfo,通过pRI->pCI->responseFunction发起XXXResponse的调用,即上一步addRequestToList指定的Solicited消息对应的Response函数。

跟进Telephony framework中RadioResponse里的sendTerminalResponseToSimResponse,可看到最终调用了RILJ的processResponseDone,完成response的发送。

2. UnSolicited消息

UnSolicited类型的消息不确定发起点,从RILJ往下跟进,在RadioIndication.java中可看到函数stkProactiveCommand,用于通知CAT并调用RILJ进行处理。

RadioIndication继承IRadioIndication.Stub,进入ril_service.cpp,radio::stkProactiveCommandInd调用了对应的stkProactiveCommand,实现了消息的传递。

消息传递图

下图即为RIL的消息处理机制,其中RILJ通过rild提供的IRadio服务发送消息,RILC通过phone提供的IRadioResponse和IRadioIndication发送消息,这一部分统称为HIDL,是基于Binder通信实现的。(AIDL,HIDL运作原理还需要进一步学习)

以IRadio为例:IRadio.hal中定义了多个函数接口,如sendTerminalResponseToSim。在ril_servise中RadioImpl再实现该函数。RILJ通过调用IRadio的接口实现消息的传递。

图中的3是RILC与Modem的交互方式,是由厂商实现,下文了解一下高通的实现方式。

RIL消息传递

与Modem通信

结构图

关于Telephony与Modem直接的通信,高通实现方式是QMI+QCRIL,QCRIL为RILC的组成部分,QMI属于Modem。

.该部分涉及高通文档,不确定是否为开源,暂时不贴图

QCRIL+QMI

Vendor RIL初始化

rild.c主函数main中获取了vendor RIL(QCRIL)的库以及初始化入口方法,并进行初始化与注册:

int main(int argc, char **argv) { 

    .......

    int i;

    const char *clientId = NULL;

    RLOGD("**RIL Daemon Started**");

    RLOGD("**RILd param count=%d**", argc);

    umask(S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH);

//1.获取QCRIL路径

    for (i = 1; i < argc ;) {

        if (0 == strcmp(argv[i], "-l") && (argc - i > 1)) {

            rilLibPath = argv[i + 1];

            i += 2;

        } else if (0 == strcmp(argv[i], "--")) {

            i++;

            hasLibArgs = 1;

            break;

        } else if (0 == strcmp(argv[i], "-c") &&  (argc - i > 1)) {

            clientId = argv[i+1];

            i += 2;

        } else {

            usage(argv[0]);

        }

    }

    .......


//2.打开vendor ril库

    dlHandle = dlopen(rilLibPath, RTLD_NOW);

    //3.启动消息循环

    RIL_startEventLoop();

    //4.获取vendor RIL的初始化入口方法

    rilInit =

        (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))

        dlsym(dlHandle, "RIL_Init");

    ......


//5.调用vendor RIL的RIL_Init方法,此方法会返回RIL_RadioFunctions

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

    RLOGD("RIL_Init rilInit completed");


//6.注册vendor RIL的处理方法

    RIL_register(funcs);

    ......

}

传入回调函数

上文中提到的funcs = rilInit(&s_rilEnv, argc, rilArgv)实际上调用了QCRIL的RIL_Init,传入了s_rilEnv,具体结构为:

static struct RIL_Env s_rilEnv = {

    RIL_onRequestComplete,

    RIL_onUnsolicitedResponse,

    RIL_requestTimedCallback,

    RIL_onRequestAck

};;

即传入rilc的方法供QCRIL回调,通过回调实现消息传递。

获取QCRIL的函数接口

查看QCRIL的RIL_Init,主要执行了以下几个函数:

qmi_ril_set_thread_name( pthread_self() , QMI_RIL_QMI_RILD_THREAD_NAME);

// Initialize the event thread */

qcril_event_init();

// Initialize QCRIL

qcril_init();

// Start event thread

qcril_event_start();

// start bootup if applicable

qmi_ril_initiate_bootup();

return &qcril_request_api[ QCRIL_DEFAULT_INSTANCE_ID ];

即初始化以及返回接口函数。

1. 其中qcril_event_int为初始化EventLoop线程,通过查看qcril_event_main的注释可知EventLoop用于读取并处理QMI的消息。

2. 通过return &qcril_request_api[ QCRIL_DEFAULT_INSTANCE_ID ]返回函数列表供RILC使用。

消息传递

以RILC到QMI为例

在QCRIL初始化时提供的qcril_request_api具体定义如下:

static const RIL_RadioFunctions qcril_request_api[] = {

  { QCRIL_RIL_VERSION, onRequest_rid, currentState_rid, onSupports_rid, onCancel_rid, getVersion_rid }

};

当RIL层有请求时,RILC调用其中的onRequest_rid:

static void onRequest_rid

(

  int request,

  void *data,

  size_t datalen,

  RIL_Token t

)

{

  onRequest( qmi_ril_process_instance_id, request, data, datalen, t );

}

实际上调用了onRequest,查看onRequest发现调用qcril_dispatch_event将消息发至对应的handler:

/* Dispatch the request to the appropriate handler */

(entry_ptr->handler)(params_ptr, &ret);

这里entry_ptr指的是qcril_dispatch_table_entry_type,该结构定义了各种消息类型下对应的调用方法,以发送TR为例:

{ QCRIL_REG_ALL_STATES( RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, qcril_gstk_qmi_request_stk_send_terminal_response ) }

查看该方法发现调用了QMI的qmi_client_send_msg_async,进一步调用qmi_service_msg发送消息至modem。

具体时序图如下:


RILC to QMI

消息从QMI到QCRIL的处理过程与上图类似:将消息发至QCRIL的队列、QCRIL调用qcril_dispatch_event将消息发至对应的handler,qcril_dispatch_table_entry_type这个结构不仅定义了上层消息对应的处理方法,还涉及modem发至QCRIL的各种消息。

总结

Telephony层涉及到的STK部分基本都是消息的传递,包括广播,Handler,HIDL。

RILJ以上的部分源码相对来说不难阅读和理解,且资料多。QCRIL+QMI部分仅根据log跟进了消息的传递。

以一张图总结整体的消息传递方式:


STK Telephony
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容