Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1639546
  • 博文数量: 584
  • 博客积分: 13857
  • 博客等级: 上将
  • 技术积分: 11883
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-16 09:34

分类: LINUX

2010-08-19 16:19:17

  Channel 在运行过程中会有很多的状态 , 所有状态如下 :

Include\asm-arm\arch-s3c2410\dma.h:

/* enum s3c2410_dma_loadst

  *

  * This represents the state of the DMA engine, wrt to the loaded / running

  * transfers. Since we don't have any way of knowing exactly the state of

  * the DMA transfers, we need to know the state to make decisions on wether

  * we can

  *

  * S3C2410_DMA_NONE

  *

  * There are no buffers loaded (the channel should be inactive)

  *

  * S3C2410_DMA_1LOADED

  *

  * There is one buffer loaded, however it has not been confirmed to be

  * loaded by the DMA engine. This may be because the channel is not

  * yet running, or the DMA driver decided that it was too costly to

  * sit and wait for it to happen.

  *

  * S3C2410_DMA_1RUNNING

  *

  * The buffer has been confirmed running, and not finisged

  *

  * S3C2410_DMA_1LOADED_1RUNNING

  *

  * There is a buffer waiting to be loaded by the DMA engine, and one

  * currently running.

*/

 

enum s3c2410_dma_loadst {

       S3C2410_DMALOAD_NONE,

       S3C2410_DMALOAD_1LOADED,

       S3C2410_DMALOAD_1RUNNING,

       S3C2410_DMALOAD_1LOADED_1RUNNING,

};

各种装态注释的很明显了 , 我就不再罗索了 .

Channel 运行时会有一个正在传输的 buf, 一个已经加载的 buf, 还有很多等待加载的 buf.

我们来把这个函数中调用的函数也 逐个分析下 :

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_dma_waitforload

  *

  * wait for the DMA engine to load a buffer, and update the state accordingly

*/

 

static int

s3c2410_dma_waitforload(struct s3c2410_dma_chan *chan, int line)

{

       int timeout = chan->load_timeout; // 初始化时 load_timeout 被设成了 1 << 18

       int took;

 

     // 该函数只在 S3C2410_DMALOAD_1LOADED 状态下被调用

       if (chan->load_state != S3C2410_DMALOAD_1LOADED) {

              printk(KERN_ERR "dma%d: s3c2410_dma_waitforload() called in loadstate %d from line %d\n", chan->number, chan->load_state, line);

              return 0;

       }

 

       if (chan->stats != NULL)

              chan->stats->loads++;  // 更新统计信息

 

       while (--timeout > 0) {

       // 获取还剩的传输量 , 左移 (32-20) 只是把 [21:20] 位移调 , 因为它仅和 0 比较 , 所以无需确切的数据

              if ((dma_rdreg(chan, S3C2410_DMA_DSTAT) << (32-20)) != 0) {

                     took = chan->load_timeout - timeout;  // 等待了这么长时间

 

                     // 保存统计信息 , 该函数更新最长 , 最短超时时间 , 并更新总超时时间

s3c2410_dma_stats_timeout(chan->stats, took); 

 

                     switch (chan->load_state) {

                     case S3C2410_DMALOAD_1LOADED:

                  // 因为有数据在传输了 , 因此更新 channel 的状态 , 从这我们也能看到 , 一次只能有一个

//buf load

                            chan->load_state = S3C2410_DMALOAD_1RUNNING; 

                            break;

 

                     default:

                            printk(KERN_ERR "dma%d: unknown load_state in s3c2410_dma_waitforload() %d\n", chan->number, chan->load_state);

                     }

 

                     return 1;

              }

       }

 

       if (chan->stats != NULL) {

              chan->stats->timeout_failed++;

       }

 

       return 0;

}

该函数很简单 , 它等待已经 load buf start 传输 , 然后更新相关统计信息 , 也正因为 load buf 被开始传输了 , 因此该函数完后 , 应该会有一个新的 buf load. 至于原先 load buf 是如何被 start , 我们以后在看 .

接下来我们看 s3c2410_dma_canload 函数 :

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_dma_canload

  *

  * work out if we can queue another buffer into the DMA engine

*/

static int

s3c2410_dma_canload(struct s3c2410_dma_chan *chan)

{ 

   // 在这 2 个状态下是可以 load

       if (chan->load_state == S3C2410_DMALOAD_NONE ||

           chan->load_state == S3C2410_DMALOAD_1RUNNING)

              return 1;

 

       return 0;

}

