当前位置: 首页 > 工具软件 > Digital Write > 使用案例 >

Android音频驱动-ASOC之PCM Write

澹台成龙
2023-12-01

调用write函数实现把数据写到设备里面去,这里会触发trigger函数也就是DMA的启动。
用户层的write到内核里面都是通过ioctl来做的,这里面会触发trigger函数的执行,等trigger执行完以后,
才会真正调用函数把用户层的东西copy到dma分配的空间,其中函数snd_pcm_lib_write1非常复杂,这里面有同步的操作,
也就是要等到有空余的空间的时候才允许写,否则就要等待,唤醒是通过函数snd_pcm_update_hw_ptr_post来做的,
这个函数会在DMA传输完一帧的中断到来的时候被调用,用来更新缓冲区指针。

对于回放的情形,PCM 数据流向大致是:

        copy_from_user           DMA                 I2S           DAC
              ^                   ^                   ^             ^
+---------+   |    +----------+   |   +-----------+   |   +-----+   |   +------+
|userspace+-------->DMA Buffer+------->I2S TX FIFO+------->CODEC+------->SPK/HP|
+---------+        +----------+       +-----------+       +-----+       +------+

ALSA的Write流程
snd_pcm_playback_ioctl => snd_pcm_playback_ioctl1 => SNDRV_PCM_IOCTL_WRITEN_FRAMES =>
snd_pcm_lib_writev => snd_pcm_lib_write1 => |||| => snd_pcm_lib_write_transfer => copy_from_user [copy user speace data to dma]
snd_pcm_start => snd_pcm_action => snd_pcm_action_group => snd_pcm_do_start => substream->ops->trigger

