Android移植RIL库的过程

Android移植RIL库的过程

参考链接:https://jingyan.baidu.com/article/3aed632e3a1ecb7010809128.html

主要记录了之前在RK3188 Android4.2.2里移植RIL库去支持LTE模块的全过程,包含了过程分析和问题处理。

工具/原料

  • 方案:RK3188

  • Linux内核:linux-3.0.36

  • OS:Android4.2.2

  • LTE模块:Kernel-i L200(FDD-LTE)

方法/步骤

  • 1.RIL运行流程

  • 在init.rc文件里有如下形式的service定义:

  • service ril-daemon /system/bin/rild -l reference-ril.so -- -d /dev/ttyUSB0

        class main

        socket rild stream 660 root radio

        socket rild-debug stream 660 radio system

        user root

        group radio cache inet misc audio log

  • 其中,service定义也可写为service ril-daemon /system/bin/rild,此时关于参数的获取可通过设置如下变量处理:

  • rild.libpath= /system/lib/libreference-ril.so

    rild.libargs= -d/dev/ttyUSB0

  • 在Android系统运行时会启动该服务,该服务会创建socket rild stream 660 root radio这样的一个socket,用于与framework里的上层代码进行通信,相应对应处理文件为:frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java,即RIL库与上层交互是通过Socket(socket rild),而与底层驱动是通过串口(/dev/ttyUSB0)。

  • 上面涉及到的rild服务源码位于hardware/ril/rild目录,而使用的RIL库对应源码位于hardware/ril/reference-ril目录,而两者交互时会使用到的libril.so源码位于hardware/ril/libril目录。

  • 下面是rild服务启动后加载RIL库及AT指令、上报数据的处理流程分析:

  • rild服务的启动流程:

    Android移植RIL库的过程

  • 其中,“开启eventLoop线程”(libril.so)的流程如下:

    Android移植RIL库的过程

  • 如果上面线程的循环处理失败而退出循环,将会调用kill(0,SIGKILL)杀死自己所在进程后重启。循环中主要是处理timer_list、pending_list、watch_table三个队列,其中对pending_list队列的处理将是每个event的最后归宿,即会调用其回调函数并清出队列。

  • “运行RIL库的RIL_Init函数,并返回funcs回调函数指针”部分会调用到reference-ril.so里的RIL_Init()函数,该函数执行流程如下:

    Android移植RIL库的过程

  • 其中,s_rilenv对应到rild.c文件里s_rilEnv的内容如下:

  • static struct RIL_Env s_rilEnv = {

        RIL_onRequestComplete,

        RIL_onUnsolicitedResponse,

        RIL_requestTimedCallback

    };

  • 而mainLoop()的执行流程如下:

    Android移植RIL库的过程

  • 上面的at_open(fd,onUnsolicited)处理过程为:初始化s_fd为AT口对应的fd,s_unsolHandler为onUnsolicited,然后再创建线程readerLoop()。

  • 而readerLoop()则是循环从AT口(s_fd)或缓存里读一行AT指令数据(readline()),如为NULL则退出循环,接下来对AT指令数据进行处理,如为SMS非请求数据(以+CMT:、+CDS:或+CBM:开头)则再读一行,如为NULL则退出循环,否则调用s_unsolHandler处理这两次读到的数据,如果不是SMS,则调用processLine()处理读到的AT数据,其最终是调用s_unsolHandler来处理非上层请求数据(即AT口自动返回数据,用于主动向上层上报)或将返回数据加入到sp_response->p_intermediates链头,让RIL库进行处理相应onRequest()上报。当循环退出后会设置s_readerClosed=1并发s_commandcond信号(在断ReaderClosed()中)。

  • 上面的RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0)相当于internalRequestTimedCallback()函数,其执行流程为:初始化p_info,配置其回调函数及参数成员p_callback和userParam为initializeCallback和NULL,创建p_info->event,其fd为-1,非persist,该event对应回调函数及参数为userTimerCallback(该回调处理最终调用上面的p_callback和参数userParam)和p_info,并加入timer_list函列(按升序),然后往s_fdWakeupWrite写入一空格,以唤醒队列处理线程,然后返回p_info。

  • 而initializeCallback()的处理流程为:setRadioState(RADIO_STATE_OFF)先向上层上报RADIO状态为OFF,然后发AT指令测试AT口能不能握手成功,成功后会再发送一系列AT指令来初始化模块,然后再通过AT指令确认模块是否正常工作,是的话就setRadioState(RADIO_STATE_ON)主动上报RADIO正常ON状态。

  • “注册funcs回调列表”(RIL_register(funcs))部分流程如下:

    Android移植RIL库的过程

  • 上面的listenCallback()函数对应执行流程如下:

    Android移植RIL库的过程

  • 上面中的processCommandsCallback()函数的执行流程为:从p_rs获取数据至p_record,如果获取成功,则processCommandBuffer()处理结束后继续从p_rs获取数据和处理这样的循环,如果获取失败或读至结尾,则退出循环,关闭s_fdCommand连接,删除s_commands_event事件,删除p_rs记录流,将s_pendingRequests队列里各元素的cancelled标志位置1,并将s_listen_event加入watch_table重启监听新连接操作。

  • 而processCommandBuffer()则将上层新发下来的命令请求里的request和token两数据项封包为RequestInfo类型的pRI中的元素,并将其加入s_pendingRequests队列,并执行对应request的s_command中对应的dispatchFunction函数(生成不同request对应的参数再通过s_callbacks.onRequest()去调用RIL库里onRequest()里对应分支的请求)。

  • 至此,整个RIL部分的大致流程已清楚了,其中RILD部分主要是启动整个RIL进程,进行队列的处理(相当于整个队列管理核心),而libril.so中相应函数主要是处理上层请求、AT处理后数据上报,而reference-ril.so中则是处理串口AT指令的获取,利用到libril.so中的AT指令解析等操作。

  • 相关文件说明:

  • atchannel.c:AT口指令收发处理

    at_tok.c:AT指令返回内容解析处理

    radiooptions.c:与RILD对应debug socket通信,调试命令。

    reference-ril.c:RIL库核心文件,处理AT口指令交互、solicited和unsolicited事件及上报处理结果。

    ril.cpp:各类回调函数、socket通信处理、上层请求处理。

    rild.cpp:整个RIL服务入口,处理参数、队列创建和管理等管理相关进程的创建。

    ril_event.cpp:rild涉及到的相关函数定义。

  • (注:上述流程图中结尾没有再往下的流程则表示该函数执行结束后返回)

  • 2.RIL主要函数

  • 1).AT指令发送

  • a. at_send_command_singleline

  • 该函数在发送AT指令后对返回值进行解析,前提是返回值是只有一行的情况才使用。

  • b. at_send_command_multiline

  • 该函数在发送AT指令后对返回值进行解析,前提是返回值是多行的情况下使用,可适用于一条AT指令返回多行数据或多条AT指令同时发送返回多行数据的情况。

  • c. at_send_command

  • 该函数仅仅发送AT指令,不对其返回值进行解析。

  • 2).AT指令返回值解析

  • a. at_tok_start

  • 该函数用于将解析的数据指针定位到AT指令头后面位置(即返回值的首个冒号“:”后面)。

  • b. at_tok_nextint

  • 该函数用于获取返回的AT数据对应指针位置后的首个Integer数据,并将指针指向数据之后。

  • c. at_tok_nextbool

  • 该函数获取的是boolean数据,同样会将指针后移。

  • d. at_tok_nextstr

  • 该函数获取的是String数据,同样会将指针后移。

  • e. at_tok_hasmore

  • 该函数用于判断还有没有数据,即是不是到达数据尾部,不会进行指针操作。

  • 3).数据上报

  • a 主动式

  • RIL_onUnsolicitedResponse,当在RIL库中需要向上层主动上报数据时,需用该函数进行处理。

  • b 被动式

  • RIL_onRequestComplete,当上层向RIL库请求处理,并且处理后向上层上报数据时,需用该函数进行处理。

  • 4).移植涉及修改的函数

  • 移植时主要是reference-ril.c文件,相关函数为:

  • static void onRequest (int request, void *data, size_t datalen, RIL_Token t);

    static void onUnsolicited (const char *s, const char *sms_pdu);

  • 主要把整个RILD服务配置好并正常启动,将对应的Request进行代码完善及匹配处理,其中onRequest()处理上层请求后上报处理的数据,而onUnsolicited()处理AT口未请求返回数据并且处理后上报处理的数据。

  • 3.拔号上网、DHCP及上网状态上报

  • 在3G模块时,使用的是PPP协议进行拔号上网及PPPD服务进行DHCP信息的获取,其使用到的是串口进行数据传输,而在LTE模块中,使用串口已无法满足速率的要求,其使用的是模拟网口的方式进行数据传输,而在拔号上需要向AT口发送配置及连接指令,连接上后需要对模拟网口进行DHCP配置。

  • 在3G模块里,一般会映射出/dev/ttyUSB0、1、2共3个USB转串口的设备结点,其中这三个串口会对应“AT指令口”、“语音通话口”(带语音功能的模块)、“数据上网口”,而在LTE模块,映射出来的有一个AT指令口(如ttyUSB0)和一个虚拟网口(如usb0、wan0)。

  • 针对不同LTE模块,其上网的AT指令不同,需根据具体情况处理,但DHCP处理这块,在RK3188平台可使用如下两种方式处理:

  • a 使用busybox udhcpc命令,在RIL库中使用system函数调用,该方法过多的使用到命令行命令,移植性不佳,故不讨论;

  • b 使用libnetutils库(system/core/libnetutils/)里的dhcp_do_request函数,相应的函数格式如下:

  • dhcp_do_request(PPP_TTY_PATH, ppp_local_ip, ppp_gw, &prefixLength, ppp_dns1, ppp_dns2, server, &lease, vendorInfo)

  • 当执行该函数时,会去启动dhcpcd_wan0的service,其中wan0为上面的PPP_TTY_PATH,还有DHCP服务到期后的重新请求服务,故而在init.rk30board.rc里添加如下内容:

  • service dhcpcd_wan0 /system/bin/dhcpcd -ABKL

        class main

        disabled

        oneshot

    service iprenew_wan0 system/bin/dhcpcd -n

        class main

        disabled

        oneshot

  • 当请求到相应数据后,我们需要上报到Android上层,此时需要使用如下格式的数据进行上报:

  • sprintf(ppp_dnses, "%s %s", ppp_dns1, ppp_dns2);

    responses = alloca(sizeof(RIL_Data_Call_Response_v6));

    responses[0].status = 0;

    responses[0].suggestedRetryTime = -1;

    responses[0].cid = 1;

    responses[0].active = 2;

    responses[0].type = "IP";

    responses[0].ifname = PPP_TTY_PATH;

    responses[0].addresses = ppp_local_ip;

    responses[0].dnses = ppp_dnses;

    responses[0].gateways = ppp_gw;

    RIL_onRequestComplete(t, RIL_E_SUCCESS, responses, sizeof(RIL_Data_Call_Response_v6));

  • 至此,涉及我们移植过程的细节问题及程序执行流程的分析,就大致说明了一遍,非常棒的一次学习体验。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值