AlSA驱动中的PCM DMA

本文介绍ASoCPlatform驱动中使用PCMDMA架构实现DMA通道申请的过程。重点讲解了devm_snd_dmaengine_pcm_register函数如何为设备注册dmaengine_pcm,并通过dmaengine_pcm_request_chan_of函数申请DMA通道。

ASoC Platform驱动中使用了PCM DMA的构架来实现了申请DMA通道。

首先得probe函数中会调用ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
为设备注册一个dmaengine_pcm。

这个调用除了注册同时还注册了一个释放接口。

核心是调用了
snd_dmaengine_pcm_register(dev, config, flags);

int devm_snd_dmaengine_pcm_register(struct device *dev,
     const struct snd_dmaengine_pcm_config *config, unsigned int flags)  
 {  
    struct device **ptr;  
     int ret;  

     ptr = devres_alloc(devm_dmaengine_pcm_release, sizeof(*ptr), GFP_KERNEL);  
     if (!ptr)  
         return -ENOMEM;  

     ret = snd_dmaengine_pcm_register(dev, config, flags);  
     if (ret == 0) {  
         *ptr = dev;  
         devres_add(dev, ptr);  
     } else {  
         devres_free(ptr);  
     }  

     return ret;  
}  

snd_dmaengine_pcm_register()中通过dmaengine_pcm_request_chan_of()去申请DMA通道。

/**
 * snd_dmaengine_pcm_register - Register a dmaengine based PCM device
 * @dev: The parent device for the PCM device
 * @config: Platform specific PCM configuration
 * @flags: Platform specific quirks
 */
int snd_dmaengine_pcm_register(struct device *dev,
    const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
    struct dmaengine_pcm *pcm;
    int ret;

    pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
    if (!pcm)
        return -ENOMEM;

    pcm->config = config;
    pcm->flags = flags;

    ret = dmaengine_pcm_request_chan_of(pcm, dev, config);
    if (ret)
        goto err_free_dma;

    ret = snd_soc_add_platform(dev, &pcm->platform,
        &dmaengine_pcm_platform);
    if (ret)
        goto err_free_dma;

    return 0;

err_free_dma:
    dmaengine_pcm_release_chan(pcm);
    kfree(pcm);
    return ret;
}

dmaengine_pcm_request_chan_of()根据flag 标志以及驱动中定义的数组

static const char * const dmaengine_pcm_dma_channel_names[] = {
[SNDRV_PCM_STREAM_PLAYBACK] = “tx”,
[SNDRV_PCM_STREAM_CAPTURE] = “rx”,
};

获取DMA-name。为参数调用dma_request_slave_channel_reason()

static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm,
    struct device *dev, const struct snd_dmaengine_pcm_config *config)
{
    unsigned int i;
    const char *name;
    struct dma_chan *chan;

    if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_DT) || !dev->of_node)
        return 0;

    if (config && config->dma_dev) {
        /*
         * If this warning is seen, it probably means that your Linux
         * device structure does not match your HW device structure.
         * It would be best to refactor the Linux device structure to
         * correctly match the HW structure.
         */
        dev_warn(dev, "DMA channels sourced from device %s",
             dev_name(config->dma_dev));
        dev = config->dma_dev;
    }

    for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE;
         i++) {
        if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
            name = "rx-tx";
        else
            name = dmaengine_pcm_dma_channel_names[i];
        if (config && config->chan_names[i])
            name = config->chan_names[i];
        chan = dma_request_slave_channel_reason(dev, name);
        if (IS_ERR(chan)) {
            if (PTR_ERR(chan) == -EPROBE_DEFER)
                return -EPROBE_DEFER;
            pcm->chan[i] = NULL;
        } else {
            pcm->chan[i] = chan;
        }
        if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
            break;
    }

    if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
        pcm->chan[1] = pcm->chan[0];

    return 0;
}


static const char * const dmaengine_pcm_dma_channel_names[] = {
    [SNDRV_PCM_STREAM_PLAYBACK] = "tx",
    [SNDRV_PCM_STREAM_CAPTURE] = "rx",
};

dma_request_slave_channel_reason()根据参数name 并解析device tree 来申请具体的DMA 通道。一个DMA控制器有8个channel,每两个作为一组,既可以输出也可以输入,但它们的控制代码是一样的,并且在DMA注册过程中,已经使DMA处于可用状态了,这里根据传入的name 参数以及在device tree 中的描述申请具体的通道。

device tree的描述如下:

axi_i2s_0: axi-i2s@0x77600000 {    
            compatible = "adi,axi-i2s-1.00.a";    
            reg = <0x77600000 0x1000>;    
             dmas = <&dmac_s 1 &dmac_s 2>;    
             dma-names = "tx", "rx";    
             clocks = <&clkc 15>, <&audio_clock>;    
             clock-names = "axi", "ref";    
        };  

每个需要使用DMA的client都会通过 dmas来引用DMA控制器和通道,通过dma-names实现name的匹配。

/**
 * of_dma_request_slave_channel - Get the DMA slave channel
 * @np:     device node to get DMA request from
 * @name:   name of desired channel
 *
 * Returns pointer to appropriate DMA channel on success or an error pointer.
 */
struct dma_chan *of_dma_request_slave_channel(struct device_node *np,
                          const char *name)
{
    struct of_phandle_args  dma_spec;
    struct of_dma       *ofdma;
    struct dma_chan     *chan;
    int         count, i, start;
    int         ret_no_channel = -ENODEV;
    static atomic_t     last_index;

    if (!np || !name) {
        pr_err("%s: not enough information provided\n", __func__);
        return ERR_PTR(-ENODEV);
    }

    /* Silently fail if there is not even the "dmas" property */
    if (!of_find_property(np, "dmas", NULL))
        return ERR_PTR(-ENODEV);

    count = of_property_count_strings(np, "dma-names");
    if (count < 0) {
        pr_err("%s: dma-names property of node '%s' missing or empty\n",
            __func__, np->full_name);
        return ERR_PTR(-ENODEV);
    }

    /*
     * approximate an average distribution across multiple
     * entries with the same name
     */
    start = atomic_inc_return(&last_index);
    for (i = 0; i < count; i++) {
        if (of_dma_match_channel(np, name,
                     (i + start) % count,
                     &dma_spec))
            continue;

        mutex_lock(&of_dma_lock);
        ofdma = of_dma_find_controller(&dma_spec);

        if (ofdma) {
            chan = ofdma->of_dma_xlate(&dma_spec, ofdma);
        } else {
            ret_no_channel = -EPROBE_DEFER;
            chan = NULL;
        }

        mutex_unlock(&of_dma_lock);

        of_node_put(dma_spec.np);

        if (chan)
            return chan;
    }

    return ERR_PTR(ret_no_channel);
}
<think>我们正在讨论ALSA驱动DMA缓冲区大小对音频延迟的影响。这是一个系统级音频处理的关键参数。注意:用户要求使用中文回答,并且按照指定的格式要求(行内公式用$...$,独立公式用$$...$$),同时生成相关问题。核心概念:在ALSA驱动中,DMA(直接内存访问)缓冲区用于音频数据传输。它由多个period(周期)组成,每个period是DMA传输的基本单位。音频延迟主要由缓冲区大小和周期大小决定。关键公式:总延迟时间=(缓冲区大小/采样率)*1000毫秒例如:缓冲区大小为1024帧,采样率为44100Hz,则延迟=(1024/44100)*1000≈23.2ms但实际延迟还受到其他因素影响,如硬件延迟、中断延迟等。影响分析:1.缓冲区大小与延迟的关系:增大缓冲区->增加延迟,但减少发生欠载(underrun)的风险(因为CPU有更多时间填充缓冲区)减小缓冲区->降低延迟,但增加欠载风险(如果CPU不能及时填充,会导致音频断断续续)2.周期大小(periodsize)与中断频率的关系:每个period传输完成后会触发中断。周期大小越小,中断频率越高,CPU响应要求越高。周期大小=缓冲区大小/周期数(periodcount)3.实时性要求:对于低延迟音频应用(如专业音频制作、实时语音通信),需要较小的缓冲区和适当的周期数。典型低延迟配置:缓冲区大小=256帧,周期数=2,则每个period为128帧,在44.1kHz下延迟≈5.8ms。4.系统负载影响:在负载高的系统中,过小的缓冲区可能导致欠载。因此需要在延迟和稳定性之间权衡。配置方法(在ALSA驱动中):在驱动代码中,通常通过`snd_pcm_hardware`结构体定义缓冲区范围:```cstaticstructsnd_pcm_hardwaremy_pcm_hw={.buffer_bytes_max=32768,//最大缓冲区字节数.period_bytes_min=64,//最小周期字节数.period_bytes_max=8192,//最大周期字节数.periods_min=2,//最小周期数.periods_max=8,//最大周期数...};```用户空间可以通过alsa-lib的API(如`snd_pcm_hw_params_set_buffer_size_near`)设置缓冲区大小。调试技巧:查看当前DMA缓冲区配置:```bashcat/proc/asound/card0/pcm0p/sub0/hw_params```输出示例:```access:MMAP_INTERLEAVEDformat:S16_LEsubformat:STDchannels:2rate:44100(44100/1)period_size:128buffer_size:256```结论:调整DMA缓冲区大小是在音频延迟和系统稳定性之间的权衡。低延迟需要小缓冲区,但要求系统实时响应能力高;大缓冲区可避免欠载,但增加延迟。生成相关问题:1.如何计算ALSA驱动的最大安全缓冲区大小以避免欠载?2.在Linux系统中如何动态调整ALSA的缓冲区大小?3.音频欠载(underrun)的根本原因是什么?4.除了DMA缓冲区,还有哪些因素影响音频延迟?5.如何为实时音频应用优化Linux内核?</think>### ALSA驱动DMA缓冲区大小对音频延迟的影响分析 #### 核心原理 在ALSA音频子系统中,DMA(直接内存访问)缓冲区是音频数据传输的核心载体。其大小直接影响音频延迟,关系可表示为: $$ \text{音频延迟} = \frac{\text{缓冲区大小}}{\text{采样率}} \times 1000 \text{ ms} $$ 例如:1024帧缓冲区在48kHz采样率下的延迟为$ \frac{1024}{48000} \times 1000 \approx 21.3 \text{ms} $[^1]。 #### 影响机制 1. **缓冲区大小与延迟关系** - **小缓冲区**(如256帧) - 优势:延迟低(5.3ms@48kHz) - 风险:易发生欠载(underrun),导致音频卡顿 - 适用场景:实时语音、专业音频制作 - **大缓冲区**(如2048帧) - 优势:抗系统抖动能力强 - 代价:延迟高(42.6ms@48kHz) - 适用场景:音乐播放、后台音频 2. **周期(Period)大小的影响** 每个DMA缓冲区由多个period组成,period大小决定中断频率: $$ \text{中断间隔} = \frac{\text{period大小}}{\text{采样率}} $$ - 小period(如128帧)→ 高中断频率(2.6ms@48kHz)→ 低延迟但CPU负载高 - 大period(如512帧)→ 低中断频率→ CPU友好但延迟增加 3. **内核驱动配置示例** ALSA驱动通过`snd_pcm_hardware`结构体定义缓冲区范围: ```c static struct snd_pcm_hardware my_hardware = { .buffer_bytes_max = 32768, // 最大缓冲区(约682帧@48kHz/16位/立体声) .period_bytes_min = 1024, // 最小周期(约21帧) .period_bytes_max = 16384, // 最大周期(约341帧) .periods_min = 2, // 最少周期数 .periods_max = 8, // 最多周期数 }; ``` #### 优化实践 1. **实时音频优化配置** ```bash # 设置128帧缓冲区+2个period (延迟≈5.3ms) sudo echo 128 > /sys/module/snd_hda_intel/parameters/buffer_size sudo echo 2 > /sys/module/snd_hda_intel/parameters/periods ``` 2. **平衡延迟与稳定性** | 应用场景 | 推荐缓冲区 | 周期数 | 典型延迟 | |----------------|------------|--------|----------| | 游戏/VR | 256帧 | 2 | 5.3ms | | 视频会议 | 512帧 | 3 | 10.6ms | | 音乐播放 | 1024帧 | 4 | 21.3ms | | 后台音频 | 2048帧 | 8 | 42.6ms | 3. **调试工具** ```bash # 查看当前配置 cat /proc/asound/card0/pcm0p/sub0/hw_params # 监控欠载次数 watch -n 0.1 grep underruns /proc/asound/card0/pcm0p/sub0/status ``` #### 典型问题分析 - **欠载(underrun)**:CPU未及时填充缓冲区,表现为音频卡顿。解决方案: - 增大缓冲区:`buffer_size_bytes = 8192` - 降低采样率:从192kHz→48kHz - 优化CPU调度:`sched_rt_runtime_us=950000` - **高延迟**:缓冲区过大导致。优化方法: ```c // 驱动中减小buffer_bytes_max .buffer_bytes_max = 16384; // 约341帧@48kHz ``` #### 设计建议 1. **动态调整机制**:根据系统负载自动切换缓冲区配置 2. **中断合并**:对非实时音频启用`CONFIG_SND_HDA_TEGRA_INTERRUPT_COALESCING` 3. **内存策略**:使用`GFP_ATOMIC`分配DMA内存避免页面错误
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值