UART蓝牙Linux调试的一些经验, DMA, FTP

这几天在调试一块Atheros 3001的蓝牙芯片,今天算是有一个里程碑了,总结一下放在这里吧,开始写博客的习惯吧。尽管这有些out,不过当作自己的一个习惯吧。

先介绍下这个芯片吧, Ath3001, 一块通过uart,也就是串口,连到host的蓝牙芯片。特点呢,我不清楚,一个比较明显的感觉就是比较快吧。最多支持3000000(3M)的波特率,如果忽略上层的话,数据吞吐量应该比较好。 不过应该主流的蓝牙芯片都差不多吧。

我的任务呢,就是要在我们的平台上, Freescale i.MX53上把这块芯片跑好,跑顺。。。说实话,我不太喜欢做第三方的东西支持,有时候交流起来比较困难,但是做成以后呢,自己又好像没多大意思似的。不过既然是咱做,咱就把它做好吧。

最后通过该驱动,打到了3M 的波特率。FTP的速度可以达到,接受140KB/s, 输出75KB/s。具体无法达到375K的理论最高速度应该是和上层的MTU之类的设置有关。插播一下,刚搜到说iPhone之间传文件最高为67KB/s,尽管我没有测试过两台mx53之间的速度,但是能和苹果达到一个数量级已经让我感到很欣慰了。阿门。

期间遇到了这么几个问题,

最早是CTS/RTS的问题。

因为这块芯片的驱动,在芯片的reset以后probe的过程中,需要手动控制RTS脚去踢芯片来唤醒它,一开始我们的UART驱动没有很好的对Linux tty标准做适配,无法在运行过程中对流控进行开关。后来在驱动中加入这个以后,芯片就起来了。

波特率

然后首先试的Profile是A2DP,也就是立体声听歌的配置,注意到声音在播放一段时间以后就会有规律的卡一次,然后从log上看是写数据下去的时候被什么东西给block住了,当时还有一个猜想就是波特率不够,在增加波特率到56K以后,有些缓解,可是还是会有一两次的卡。这个问题一直到最近才解决掉了。是uart驱动里面对于tx的处理有些问题。

DMA传输

现在uart传输是用中断模式做的,这样做的好处,好处就是简单,呵呵。但是坏处比较大,如果我以一个高的波特率传输音乐或者文件,可怜的cpu被中断的非常痛苦,这时候会对系统新能造成很大影响。 具体计算, 可以简单的用你的速度/fifo大小来得出,比如我们以30K/S的速度传文件,我们的FIFO是32个字节,那么每秒CPU将被中断1000次,所以这是不可接受的,当然这么多中断的话,就不可能达到这个速度 :)

解决办法只有一个, 打开DMA传输。只有在打开和不打开之间,才可以体验到这个东西的优越性。 如果用中断,我大概A2DP的时候是90+%的CPU loading, 如果是DMA,那么就到了3%的loading了。 差距啊。 DMA设置的BUFFER size可以比你的fifo大,比如我设置的是128的,fifo只有32。 DMA控制器会从你的uart fifo里面一直搬数据,搬满了就给你个中断。 让你去处理这些数据。

当开DMA的过程也有些曲折,因为打开DMA的以后会要求SOC打开一个访问DDR的emi_fast的clock,当时也费了点功夫查这个问题。 ps, 53的clock真复杂。

A2DP的调试还好,因为听歌传错一两个帧没什么大碍。但是传文件这个就不一样了。错一点都不行。

UART驱动的BUG

于是在调试FTP的时候,又发现了两个小BUG在我们的uart驱动中。

一个是在传输的时候,因为是用DMA传送的,DMA在传完一个buffer的时候会去调用一个callback,这时候原来的驱动会设置好新的buffer地址以后去启动一个tasklet来进行传下一个buffer。可是我发现这种异步的方法会有丢一些数据,换成直接调用那个函数来传下一个buffer就没问题了。我想这个问题大概是这样的,因为dmaread的callback和write的callback都修改一个bufferid的变量来记录哪段buffer,而如果调用tasklet来传输下一段buffer,会有这样一种情况:
dma_wirtecallback->让tasklet开始run.
可是由于tasklet在soft irq的context下面,会有一点的延迟。
这个时候,dma_readcallback来了,并且在这个tasklet前面执行,所以就会修改掉buffer id。
然后传输失败了,也有可能是传错东西了,远端不认识这些数据。 就没给正确的回应。所以蓝牙驱动里面就报错了。

这时候,FTP协议就会不正常了。

另外一个问题是read的时候, 由于使用了dma传输,每次会有128或者更多的data从给到dma_read的callback上面,这时候原来的驱动会去调用tty_buffer_request_room()来去申请空间,然后把申请到空间的长度的数据调用tty_insert_flip_string来送给上层。这么做是不对的,申请空间的工作应该由tty_insert_flip_string来完成,它在 tty_insert_flip_string_fixed_flag() 函数中会有一个循环来request_room,而每次reqeust_room的值都不会太大以至于失败。而uart驱动中就是在这里一次申请了太大的数据,比如128之类的,导致申请失败返回,所以数据就丢掉了。

我在传输两边用md5sum能够看到传输的文件内容不同了。

硬件的波特率

在调试过程中还碰到过一种情况,就是在ftp传文件传的很high的时候,会莫名其妙的把数据传错。最后发现是因为我们用的这个ATH3001的卡上的传输器(transceiver)天生只对几种波特率支持的比较好,比如1500000,3000000之类的,而我当时用的是1152000的波特率。当切换了波特率以后就不会出现这种莫名其妙的数据错误了。

接下来做做HFP的工作吧,这大概是蓝牙这玩意最有用的功能了。

PS,我这个驱动主要是在arm的ubuntu下面调试完成的,由于强大的工具支持,这个过程会比较顺畅。 调试好了底层,把东西跑在Android上也是非常顺利的。