Android 오디오 드라이브 - ASOC의 PCM Write
33164 단어 ALSA
재생의 경우 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,
};*/
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Ubuntu16.04LTS 전면 패널의 사운드 출력 단자에 사운드가 없습니다.Ubuntu16.04에서 자체 제작 PC를 조립할 때 전면 패널에 연결된 음성 출력 단자의 장치에 소리가 없는 문제가 발생했습니다.이 사건이 발생한 환경은 다음과 같다. 자체 제작 PC 케이스(IN WIN 805) ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.