Platform: Rockchip
OS: Android 6.0
Kernel: 3.10.92
DMA(Direct Memory Access,直接内存存取),顾名思义,不占用cpu资源,从一个硬件存储区域把一部分连续的数据复制到另一个硬件存储区域。
这里的用例就是:
录音时,把录音数据从I2S搬运到驱动内存。
放音时,把驱动里的播放数据搬到I2S。
DMA buffer配置:
I2S初始化的时候会有如下调用:
rockchip_i2s_probe -> rk_i2s.c
rockchip_pcm_platform_register -> rk_pcm.c
snd_dmaengine_pcm_register -> //rockchip_dmaengine_pcm_config中包含了预分配的size,prealloc_buffer_size = PAGE_SIZE * 512;
snd_soc_add_platform -> //后面会调用dmaengine_no_residue_pcm_platform中的dmaengine_pcm_new()
dmaengine_no_residue_pcm_platform结构:
static const struct snd_soc_platform_driver dmaengine_no_residue_pcm_platform = {
.ops = &dmaengine_no_residue_pcm_ops,
.pcm_new = dmaengine_pcm_new,
.pcm_free = dmaengine_pcm_free,
.probe_order = SND_SOC_COMP_ORDER_LATE,
};
那么dmaengine_pcm_new ()什么时候会被调用?
音频初始化创建pcm的时候会调用soc_new_pcm():
soc_new_pcm -> soc-pcm.c
platform->driver->pcm_new ->
dmaengine_pcm_new -> soc-generic-dmaengine-pcm.c //这里就调用到了。
snd_pcm_lib_preallocate_pages -> //参数config->prealloc_buffer_size前面说了config->pcm_hardware->buffer_bytes_max在rockchip_pcm_hardware结构中赋值。
snd_pcm_lib_preallocate_pages1 ->
preallocate_pcm_pages -> //预分配
snd_dma_alloc_pages -> pcm_memory.c
dmab->area = snd_malloc_dev_pages(......,dmab->addr)) //type是SNDRV_DMA_TYPE_DEV, dmab->area存虚拟地址,dmab->addr存物理地址
dmab->bytes = size; //保存分配的size
分配完成之后,dma的buffer信息会被放到pcm runtime中管理:
static inline void snd_pcm_set_runtime_buffer(struct snd_pcm_substream *substream,
struct snd_dma_buffer *bufp)
{
struct snd_pcm_runtime *runtime = substream->runtime;
if (bufp) {
runtime->dma_buffer_p = bufp;
runtime->dma_area = bufp->area;
runtime->dma_addr = bufp->addr;
runtime->dma_bytes = bufp->bytes;
} else {
......
}
}
dma端自身的控制是:
I2S buffer配置:
很多平台调用的是snd_soc_dai_set_dma_data(), rk3288平台上直接赋值了。
static int rockchip_i2s_probe(struct platform_device *pdev)
{
......
i2s->playback_dma_data.addr = res->start + I2S_TXDR;
i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
i2s->playback_dma_data.maxburst = I2S_DMA_BURST_SIZE;
i2s->capture_dma_data.addr = res->start + I2S_RXDR;
i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
i2s->capture_dma_data.maxburst = I2S_DMA_BURST_SIZE;
......
}
I2S的buffer 信息放到dai中:
static int rockchip_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct rk_i2s_dev *i2s = to_info(dai);
dai->capture_dma_data = &i2s->capture_dma_data;
dai->playback_dma_data = &i2s->playback_dma_data;
return 0;
}
这样snd_soc_dai_get_dma_data()就可以获取到了:
static inline void *snd_soc_dai_get_dma_data(const struct snd_soc_dai *dai,
const struct snd_pcm_substream *ss)
{
return (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
dai->playback_dma_data : dai->capture_dma_data;
}
dma端自身控制:
void snd_dmaengine_pcm_set_config_from_dai_data(
const struct snd_pcm_substream *substream,
const struct snd_dmaengine_dai_dma_data *dma_data,
struct dma_slave_config *slave_config)
{
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
slave_config->dst_addr = dma_data->addr;
slave_config->dst_maxburst = dma_data->maxburst;
if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
slave_config->dst_addr_width = dma_data->addr_width;
} else {
slave_config->src_addr = dma_data->addr;
slave_config->src_maxburst = dma_data->maxburst;
if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
slave_config->src_addr_width = dma_data->addr_width;
}
slave_config->slave_id = dma_data->slave_id;
}
这样用户空间可以读写dma了. 如用户空间读取dma buffer:
snd_pcm_capture_ioctl -> pcm_lib.c
snd_pcm_capture_ioctl1 ->
snd_pcm_lib_read ->
snd_pcm_lib_read1 ->
snd_pcm_lib_read_transfer
static int snd_pcm_lib_read_transfer(struct snd_pcm_substream *substream,
unsigned int hwoff,
unsigned long data, unsigned int off,
snd_pcm_uframes_t frames)
{
struct snd_pcm_runtime *runtime = substream->runtime;
......
//dma buffer的数据给用户空间的buf.
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, frames)))
return -EFAULT;
......
}