引言
本文讨论的双核终端硬件以AP(Application Processor,应用处理器)+CP(Cell phone Processor,基带处理器)的架构构建,CP运行TD/GSM双模协议栈,AP运行Android软件。两个芯片间通过串口进行IPC(InterProcessor Communication,芯片间通信)通信,像短信、电话等AT指令以及PS业务等均通过串口实现AP和CP的通信。当3G的网络环境对数据传输速率要求较高时,普通串口驱动已经满足不了这样的要求,因此需要用基于DMA方式的串口驱动,来支持高波特率的数据传输。AP侧为Android平台,CP的协议栈软件基于Nucleus平台[1]。AP与CP串口通信结构图如图1所示。
图1 AP与CP通信的UART通道
2 高速串口驱动设计原则
2.1 DMA的工作原理
目前应用比较广泛的串口驱动都是采用普通传输方式,在实现发送功能时,是CPU将发送缓冲区(上层应用将要发送的数据写到circ_buf中)中的数据逐个送入串口的发送FIFO,从而将数据发送出去。在接收时,将接收FIFO中的数据送入TTY层,供上层的应用读取。在串口的传输速度较低时,这种方式可以很稳定地工作;而当波特率设置得很高时,将会导致CPU来不及取走数据,导致串口的FIFO溢出,从而造成丢数,针对上述问题,提出了基于DMA(Direct Memory Access)方式的串口传输方式。
DMA是一种无需CPU参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率。DMA方式的数据传输由DMA控制器(DMAC)控制,在传输期间,CPU可以并发地执行其他任务。当DMA传输结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的中断服务程序进行后处理。内存中用于与外设交互数据的一块区域被称作DMA缓冲区,DMA缓冲区必须是物理上连续的[23]。
将DMA用于串口驱动时,在接收时需要申请一片和外设接收FIFO进行直接交互的缓冲区,通过void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)来实现,DMA负责将接收FIFO中的数据送到申请的缓冲区中,然后CPU负责将缓冲区中的数据推至TTY层。发送时需要将发送缓冲区映射出其总线地址才能和外设发送FIFO进行交互,通过dma_addr_t dma_map_single(struct device *dev ,void *buffer,size_t size,enum dma_data_dircetion direction)来实现。
1.2 申请的DMA缓冲区交替接收机制
在用DMA方式接收时,并不直接将接收FIFO中的数据送到TTY层,而是申请了一片2 KB大小的DMA缓冲区,并将其分成两个1 KB的缓冲区,用来交替从接收FIFO中接收数据,将2 KB大小的缓冲区分成为大小为1 KB的rx_buf[0]和rx_buf[1],在串口的设备节点被打开时,把这两个缓冲区排队到DMA buf队列中。这里利用申请的两个缓冲区交替从FIFO中接收数据,以提高传输效率。流程大致如下:
① 启动DMA,等待接收FIFO中的数据达到触发值,产生中断来进行DMA传输,将接收FIFO中的数据送到rx_buf中。
② 当其中一个rx_buf接收数据满时,将其数据送到上层之后排队到DMA buf队列中。
③ 而此时另外一块之前已经排队的rx_buf已经开始接收数据,等待接收满时,执行步骤②,两块rx_buf如此交替从接收FIFO中接收数据,当接收满时排队到DMA buf队列中去,等待下次继续从接收FIFO中接收数据。
2 高速串口驱动设计流程
2.1 相应的数据结构
下面的结构体用来描述DMA来负责接收时,申请的一片DMA缓冲区的信息,此缓冲区用来暂存从接收FIFO中接收来的数据。
struct uart_dma_buffer{
unsigned char *area;//缓冲区的虚拟地址的首地址
unsigned char *pos;//缓冲区虚拟地址当前的位移
dma_addr_t dma_addr_rx; //缓冲区的物理地址size_t
bytes;//申请缓冲区的大小
unsigned int state;//缓冲区当前的状态
}
下面的结构体用来在uart_port基础上增加一些DMA方式需要的成员变量。
struct uart_dma_port{
struct uart_port uport;//封装uart_port结构体
int dma_channel_tx;//DMA发送通道
int dma_channel_rx;//DMA接收通道
dma_addr_t dma_addr_tx; //发送缓冲区映射出来的物理地址
int tx_count;//DMA方式发送的count
int tx_countp;//DMA方式发送的countp
struct uart_dma_buffer rx_buffer[2];//申请的接收缓冲区
struct uart_dma_buffer *pending; //指向需要向TTY’层推送数据的缓冲区
int rx_count;
int rx_countp;
int curr;
dma_addr_t dma_dest;
struct tasklet_struct rx_tlet;
struct tasklet_struct tx_tlet;
}
2.2 发送流程设计
为了提高发送效率,在发送前首先会判断发送缓冲区中的数据个数,只有数据量较大时,才会用DMA方式进行发送,当数据较少时直接用普通方式进行发送,这样可以提高效率。DMA方式发送流程设计如图2所示,图中n的值可自由配置,本设计中n=32。
图2 发送流程图
发送流程大致如下:
① 首先将发送缓冲区映射出其总线地址以供DMA方式使用,配置DMA通道的各个寄存器以及传输方式,并设置DMA传输完成的回调函数。
② 当上层调用write系统调用时,将向circ_buf缓冲区中写入数据,首先调用start tx发送数据,并根据缓冲区数据的多少确定发送的方式。
③ 缓冲区中的数据个数小于预定值时使用普通方式进行传输,当大于这个值时使用DMA方式进行发送(DMA控制器会根据设置的源地址和目的地址,将缓冲区中的数据搬送到发送FIFO中,DMA传输完毕后,调用之前注册的DMA完成的回调函数,此时更新缓冲区的尾指针以及其他数据)。
④ 当发送FIFO中的数据个数小于设定的个数时,产生中断然后重复步骤③。
2.3 接收流程设计
考虑到接收时,接收FIFO中的数据个数达不到设定的值时不会触发中断,这样容易造成在FIFO中遗留数据,所以使能超时中断。当接收FIFO在设定时间内没有收到数据时会产生超时中断,此时用普通方式将接收FIFO中的数据送到TTY层。但是这样操作前要确保申请的DMA缓冲区中的数据都被送到了TTY层,否则会造成数据乱序。接收流程图如图3所示。
图3 接收流程图
接收流程大致如下:
① 首先申请2 KB的DMA缓冲区,分成两个接收rx_buf,配置DMA的各个寄存器,将2个rx_buf均排队到DMA buffer队列,启动DMA。
② 当接收FIFO中的数据达到设定值时,串口会送给DMA一个中断。由于此时DMA已经是启动状态,所以开始DMA传输,传送完一个countp(DMA控制器周期传输长度寄存器的值)时,每来一个中断,传输一个countp,直到count(DMA控制器传输长度寄存器的值)减小到0时执行DMA完成的回调函数。
③ 在DMA的回调函数中将会更新DMA buf队列中的缓冲区,将下一个排队中的缓冲区作为接收数据的缓冲区。同时count为0时,说明当前接收buf中的数据已满,开始将rx_buf中的数据推向上层。推向上层之后,排队到DMA buffer队列,为下次接收数据作准备。
④ 在count寄存器中的值减小为0时,调用DMA的回调函数,使正在排队的另一块申请的rx_buf开始从接收FIFO中接收数据,直到接收到count减小到0时,再执行步骤③。
⑤ 当一定时间内接收FIFO中未接收到数据,则此时产生超时中断,这时需要先将rx_buf中的数据先推到TTY层,然后再用普通方式将接收FIFO中的数据送入TTY层。
3 实验结果
AP芯片运行Android4.0操作系统(其中内核版本为Linux 3.0),在串口波特率设置为3 Mbps时,通过回环测试,查看串口的实际传输速率,测试结果如表1所列。
表1 两种传输方式数据传输速度
通过测试结果可以看出,当波特率设置为3 Mbps时,普通传输方式的实际速度只能达到1.8 Mbps,满足不了数据的高速率传输要求,并且由于对系统的资源消耗较大,对CPU的占用率为12%。而用DMA方式相对较普通方式传输可以提高传输速率,并且系统开销很低,CPU占用率为 8%。可以看出使用DMA方式可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而降低系统的开销。
通过在3G环境中的实验,使用DMA方式的高速串口驱动可以正常使用PS业务,可以享有高速率的数据下载业务。而普通方式在3G上网环境下的高速传输数据,
由于FIFO溢出,导致重传,从而效率较低,数据下载速度较低。
结语
通过将串口驱动普通传输方式改写为基于DMA的工作方式,可以提高传输速率,同时降低系统的开销,满足在3G网络环境下高速上网的需求。本文提出的基于DMA方式的串口驱动对其他需要利用串口高速传输数据的应用也具有借鉴意义。