Android音訊驅動-ASOC之PCM Write
阿新 • • 發佈:2019-01-09
呼叫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,
};*/