1概述
前言
本文档作为一个学习输出,通过分析STK不同层级间命令的传递方式,总结STK模块在Telephony部分的知识。涉及framework与package目录下的代码。
背景
STK即SIM Tool Kit,它提供一系列用于移动设备与SIM卡间交互的机制。通过这些机制,支持STK的手机可以操作SIM卡里的应用。其Framework层负责消息的转化和传递,实现RIL层与应用层间的交互。
简介
图解
目前的手机硬件结构大多分AP端与BP端。AP端运行操作系统以及各种基于操作系统的应用,BP端负责无线通信基本能力,Telephony的业务跨越了AP与BP,是做协议开发主要涉及的内容,具体如下图。
从这个结构图可以看出:
- 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
类图
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消息处理机制如下图:
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部分的传输过程:
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的交互方式,是由厂商实现,下文了解一下高通的实现方式。
与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。
具体时序图如下:
消息从QMI到QCRIL的处理过程与上图类似:将消息发至QCRIL的队列、QCRIL调用qcril_dispatch_event将消息发至对应的handler,qcril_dispatch_table_entry_type这个结构不仅定义了上层消息对应的处理方法,还涉及modem发至QCRIL的各种消息。
总结
Telephony层涉及到的STK部分基本都是消息的传递,包括广播,Handler,HIDL。
RILJ以上的部分源码相对来说不难阅读和理解,且资料多。QCRIL+QMI部分仅根据log跟进了消息的传递。
以一张图总结整体的消息传递方式: