uart dma实现方式分析



       在做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[i];
            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[i];   //前面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; irx_dma_buf.head;i++) {
                if (uart_handle_sysrq_char(&uart->port, uart->rx_dma_buf.buf[i]))
                    goto dma_ignore_char;
                uart_insert_char(&uart->port, status, UART_LSR_OE, uart->rx_dma_buf.buf[i], 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);

}

  5.5 UART错误中断处理,暂省略

你可能感兴趣的:(uart分析)