// external/tinyalsa/pcm.c,用户层通过ioctl的方式来调用kernel
int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{
    struct snd_xferi x;

    if (pcm->flags & PCM_IN)
        return -EINVAL;

    x.buf = (void*)data;
    x.frames = count / (pcm->config.channels *
                        pcm_format_to_bits(pcm->config.format) / 8);

    for (;;) {
        if (!pcm->running) {
            int prepare_error = pcm_prepare(pcm);
            if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))//写入初始化数据
                return oops(pcm, errno, "cannot write initial data");
            pcm->running = 1;//写入初始化之后设置标致位
            return 0;
        }
        if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {//写入普通pcm数据
            pcm->prepared = 0;//如果写入失败,则重置
            pcm->running = 0;//如果写入失败,则重置
            if (errno == EPIPE) {
                pcm->underruns++;
                if (pcm->flags & PCM_NORESTART)
                    return -EPIPE;
                continue;
            }
            return oops(pcm, errno, "cannot write stream data");
        }
        return 0;
    }
}
// kernel-3.18/sound/core/pcm_native.c,kernel层的实现
// 在内核中发起系统调用,执行本应用户空间发起调用的fops函数集,完成参数设置任务
/*const struct file_operations snd_pcm_f_ops[2] = {
    {
        .owner =        THIS_MODULE,
        .write =        snd_pcm_write,
        .aio_write =        snd_pcm_aio_write,
        .open =         snd_pcm_playback_open,
        .release =      snd_pcm_release,
        .llseek =       no_llseek,
        .poll =         snd_pcm_playback_poll,
        .unlocked_ioctl =   snd_pcm_playback_ioctl,//pcm文件的ioctl函数
        .compat_ioctl =     snd_pcm_ioctl_compat,
        .mmap =         snd_pcm_mmap,
        .fasync =       snd_pcm_fasync,
        .get_unmapped_area =    snd_pcm_get_unmapped_area,
    },
    {
        .owner =        THIS_MODULE,
        .read =         snd_pcm_read,
        .aio_read =     snd_pcm_aio_read,
        .open =         snd_pcm_capture_open,
        .release =      snd_pcm_release,
        .llseek =       no_llseek,
        .poll =         snd_pcm_capture_poll,
        .unlocked_ioctl =   snd_pcm_capture_ioctl,
        .compat_ioctl =     snd_pcm_ioctl_compat,
        .mmap =         snd_pcm_mmap,
        .fasync =       snd_pcm_fasync,
        .get_unmapped_area =    snd_pcm_get_unmapped_area,
    }
};*/
 static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd,
                   unsigned long arg)
{
    struct snd_pcm_file *pcm_file;

    pcm_file = file->private_data;

    if (((cmd >> 8) & 0xff) != 'A')
        return -ENOTTY;

    return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd,
                       (void __user *)arg);
}
static int snd_pcm_playback_ioctl1(struct file *file,
                   struct snd_pcm_substream *substream,
                   unsigned int cmd, void __user *arg)
{
    if (snd_BUG_ON(!substream))
        return -ENXIO;
    if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
        return -EINVAL;
    switch (cmd) {
    case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
    {
        struct snd_xferi xferi;
        struct snd_xferi __user *_xferi = arg;
        struct snd_pcm_runtime *runtime = substream->runtime;
        snd_pcm_sframes_t result;
        if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
            return -EBADFD;
        if (put_user(0, &_xferi->result))
            return -EFAULT;
        if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
            return -EFAULT;
        result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
        __put_user(result, &_xferi->result);
        return result < 0 ? result : 0;
    }
}
snd_pcm_sframes_t snd_pcm_lib_write(struct snd_pcm_substream *substream, 
                                    const void __user *buf, 
                                    snd_pcm_uframes_t size)
{
    struct snd_pcm_runtime *runtime;
    int nonblock;
    int err;

    err = pcm_sanity_check(substream);

    runtime = substream->runtime;
    nonblock = !!(substream->f_flags & O_NONBLOCK);

    if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
        runtime->channels > 1)
        return -EINVAL;
    return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock,
                  snd_pcm_lib_write_transfer);
}
static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream, 
                        unsigned long data,
                        snd_pcm_uframes_t size,
                        int nonblock,
                        transfer_f transfer)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    snd_pcm_uframes_t xfer = 0;
    snd_pcm_uframes_t offset = 0;
    snd_pcm_uframes_t avail;
    int err = 0;

    if (size == 0)
        return 0;

    snd_pcm_stream_lock_irq(substream);
    switch (runtime->status->state) {
    case SNDRV_PCM_STATE_PREPARED:
    case SNDRV_PCM_STATE_RUNNING:
    case SNDRV_PCM_STATE_PAUSED:
        break;
    case SNDRV_PCM_STATE_XRUN:
        err = -EPIPE;
        goto _end_unlock;
    case SNDRV_PCM_STATE_SUSPENDED:
        err = -ESTRPIPE;
        goto _end_unlock;
    default:
        err = -EBADFD;
        goto _end_unlock;
    }

    runtime->twake = runtime->control->avail_min ? : 1;
    if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
        snd_pcm_update_hw_ptr(substream);
    avail = snd_pcm_playback_avail(runtime);
    while (size > 0) {
        snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
        snd_pcm_uframes_t cont;
        if (!avail) {
            if (nonblock) {
                err = -EAGAIN;
                goto _end_unlock;
            }
            runtime->twake = min_t(snd_pcm_uframes_t, size,
                    runtime->control->avail_min ? : 1);
            err = wait_for_avail(substream, &avail);
            if (err < 0)
                goto _end_unlock;
        }
        frames = size > avail ? avail : size;
        cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
        if (frames > cont)
            frames = cont;
        if (snd_BUG_ON(!frames)) {
            runtime->twake = 0;
            snd_pcm_stream_unlock_irq(substream);
            return -EINVAL;
        }
        appl_ptr = runtime->control->appl_ptr;
        appl_ofs = appl_ptr % runtime->buffer_size;
        snd_pcm_stream_unlock_irq(substream);
        err = transfer(substream, appl_ofs, data, offset, frames);//向DMA写入数据
        snd_pcm_stream_lock_irq(substream);
        if (err < 0)
            goto _end_unlock;
        switch (runtime->status->state) {
        case SNDRV_PCM_STATE_XRUN:
            err = -EPIPE;
            goto _end_unlock;
        case SNDRV_PCM_STATE_SUSPENDED:
            err = -ESTRPIPE;
            goto _end_unlock;
        default:
            break;
        }
        appl_ptr += frames;
        if (appl_ptr >= runtime->boundary)
            appl_ptr -= runtime->boundary;
        runtime->control->appl_ptr = appl_ptr;
        if (substream->ops->ack)
            substream->ops->ack(substream);

        offset += frames;
        size -= frames;
        xfer += frames;
        avail -= frames;
        if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
            snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {
            err = snd_pcm_start(substream);//开始dma传送数据的过程
            if (err < 0)
                goto _end_unlock;
        }
    }
 _end_unlock:
    runtime->twake = 0;
    if (xfer > 0 && err >= 0)
        snd_pcm_update_state(substream, runtime);
    snd_pcm_stream_unlock_irq(substream);
    return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
}