跑完 s3c2410_dma_waitforload 后如果正确 , 则状态应该是 S3C2410_DMALOAD_1RUNNING, 所以这里就是可以加载了 ,   那当然要看加载函数了

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_dma_loadbuffer

  *

  * load a buffer, and update the channel state

*/

 

static inline int

s3c2410_dma_loadbuffer(struct s3c2410_dma_chan *chan,

                     struct s3c2410_dma_buf *buf)

{

       unsigned long reload;

 

       pr_debug("s3c2410_chan_loadbuffer: loading buff %p (0x%08lx,0x%06x)\n",

                buf, (unsigned long)buf->data, buf->size);

 

       if (buf == NULL) {

              dmawarn("buffer is NULL\n");

              return -EINVAL;

       }

 

       /* check the state of the channel before we do anything */

    // 状态错误 , 只能有 1 loaded buf

       if (chan->load_state == S3C2410_DMALOAD_1LOADED) { 

              dmawarn("load_state is S3C2410_DMALOAD_1LOADED\n");

       }

         

  // 状态错误 , 只能有 1 loaded buf

       if (chan->load_state == S3C2410_DMALOAD_1LOADED_1RUNNING) {

              dmawarn("state is S3C2410_DMALOAD_1LOADED_1RUNNING\n");

       }

 

       /* it would seem sensible if we are the last buffer to not bother

         * with the auto-reload bit, so that the DMA engine will not try

         * and load another transfer after this one has finished...

         */

    // 判断是否要自动加载后续的 buf, 如果有后续的 buf 则自动加载

       if (chan->load_state == S3C2410_DMALOAD_NONE) {

              pr_debug("load_state is none, checking for noreload (next=%p)\n",

                       buf->next);

              reload = (buf->next == NULL) ? S3C2410_DCON_NORELOAD : 0;

       } else {

              //pr_debug("load_state is %d => autoreload\n", chan->load_state);

              reload = S3C2410_DCON_AUTORELOAD;

       }

 

       if ((buf->data & 0xf0000000) != 0x30000000) {

              dmawarn("dmaload: buffer is %p\n", (void *)buf->data);

       }

 

       writel(buf->data, chan->addr_reg);  // 写地址寄存器

   

    // 不解释了 , 看寄存器说明吧

       dma_wrreg(chan, S3C2410_DMA_DCON,

                chan->dcon | reload | (buf->size/chan->xfer_unit));

 

       chan->next = buf->next;  // 更新链表

 

       /* update the state of the channel */

  

    // 更新状态

       switch (chan->load_state) {

       case S3C2410_DMALOAD_NONE:

              chan->load_state = S3C2410_DMALOAD_1LOADED;

              break;

 

       case S3C2410_DMALOAD_1RUNNING:

              chan->load_state = S3C2410_DMALOAD_1LOADED_1RUNNING;

              break;

 

       default:

              dmawarn("dmaload: unknown state %d in loadbuffer\n",

                     chan->load_state);

              break;

       }

 

       return 0;

}

该函数主要是把要传输的数据的 地址先存入寄存器中 , 等当前的传输完成后会根据时候 auto reload 的情况来确定是否开始这次的传输 .

OK, 到目前为止讲完了所有的 export 的函数 , 现在还剩下 dma 的操作函数和中断函数没讲了 , let’s go!

我们先看中断函数 , 该函数在一次传输完成后被调用

Arch\arm\plat-s3c24xx\dma.c:

static irqreturn_t

s3c2410_dma_irq(int irq, void *devpw)

