- 易迪拓培训,专注于微波、射频、天线设计工程师的培养
CC2640 uart dma实现方式分析教程
录入:edatop.com 点击:
CC2640 uart dma实现方式分析教程
CC2640在做uart DMA驱动期间,前后加起来也有1个月左右的时间,总的来说比较全面的了解了uart,DMA的工作原理。
在调试中,遇到了最大问题就是关于DMA操作这快的不熟悉,导致浪费了很多的时间和精力。对UART,DMA的工作原理可以看LDD3,或者设备驱动一书中也有
详细介绍,uart-dma驱动的移植可以参好drivers/serial/bfin_5xx.c
UART-DMA总体思路如下:
1.本UART-DMA采用的是,DMA+POLLING(轮询)的方式,其中轮询采用的是定时器。
2.在驱动中发送DMA需要用户层主动发起;
接收DMA:在UART open操作时enable_dma(rx),等待接收数据,当到达DMA counter值时,产生DMA中断,
在DMA中断处理中 做如下处理 disable_dma(rx),CPU取走数据,enable_dma(rx),以便接收下次数据;
3.POLLING(轮询)在这里的作用,如果在接收DMA过程中,还没有达到DMA counter值时,不会产生中断,这样接收到的数据
就不能及时的被CPU取走,为了解决这个问题,采用轮询--定时器方式,通过每50ms 读取DMA counter寄存器一次看是否有数据上的
变化,如果有这CPU把数据取走,没有则不做处理。
4.错误处理:如果有错误(溢出、校验……),产生错误中断,在错误处理中断中有相应的处理。
现分析如下:
1.内核起来之后,最开始经过 setup_arch() /* init/main.c */-->arch_mem_init()-->plat_mem_setup()-->clx_serial_setup()
在clx_serial_setup()函数中
void __init clx_serial_setup(void)
{
*******************
REG8(UART0_FCR) |= UARTFCR_UUE; //设置uart0的 FIFO 控制寄存器,disable UART
REG8(UART1_FCR) |= UARTFCR_UUE; //设置uart1的 FIFO 控制寄存器,disable UART
s.type = PORT_16550A; //设置uart的其它属性
s.iotype = UPIO_MEM;
s.regshift = 2;
s.fifosize = 1;
s.uartclk= clx_clocks.uartclk;
s.flags = STD_COM_FLAGS;
#if !defined(CONFIG_CLX_UART0_REMR)
s.line = line;
s.irq = IRQ_UART0;
s.membase = (unsigned char __iomem *)UART0_BASE;
if (early_serial_setup(&s) != 0) //调用early_serial_setup()来初始化串口0
printk(KERN_ERR "Serial ttyS0 setup failed!\n");
line++;
#endif
#if !defined(CONFIG_CLX_UART1_REMR)
s.line = line;
s.line = 1;
s.irq = IRQ_UART1;
s.membase = (unsigned char __iomem *)UART1_BASE;
if (early_serial_setup(&s) != 0) //调用early_serial_setup()来初始化串口1
printk(KERN_ERR "Serial ttyS1 setup failed!\n");
#endif
}
2.early_serial_setup()函数是在你自己写的驱动里边定义的
static struct uart_8250_port cq8401_serial_ports[UART_NR];
int __init early_serial_setup(struct uart_port *port)
{
if (port->line >= ARRAY_SIZE(cq8401_serial_ports))
return -ENODEV;
cq8401_isa_init_ports(); //初始化相应的UART
cq8401_serial_ports[port->line].port = *port; //将传过来的参数port,赋值给cq8401_serial_ports[port->line].port
cq8401_serial_ports[port->line].port.ops = &cq8401_serial8250_pops;
//同样,相应的OPS操作赋值
return 0;
}
到这里,setup_arch()里面UART的初始化到这里就结束了。
3.接着是内核里面
console_init() /* init/main.c */ 的初始化
void __init console_init(void)
{
initcall_t *call;
/* Setup the default TTY line discipline. */
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
/*
* set up the console device so that later boot sequences can
* inform about problems etc..
*/
call = __con_initcall_start;
while (call < __con_initcall_end) {
(*call)();
call++;
}
}
关于console 初始化,在网上找了一段描述,就直接套用过来了。
网址:http://blog.csdn.net/lights_joy/archive/2009/01/31/3855530.aspx
************************************************
在linux初始化过程中,除非启用了early console,否则直到console_init调用之前是没有任何输出的,它们的输出都放在__log_buf这个缓冲内的,在console_init调用时再将这个缓冲区内的数据一次性输出。
console和串口的初始化操作应该是由__con_initcall_start到__con_initcall_end之间的函数调用来完成的
在linux的头文件中搜索initcall,发现了这样一个定义(include\linux\init.h):
#define console_initcall(fn) \
static initcall_t __initcall_##fn \
__attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn
在此函数中的所有操作都是与硬件无关的,据此可以猜测,应该还有一个与硬件相关的文件,且在此文件中应该使用console_initcall这个宏。
在内核源码中搜索console_initcall,会找到在clx_uart_dma.c中有 console_initcall(cq8401_console_init);这样一句话。
关于具体console的初始化在这里不必多说,具体的可以看下别人的分析。
**************************************************
4. console初始化好了以后,后面就是UART驱动初始化的时候了
static int __init cq8401_serial8250_init(void)
{
int ret, i;
**************
cq8401_isa_init_ports(); //相应串口的初始化
ret = uart_register_driver(&cq8401_serial_reg); //串口驱动的注册
if (ret)
goto out;
cq8401_serial_init_ports(); //端口的添加,定义如下
**************
}
static void __exit cq8401_serial8250_exit(void)
{
int i;
struct uart_8250_port *up;
for (i = 0; i < nr_uarts; i++)
{
up= &cq8401_serial_ports;
uart_remove_one_port (&cq8401_serial_reg,&up->port );
}
uart_unregister_driver(&cq8401_serial_reg);
}
module_init(cq8401_serial8250_init);
module_exit(cq8401_serial8250_exit);
*******************************************
cq8401_serial_init_ports()函数分析
static void cq8401_serial_init_ports(void)
{
int i;
for (i = 0; i < nr_uarts; i++) {
struct uart_8250_port *up = &cq8401_serial_ports; //前面early_serial_setup()中以对cq8401_serial_ports[i]赋值
up->port.line = i;
uart_add_one_port (&cq8401_serial_reg, &up->port); //端口的添加
}
}
在这里uart驱动的初始化到这里就结束了,下面重点分析 uart对应的OPS操作。
5. UART OPS操作分析
static struct uart_ops cq8401_serial8250_pops = {
.tx_empty = cq8401_serial8250_tx_empty,
.set_mctrl = cq8401_serial8250_set_mctrl,
.get_mctrl = cq8401_serial8250_get_mctrl,
.stop_tx = cq8401_serial8250_stop_tx,
.start_tx = cq8401_serial8250_start_tx,
.stop_rx = cq8401_serial8250_stop_rx,
.enable_ms = cq8401_serial8250_enable_ms,
.break_ctl = cq8401_serial8250_break_ctl,
.startup = cq8401_serial8250_startup,
.shutdown = cq8401_serial8250_shutdown,
.set_termios = cq8401_serial8250_set_termios,
// .pm = cq8401_serial8250_pm,
.type = cq8401_serial8250_type,
.release_port = cq8401_serial8250_release_port,
.request_port = cq8401_serial8250_request_port,
.config_port = cq8401_serial8250_config_port,
.verify_port = cq8401_serial8250_verify_port,
};
在分析之前,先看看uart_8250_port 结构体 的定义
struct uart_8250_port {
.............
#ifdef CONFIG_SERIAL_CQ8401_DMA
struct tx_buffer tx_buf;
int tx_done;
int tx_count;
wait_queue_head_t tx_wait_queue; //UART发送队列
unsigned int rx_dma_addr; //UART接收DMA地址
// struct circ_buf rx_dma_buf;
struct timer_list rx_dma_timer; //接收定时器
unsigned int tx_dma_channel; //发送DMA通道
unsigned int rx_dma_channel; //接收DMA通道
unsigned int tx_channel; //接收 DMA SOC ID
unsigned int rx_channel; //发送 DMA SOC ID
#endif
}
5.1 .startup = cq8401_serial8250_startup //UART的OPEN操作
static int cq8401_serial8250_startup(struct uart_port *port)
{
...................
if(up->port.type==PORT_16550A){
printk("***************init cq8401 dma**************\n");
int r;
char *serial;
init_waitqueue_head(&up->tx_wait_queue);
serial_outp(up, UART_LCR, serial_in(up,UART_LCR) | UART_LCR_WLEN8); //8位数据位
udelay (10);
serial_outp(up,UART_FCR,0x0f); //reset tx,rx fifo;enable dma; disable uart
udelay (10);
printk("irq=%d\n",up->port.irq);
/*
* register error handle interrupt
*/
if(up->port.line)
serial="serial1";
else
serial="serial0";
//注册 UART 错误处理中断
r = request_irq(up->port.irq,serialirqxx,SA_INTERRUPT, serial, up);
printk("r=%d",r);
if (r<0)
return r;
serial_outp(up,UART_IER,0x14); //禁止接收、发送 中断,允许超时、错误处理中断
udelay (10);
if( serial_init_dma(up,up->port.line) > 1){ //serial_init_dma(),申请发送,接收DMA通道和 BUF
return -EBUSY;
}
......................
}
serial_init_dma(),申请发送,接收DMA通道和 BUF 如下:
参数:
up:对应串口结构体
line:串口号
static int serial_init_dma(struct uart_8250_port *up,unsigned int line)
{
......................
/*
* request transmit dma channel and dma buffer for the corresponding uart
*/
if(line){
uart="uart1_tx";
}
else
uart="uart0_tx";
//片上SOC设备 DMA通道申请,
up->tx_channel:SOC 设备ID;
uart:申请的DMA设备名;
uart0_tx_dma_intr:DMA中断处理函数;
SA_INTERRUPT:the irq handler flags
up:the irq handler device id for shared irq
r=clx_request_dma(up->tx_channel, uart , uart0_tx_dma_intr,
SA_INTERRUPT, up);
if (r <0){
printk("Unable to APPLY UART%d TX DMA channel\n",line);
return -EBUSY;
}
up->tx_dma_channel=r;
up->tx_done = 1;
up->tx_count = 0;
/*
*request receive dma channel and dma buffer for the corresponding uart
*/
if(line)
uart="uart1_rx";
else
uart="uart0_rx";
//接收DMA通道申请
r = clx_request_dma(up->rx_channel, uart , uart0_rx_dma_intr,
SA_INTERRUPT, up);
if (r <0){
printk("Unable to APPLY UART%d RX DMA channel\n",line);
return -EBUSY;
}
up->rx_dma_channel=r;
up->rx_dma_buf.head = 0;
up->rx_dma_buf.tail = 0;
//接收DMA BUF 申请,这里需要说明一下,在DMA操作中,是操作的总线地址,而且为了解决 DMA 和 cache的一致性问题;
所以最好用 dma_alloc_coherent()函数
void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t * dma_handle, gfp_t gfp)
dev:设备
size:申请的大小
dma_handle:申请后返回的总线地址,这里即是&up->rx_dma_addr
gfp:申请内存的标识(GFP_KERNEL,GFP_DMA)
该函数返回供驱动操作的虚拟地址,这里即是up->rx_dma_buf.buf
当然也可以采用其它方式申请DMA BUF,如:
kmalloc(),get_free_pages();申请虚拟地址;
用virt_to_bus();将上面申请到的虚拟地址转化成总线地址。但是强调一点,如果用这种方式,在DMA操作开始之后,如果CPU在还没有到达
DMA COUNTER值时就取数据的话,必须在每次CPU取数据之前做dma_cache_wback_inv()操作,来避免 cache中有 DMA BUF 的数据备份,
也就是CPU每次取数据必须从 DMA BUF中取。
up->rx_dma_buf.buf=(unsigned char *)dma_alloc_coherent(NULL, PAGE_SIZE, &up->rx_dma_addr, GFP_DMA);;
up->rx_dma_addr -=CLXSOC_PCI_CORE_START; //这里
/*
* enable receive dma channel
*/
spin_unlock(&up->port.lock);
//设置 DMA 相关寄存器;enable_dma(rx),enable 接收DMA通道,准备接收数据,分析如下
uart_start_dma(up->rx_dma_channel,up->rx_dma_addr,DMA_RX_XCOUNT,DMA_MODE_READ);
serial_outp(up,UART_FCR,0x19);
udelay (10);
printk("enable rx dma\n");
return 1;
}
设置 DMA 相关寄存器, enable发送,接收DMA通道函数分析
参数:
chan:通道号
phyaddr:DMA BUF的总线地址
count:DMA COUNTER寄存器的值
mode:根据读,写的不同,来设置DMA DCCR寄存器的相关值,比如说:SAI,DAI,DRTR等值
static void uart_start_dma(int chan, unsigned long phyaddr,unsigned int count, int mode)
{
unsigned int flags;
if (count == 0) {
count++;
printk(KERN_DEBUG "%s: CLXSOC DMA controller can't set dma count zero!\n",
__FUNCTION__);
return;
}
flags = claim_dma_lock();
disable_dma(chan); //先disable_dma
clear_dma_ff(chan);
set_dma_addr(chan, phyaddr); //设置DMA 源地址,目的地址;phyaddr:源地址,目的地址前面 clx_request_dma,中已经给结构体赋值,这里只需要赋值即可
set_dma_count(chan, count); //DCCR,即DMA COUNTER值的设置
set_dma_mode(chan, mode); //根据DMA 接收,发送的不同来设置DCCR.SAI,DCCR.DAI位
enable_dma(chan); //enable DMA
release_dma_lock(flags);
}
根据各个芯片的DMA控制器的不同,详细的可参考DATASHEET。
5.2 .start_tx = cq8401_serial8250_start_tx //UART 发送操作
发送操作流程如下:
用户在APP层,主动发起一次数据到外部设备的传输,先open UART,然后 write data 到 uart;这是write操作最终会调用到驱动的
start_tx操作,在UART-DMA驱动中,将用户保存在circ_buf中的数据 通过 DMA到 UART 发送寄存器中(FIFO 模式下);完成一次DMA操作,
这是DMA 产生 DMA中断,通知CPU数据已发送出去,CPU根据circ_buf的情况做相应的处理
static void cq8401_serial8250_start_tx(struct uart_port *port)
{
struct uart_8250_port *up = (struct uart_8250_port *)port;
serial_dma_tx_chars(up);
}
static void serial_dma_tx_chars(struct uart_8250_port *uart)
{
struct circ_buf *xmit = &uart->port.info->xmit;
int flags = 0;
if (!uart->tx_done)
return;
uart->tx_done = 0;
#ifdef CONFIG_SERIAL_CQ8401_CTSRTS
check_modem_status(uart); // 如果串口有RTS,CTS流控线的话,应在最开始做流控的相应操作,这里我们不做具体介绍
#endif
if (uart->port.x_char) {
serial_outp(uart, UART_TX, uart->port.x_char);
uart->port.icount.tx++;
uart->port.x_char = 0;
uart->tx_done = 1;
return;
}
if (uart_circ_empty(xmit) || uart_tx_stopped(&uart->port)) {
cq8401_serial8250_stop_tx(&uart->port);
uart->tx_done = 1;
return;
}
spin_lock_irqsave(&uart->port.lock, flags);
uart->tx_count = CIRC_CNT(xmit->head, xmit->tail, UART_XMIT_SIZE); //circ_buf中现在有多少数据就发送多少数据,好像circ_buf只有1K把,这个不是很清楚
if (uart->tx_count > (UART_XMIT_SIZE - xmit->tail))
uart->tx_count = UART_XMIT_SIZE - xmit->tail;
serial_outp(uart,UART_FCR,0x19); //enable dma and uart
udelay (10);
// 清空 DCache 数据缓存的数据
dma_cache_wback_inv((unsigned int)(xmit->buf+xmit->tail),uart->tx_count);
//发送DMA寄存器设置并 enable_dma(tx)
uart_start_dma(uart->tx_dma_channel,virt_to_phys(xmit->buf+xmit->tail), uart->tx_count,DMA_MODE_WRITE);
interruptible_sleep_on(&uart->tx_wait_queue);//发送队列睡眠
spin_unlock_irqrestore(&uart->port.lock, flags);
}
完成一次DMA操作后产生,会产生发送DMA中断,如下:
static irqreturn_t uart0_tx_dma_intr(int irq, void *dev_id)
{
struct uart_8250_port *uart = dev_id;
struct circ_buf *xmit = &uart->port.info->xmit;
spin_lock(&uart->port.lock);
disable_dma(uart->tx_dma_channel); //disbale_dma(tx)
if(__dmac_channel_transmit_end_detected(uart->tx_dma_channel)){ //判断DCCR.CT是否等于1,1:DMA 操作结束,0:DMA 操作没结束
xmit->tail = (xmit->tail+uart->tx_count) &(UART_XMIT_SIZE -1);
uart->port.icount.tx+=uart->tx_count;
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) //如果circ_buf数据小于WAKEUP_CHARS,唤醒上层向circ_buf写数据
uart_write_wakeup(&uart->port);
if (uart_circ_empty(xmit)) //circ_buf为空,停止发送
cq8401_serial8250_stop_tx(&uart->port);
uart->tx_done = 1;
__dmac_channel_clear_transmit_end(uart->tx_dma_channel); //dccr.ct=0,以便下此DMA操作,具体看DATASHEET
wake_up_interruptible(&uart->tx_wait_queue); //唤醒发送队列
}
spin_unlock(&uart->port.lock);
return IRQ_HANDLED;
}
自此发送过程以分析完成。
5.3 接收流程
由于在UART的操作中,接收都是被动的,所以在UART OPEN的时候,就 enable_dma(rx),只要UART 接收寄存器(FIFO 模式下)
中有数据就会 通过 DMA 到 RX DMA BUF中,为了CPU能及时取走数据,才用定时器操作,每50ms 定时器去读取 DMA COUNTER寄存器,
看是否有数据接收到,如果有则 CPU 取走数据;当传输的数据到达 DAM COUNTER值时,DMA中断产生,CPU 接管工作。
定时器函数如下:
void serial_rx_dma_timeout(struct uart_8250_port *uart)
{
unsigned int x_pos,pos=0;
int flags=0;
spin_lock_irqsave(&uart->port.lock, flags);
//读取 DMA COUNTER寄存器看时候有数据过来
x_pos =DMA_RX_XCOUNT - get_dma_residue(uart->rx_dma_channel);
if (x_pos == DMA_RX_XCOUNT)
x_pos = 0;
pos = x_pos;
if ( pos > uart->rx_dma_buf.tail) { //如果有数据过来,CPU 取走数据
uart->rx_dma_buf.head = pos;
serial_dma_rx_chars(uart); //CPU 取走数据
uart->rx_dma_buf.tail = uart->rx_dma_buf.head;
}
spin_unlock_irqrestore(&uart->port.lock, flags);
mod_timer(&(uart->rx_dma_timer), jiffies + DMA_RX_FLUSH_JIFFIES);
}
CPU 取走数据
static void serial_dma_rx_chars(struct uart_8250_port *uart)
{
............
//dma_cache_wback_inv((unsigned int)uart->rx_dma_buf.buf, DMA_RX_XCOUNT); //这就是前面强调的 用kmalloc(),get_free_pages()申请的DMA BUF在每次 CPU 取数据的时候要清空 Dcache,使cpu从RAM中取数据而不是从cache 中
uart->port.icount.rx +=CIRC_CNT(uart->rx_dma_buf.head, uart->rx_dma_buf.tail, UART_XMIT_SIZE);
if (status & UART_LSR_BI) {
uart->port.icount.brk++;
if (uart_handle_break(&uart->port))
goto dma_ignore_char;
status &= ~(UART_LSR_PE | UART_LSR_FE);
}
if (status & UART_LSR_PE)
uart->port.icount.parity++;
if (status & UART_LSR_OE)
uart->port.icount.overrun++;
if (status & UART_LSR_FE)
uart->port.icount.frame++;
status &= uart->port.read_status_mask;
if (status & UART_LSR_BI)
flg = TTY_BREAK;
else if (status & UART_LSR_PE)
flg = TTY_PARITY;
else if (status & UART_LSR_FE)
flg = TTY_FRAME;
else
flg = TTY_NORMAL;
//从DMA BUF中取走数据,insert到flip_buf中
for (i = uart->rx_dma_buf.tail; i<uart->rx_dma_buf.head;i++) {
if (uart_handle_sysrq_char(&uart->port, uart->rx_dma_buf.buf))
goto dma_ignore_char;
uart_insert_char(&uart->port, status, UART_LSR_OE, uart->rx_dma_buf.buf, flg);
}
dma_ignore_char:
tty_flip_buffer_push(tty); //将flip_buf中的数据PUSH 到TTY 线路规程
}
接收DMA中断
static irqreturn_t uart0_rx_dma_intr(int irq, void *dev_id)
{
struct uart_8250_port *uart = dev_id;
disable_dma(uart->rx_dma_channel); //disable_dma(rx)
spin_lock(&uart->port.lock);
if(__dmac_channel_transmit_end_detected(uart->rx_dma_channel)){
uart->rx_dma_buf.head = DMA_RX_XCOUNT;
serial_dma_rx_chars(uart); //CPU将上次tail到 DMA COUNTER值之间的数据取走
uart->rx_dma_buf.tail = uart->rx_dma_buf.head=0;
memset(uart->rx_dma_buf.buf, 0x00, DMA_RX_XCOUNT); //clear DMA BUF
dma_cache_wback_inv((unsigned int)uart->rx_dma_buf.buf, DMA_RX_XCOUNT);
__dmac_channel_clear_transmit_end(uart->rx_dma_channel);
//enable_dma(rx),以便下次数据的接收
uart_start_dma(uart->rx_dma_channel,uart->rx_dma_addr,DMA_RX_XCOUNT,DMA_MODE_READ);
}
spin_unlock(&uart->port.lock);
mod_timer(&(uart->rx_dma_timer), jiffies);
return IRQ_HANDLED;
}
自此接收DMA过程分析完毕
5.4 .shutdown = cq8401_serial8250_shutdown //串口的关闭
主要做的就是释放DMA通道,DMA BUF,中断;删除定时器,
static void cq8401_serial8250_shutdown(struct uart_port *port)
{
struct uart_8250_port *up = (struct uart_8250_port *)port;
disable_dma(up->tx_dma_channel);
clx_free_dma(up->tx_dma_channel);
disable_dma(up->rx_dma_channel);
clx_free_dma(up->rx_dma_channel);
dma_free_coherent(NULL,DMA_RX_XCOUNT, up->rx_dma_buf.buf,up->rx_dma_addr);
del_timer(&(up->rx_dma_timer));
free_irq(up->port.irq, up);
}
提供CC2541 CC2640R2 CC1310等CC系列免费样片 Q.Q 122982582
上一篇:CC2640去采集lmt70芯片的温度时,程序不能运行起来?
下一篇:CC2640 广播轮训功能进不去,什么原因?