transfer的具体实现

static int snd_pcm_lib_write_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;
    int err;
    char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
    if (substream->ops->copy) {
        if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
            return err;
    } else {
        char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
        if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))
            return -EFAULT;
    }
    return 0;
}
static int mtk_pcm_I2S0dl1_copy(struct snd_pcm_substream *substream,
                int channel, snd_pcm_uframes_t pos,
                void __user *dst, snd_pcm_uframes_t count)
{
    AFE_BLOCK_T  *Afe_Block = NULL;
    int copy_size = 0, Afe_WriteIdx_tmp;
    unsigned long flags;
    /* struct snd_pcm_runtime *runtime = substream->runtime; */
    char *data_w_ptr = (char *)dst;

    /* get total bytes to copy */
    count = audio_frame_to_bytes(substream , count);

    /* check which memif nned to be write */
    Afe_Block = &pI2S0dl1MemControl->rBlock;

    /* handle for buffer management */
    if (Afe_Block->u4BufferSize == 0) {
        pr_err(" u4BufferSize=0 Error");
        return 0;
    }

    AudDrv_checkDLISRStatus();

    spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);
    copy_size = Afe_Block->u4BufferSize -
            Afe_Block->u4DataRemained;  /* free space of the buffer */
    spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);

    if (count <=  copy_size) {
        if (copy_size < 0)
            copy_size = 0;
        else
            copy_size = count;
    }

    copy_size = Align64ByteSize(copy_size);

    if (copy_size != 0) {
        spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);
        Afe_WriteIdx_tmp = Afe_Block->u4WriteIdx;
        spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);

        if (Afe_WriteIdx_tmp + copy_size < Afe_Block->u4BufferSize) { /* copy once */
            if (!access_ok(VERIFY_READ, data_w_ptr, copy_size)) {

            } else {
                if (copy_from_user((Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp), data_w_ptr,
                           copy_size)) {
                    return -1;
                }
            }

            spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);
            Afe_Block->u4DataRemained += copy_size;
            Afe_Block->u4WriteIdx = Afe_WriteIdx_tmp + copy_size;
            Afe_Block->u4WriteIdx %= Afe_Block->u4BufferSize;
            spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);
            data_w_ptr += copy_size;
            count -= copy_size;

        } else { /* copy twice */
            kal_uint32 size_1 = 0, size_2 = 0;

            size_1 = Align64ByteSize((Afe_Block->u4BufferSize - Afe_WriteIdx_tmp));
            size_2 = Align64ByteSize((copy_size - size_1));

            if (!access_ok(VERIFY_READ, data_w_ptr, size_1)) {

            } else {
                if ((copy_from_user((Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp), data_w_ptr ,
                            size_1))) {
                    PRINTK_AUDDRV(" Fail 1 copy from user");
                    return -1;
                }
            }
            spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);
            Afe_Block->u4DataRemained += size_1;
            Afe_Block->u4WriteIdx = Afe_WriteIdx_tmp + size_1;
            Afe_Block->u4WriteIdx %= Afe_Block->u4BufferSize;
            Afe_WriteIdx_tmp = Afe_Block->u4WriteIdx;
            spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);

            if (!access_ok(VERIFY_READ, data_w_ptr + size_1, size_2)) {

            } else {
                if ((copy_from_user((Afe_Block->pucVirtBufAddr + Afe_WriteIdx_tmp),
                            (data_w_ptr + size_1), size_2))) {
                    return -1;
                }
            }
            spin_lock_irqsave(&auddrv_I2S0dl1_lock, flags);

            Afe_Block->u4DataRemained += size_2;
            Afe_Block->u4WriteIdx = Afe_WriteIdx_tmp + size_2;
            Afe_Block->u4WriteIdx %= Afe_Block->u4BufferSize;
            spin_unlock_irqrestore(&auddrv_I2S0dl1_lock, flags);
            count -= copy_size;
            data_w_ptr += copy_size;
        }
    }
    return 0;
}