{

       struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw;

       struct s3c2410_dma_buf  *buf;

 

       buf = chan->curr;  // 当前传输完毕的 buf

 

       dbg_showchan(chan);

 

       /* modify the channel state */

  // 修改当前状态

       switch (chan->load_state) {

       case S3C2410_DMALOAD_1RUNNING:

              /* TODO - if we are running only one buffer, we probably

                * want to reload here, and then worry about the buffer

                * callback */

 

              chan->load_state = S3C2410_DMALOAD_NONE;

              break;

 

       case S3C2410_DMALOAD_1LOADED:

              /* iirc, we should go back to NONE loaded here, we

                * had a buffer, and it was never verified as being

                * loaded.

                */

 

              chan->load_state = S3C2410_DMALOAD_NONE;

              break;

 

       case S3C2410_DMALOAD_1LOADED_1RUNNING:

              /* we'll worry about checking to see if another buffer is

                * ready after we've called back the owner. This should

                * ensure we do not wait around too long for the DMA

                * engine to start the next transfer

                */

 

              chan->load_state = S3C2410_DMALOAD_1LOADED;

              break;

 

       case S3C2410_DMALOAD_NONE:

              printk(KERN_ERR "dma%d: IRQ with no loaded buffer?\n",

                     chan->number);

              break;

 

       default:

              printk(KERN_ERR "dma%d: IRQ in invalid load_state %d\n",

                     chan->number, chan->load_state);

              break;

       }

 

       if (buf != NULL) {

              /* update the chain to make sure that if we load any more

                * buffers when we call the callback function, things should

                * work properly */

 

              chan->curr = buf->next;   // 更新传输的 buf

              buf->next  = NULL;

 

              if (buf->magic != BUF_MAGIC) {

                     printk(KERN_ERR "dma%d: %s: buf %p incorrect magic\n",

                            chan->number, __FUNCTION__, buf);

                     return IRQ_HANDLED;

              }

 

              s3c2410_dma_buffdone(chan, buf, S3C2410_RES_OK);  //buf 传输完成后的操作

 

              /* free resouces */

              s3c2410_dma_freebuf(buf);  // 释放 buf, 我们看到传输前有申请 buf

       } else {

       }

 

       /* only reload if the channel is still running... our buffer done

         * routine may have altered the state by requesting the dma channel

         * to stop or shutdown... */

 

       /* todo: check that when the channel is shut-down from inside this

         * function, we cope with unsetting reload, etc */

   // 还有要传输的 buf, 则继续传输

       if (chan->next != NULL && chan->state != S3C2410_DMA_IDLE) {

              unsigned long flags;

 

              switch (chan->load_state) {

              case S3C2410_DMALOAD_1RUNNING:

                     /* don't need to do anything for this state */

                     break;

 

              case S3C2410_DMALOAD_NONE:

                     /* can load buffer immediately */

                     break;

 

              case S3C2410_DMALOAD_1LOADED:

                     if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {  // 等待被传输

                            /* flag error? */

                            printk(KERN_ERR "dma%d: timeout waiting for load (%s)\n",

                                   chan->number, __FUNCTION__);

                            return IRQ_HANDLED;

                     }

 

                     break;

 

              case S3C2410_DMALOAD_1LOADED_1RUNNING:

                     goto no_load;

 

              default:

                     printk(KERN_ERR "dma%d: unknown load_state in irq, %d\n",

                            chan->number, chan->load_state);

                     return IRQ_HANDLED;

              }

 

              local_irq_save(flags);

              s3c2410_dma_loadbuffer(chan, chan->next);   // 加载下一个 buf

              local_irq_restore(flags);

       } else {  // 所有的传输完成

              s3c2410_dma_lastxfer(chan);   // 完成处理工作

 

              /* see if we can stop this channel.. */

              if (chan->load_state == S3C2410_DMALOAD_NONE) {

                     pr_debug("dma%d: end of transfer, stopping channel (%ld)\n",

                              chan->number, jiffies);

                     s3c2410_dma_ctrl(chan->number | DMACH_LOW_LEVEL,

                                     S3C2410_DMAOP_STOP);  // 停止 dma 传输

              }

       }

 

  no_load:

       return IRQ_HANDLED;

}

我们看到当传输队列中还有 buf 要传输时 , 没有看到 start 的操作 , 这是为什么呢 ? 因为在 load 的时候我们分析过 , 如果后续还有 buf 要传输 , 则自动加载运行 ,  所以这里没有必要手工 start.

s3c2410_dma_buffdone() 函数仅仅是调用前面注册的回调函数 , 这里不列出来了 .

s3c2410_dma_freebuf() 也很简单 , 就是把 buf 归还到缓冲池去 .

我们看下 s3c2410_dma_lastxfer

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_dma_lastxfer

  *

  * called when the system is out of buffers, to ensure that the channel

  * is prepared for shutdown.

*/

 

static inline void

s3c2410_dma_lastxfer(struct s3c2410_dma_chan *chan)

{

#if 0

       pr_debug("dma%d: s3c2410_dma_lastxfer: load_state %d\n",

                chan->number, chan->load_state);

#endif

 

       switch (chan->load_state) {

       case S3C2410_DMALOAD_NONE:

              break;

 

       case S3C2410_DMALOAD_1LOADED:

              if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {  // 等待加载的 buf 被执行

                            /* flag error? */

                     printk(KERN_ERR "dma%d: timeout waiting for load (%s)\n",

                            chan->number, __FUNCTION__);

                     return;

              }

              break;

 

       case S3C2410_DMALOAD_1LOADED_1RUNNING:

              /* I belive in this case we do not have anything to do

                * until the next buffer comes along, and we turn off the

                * reload */

              return;

 

       default:

              pr_debug("dma%d: lastxfer: unhandled load_state %d with no next\n",

                       chan->number, chan->load_state);

              return;

 

       }

 

       /* hopefully this'll shut the damned thing up after the transfer... */

    // 清楚自动加载标记 , 因为无后续要传输的 buf, 所以要清这个标记

       dma_wrreg(chan, S3C2410_DMA_DCON, chan->dcon | S3C2410_DCON_NORELOAD);

}

