DMA_CYCLIC使用

DMA传输类型:
 enum dma_transaction_type {
         DMA_MEMCPY,                // 内存拷贝
         DMA_XOR,                   // 异或操作
         DMA_PQ,                    // 乘方操作
         DMA_XOR_VAL,               // 异或值操作
         DMA_PQ_VAL,                // 乘方值操作
         DMA_INTERRUPT,             // DMA中断?
         DMA_SG,                    // 散射-聚集(Scatter-Gather)传输
         DMA_PRIVATE,               // 私有的DMA传输
         DMA_ASYNC_TX,              // 异步传输
         DMA_SLAVE,                 // DMA控制器设备功能,必有
         DMA_CYCLIC,                // 循环DMA传输
         DMA_INTERLEAVE,            // 交错DMA传输
 /* last transaction type for creation of the capabilities mask */
         DMA_TX_TYPE_END,
 };

 

此节以audio使用DMA_CYCLIC举例,说明下如何使用:
 static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
 {
         struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
         struct dma_chan *chan = prtd->dma_chan;
         struct dma_async_tx_descriptor *desc;
         enum dma_transfer_direction direction;
         unsigned long flags = DMA_CTRL_ACK;

         direction = snd_pcm_substream_to_dma_direction(substream);

         if (!substream->runtime->no_period_wakeup)
                 flags |= DMA_PREP_INTERRUPT;

         prtd->pos = 0;
         desc = dmaengine_prep_dma_cyclic(chan,
                 substream->runtime->dma_addr,
                 snd_pcm_lib_buffer_bytes(substream),
                 snd_pcm_lib_period_bytes(substream), direction, flags);    // 进行dmac cyclic传输

         if (!desc)
                 return -ENOMEM;

         desc->callback = dmaengine_pcm_dma_complete;    // 传输完成时的回调函数
         desc->callback_param = substream;                // 传输完成时回调函数的参数
         prtd->cookie = dmaengine_submit(desc);            // 提交一次传输

         return 0;
 }

 

调用到dmaengine的核心层:
 static inline struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
                 struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
                 size_t period_len, enum dma_transfer_direction dir,
                 unsigned long flags)
 {
         if (!chan || !chan->device || !chan->device->device_prep_dma_cyclic)
                 return NULL;

         return chan->device->device_prep_dma_cyclic(chan, buf_addr, buf_len,
                                                 period_len, dir, flags);        // dmac具体实现
 }
 
 struct dma_async_tx_descriptor {
         dma_cookie_t cookie;                // 一个整型数,用于追踪本次传输
         enum dma_ctrl_flags flags; /* not a 'long' to pack with cookie */
         dma_addr_t phys;
         struct dma_chan *chan;
         dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx);
         int (*desc_free)(struct dma_async_tx_descriptor *tx);
         dma_async_tx_callback callback;                // 传输完成时的回调函数
         dma_async_tx_callback_result callback_result;
         void *callback_param;                            // 传输完成时回调函数的参数
         struct dmaengine_unmap_data *unmap;
         enum dma_desc_metadata_mode desc_metadata_mode;
         struct dma_descriptor_metadata_ops *metadata_ops;
 #ifdef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH
         struct dma_async_tx_descriptor *next;
         struct dma_async_tx_descriptor *parent;
         spinlock_t lock;
 #endif
 };   

 

调用到dma控制器的dma_device.device_prep_dma_cyclic具体实现,device_prep_dma_cyclic会对dmac进行配置,返回dma_async_tx_descriptor描述符
在dmac实现中,通常会对每一个vchan进行vchan_init操作,例如:
... ...
         for (i = 0; i < sdc->cfg->nr_max_vchans; i++) {
                 struct sun6i_vchan *vchan = &sdc->vchans[i];

                 INIT_LIST_HEAD(&vchan->node);
                 vchan->vc.desc_free = sun6i_dma_free_desc;
                 vchan_init(&vchan->vc, &sdc->slave);
         }
... ...

 

而在vchan_init函数中,会定义一个tasklet任务,如下:
 void vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev)
 {
         dma_cookie_init(&vc->chan);

         spin_lock_init(&vc->lock);
         INIT_LIST_HEAD(&vc->desc_submitted);
         INIT_LIST_HEAD(&vc->desc_issued);
         INIT_LIST_HEAD(&vc->desc_completed);

         tasklet_init(&vc->task, vchan_complete, (unsigned long)vc);        // 初始化一个tasklet任务vchan_complete

         vc->chan.device = dmadev;
         list_add_tail(&vc->chan.device_node, &dmadev->channels);
 }

 

