Chinaunix首页 | 论坛 | 博客
  • 博客访问: 353577
  • 博文数量: 570
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2015-11-17 10:38
文章分类

全部博文(570)

文章存档

2015年(570)

我的朋友

分类: 嵌入式

2015-11-17 11:04:15

浅析打开/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,
};

阅读(327) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~