浅析打开/dev/dsp设备节点代码流程
《浅析uda134x声卡驱动probe的精简步骤》《浅析放音模式--向/dev/dsp设备write写入音频数据的代码流》
《浅析alsa声卡放音模式时s3c24xx处理器DMA中断如何建立和处理》
设备节点/dev/dsp的主节点号为SOUND_MAJOR即14,该节点方法集为soundcore_fops,
对应的open方法实现为:
soundcore_open
==> __look_for_unit查找在alsa_pcm_oss_init()中使用snd_pcm_notify(&snd_pcm_oss_notify, 0)
登记的当创建pcm节点时snd_pcm_notify通知回调结构体:snd_pcm_oss_notify,
函数snd_pcm_oss_register_minor调用register_oss_dsp-->snd_register_oss_device
登记snd_oss_minors[minor] = preg;同时preg->private_data = private_data;
就是preg->private_data = pcm;以备下面的fops方法集snd_pcm_oss_f_reg在open,read,write等时使用
函数register_sound_special_device-->sound_insert_unit插入的pcm,该pcm的fops为[luther.gliethttp]
函数snd_pcm_oss_f_reg
==> file->f_op = new_fops;将查找到的pcm对应的fops,即:snd_pcm_oss_f_reg赋值给f_op
作为应用程序操作/dev/dsp设备的默认fops方法集
==> file->f_op->open(inode,file);调用snd_pcm_oss_f_reg中的open方法snd_pcm_oss_open
==> snd_pcm_oss_open
// 在snd_soc_new_pcms==>soc_new_pcm中设置放音和录音流的ops为soc_pcm_ops
// /* ASoC PCM operations */
// static struct snd_pcm_ops soc_pcm_ops = {
// .open = soc_pcm_open,
// .close = soc_codec_close,
// .hw_params = soc_pcm_hw_params,
// .hw_free = soc_pcm_hw_free,
// .prepare = soc_pcm_prepare,
// .trigger = soc_pcm_trigger,
// };
// ==> snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback, capture, &pcm);
// ==> static struct snd_device_ops ops = {
// .dev_free = snd_pcm_dev_free,
// .dev_register = snd_pcm_dev_register, 在snd_card_register==>snd_device_register_all中将调用snd_pcm_dev_register注册对应的pcm
// .dev_disconnect = snd_pcm_dev_disconnect,
// };
// 在snd_pcm_dev_register执行的最后将调用alsa_pcm_oss_init注册的snd_pcm_oss_notify回调结构体
// oss的snd_pcm_oss_register_minor处理函数将被调用
// 于是register_oss_dsp将调用,相应/dev/dsp节点或/dev/dsp1将被创建,同时操作该节点的
// 方法集snd_pcm_oss_f_reg将被登记.
static int snd_pcm_oss_open(struct inode *inode, struct file *file)
{
int err;
char task_name[32];
struct snd_pcm *pcm;
struct snd_pcm_oss_file *pcm_oss_file;
struct snd_pcm_oss_setup setup[2];
int nonblock;
wait_queue_t wait;
pcm = snd_lookup_oss_minor_data(iminor(inode),
SNDRV_OSS_DEVICE_TYPE_PCM); // 返回preg->private_data,也就是pcm[luther.gliethttp]
if (pcm == NULL) {
err = -ENODEV;
goto __error1;
}
err = snd_card_file_add(pcm->card, file);// 将操作该声卡card的应用程序添加到card->files_list
// 链表上list_add(&mfile->list, &card->files_list);
if (err < 0)
goto __error1;
if (!try_module_get(pcm->card->module)) {
err = -EFAULT;
goto __error2;
}
if (snd_task_name(current, task_name, sizeof(task_name)) < 0) {
err = -EFAULT; // 获取打开/dev/dsp节点的应用程序名称[luther.gliethttp]
goto __error; // 比如我们测试程序a.out,那么这里task_name将等于"a.out"
}
memset(setup, 0, sizeof(setup));
if (file->f_mode & FMODE_WRITE) // 如果具有写模式,那么打开pcm的放音通道
snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK,
task_name, &setup[0]);
if (file->f_mode & FMODE_READ) // 如果具有读模式,那么打开pcm的录音通道
snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE,
task_name, &setup[1]);
nonblock = !!(file->f_flags & O_NONBLOCK);
if (!nonblock)
nonblock = nonblock_open;
init_waitqueue_entry(&wait, current);
add_wait_queue(&pcm->open_wait, &wait); // 该应用程序有可能在下面的while(1)中睡眠
mutex_lock(&pcm->open_mutex);
while (1) {
err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file,
iminor(inode), setup);// 为该应用程序a.out进行尝试性打开/dev/dsp设备
if (err >= 0)
break;
if (err == -EAGAIN) {
if (nonblock) {
err = -EBUSY;
break;
}
} else
break;
set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&pcm->open_mutex);
schedule(); // 因为当前pcm设备,被其他程序使用中,所以这里pending该
mutex_lock(&pcm->open_mutex); // 再次尝试打开/dev/dsp的a.out应用程序[luther.gliethttp]
if (signal_pending(current)) {
err = -ERESTARTSYS;
break;
}
}
remove_wait_queue(&pcm->open_wait, &wait);
mutex_unlock(&pcm->open_mutex);
if (err < 0)
goto __error;
return err;
__error:
module_put(pcm->card->module);
__error2:
snd_card_file_remove(pcm->card, file);
__error1:
return err;
}
static int snd_pcm_oss_open_file(struct file *file,
struct snd_pcm *pcm,
struct snd_pcm_oss_file **rpcm_oss_file,
int minor,
struct snd_pcm_oss_setup *setup)
{
int idx, err;
struct snd_pcm_oss_file *pcm_oss_file;
struct snd_pcm_substream *substream;
fmode_t f_mode = file->f_mode;
if (rpcm_oss_file)
*rpcm_oss_file = NULL;
pcm_oss_file = kzalloc(sizeof(*pcm_oss_file), GFP_KERNEL);
if (pcm_oss_file == NULL)
return -ENOMEM;
if ((f_mode & (FMODE_WRITE|FMODE_READ)) == (FMODE_WRITE|FMODE_READ) &&
(pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)) // 为半双工的话,那么强制选择放音模式
f_mode = FMODE_WRITE;
file->f_flags &= ~O_APPEND;
for (idx = 0; idx < 2; idx++) {
if (setup[idx].disable) // 第1次进来的话,setup[idx].disable等于0
continue;
if (! pcm->streams[idx].substream_count) // 如果本card的该pcm没有对应的放音或录音流通道
continue; /* no matching substream */
if (idx == SNDRV_PCM_STREAM_PLAYBACK) {
if (! (f_mode & FMODE_WRITE)) // 当前处理放音,但f_mode不做放音操作,那么continue.
continue;
} else {
if (! (f_mode & FMODE_READ)) // 当前处理录音,但f_mode不做录音操作,那么continue.
continue;
}
err = snd_pcm_open_substream(pcm, idx, file, &substream); // ok, 匹配, 尝试打开该stream流
if (err < 0) {
snd_pcm_oss_release_file(pcm_oss_file);
return err;
}
pcm_oss_file->streams[idx] = substream; // 本应用程序a.out持有的idx类型的stream流
substream->file = pcm_oss_file; // 该stream流归本pcm_oss_file使用
snd_pcm_oss_init_substream(substream, &setup[idx], minor);
// 赋值setup[idx]到substream->oss.setup
// 初始化runtime->oss默认数值
// runtime->oss.params = 1;
// runtime->oss.trigger = 1;
// runtime->oss.rate = 8000;
// runtime->oss.format = AFMT_U8;或runtime->oss.format = AFMT_S16_LE;或runtime->oss.format = AFMT_MU_LAW;
// runtime->oss.channels = 1;
// runtime->oss.subdivision = 0;
}
// pcm_oss_file->streams[idx] = substream;
// 如果成功获取到stream流通道,那么pcm_oss_file->streams[2]中至少有1个非0
if (!pcm_oss_file->streams[0] && !pcm_oss_file->streams[1]) {
snd_pcm_oss_release_file(pcm_oss_file);
return -EINVAL;
}
file->private_data = pcm_oss_file; // ok, 正常打开stream流通道,存入private_data
if (rpcm_oss_file) // 以备close,read和write中使用[luther.gliethttp]
*rpcm_oss_file = pcm_oss_file;
return 0;
}
int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
struct file *file,
struct snd_pcm_substream **rsubstream)
{
struct snd_pcm_substream *substream;
int err;
err = snd_pcm_attach_substream(pcm, stream, file, &substream); // 尝试为打开/dev/dsp设备节点的本应用程序attach一个stream音频控制流逻辑[luther.gliethttp]
if (err < 0)
return err;
if (substream->ref_count > 1) { // 只有当O_APPEND追加模式才会出现ref_count大于1的情况
*rsubstream = substream;
return 0;
}
// O_APPEND或者非O_APPEND,执行到这里,表示ref_count等于1,是第1次打开该stream流通道[luther.gliethttp]
err = snd_pcm_hw_constraints_init(substream); // 初始化runtime硬性约束条件
if (err < 0) {
snd_printd("snd_pcm_hw_constraints_init failed\n");
goto error;
}
// 在snd_soc_new_pcms==>soc_new_pcm中设置放音和录音流的ops为soc_pcm_ops
// /* ASoC PCM operations */
// static struct snd_pcm_ops soc_pcm_ops = {
// .open = soc_pcm_open,
// .close = soc_codec_close,
// .hw_params = soc_pcm_hw_params,
// .hw_free = soc_pcm_hw_free,
// .prepare = soc_pcm_prepare,
// .trigger = soc_pcm_trigger,
// };
if ((err = substream->ops->open(substream)) < 0) // 执行soc_pcm_open,分析见后
goto error;
substream->hw_opened = 1;
err = snd_pcm_hw_constraints_complete(substream);
if (err < 0) {
snd_printd("snd_pcm_hw_constraints_complete failed\n");
goto error;
}
*rsubstream = substream; // 至此stream流初始化工作全部完成
return 0;
error:
snd_pcm_release_substream(substream);
return err;
}
int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
struct file *file,
struct snd_pcm_substream **rsubstream)
{
struct snd_pcm_str * pstr;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
struct snd_ctl_file *kctl;
struct snd_card *card;
int prefer_subdevice = -1;
size_t size;
if (snd_BUG_ON(!pcm || !rsubstream))
return -ENXIO;
*rsubstream = NULL;
pstr = &pcm->streams[stream];
if (pstr->substream == NULL || pstr->substream_count == 0)
return -ENODEV;
card = pcm->card;
read_lock(&card->ctl_files_rwlock);
list_for_each_entry(kctl, &card->ctl_files, list) {
if (kctl->pid == current->pid) { // ctl_files控制文件中如果为本current->pid应用程序强制干预
// 事先做了流通道优选,
prefer_subdevice = kctl->prefer_pcm_subdevice; // 那么将kctl->prefer_pcm_subdevice
if (prefer_subdevice != -1) // 存入prefer_subdevice流通道优选变量[luther.gliethttp]
break; // 应用程序在O_APPEND模式下,
// prefer_subdevice将作为应用程序唯一可选可操作stream.
}
}
read_unlock(&card->ctl_files_rwlock);
switch (stream) {
case SNDRV_PCM_STREAM_PLAYBACK:
if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
// 如果为半双工,那么检查录音stream是否占用,如果录音正在使用,那么放音open操作将失败[luther.gliethttp]
for (substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; substream; substream = substream->next) {
if (SUBSTREAM_BUSY(substream)) // 检查ref_count引用计数是否大于0
return -EAGAIN;
}
}
break;
case SNDRV_PCM_STREAM_CAPTURE:
if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) {
// 如果为半双工,那么进查放音stream是否占用,如果放音正在使用,那么录音open操作将失败[luther.gliethttp]
for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) {
if (SUBSTREAM_BUSY(substream)) // 检查ref_count引用计数是否大于0
return -EAGAIN;
}
}
break;
default:
return -EINVAL;
}
if (file->f_flags & O_APPEND) { // 追加模式
if (prefer_subdevice < 0) {
// prefer_subdevice小于0,表示驱动这里可以任意选择一个符合条件的
if (pstr->substream_count > 1) // 该pcm的stream子流只能等于1
return -EINVAL; /* must be unique */
substream = pstr->substream;
} else {
// prefer_subdevice非负,说明,驱动这里只能选择prefer_subdevice索引号对应的pcm流.
for (substream = pstr->substream; substream;
substream = substream->next)
if (substream->number == prefer_subdevice) // 等于prefer_subdevice
break;
}
if (! substream) // 没有在pcm下找到匹配条件的stream流
return -ENODEV;
if (! SUBSTREAM_BUSY(substream))// 匹配上的stream流必须非busy,即substream->ref_count等于0
return -EBADFD;
substream->ref_count++; // 增加引用计数,
*rsubstream = substream;// 查找任务完成,return 0;返回
return 0;
}
// 非O_APPEND追加模式, 如果prefer_subdevice优选stream没有找到,那么可以在pstr->substream链表上
// 任意选择1个可用非busy的stream,作为此次操作的返回结果[luther.gliethttp].
if (prefer_subdevice >= 0) {
// prefer_subdevice非负,说明,驱动这里只能选择prefer_subdevice索引号对应的pcm流.
for (substream = pstr->substream; substream; substream = substream->next)
if (!SUBSTREAM_BUSY(substream) && substream->number == prefer_subdevice)
goto __ok; // 如果找到匹配的stream,那么__ok,否则一个个的来,查找其他候选可用stream
}
// 如果prefer_subdevice非负,同时还执行到了这里,说明prefer_subdevice优选的stream没有找到,那么
// 下面将遍历pstr->substream,查找所有stream选择第1个可用stream
for (substream = pstr->substream; substream; substream = substream->next)
if (!SUBSTREAM_BUSY(substream)) // 如果非busy,那么太好了
break;
__ok:
if (substream == NULL)
return -EAGAIN;
runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); // 为该stream申请runtime运行空间
if (runtime == NULL)
return -ENOMEM;
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
runtime->status = snd_malloc_pages(size, GFP_KERNEL); // 申请空间
if (runtime->status == NULL) {
kfree(runtime);
return -ENOMEM;
}
memset((void*)runtime->status, 0, size);
size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control));
runtime->control = snd_malloc_pages(size, GFP_KERNEL);// 申请空间
if (runtime->control == NULL) {
snd_free_pages((void*)runtime->status,
PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));
kfree(runtime);
return -ENOMEM;
}
memset((void*)runtime->control, 0, size);
init_waitqueue_head(&runtime->sleep);
runtime->status->state = SNDRV_PCM_STATE_OPEN;
substream->runtime = runtime; // 本stream对应的runtime运行空间
substream->private_data = pcm->private_data;
substream->ref_count = 1; // 本stream引用计数置1
substream->f_flags = file->f_flags;
pstr->substream_opened++; // 本pcm相应的放音或录音stream使用计数++
*rsubstream = substream; // ok,在本pcm中成功打开一个stream,
return 0; // 并为其建立了runtime运行空间.
}
// 初始化runtime->oss默认数值
static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream,
struct snd_pcm_oss_setup *setup,
int minor)
{
struct snd_pcm_runtime *runtime;
substream->oss.oss = 1;
substream->oss.setup = *setup;
if (setup->nonblock)
substream->f_flags |= O_NONBLOCK;
else if (setup->block)
substream->f_flags &= ~O_NONBLOCK;
runtime = substream->runtime;
runtime->oss.params = 1;
runtime->oss.trigger = 1;
runtime->oss.rate = 8000;
mutex_init(&runtime->oss.params_lock);
switch (SNDRV_MINOR_OSS_DEVICE(minor)) {
case SNDRV_MINOR_OSS_PCM_8:
runtime->oss.format = AFMT_U8;
break;
case SNDRV_MINOR_OSS_PCM_16:
runtime->oss.format = AFMT_S16_LE;
break;
default:
runtime->oss.format = AFMT_MU_LAW;
}
runtime->oss.channels = 1;
runtime->oss.fragshift = 0;
runtime->oss.maxfrags = 0;
runtime->oss.subdivision = 0;
substream->pcm_release = snd_pcm_oss_release_substream;
}
soc_pcm_open // 将初始化runtime->hw所有硬件匹配数据
==> platform->pcm_ops->open
对于s3c24xx平台来说就是
s3c24xx_soc_platform
==> s3c24xx_pcm_ops
==> s3c24xx_pcm_open
==> snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware); 配置runtime->hw部分硬件参数
static const struct snd_pcm_hardware s3c24xx_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_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128*1024,
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = PAGE_SIZE*2,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};