很简单的一个函数 , 这里不多说了 .

好了 , 到这个该着中分析操作函数了 .

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_dma_start

  *

  * start a dma channel going

*/

 

static int s3c2410_dma_start(struct s3c2410_dma_chan *chan)

{

       unsigned long tmp;

       unsigned long flags;

 

       pr_debug("s3c2410_start_dma: channel=%d\n", chan->number);

 

       local_irq_save(flags);

 

       if (chan->state == S3C2410_DMA_RUNNING) {  // 已经有 run 的了

              pr_debug("s3c2410_start_dma: already running (%d)\n", chan->state);

              local_irq_restore(flags);

              return 0;

       }

 

       chan->state = S3C2410_DMA_RUNNING;  // 更新状态

 

       /* check wether there is anything to load, and if not, see

         * if we can find anything to load

         */

 

       if (chan->load_state == S3C2410_DMALOAD_NONE) { // 没有加载过 buf

              if (chan->next == NULL) {  // 没有 buf 要传送的

                     printk(KERN_ERR "dma%d: channel has nothing loaded\n",

                            chan->number);

                     chan->state = S3C2410_DMA_IDLE;

                     local_irq_restore(flags);

                     return -EINVAL;

              }

 

              s3c2410_dma_loadbuffer(chan, chan->next);  // 加载 buf, 加载状态也会相应更新

       }

 

       dbg_showchan(chan);

 

       /* enable the channel */

 

       if (!chan->irq_enabled) {

              enable_irq(chan->irq);  // 使能中断

              chan->irq_enabled = 1;

       }

 

       /* start the channel going */

    // 启动 DMA 传输 ,

       tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);

       tmp &= ~S3C2410_DMASKTRIG_STOP;

       tmp |= S3C2410_DMASKTRIG_ON;

       dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);

 

       pr_debug("dma%d: %08lx to DMASKTRIG\n", chan->number, tmp);

 

#if 0

       /* the dma buffer loads should take care of clearing the AUTO

         * reloading feature */

       tmp = dma_rdreg(chan, S3C2410_DMA_DCON);

       tmp &= ~S3C2410_DCON_NORELOAD;

       dma_wrreg(chan, S3C2410_DMA_DCON, tmp);

#endif

 

       s3c2410_dma_call_op(chan, S3C2410_DMAOP_START);  // 调用注册的 op 回调函数

 

       dbg_showchan(chan);

 

       /* if we've only loaded one buffer onto the channel, then chec

         * to see if we have another, and if so, try and load it so when

         * the first buffer is finished, the new one will be loaded onto

         * the channel */

    // 由于当前 load 的已经在运行了 , 因此如果还有要传输的 buf load 进来

       if (chan->next != NULL) {

              if (chan->load_state == S3C2410_DMALOAD_1LOADED) {

            // 等待该 buf 被运行 , 别忘了我们设了自动加载运行 .

                     if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {

                            pr_debug("%s: buff not yet loaded, no more todo\n",

                                     __FUNCTION__);

                     } else {

                            chan->load_state = S3C2410_DMALOAD_1RUNNING;

                            s3c2410_dma_loadbuffer(chan, chan->next);  // 加载 buf

                     }

 

              } else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {

                     s3c2410_dma_loadbuffer(chan, chan->next); // 加载 buf

              }

       }

 

 

       local_irq_restore(flags);

 

       return 0;

}

整个启动流程就这样完了 .

 

Arch\arm\plat-s3c24xx\dma.c:

static int s3c2410_dma_dostop(struct s3c2410_dma_chan *chan)

{

       unsigned long flags;

       unsigned long tmp;

 

       pr_debug("%s:\n", __FUNCTION__);

 

       dbg_showchan(chan);

 

       local_irq_save(flags);

 

       s3c2410_dma_call_op(chan,  S3C2410_DMAOP_STOP);  // 通知用户该操作

   

    // 停止 DMA 传输

       tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);

       tmp |= S3C2410_DMASKTRIG_STOP;

       //tmp &= ~S3C2410_DMASKTRIG_ON;

       dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);

 

