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);
分析完毕