Android 오디오 드라이브 - ASOC의 PCM Write

33164 단어 ALSA
write 함수를 호출하여 데이터를 장치에 기록합니다. 여기에서 trigger 함수인 DMA의 시작을 터치합니다.사용자층의 write가 내부 핵에 들어가는 것은 모두 ioctl을 통해 이루어진다. 이 안에서 trigger 함수의 집행을 촉발한다. trigger가 실행된 후에야 진정으로 함수를 호출하여 사용자층의 물건을 dma로 분배하는 공간이다. 그 중에서 함수 sndpcm_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 프로세스 sndpcm_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
", 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, };*/

좋은 웹페이지 즐겨찾기