#if 0

       /* should also clear interrupts, according to WinCE BSP */

       tmp = dma_rdreg(chan, S3C2410_DMA_DCON);

       tmp |= S3C2410_DCON_NORELOAD;

       dma_wrreg(chan, S3C2410_DMA_DCON, tmp);

#endif

   

    // 更新状态

       /* should stop do this, or should we wait for flush? */

       chan->state      = S3C2410_DMA_IDLE;

       chan->load_state = S3C2410_DMALOAD_NONE;

 

       local_irq_restore(flags);

 

       return 0;

}

该函数比较简单 , 接着看

 

Arch\arm\plat-s3c24xx\dma.c:

/* s3c2410_dma_flush

  *

  * stop the channel, and remove all current and pending transfers

*/

 

static int s3c2410_dma_flush(struct s3c2410_dma_chan *chan)

{

       struct s3c2410_dma_buf *buf, *next;

       unsigned long flags;

 

       pr_debug("%s: chan %p (%d)\n", __FUNCTION__, chan, chan->number);

 

       dbg_showchan(chan);

 

       local_irq_save(flags);

 

       if (chan->state != S3C2410_DMA_IDLE) {

              pr_debug("%s: stopping channel...\n", __FUNCTION__ );

              s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_STOP);  // 停调传输

       }

 

       buf = chan->curr;

       if (buf == NULL)

              buf = chan->next;

 

       chan->curr = chan->next = chan->end = NULL;

 

       if (buf != NULL) {

              for ( ; buf != NULL; buf = next) {

                     next = buf->next;

 

                     pr_debug("%s: free buffer %p, next %p\n",

                            __FUNCTION__, buf, buf->next);

 

                     s3c2410_dma_buffdone(chan, buf, S3C2410_RES_ABORT);  // 通知用户中断了传输

                     s3c2410_dma_freebuf(buf);  // 释放 buf

              }

       }

 

       dbg_showregs(chan);

 

       s3c2410_dma_waitforstop(chan);  

 

#if 0

       /* should also clear interrupts, according to WinCE BSP */

       {

              unsigned long tmp;

 

              tmp = dma_rdreg(chan, S3C2410_DMA_DCON);

              tmp |= S3C2410_DCON_NORELOAD;

              dma_wrreg(chan, S3C2410_DMA_DCON, tmp);

       }

#endif

 

       dbg_showregs(chan);

 

       local_irq_restore(flags);

 

       return 0;

}

  该函数的作用就是中断所有的传输 , 并把所有队列中等待传输的 buf 都清掉 .

 

Arch\arm\plat-s3c24xx\dma.c:

static int s3c2410_dma_started(struct s3c2410_dma_chan *chan)

{

       unsigned long flags;

 

       local_irq_save(flags);

 

       dbg_showchan(chan);

 

       /* if we've only loaded one buffer onto the channel, then chec

         * to see if we have another, and if so, try and load it so when

         * the first buffer is finished, the new one will be loaded onto

         * the channel */

    // 看注释吧 , 不解释了

       if (chan->next != NULL) {

              if (chan->load_state == S3C2410_DMALOAD_1LOADED) {

 

                     if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {

                            pr_debug("%s: buff not yet loaded, no more todo\n",

                                     __FUNCTION__);

                     } else {

                            chan->load_state = S3C2410_DMALOAD_1RUNNING;

                            s3c2410_dma_loadbuffer(chan, chan->next);

                     }

 

              } else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {

                     s3c2410_dma_loadbuffer(chan, chan->next);

              }

       }

 

 

       local_irq_restore(flags);

 

       return 0;

 

}

最后的这个函数就由大家自己分 析吧 .

s3c2410_dma_config 函数可以看出 , 该驱动只支持硬件请求模式 , 而从 s3c2410_dma_devconfig 函数可以看出 , 该驱动只支持设备和 memory 之间的 DMA 传输 .

至于如何使用的问题 , 可以去代码里搜一下哪些地方调用了 export 出来的函数就懂了 , 2410 的板子上 PCM 会用到 DMA 传输 ,   使用流程为 :

    s3c2410_dma_request ->

s3c2410_dma_devconfig ->

s3c2410_dma_config ->            

s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);

当然一般还会注册回调函数的 .

到此为止整个 DMA 的操作流程都分析完了 , 希望对你有所帮助 , 

以后会写些 cache, mmu, write buffer 等方面的驱动分析 .

阅读(1280) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~