// 启动传输,将数据

int snd_pcm_start(struct snd_pcm_substream *substream)
{
    return snd_pcm_action(&snd_pcm_action_start, substream,
                  SNDRV_PCM_STATE_RUNNING);
}
static int snd_pcm_action(struct action_ops *ops,
              struct snd_pcm_substream *substream,
              int state)
{
    int res;

    if (substream->pcm->nonatomic)
        return snd_pcm_action_mutex(ops, substream, state);

    if (snd_pcm_stream_linked(substream)) {
        res = snd_pcm_action_group(ops, substream, state, 1);
    } else {
        res = snd_pcm_action_single(ops, substream, state);
    }
    return res;
}
static int snd_pcm_action_single(struct action_ops *ops,
                 struct snd_pcm_substream *substream,
                 int state)
{
    int res;

    res = ops->pre_action(substream, state);
    if (res < 0)
        return res;
    res = ops->do_action(substream, state);
    if (res == 0)
        ops->post_action(substream, state);
    else if (ops->undo_action)
        ops->undo_action(substream, state);
    return res;
}

其中trigger的逻辑如下,简单的说就是启动DMA。

/*static struct action_ops snd_pcm_action_start = {
    .pre_action = snd_pcm_pre_start,
    .do_action = snd_pcm_do_start,
    .undo_action = snd_pcm_undo_start,
    .post_action = snd_pcm_post_start
};*/
static int snd_pcm_do_start(struct snd_pcm_substream *substream, int state)
{
    if (substream->runtime->trigger_master != substream)
        return 0;
    return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
}
//pcm subtream的操作函数
/*int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
    ......
    if (rtd->dai_link->dynamic) {
        rtd->ops.open       = dpcm_fe_dai_open;
        rtd->ops.hw_params  = dpcm_fe_dai_hw_params;
        rtd->ops.prepare    = dpcm_fe_dai_prepare;
        rtd->ops.trigger    = dpcm_fe_dai_trigger;
        rtd->ops.hw_free    = dpcm_fe_dai_hw_free;
        rtd->ops.close      = dpcm_fe_dai_close;
        rtd->ops.pointer    = soc_pcm_pointer;
        rtd->ops.ioctl      = soc_pcm_ioctl;
    } else {
        rtd->ops.open       = soc_pcm_open;
        rtd->ops.hw_params  = soc_pcm_hw_params;
        rtd->ops.prepare    = soc_pcm_prepare;
        rtd->ops.trigger    = soc_pcm_trigger;
        rtd->ops.hw_free    = soc_pcm_hw_free;
        rtd->ops.close      = soc_pcm_close;
        rtd->ops.pointer    = soc_pcm_pointer;
        rtd->ops.ioctl      = soc_pcm_ioctl;
    }

    if (platform->driver->ops) {
        rtd->ops.ack        = platform->driver->ops->ack;
        rtd->ops.copy       = platform->driver->ops->copy;
        rtd->ops.silence    = platform->driver->ops->silence;
        rtd->ops.page       = platform->driver->ops->page;
        rtd->ops.mmap       = platform->driver->ops->mmap;
    }

    if (playback)
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

    if (capture)
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
        ......
}*/
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
    struct snd_soc_pcm_runtime *rtd = substream->private_data;
    struct snd_soc_platform *platform = rtd->platform;
    struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
    struct snd_soc_dai *codec_dai;
    int i, ret;

    for (i = 0; i < rtd->num_codecs; i++) {
        codec_dai = rtd->codec_dais[i];
        if (codec_dai->driver->ops && codec_dai->driver->ops->trigger) {
            ret = codec_dai->driver->ops->trigger(substream,
                                  cmd, codec_dai);//调用codec_dai driver的trigger函数
    }

    if (platform->driver->ops && platform->driver->ops->trigger) {
        ret = platform->driver->ops->trigger(substream, cmd);//调用platform driver的trigger函数
    }

    if (cpu_dai->driver->ops && cpu_dai->driver->ops->trigger) {
        ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai);//调用cpu_dai driver的trigger函数
    }

    if (rtd->dai_link->ops && rtd->dai_link->ops->trigger) {
        ret = rtd->dai_link->ops->trigger(substream, cmd);//调用dai_link driver的trigger函数
    }

    return 0;
}

