rilmulticard.docx
《rilmulticard.docx》由会员分享,可在线阅读,更多相关《rilmulticard.docx(13页珍藏版)》请在冰豆网上搜索。
rilmulticard
AndroidhardwareRIL层多卡功能的具体实现
修改记录
作者
日期
修改内容
黄理洪
2010-5-20
加入智能识别卡槽、逻辑卡号到真实卡号的映射、自动关闭未插卡的modem等说明。
黄理洪
2010-5-15
增加“修改上报radiostate的方法”
黄理洪
2010-4-11
初稿
一、原有RIL层简介
android的hardwareRIL(radiointerfacelayer)是手机modem和androidframework层的接口,代码位于hardware/ril/,文件类型包含c和c++,共约6千多行。
整个ril编译出来得到三个文件:
libreference-ril.so(负责直接和modem交互)
libril.so(负责通过socket和framework层交互)
rild(作为守护进程的可执行程序,是上面两个库的入口)
上面两个.so库实现了三个的线程:
Mainloopthread,只是起初始化串口和modem的作用,后期处于idle状态;
requestdispatchthread,负责分发framework层对modem的ATcommand;
responsereaderthread,负责从modem读取response。
下图是RIL层的总体框图:
绿色箭头指示
responsereaderthread
ATcommand是发送给modem的AT指令,如自动注册网络用AT+COPS=0,拨打移动客服用ATD10086;挂电话用ATH。
Response是由modem返回的响应信息,包括请求响应(指对ATcommand的响应)和主动响应(英文称为unsolicitedresponse,包括短信和手机网络主动上报信息)。
二、改造需求
现在的ril只支持一个modem带一张手机卡,我们要实现多模多卡多待手机就需要让它同时支持多个modem,多张手机卡。
需要支持的modem按照网络模式分,有下列四类:
1、GSMmodem
a.单卡GSMmodem
b.双卡GSMmodem
2、TDS-CDMAmodem(属于中国移动的3G标准)
3、CDMA2000EVDOmodem(属于中国电信的3G标准)
4、WCDMAmodem(属于中国联通的3G标准)
目前尚未发现支持双卡的3Gmodem,所以一个多模手机的极限情况是同时具有上面四种modem,带5张手机卡(2张2G的sim卡,3张3G卡)。
我们的ril层改造目标就是支持这种极限连接方式。
如同其他对android的改造一样,在达到目的的前提下,我们希望对android的修改越少越好。
目前我们只有智源单卡GSMmodemIW368,和联芯TDS-CDMA\modemLC6311,这两个模块可以用于多卡功能的前期调试。
三、具体实现
1.ril层对modem和framework层的信息接口的重新约定
Modem主要有uart接口和usb接口(3G比2G有更高的数据传输速度,一般用usb接口;华为海思正在研发的4GLTEmodem加上了1Gbit/s的以太网接口实现更高的速度),从ril角度来看,它是几个虚拟的串口设备。
比如展讯的双卡GSMmodem,接在物理的uart2上,那么uart2对应的/dev/ttyS2将虚拟出/dev/pts/0、/dev/pts/1和/dev/pts/2三个虚拟串口设备分别对应sim1和sim2的AT指令通道和上网的gprs通道;又如大唐的3GmodemLC6311,接在我们cpu的usbhost接口上,通过usbserial驱动虚拟出/dev/ttyUSB0~5,共6个ttyUSB串口设备,我们只用其中的ttyUSB3作为AT指令通道,ttyUSB5作为上网数据通道。
对于ril层多卡的改造,我们主要考虑AT指令通道的串口设备(下面简称为AT串口设备),暂时不考虑上网数据通道的串口设备,因为ril层三个线程Mainloopthread,requestdispatchthread,ATreaderthread操作的都是AT指令通道。
上网数据通道仅被一个datacallrequest调用,很单纯。
一张手机卡(下面有时称其为card)对应一个AT串口设备,所以要建一个cardid和AT串口设备的描述符fd的对应表:
Modem网络模式
fd
(AT串口设备描述符)
Cardid(十进制)
GSM
fd_G0
11
fd_G1
12
WCDMA
fd_W0
21
TDS-CDMA
fd_T0
31
CDMA2000(EVDO)
fd_C0
41
其中,双卡GSMmodem的fd_G0和fd_G1是通过open/dev/pts/0和/dev/pts/1得到的设备描述符,分别对应两张sim卡。
fd_T0是openttyUSB3得到的设备描述符,对应一张中国移动手机卡。
严格地说,cardid其实应该称为卡槽id,因为对于多卡手机来说,卡槽常有而卡不常有。
每次开机,ril层首先自己探测哪些卡槽插了卡,当与framework的socket通信建立时,就把所有已插入卡的id传给framework。
framework层通过cardid的十位数就可以得知该手机卡的网络模式。
在reference-ril/misc.h中,定义下面表示真实卡号的宏,和表示卡槽信息的结构体:
#defineCARDID_GSM_011
#defineCARDID_GSM_112
#defineCARDID_TDSCDMA_021
#defineCARDID_EVDO_031
#defineCARDID_WCDMA_041
structcard_slot_t{
intid;
char*modem_name;
char*tty_name;
intfd;
};
在reference-ril/misc.c中定义具体的卡槽信息:
structcard_slot_tcard_slot_info[]={
{CARDID_TDSCDMA_0,"LC6311","/dev/ttyUSB3",-1},
{CARDID_GSM_0,"IW368","/dev/pts/2",-1},
{0}
};
其中card_slot_info[].fd是在open/dev/ttyUSB3或/dev/pts/2成功后,用open的返回值来赋值。
在framework层,GSM、WCDMA和TDS-CDMA三种网络模式都使用GSMPhone类,EVDO使用CDMAPhone类。
为了方便处理,framework使用逻辑卡号,逻辑卡号的十位数表示该卡使用GSMPhone类还是CDMAPhone类,分别对应于1和2;个位数表示某类的第几张卡。
举个例子,如果实际插入卡槽的卡的真实卡号有11、31和41,那么它们对应的逻辑卡号分别是11、12和21。
真实卡号比逻辑卡号带有更多的网络类型信息,也需要保留。
理论上逻辑卡号和真实卡号的转换工作可以放在framework中做,但在实践中遇到了一些困难,所以还是放在hardwareril中来做。
这样framework和ril之间就采用逻辑卡号来通信。
也就是说framework把“逻辑卡号+request”发给ril,ril把“逻辑卡号+response”发给framework。
清楚了ril层对modem和framework层的信息接口,下面就可以展开具体的ril层的修改工作了。
修改ril主要有三部分工作,分别对应ril的三个线程。
首先需要增加两个全局变量:
request_card_id(表示正分发的ATcommandrequest发向哪张卡,会被mainloop和requestdispatch两个线程使用)和response_card_id(表示正从哪张卡读取response信息,只被responsereaderthread使用)。
2.修改mainloopthread
上面提到,该线程只是起初始化串口和modem的作用,原有程序只初始化了一个串口设备和一个modem,现在要根据card_slot_info[]的内容来初始化多个串口设备和modem。
下图为修改之后的mainloop相关的流程图,蓝色为修改部分。
01
3.修改requestdispatchthread
framework层通过socket把requestcardid传递给ril,ril在processCommandBuffer()中把requestcardid从socketdata中取出,最后在writeline()和writectrlz()中通过requestcardid判断往哪个modem发送指令。
下图为修改之后的requestdispatchthread相关的流程图,蓝色为修改部分。
02
4.修改ATreaderthread
修改readloop函数,使其用select方法阻塞式地同时监视所有手机卡的fd是否可读,某个fd可读就解除阻塞,根据fd设置response_card_id,然后调用readline读取response。
在程序中response被分为三种:
1、短信息;
2、主动上报(unsolicitedresponse),如网络状态、信号强度等;
3、ATcommandrequest的response。
如果是短信息或主动上报,那么就在RIL_onUnsolicitedResponse()里把response_card_id和response一起封到parcel里,通过socket传给framework层。
如果是ATcommandresponse,并且此时responsecardid==requestcardid,那么释放信号量解除ATcommandrequestthread的阻塞。
会不会出现读到的是ATresponse但responsecardid!
=requestcardid的情况?
如果不考虑主动上报被误认为ATresponse的情况(这种情况在后面的技术要点备忘里讨论),那么不会发生这种情况,因为某卡读到了ATresponse说明之前该卡发过ATcommandrequest,它发ATcommand的时候,就会阻塞住,一直等ATresponse,不会让其他卡有发ATcommand的机会。
所以,当收到ATresponse时,不用判断response_card_id和request_card_id是否相等,就可以直接解除ATcommand的阻塞了。
接触阻塞之后的requestdispatchthread在RIL_onRequestComplete()里把request_card_id和经过解析的response一起封到parcel里,通过socket传给framework层。
下图是ATreaderthread的程序流程图,其中蓝色部分是针对多卡功能的修改。
03
5.修改上报radiostate的方法
原始的androidRIL代码只用一个变量sState来表示卡的radiostate,要支持多卡,就必须用一个数组sState[MAX_CARD_NUM]来表示所有卡的radiostate。
除了与framework层的socket通信刚建立时的radiostate是非请求的主动上报外,其余的radiostate都是framework通过requestradiopower获取的。
下图是RIL处理requestradiopower的程序流程图,其中蓝色部分是针对多卡功能的修改。
04
下图是android原始的framework收到RIL上报的radiostate后的相应动作的程序流程图:
四、技术要点备忘
1.表示多卡中哪一张卡的信息,用全局变量表示还是使用参数传递的选择
使用参数传递的方法对代码的改动量很大,需要给很多相关函数增加相应的表示哪张卡的参数。
使用两个全局变量request_card_id和response_card_id,大大简化改造工作,因为rild是守护进程,只会运行一个,request_card_id和response_card_id只会被一个线程使用,不存在多线程安全问题。
2.读取多卡时使用多线程还是select的选择
如果不使用select同时检测多张卡,那么就要对每张卡开一个线程来读取信息,这将使改造工作很复杂。
3.多卡功能使atchannel.c中的readerLoop()的情况变复杂,要针对各种情景进行修改。
情景1:
一张卡一次主动上报了大量信息,里面包含了若干行信息,都缓存到readbuffer里了,readline()只能一次返回一行数据进行处理,如果在处理完所有信息行之前,另一张卡产生response了,select检测到它的fd可读,于是responsecardid改为这张的id了,之前缓存在readbuffer里的数据就都被误认为是这张卡的了。
要避免这种情况产生,就需要在一张卡可读时,把该卡缓存在readbuffer中的response全都处理完。
情景2:
比如发送下面查询网络注册状态的AT指令:
AT+CREG?
response返回下面两行:
+CREG:
1,1,"0408","5C15"
OK
response的每一行都是以\r\n结尾的,readline()根据行尾的\r或\n得知读取到一行,对于上面的两行response,line=readline()将被调用两次,line是指向所读到的当前行的指针。
第一行不是finalresponse,程序会调用addIntermediate(line)把它们保存起来,等到作为finalresponse的“OK”返回时,因发送查询网络状态请求“AT+CREG?
”而阻塞的ATcommandthread才解除阻塞。
如果在读取到+CREG:
1,1,"0408","5C15"时,其它卡突然主动上报了一个网络状态的信息,主动上报的前缀+CREG与上一张卡AT+CREG?
的response的前缀一样,ATcommandthread会误以为这是上一张卡的response的信息而错误地用addIntermediate(line)来保存信息。
有两种解决方法:
一是,在addIntermediate(line)之前判断responsecardid与requestcardid是否相等,相等才add,不相等就认为是主动上报或短信;二是,一旦某张卡开始接收ATcommandresponse,就一直针对这张卡循环调用readline()和processline(),直到该ATcommandresponse完全被处理完毕。
我们采用第二种方法,因为这种方法代码修改起来相对简单。
情景3:
当读到下面的response:
OK
readline()会返回指向”OK”的指针,同时s_ATBufferCur跳过,指向了。
再次调用readline()时,readline()首先判断s_ATBufferCur是否为NULL,如果非NULL就认为s_ATBuffer里有未处理完的数据,但其实只有一个,这种情况下会再去阻塞地read,会一直阻塞住直到下次该卡可读为止,这期间,就算其他卡可读也没有机会去读了。
这对于单卡可以容忍,但对于多卡显然是不合理的。
综合解决方法:
综合考虑上面三种情景,对atchannel.c的readerLoop()做了下面蓝色部分所示的修改。
staticvoid*readerLoop(void*arg)
{
…
for(;;){
constchar*line;
memcpy(&rfds,&s_readFds,sizeof(fd_set));
n=select(nfds,&rfds,NULL,NULL,NULL);
…
for(i=0;iif(FD_ISSET(card_slot_info[i].fd,&rfds)){
s_read_fd=card_slot_info[i].fd;
break;
}
}
response_card_id=fd_to_card_id(s_read_fd);
card_response_final=1;//addIntermediate(line)canmakeit0
for(;;){
line=readline();
…
if(isSMSUnsolicited(line)){
…
s_unsolHandler(line1,line2);
…
}else{
processLine(line);
}
if((card_response_final==1)&&((*s_ATBufferCur=='\0')||((*s_ATBufferCur=='\n')&&(strlen(s_ATBufferCur)==1))))
break;
}
}
…
}
当一张卡被select认为可读时,会循环调用readline()进行读取,退出循环的条件是(card_response_final==1)&&((*s_ATBufferCur=='\0')||((*s_ATBufferCur=='\n')&&(strlen(s_ATBufferCur)==1))),其中条件((*s_ATBufferCur=='\0')||((*s_ATBufferCur=='\n')&&(strlen(s_ATBufferCur)==1))避免了第一个和第三个情景的问题,条件(card_response_final==1)避免了第二个情景的问题。
4.如果有多个modem,可以把没插卡的modem关闭,以节省功耗
但如果所有的modem都没插卡,那么不能都把它们都关闭,要留一个开着,否则不能打紧急电话。
这种情况下是默认把card_slot_info[0]对应的modem打开。
对于没有插卡的卡槽,不必让select监视它是否可读,最好从fdset中把它的fd删除,因为内核对select实现是轮询方式,监听的fd越多,越费时间,但考虑多监听一两个空卡槽对效率影响可以忽略不计,所以这个改进目前还没实现。
一般需要考虑select效率的是网络服务器,它可能需要监听几千个fd,而有些fd很不活跃,这时就要考虑用epoll来替代select,epoll可以根据fd的活跃度调整监听策略。
5.智能识别卡槽
如果手机modem支持的卡槽个数大于手机实际拥有的卡槽个数,那么就要使用卡槽的智能识别。
比如某手机里有一个支持双卡的GSMmodem和一个支持单卡的EVDOmodem,理论上需要安装三个卡槽,但受到手机内部空间的限制,只能存在两个卡槽,插入这两个卡槽的卡的组合可能是:
G卡+G卡,G卡+C卡和C卡+G卡。
C卡必须接在EVDOmodem上,G卡必须连在GSMmodem上,所以卡槽和modem连接需要智能调节。
本文以这种情况为例,说明一下智能识别卡槽的方法。
初始状态让卡槽1和卡槽2都默认连接在GSMmodem上(如上图所示),这种初始状态可以让探测卡槽过程中切换卡槽次数最少。
为了让程序健壮,考虑了插卡的各种组合情况:
G+G,G+C,C+G,C+C,错卡+G或C。
具体实现的程序流程图如下:
05
6.linux内核中和modem相关的驱动
drivers/misc/jz_modem.c:
hardwareril通过该文件实现的ioctl来控制各modem底层行为,如上电、复位、切换卡槽、切换音频连接等。
在hardwareril中,ioctl的case相关的宏定义位于reference-ril/misc.h,这些定义要和jz_modem.c中的定义一致。
drivers/input/misc/jz_ringin.c:
处理在cpu休眠时,modem的ringin中断把cpu唤醒。
五、针对RIL多卡的具体代码
在android源码根目录下,
cdhardware/ril
svndiff-r673:
HEAD
六、参考资料
1.AdvancedProgrammingintheUNIX®Environment(关于socket、select和pthread的相关知识)
2.各modem的AT指令集
文中程序流程图采用MicrosoftOfficeVisio2003绘制。