这个tasklet在一次描述符传输完成时被调度,例如:
 static void sun6i_dma_issue_pending(struct dma_chan *chan)
 {
         struct sun6i_dma_dev *sdev = to_sun6i_dma_dev(chan->device);
         struct sun6i_vchan *vchan = to_sun6i_vchan(chan);
         unsigned long flags;

         spin_lock_irqsave(&vchan->vc.lock, flags);

         if (vchan_issue_pending(&vchan->vc)) {
                 spin_lock(&sdev->lock);

                 if (!vchan->phy && list_empty(&vchan->node)) {
                         list_add_tail(&vchan->node, &sdev->pending);
                         tasklet_schedule(&sdev->task);                    // 调度vchan_init初始化的tasklet,也就是调度vchan_complete
                         dev_dbg(chan2dev(chan), "vchan %p: issued\n",
                                 &vchan->vc);
                 }
... ...
 }
 
 或调用vchan_cyclic_callback函数调度

   static inline void vchan_cyclic_callback(struct virt_dma_desc *vd)
 {
         struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);

         vc->cyclic = vd;
         tasklet_schedule(&vc->task);
 }

 

在vchan_complete函数中,
 static void vchan_complete(struct tasklet_struct *t)
 {
         struct virt_dma_chan *vc = from_tasklet(vc, t, task);
         struct virt_dma_desc *vd, *_vd;
         struct dmaengine_desc_callback cb;
         LIST_HEAD(head);

         spin_lock_irq(&vc->lock);
         list_splice_tail_init(&vc->desc_completed, &head);
         vd = vc->cyclic;
         if (vd) {
                 vc->cyclic = NULL;
                 dmaengine_desc_get_callback(&vd->tx, &cb);    // 获取pcm设置的函数
         } else {
                 memset(&cb, 0, sizeof(cb));
         }
         spin_unlock_irq(&vc->lock);

         dmaengine_desc_callback_invoke(&cb, &vd->tx_result);        // 进行回调

         list_for_each_entry_safe(vd, _vd, &head, node) {
                 dmaengine_desc_get_callback(&vd->tx, &cb);

                 list_del(&vd->node);
                 dmaengine_desc_callback_invoke(&cb, &vd->tx_result);
                 vchan_vdesc_fini(vd);
         }
 }

 

dmaengine_desc_get_callback和dmaengine_desc_callback_invoke定义如下:
 static inline void
 dmaengine_desc_get_callback(struct dma_async_tx_descriptor *tx,
                             struct dmaengine_desc_callback *cb)
 {
         cb->callback = tx->callback;
         cb->callback_result = tx->callback_result;
         cb->callback_param = tx->callback_param;
 }
... ...
 static inline void
 dmaengine_desc_callback_invoke(struct dmaengine_desc_callback *cb,
                                const struct dmaengine_result *result)
 {
         struct dmaengine_result dummy_result = {
                 .result = DMA_TRANS_NOERROR,
                 .residue = 0
         };

         if (cb->callback_result) {    // 没设置callback_result就调用callback
                 if (!result)
                         result = &dummy_result;
                 cb->callback_result(cb->callback_param, result);
         } else if (cb->callback) {
                 cb->callback(cb->callback_param);
         }
 }

 

自此回来分析的起点dmaengine_pcm_prepare_and_submit,在这个函数中我们定义了callback函数,该callback函数定义如下:
 static void dmaengine_pcm_dma_complete(void *arg)
 {
         struct snd_pcm_substream *substream = arg;
         struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);

         prtd->pos += snd_pcm_lib_period_bytes(substream);
         if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream))
                 prtd->pos = 0;
         // 通知 ALSA 子系统一个音频周期已经结束,并且 ALSA 子系统会自动进行后续处理,包括更新 PCM 子流的状态以及准备处理下一个周期的音频数据。
         snd_pcm_period_elapsed(substream);    
 }
 ... ...
  void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
 {
         struct snd_pcm_runtime *runtime;
         unsigned long flags;

         if (snd_BUG_ON(!substream))
                 return;

         snd_pcm_stream_lock_irqsave(substream, flags);
         if (PCM_RUNTIME_CHECK(substream))
                 goto _unlock;
         runtime = substream->runtime;

         if (!snd_pcm_running(substream) ||
             snd_pcm_update_hw_ptr0(substream, 1) < 0)        //  ALSA 中用于更新 PCM(Pulse Code Modulation)音频子流的硬件指针(hardware pointer)的函数
                 goto _end;

 #ifdef CONFIG_SND_PCM_TIMER
         if (substream->timer_running)
                 snd_timer_interrupt(substream->timer, 1);
 #endif
  _end:
         kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
  _unlock:
         snd_pcm_stream_unlock_irqrestore(substream, flags);
 }
 EXPORT_SYMBOL(snd_pcm_period_elapsed);
分析完毕
 
 
 
 
 
 
 
posted @ 2024-04-01 18:42  lethe1203  阅读(134)  评论(0编辑  收藏  举报