处理platform、codec-dai、cpu-dai的trigger回调函数

//codec_dai driver的trigger函数,没有实际作用
/*static const struct snd_soc_dai_ops mt6323_aif1_dai_ops = {
    .startup = mt63xx_codec_startup,
    .prepare = mt63xx_codec_prepare,
    .trigger = mt6323_codec_trigger,
};*/
static int mt6323_codec_trigger(struct snd_pcm_substream *substream, int command,
                struct snd_soc_dai *Daiport)
{
    switch (command) {
    case SNDRV_PCM_TRIGGER_START:
    case SNDRV_PCM_TRIGGER_RESUME:
    case SNDRV_PCM_TRIGGER_STOP:
    case SNDRV_PCM_TRIGGER_SUSPEND:
        break;
    }
    return 0;
}
//platform driver的trigger函数
static int mtk_pcm_I2S0dl1_trigger(struct snd_pcm_substream *substream, int cmd)
{
    /* pr_warn("mtk_pcm_I2S0dl1_trigger cmd = %d\n", cmd); */

    switch (cmd) {
    case SNDRV_PCM_TRIGGER_START:
    case SNDRV_PCM_TRIGGER_RESUME:
        return mtk_pcm_I2S0dl1_start(substream);// 启动 dma 传输
    case SNDRV_PCM_TRIGGER_STOP:
    case SNDRV_PCM_TRIGGER_SUSPEND:
        return mtk_pcm_I2S0dl1_stop(substream);//停止 dma 传输
    }
    return -EINVAL;
}
static int mtk_pcm_I2S0dl1_start(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    /* here start digital part */
    SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I05,
              Soc_Aud_InterConnectionOutput_O00);
    SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I06,
              Soc_Aud_InterConnectionOutput_O01);
    SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I05,
              Soc_Aud_InterConnectionOutput_O03);
    SetConnection(Soc_Aud_InterCon_Connection, Soc_Aud_InterConnectionInput_I06,
              Soc_Aud_InterConnectionOutput_O04);

    /* here to set interrupt */
    irq_add_user(substream,
             Soc_Aud_IRQ_MCU_MODE_IRQ1_MCU_MODE,
             substream->runtime->rate,
             irq1_cnt ? irq1_cnt : substream->runtime->period_size);
    irq_user_id = substream;

    SetSampleRate(Soc_Aud_Digital_Block_MEM_DL1, runtime->rate);
    SetChannels(Soc_Aud_Digital_Block_MEM_DL1, runtime->channels);
    SetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_DL1, true);

    EnableAfe(true);

#ifdef _DEBUG_6328_CLK
    /* Debug 6328 Digital to Analog path clock and data work or not. and read TEST_OUT(0x206) */
    /* Ana_Set_Reg(AFE_MON_DEBUG0, 0x4600 , 0xcf00); // monitor 6328 digitala data sent to analog or not */
    Ana_Set_Reg(AFE_MON_DEBUG0, 0x4200 ,
            0xcf00); /* monitor 6328 digitala data sent to analog or not */
    Ana_Set_Reg(TEST_CON0, 0x0e00 , 0xffff);
#endif
    return 0;
}
//cpu_dai driver的trigger函数,没有实现
/*static struct snd_soc_dai_ops mtk_dai_stub_ops = {
    .startup    = multimedia_startup,
};*/
//dai_link driver的trigger函数,没有实现
/*static struct snd_soc_ops mt_machine_audio_ops = {
    .startup = mtmachine_startup,
    .prepare = mtmachine_prepare,
};*/
 类似资料: