rk音频驱动分析之tinyplay播放
一.tinyplay播放
操作命令:tinyplay /sdcard/test.wav
Tinyplay.c (external\tinyalsa)
file = fopen(filename, "rb"); //对应的音频文件
fread(&riff_wave_header, sizeof(riff_wave_header), 1, file); //读取音频文件头部
/* parse command line arguments */
处理一些参数的问题
//参数分别是播放的文件,声卡,通道,采样率,位数,DMA一次传输的数据量,传输次数
play_sample(file, card, device, chunk_fmt.num_channels, chunk_fmt.sample_rate,
chunk_fmt.bits_per_sample, period_size, period_count);
struct pcm_config config; //用来记录音频信息
config.channels = channels;
config.rate = rate;
config.period_size = period_size;
config.period_count = period_count;
if (bits == 32) //格式
config.format = PCM_FORMAT_S32_LE;
else if (bits == 16)
config.format = PCM_FORMAT_S16_LE;
if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count))
struct pcm_params *params;
params = pcm_params_get(card, device, PCM_OUT);
//我们这里是/dev/snd/pcmC0D0p
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');
fd = open(fn, O_RDWR); //打开播放dev,驱动分析一
//分配参数结构体
params = calloc(1, sizeof(struct snd_pcm_hw_params));
param_init(params); //初始化
ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params) //会调用到驱动,获取参数,驱动分析二
pcm = pcm_open(card, device, PCM_OUT, &config);
pcm->config = *config;
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, flags & PCM_IN ? 'c' : 'p');
pcm->flags = flags;
pcm->fd = open(fn, O_RDWR); //打开/dev/snd/pcmC0D0p节点,已经分析了
////获取pcm的信息,包括card,流,dma各种信息
ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info) //获取PCM信息,驱动分析三
param_init(¶ms); 初始化参数
//包括format,channels等
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT,pcm_format_to_alsa(config->format));
。。。。。。很多设置。。。。。
ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms) //驱动分析四
/* get our refined hw_params */
pcm->buffer_size = config->period_count * config->period_size; //缓冲区buf大小
if (flags & PCM_MMAP) //如果支持mmap
//mmap将一个文件或者其它对象映射进内存,调用底层的mmap函数,驱动分析五
起始地址 长度
pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
表示页可以读写,与其它进程共享映射空间 有效的文件描述词
PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
params.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
sparams.period_step = 1;
//这种开始和定制阈值
pcm->config.start_threshold = sparams.start_threshold = config->period_count * config->period_size / 2;
pcm->config.stop_threshold = sparams.stop_threshold = config->period_count * config->period_size;
if (pcm->flags & PCM_MMAP)
pcm->config.avail_min = sparams.avail_min = pcm->config.period_size;
sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams) ////主要是把上层的参数赋值给snd_pcm_runtime,驱动分析六
rc = pcm_hw_mmap_status(pcm); //单独分析
#ifdef SNDRV_PCM_IOCTL_TTSTAMP
int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg); //设置为绝对时间
#endif
size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
//frames * pcm->config.channels * byte
return frames * pcm->config.channels *(pcm_format_to_bits(pcm->config.format) >> 3);
buffer = malloc(size); //分配内存
do {
num_read = fread(buffer, 1, size, file); //把文件的内容读到buffer里
if (num_read > 0)
if (pcm_write(pcm, buffer, num_read)) //然后写入pcm,单独分析
} while (!close && num_read > 0);
1.static int pcm_hw_mmap_status(struct pcm *pcm)
pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS); //见驱动分析五,就是SNDRV_PCM_MMAP_OFFSET_STATUS这个不一样
if (pcm->flags & PCM_MMAP) //最少的可以用的大小
pcm->mmap_control->avail_min = pcm->config.avail_min;
2.pcm_write(pcm, buffer, num_read)
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);
ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) //调用驱动,驱动分析七
pcm->prepared = 1;
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) //调用驱动,驱动分析八
二.驱动分析一
Pcm_native.c (kernel\sound\core)
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,
.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,
}
};
1.打开播放dev,驱动分析1
在_snd_pcm_new函数中注册snd_pcm_dev_register函数,后面snd_device_register_all中统一调用dev->ops->dev_register。
在snd_pcm_dev_register函数中会注册/dev/snd/pcmC0D0p和/dev/snd/pcmC0D0c,并提供操作函数,我们这里分析open
static int snd_pcm_playback_open(struct inode *inode, struct file *file)
//通过次设备号和SNDRV_DEVICE_TYPE_PCM_PLAYBACK获取snd_pcm指针
pcm = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
//This function adds the file to the file linked-list of the card. This linked-list is used to keep tracking the connection state,
// and to avoid the release of busy resources by hotplug.
//这个函数把file 加入到cardlinked-list ,这个linked-list 是用来持续追踪链接状态的,可以避免很忙的资源被hotplug释放
err = snd_card_file_add(pcm->card, file);
struct snd_monitor_file *mfile;
mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
mfile->file = file;
list_add(&mfile->list, &card->files_list); //加入card->files_list链表
try_module_get(pcm->card->module)
init_waitqueue_entry(&wait, current); //初始化等待队列,当前的进程
add_wait_queue(&pcm->open_wait, &wait); //把等待队列加入到pcm->open_wait里面
while (1)
err = snd_pcm_open_file(file, pcm, stream);
err = snd_pcm_open_substream(pcm, stream, file, &substream); //主要是初始化snd_pcm_runtime相关的东西,单独分析2
pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL); //分配snd_pcm_file
pcm_file->substream = substream;
if (substream->ref_count == 1) //因为snd_pcm_open_substream已经初始化,这里是1
substream->file = pcm_file;
set_current_state(TASK_INTERRUPTIBLE); //设置当前进程可被中断唤醒substream->pcm_release = pcm_release_private;file->private_data = pcm_file;
schedule(); //放弃cpu
if (signal_pending(current)) //检查当前进程是否有信号处理,返回不为0表示有信号需要处理。remove_wait_queue(&pcm->open_wait, &wait);
2.//单独分析2
err = snd_pcm_open_substream(pcm, stream, file, &substream);
struct snd_pcm_substream *substream;
err = snd_pcm_attach_substream(pcm, stream, file, &substream);
struct snd_pcm_str * pstr;
pstr = &pcm->streams[stream]; //这里是SNDRV_PCM_STREAM_PLAYBACK,找到对应的snd_pcm_str
card = pcm->card;
list_for_each_entry(kctl, &card->ctl_files, list) //遍历snd_ctl_file
if (kctl->pid == task_pid(current)) //找到与当前进程pid一样的snd_ctl_file
prefer_subdevice = kctl->prefer_pcm_subdevice;
switch (stream)
case SNDRV_PCM_STREAM_PLAYBACK:
if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) //如果是半双工
for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next)
if (SUBSTREAM_BUSY(substream)) //如果正常忙,就是正在工作,就返回
case SNDRV_PCM_STREAM_CAPTURE: //录音和播放一样,有在工作的就返回
。。。。。中间有很多判断设备是不是在忙的代码。。。。。。。
runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); //分配snd_pcm_runtime结构体
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)); //环形缓冲区结构体大小,需要字节对齐
runtime->status = snd_malloc_pages(size, GFP_KERNEL); //分配页
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)); //映射控制
runtime->control = snd_malloc_pages(size, GFP_KERNEL);
init_waitqueue_head(&runtime->sleep); //初始化睡眠等待队列
init_waitqueue_head(&runtime->tsleep);
runtime->status->state = SNDRV_PCM_STATE_OPEN; //状态为开启
substream->runtime = runtime; //赋值给substream
substream->private_data = pcm->private_data;
substream->ref_count = 1;
substream->pid = get_pid(task_pid(current));
pstr->substream_opened++;
*rsubstream = substream;
//给pcm加一些限制,// 为该runtime追加hw硬件rules规则,来最终约束substream所属声卡支持的参数特性
err = snd_pcm_hw_constraints_init(substream); //设置一些限制,比较繁琐,暂不分析
err = substream->ops->open(substream) //调用到pcm的open,主要是初始化code,platform,dai等,单独分析3
substream->hw_opened = 1; //说明已经打开
err = snd_pcm_hw_constraints_complete(substream); //主要设置一些限制,包括权限,格式,channels等
err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask);
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
hw->channels_min, hw->channels_max);
3.pcm的open,单独分析3
static int soc_pcm_open(struct snd_pcm_substream *substream)
pm_runtime_get_sync(cpu_dai->dev);
pm_runtime_get_sync(codec_dai->dev);
pm_runtime_get_sync(platform->dev);
/* startup the audio subsystem */
if (cpu_dai->driver->ops->startup) //这里没有,建立cpu dai链接
ret = cpu_dai->driver->ops->startup(substream, cpu_dai);
if (platform->driver->ops && platform->driver->ops->open)
ret = platform->driver->ops->open(substream); //打开platform,也就是设置DMA相关参数,单独分析4
//Soc-generic-dmaengine-pcm.c (kernel\sound\soc),调用dmaengine_pcm_open函数
if (codec_dai->driver->ops->startup) //调用codec_dai的startup函数,只是设置了速度限制,单独分析5
ret = codec_dai->driver->ops->startup(substream, codec_dai);
if (rtd->dai_link->ops && rtd->dai_link->ops->startup) //dai_link的startup函数,暂时没有
ret = rtd->dai_link->ops->startup(substream);
/* Check that the codec and cpu DAIs are compatible */
//检查codec和cpu DAIs会不会兼容,检查的有rate,channels,formats,rates,
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) //这里播放
//取读取速率的最大值,也就是codec和cpu DAIs都支持的值
runtime->hw.rate_min = max(codec_dai_drv->playback.rate_min, cpu_dai_drv->playback.rate_min);
runtime->hw.rate_max = min(codec_dai_drv->playback.rate_max, cpu_dai_drv->playback.rate_max);
。。。。。。。。
else //录音,与播放大致一样的检查步骤
snd_pcm_limit_hw_rates(runtime); //determine rate_min/rate_max fields,设置最大最小速度
soc_pcm_apply_msb(substream, codec_dai); //设置多少位的限制,codec_dai和cpu_dai一样
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
bits = dai->driver->playback.sig_bits; //内容的比特数
for (i = 0; i < ARRAY_SIZE(sample_sizes); i++) //这里判断是24位的还是32位
if (bits >= sample_sizes[i])
continue
//设置限制
ret = snd_pcm_hw_constraint_msbits(substream->runtime, 0, sample_sizes[i], bits);
soc_pcm_apply_msb(substream, cpu_dai);
/* Symmetry only applies if we've already got an active stream. */
//只有当我们已经有了一个活动流时才会出现对称性
if (cpu_dai->active) //设置对称性
ret = soc_pcm_apply_symmetry(substream, cpu_dai);
if (codec_dai->active) {
ret = soc_pcm_apply_symmetry(substream, codec_dai);
4.打开platform,也就是DMA相关的东西,单独分析4
platform相关的参数
static const struct snd_pcm_hardware rockchip_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE ,
.channels_min = 2,
.channels_max = 8,
.buffer_bytes_max = 2*1024*1024,/*128*1024,*/
.period_bytes_min = 64,
.period_bytes_max = 512*1024,/*32*1024,//2048*4,///PAGE_SIZE*2,*/
.periods_min = 3,
.periods_max = 128,
.fifo_size = 16,
};
static int dmaengine_pcm_open(struct snd_pcm_substream *substream)
//首先注册上叙的参数,这个在注册的platform的会赋值rockchip_pcm_hardware 结构体
ret = snd_soc_set_runtime_hwparams(substream, pcm->config->pcm_hardware);
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->hw.info = hw->info;
runtime->hw.formats = hw->formats;
runtime->hw.period_bytes_min = hw->period_bytes_min;
runtime->hw.period_bytes_max = hw->period_bytes_max;
runtime->hw.periods_min = hw->periods_min;
runtime->hw.periods_max = hw->periods_max;
runtime->hw.buffer_bytes_max = hw->buffer_bytes_max;
runtime->hw.fifo_size = hw->fifo_size;
return snd_dmaengine_pcm_open(substream, chan);
struct dmaengine_pcm_runtime_data *prtd;
//设置限制参数,暂不分析
ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS);
prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); //分配dmaengine_pcm_runtime_data,用于记录DMA数据
prtd->dma_chan = chan;
substream->runtime->private_data = prtd; //把数据赋值给runtime的private_data指针
5.调用codec_dai的startup函数,单独分析5
Es8323.c (kernel\sound\soc\codecs)
static int es8323_pcm_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
//设置约束,主要是大约的速度,暂不具体分析
snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, es8323->sysclk_constraints);
return snd_pcm_hw_rule_add(runtime, cond, var, snd_pcm_hw_rule_list, (void *)l, var, -1);
三.驱动分析二
snd_pcm_common_ioctl1
case SNDRV_PCM_IOCTL_HW_REFINE:
return snd_pcm_hw_refine_user(substream, arg);
params = memdup_user(_params, sizeof(*params)); //用户态到内核态的拷贝
err = snd_pcm_hw_refine(substream, params); //根据底层的限制,重新给params 赋值
struct snd_pcm_hw_constraints *constrs = &substream->runtime->hw_constraints;
m = hw_param_mask(params, k);
copy_to_user(_params, params, sizeof(*params)) //拷贝给用户空间
四.驱动分析三
static int snd_pcm_common_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
case SNDRV_PCM_IOCTL_INFO:
return snd_pcm_info_user(substream, arg); //获取pcm的信息,包括card,流,dma各种信息
info = kmalloc(sizeof(*info), GFP_KERNEL); //分配snd_pcm_info
info->card = pcm->card->number;
info->device = pcm->device;
info->stream = substream->stream;
info->subdevice = substream->number;
strlcpy(info->id, pcm->id, sizeof(info->id));
strlcpy(info->name, pcm->name, sizeof(info->name));
info->dev_class = pcm->dev_class;
info->dev_subclass = pcm->dev_subclass;
info->subdevices_count = pstr->substream_count;
info->subdevices_avail = pstr->substream_count - pstr->substream_opened;
strlcpy(info->subname, substream->name, sizeof(info->subname));
runtime = substream->runtime;
if (runtime) { //这里没有什么用
info->sync = runtime->sync;
substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info);
copy_to_user(_info, info, sizeof(*info)) //赋值给用户空间
五.驱动分析四
case SNDRV_PCM_IOCTL_HW_PARAMS:
return snd_pcm_hw_params_user(substream, arg);
params = memdup_user(_params, sizeof(*params)); //从用户空间获取
err = snd_pcm_hw_params(substream, params);
runtime = substream->runtime;
err = snd_pcm_hw_refine(substream, params); //已经分析了,根据底层的限制,重新给params 赋值
//choose a configuration defined by @params
err = snd_pcm_hw_params_choose(substream, params);
//对于所有的参数,包括FORMAT,CHANNELS,根据底层都返回最小值
for (v = vars; *v != -1; v++) // refine config space and return minimum value
err = snd_pcm_hw_param_first(pcm, params, *v, NULL);
if (substream->ops->hw_params != NULL)
//这里只有是设置cpu dai和codec dai的类型和时钟等
err = substream->ops->hw_params(substream, params); //调用到rk音频驱动分析之machine的soc_pcm_hw_params函数
copy_to_user(_params, params, sizeof(*params)) //拷贝给用户空间
六.驱动分析五
DMA内存分配
static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = {
.open = snd_pcm_mmap_data_open,
.close = snd_pcm_mmap_data_close,
.fault = snd_pcm_mmap_data_fault,
};
static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area)
pcm_file = file->private_data;
substream = pcm_file->substream;
offset = area->vm_pgoff << PAGE_SHIFT;
switch (offset) //我们这里应该是snd_pcm_mmap_data
case SNDRV_PCM_MMAP_OFFSET_STATUS:
if (pcm_file->no_compat_mmap)
return snd_pcm_mmap_status(substream, file, area); //只是加了一个操作函数
size = area->vm_end - area->vm_start;
area->vm_ops = &snd_pcm_vm_ops_status;
area->vm_private_data = substream;
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
case SNDRV_PCM_MMAP_OFFSET_CONTROL:
return snd_pcm_mmap_control(substream, file, area);
default:
return snd_pcm_mmap_data(substream, file, area);
//mmap DMA buffer,分配DMA内存
size = area->vm_end - area->vm_start;
offset = area->vm_pgoff << PAGE_SHIFT;
dma_bytes = PAGE_ALIGN(runtime->dma_bytes); //需要字节对齐
area->vm_ops = &snd_pcm_vm_ops_data; //操作函数
area->vm_private_data = substream; //是substream指针
if (substream->ops->mmap) //调用platform->driver->ops->mmap;,这个有在soc_new_pcm赋值
err = substream->ops->mmap(substream, area); //这里没有
else
err = snd_pcm_lib_default_mmap(substream, area); //mmap the DMA buffer on RAM
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
//分配DMA缓冲区
return dma_mmap_coherent(substream->dma_buffer.dev.dev, area, substream->runtime->dma_area,
substream->runtime->dma_addr, area->vm_end - area->vm_start);
/* mmap with fault handler */
area->vm_ops = &snd_pcm_vm_ops_data_fault; //操作函数
七.驱动分析六
//主要是把上层的参数赋值给snd_pcm_runtime
static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream, struct snd_pcm_sw_params __user * _params)
copy_from_user(¶ms, _params, sizeof(params))
err = snd_pcm_sw_params(substream, ¶ms);
struct snd_pcm_runtime *runtime;
.......一些判断......
runtime->tstamp_mode = params->tstamp_mode; //给runtime相关赋值
runtime->period_step = params->period_step;
runtime->control->avail_min = params->avail_min;
runtime->start_threshold = params->start_threshold;
runtime->stop_threshold = params->stop_threshold;
runtime->silence_threshold = params->silence_threshold;
runtime->silence_size = params->silence_size;
params->boundary = runtime->boundary;
if (snd_pcm_running(substream)) //如果正在运行
//fill ring buffer with silence,用安静填满环形缓冲区
snd_pcm_playback_silence(substream, ULONG_MAX);
err = snd_pcm_update_state(substream, runtime); //更新状态
copy_to_user(_params, ¶ms, sizeof(params))
八.驱动分析七
//prepare the PCM substream to be triggerable,预备pcm可以触发
static struct action_ops snd_pcm_action_prepare = {
.pre_action = snd_pcm_pre_prepare,
.do_action = snd_pcm_do_prepare,
.post_action = snd_pcm_post_prepare
};
static int snd_pcm_prepare(struct snd_pcm_substream *substream, struct file *file)
// wait until the power-state is changed.等待电源标志变化
if ((res = snd_power_wait(card, SNDRV_CTL_POWER_D0))
init_waitqueue_entry(&wait, current); //这个函数的作用是用新进程来初始化队列
add_wait_queue(&card->power_sleep, &wait); //把等待元素wait,加到 card->power_slee队列前面
while (1)
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(30 * HZ);
res = snd_pcm_action_nonatomic(&snd_pcm_action_prepare, substream, f_flags);
if (snd_pcm_stream_linked(substream)) //查看有没有连接其他的substream
res = snd_pcm_action_group(ops, substream, state, 0); //暂不分析
else
res = snd_pcm_action_single(ops, substream, state);
res = ops->pre_action(substream, state); //调用snd_pcm_action_prepare ,前面有赋值
//we use the second argument for updating f_flags,这里只是更新f_flags
//这些是文件标志, 例如 O_RDONLY, O_NONBLOCK,驱动应当检查O_NONBLOCK 标志来看是否是请求非阻塞操作
substream->f_flags = f_flags;
res = ops->do_action(substream, state);
//主要是通知widget更新电源状态,然后设置声卡打开播放
err = substream->ops->prepare(substream); //初始化赋值soc_pcm_prepare,单独分析
return snd_pcm_do_reset(substream, 0);
int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
if (platform->driver->ops->ioctl)
return platform->driver->ops->ioctl(substream, cmd, arg); //这里调用rk音频驱动之platform的snd_pcm_lib_ioctl
return snd_pcm_lib_ioctl(substream, cmd, arg); //这里不会到这里
runtime->hw_ptr_base = 0;
.....环形缓冲区指针状态相关的reset...
ops->post_action(substream, state); //初始化赋值snd_pcm_post_prepare
runtime->control->appl_ptr = runtime->status->hw_ptr;
snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED); //更新状态/为* stream is ready to start */
1.soc_pcm_prepare
/*
* Called by ALSA when the PCM substream is prepared, can set format, sample
* rate, etc. This function is non atomic and can be called multiple times,
* it can refer to the runtime info.
*/
if (rtd->dai_link->ops && rtd->dai_link->ops->prepare)
ret = rtd->dai_link->ops->prepare(substream); //我们这里没有
if (platform->driver->ops && platform->driver->ops->prepare) //这里没有
ret = codec_dai->driver->ops->prepare(substream, codec_dai);
if (cpu_dai->driver->ops->prepare) //这里没有
ret = cpu_dai->driver->ops->prepare(substream, cpu_dai);
//Sends a stream event to the dapm core. The core then makes any necessary widget power changes.
//这里会调动到widget
snd_soc_dapm_stream_event(rtd, substream->stream, SND_SOC_DAPM_STREAM_START);
soc_dapm_stream_event(rtd, stream, event);
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
w_cpu = cpu_dai->playback_widget;
w_codec = codec_dai->playback_widget;
else
......
if (w_cpu)
dapm_mark_dirty(w_cpu, "stream event"); //加入dapm_dirty链表
list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);
if (w_codec)
dapm_mark_dirty(w_codec, "stream event");
//Scan each dapm widget for complete audio path,这里就是搜索path
dapm_power_widgets(&rtd->card->dapm, event);
snd_soc_dai_digital_mute(codec_dai, 0, substream->stream);
//Mutes the DAI DAC.
if (dai->driver->ops->mute_stream)
return dai->driver->ops->mute_stream(dai, mute, direction); //我们这里没有
else if (direction == SNDRV_PCM_STREAM_PLAYBACK && dai->driver->ops->digital_mute)
return dai->driver->ops->digital_mute(dai, mute); //调用rk音频驱动分析之codec里面的es8323_mute,进行播放准备
九.驱动分析八
snd_pcm_playback_ioctl里面的snd_pcm_playback_ioctl1的
static int snd_pcm_playback_ioctl1(struct file *file, struct snd_pcm_substream *substream, unsigned int cmd, void __user *arg)
switch (cmd)
case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
if (put_user(0, &_xferi->result)) //给应用发送0的返回值
copy_from_user(&xferi, _xferi, sizeof(xferi)) //从用户拷贝数据
result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
/* sanity-check for read/write methods */
err = pcm_sanity_check(substream); //检查有不有读写函数
runtime = substream->runtime;
nonblock = !!(substream->f_flags & O_NONBLOCK);
//传输函数,单独分析, snd_pcm_lib_write_transfer这个也要单独分析
return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_write_transfer);
1.snd_pcm_lib_write1
runtime->twake = runtime->control->avail_min ? : 1;
if (runtime->status->state == SNDRV_PCM_STATE_RUNNING) //如果正在进行
snd_pcm_update_hw_ptr(substream); ///* CAUTION: call it with irq disabled */
return snd_pcm_update_hw_ptr0(substream, 0); //暂不分析
avail = snd_pcm_playback_avail(runtime); //获取当前ring buf的可用大小
// 读指针+总的buf size-写指针的数值
//上图白色部分avail ,灰色是已经用掉的
snd_pcm_sframes_t avail = snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;;
if (avail < 0) //读写指针换位的时候
avail += runtime->boundary; // avail=runtime->boundary + avail;加负数,还是等于可以用的
else if ((snd_pcm_uframes_t) avail >= runtime->boundary) //溢出了,无可用的,已经是负的
avail -= runtime->boundary; //avail = runtime->boundary - avail ; //返回负的可以的大小
while (size > 0)
snd_pcm_uframes_t frames, appl_ptr, appl_ofs; //每块buf
snd_pcm_uframes_t cont;
if (!avail) //无可用的缓冲区
if (nonblock) //非阻塞模式,返回
err = -EAGAIN;
//如果非0,就唤醒,然后开始传输
runtime->twake = min_t(snd_pcm_uframes_t, size, runtime->control->avail_min ? : 1);
//Wait until avail_min data becomes available,等待avail_min 可以用
err = wait_for_avail(substream, &avail);
frames = size > avail ? avail : size; //得到frames 大小
cont = runtime->buffer_size - runtime->control->appl_ptr % runtime->buffer_size;
appl_ptr = runtime->control->appl_ptr; //HW BUF写指针的位置
appl_ofs = appl_ptr % runtime->buffer_size; //hw_ofs是读指针在当前HW buffer中的位置。
//传输,这个是传进来的snd_pcm_lib_write_transfer函数指针,单独分析2
err = transfer(substream, appl_ofs, data, offset, frames); //这里只是从用户空间把数据拷贝到DMA
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; //offset = offset + frames //偏移增加
size -= frames; // size = size - frames;
xfer += frames; //xfer = xfer + frames;
avail -= frames; //avail = 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); //开始传输,单独分析3
2.单独分析2:snd_pcm_lib_write_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)
if (substream->ops->copy) //这里没有
err = substream->ops->copy(substream, -1, hwoff, buf, frames) //这里会调用platform->driver->ops->copy;
platform->driver->ops->copy; //没有
else
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)) //从用户空间拷贝
单独分析3:snd_pcm_start
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
};
int snd_pcm_start(struct snd_pcm_substream *substream)
return snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
res = snd_pcm_action_single(ops, substream, state);
res = ops->pre_action(substream, state); //调用snd_pcm_pre_start
//check whether any data exists on the playback buffer
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !snd_pcm_playback_data(substream))
if (runtime->stop_threshold >= runtime->boundary)
return 1;
//如果可用的buf少于总的的buf,说明有数据,就是可以的
return snd_pcm_playback_avail(runtime) < runtime->buffer_size;
runtime->trigger_master = substream;
res = ops->do_action(substream, state); \\调用snd_pcm_do_start
return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); //开始传输,单独分析4
ops->post_action(substream, state); \\调用snd_pcm_post_start
snd_pcm_trigger_tstamp(substream); //获取时间
runtime->hw_ptr_jiffies = jiffies; //系统时间
runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) / runtime->rate; //传输需要的时间
runtime->status->state = state; //SNDRV_PCM_STATE_RUNNING
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && runtime->silence_size > 0)
//when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately
snd_pcm_playback_silence(substream, ULONG_MAX); //如果有silence_size ,就把他填到缓冲区里
if (substream->timer)
snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART, &runtime->trigger_tstamp);
单独分析4:soc_pcm_trigger
static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
if (codec_dai->driver->ops->trigger) //这里没有
ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai);
if (platform->driver->ops && platform->driver->ops->trigger) //进行DMA传输到I2S控制器
ret = platform->driver->ops->trigger(substream, cmd); //rk音频驱动之platform的snd_dmaengine_pcm_trigger
if (cpu_dai->driver->ops->trigger) //调用rk音频驱动分析之codec的rockchip_i2s_trigger
ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai); //从i2s寄存器到codec的传输