关闭

/dev/dsp与alsa框架下设备节点打开和创建简易流程

标签: 框架structplaybackcodecstreammodule
1521人阅读 评论(0) 收藏 举报
分类:
浅析ASoC-audio驱动oss框架下/dev/dsp与alsa框架下设备节点打开和创建简易流程

对于oss设备节点
1. soundcore_fops       --  提供主设备号为14的oss节点open("/dev/dsp")操作soundcore_open,最后将调用snd_pcm_oss_open
2. snd_pcm_oss_f_reg    --  提供最终的file->f_op应用程序调用方法集
对于alsa设备节点
1. snd_fops             --  提供主设备号为116的alsa节点open("/dev/snd/pcmC0D0c")操作snd_open
2. snd_pcm_f_ops[2]     --  提供最终的file->f_op应用程序调用方法集snd_pcm_f_ops[0]用于放音,snd_pcm_f_ops[1]用于录音.

可能后面的流程都是混杂的,不能区分很清楚,所以先来看最直观的oss设备节点"/dev/dsp"打开流程[luther.gliethttp].
static const struct file_operations soundcore_fops=
{
    /* We must have an owner or the module locking fails */
    .owner    = THIS_MODULE,
    .open    = soundcore_open,                                           // 类似chrdev_open的实现,现在很多集中管理的驱动都这样
};                                                                      // 来界定设备[luther.gliethttp].

static const struct file_operations snd_pcm_oss_f_reg =
{
    .owner =    THIS_MODULE,
    .read =        snd_pcm_oss_read,
    .write =    snd_pcm_oss_write,
    .open =        snd_pcm_oss_open,
    .release =    snd_pcm_oss_release,
    .poll =        snd_pcm_oss_poll,
    .unlocked_ioctl =    snd_pcm_oss_ioctl,
    .compat_ioctl =    snd_pcm_oss_ioctl_compat,
    .mmap =        snd_pcm_oss_mmap,
};

我们先来看看打开/dev/dsp字符设备节点的流程[luther.gliethttp].
luther@gliethttp:~$ ll /dev/dsp
crw-rw----+ 1 root audio 14, 3 2009-08-15 14:59 /dev/dsp

module_init(init_soundcore);                                            // 模块人口
static int __init init_soundcore(void)
{
    // #define SOUND_MAJOR      14
    if (register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops)==-1) {   // 主设备号为14的所有256个字符设备节点都将调用该方法集
        printk(KERN_ERR "soundcore: sound device already in use.\n");   // 比如打开/dev/dsp设备,那么将首先执行这里的soundcore_open
        return -EBUSY;
    }
    sound_class = class_create(THIS_MODULE, "sound");                   // 创建/sys/class/sound类目录[luther.gliethttp]
    if (IS_ERR(sound_class))
        return PTR_ERR(sound_class);

    return 0;
}

int soundcore_open(struct inode *inode, struct file *file)
{
   int unit = iminor(inode);                                           //根据inode节点的minor次设备号锁定声卡设备,对于inode节点的自动创建在后面我们会慢慢谈到[luther.gliethttp].
    struct sound_unit *s;
    ......
/*
 *    Allocations
 *
 *    0    *16        Mixers
 *    1    *8        Sequencers
 *    2    *16        Midi
 *    3    *16        DSP
 *    4    *16        SunDSP
 *    5    *16        DSP16
 *    6    --        sndstat (obsolete)
 *    7    *16        unused
 *    8    --        alternate sequencer (see above)
 *    9    *16        raw synthesizer access
 *    10    *16        unused
 *    11    *16        unused
 *    12    *16        unused
 *    13    *16        unused
 *    14    *16        unused
 *    15    *16        unused

static struct sound_unit *chains[SOUND_STEP];
*/
    chain=unit&0x0F;                                                    // 当前不超过16个SOUND_STEP
    s = __look_for_unit(chain, unit);                                   // 从chains[chain]全局链表上寻找索引号为unit的sound_unit.
    if (s)
       new_fops = fops_get(s->unit_fops);                              //使用s->unit_fops=snd_pcm_oss_f_reg替换原有的soundcore_fops函数集
    file->f_op = new_fops;
    err = file->f_op->open(inode,file);                                 // 使用snd_pcm_oss_open进一步打开
}

static struct sound_unit *__look_for_unit(int chain, int unit)
{
    struct sound_unit *s;
    
    s=chains[chain];
    while(s && s->unit_minor <= unit)
    {
        if(s->unit_minor==unit)
            return s;                                                   // ok,找到
        s=s->next;
    }
    return NULL;
}

到目前为止我们粗略讨论了打开/dev/dsp设备节点的流程,下面我们继续看看创建/dev/dsp设备节点的流程是怎么样的[luther.gliethttp],

module_init(alsa_pcm_oss_init)还有一个module_init(alsa_mixer_oss_init)和alsa_pcm_oss_init过程差不多.
==>alsa_pcm_oss_init                                   //登记snd_pcm_oss_notify,同时为snd_pcm_devices链表上的的pcm设备执行snd_pcm_oss_register_minor函数
==*>snd_pcm_notify(&snd_pcm_oss_notify, 0)              // 将snd_pcm_oss_notify追加到snd_pcm_notify_list通知链表
    list_add_tail(&notify->list, &snd_pcm_notify_list);
    list_for_each_entry(pcm, &snd_pcm_devices, list)    // 同时为snd_pcm_oss_notify遍历已经注册登记到snd_pcm_devices链表上的的pcm设备
            notify->n_register(pcm);                    // 为他们分别执行snd_pcm_oss_notify的n_register方法[luther.gliehtttp]

static struct snd_pcm_notify snd_pcm_oss_notify =
{
    .n_register =    snd_pcm_oss_register_minor,
    .n_disconnect = snd_pcm_oss_disconnect_minor,
    .n_unregister =    snd_pcm_oss_unregister_minor,
};

snd_pcm_oss_register_minor                               // 当检测到新的声卡设备时,就会调用该notifer函数,为其注册登记生成设备节点
==> register_oss_dsp(pcm, 0);和register_oss_dsp(pcm, 1); // index=0或者index=1,即第0个16组或者第1个16组
static void register_oss_dsp(struct snd_pcm *pcm, int index)
{
    char name[128];
    sprintf(name, "dsp%i%i", pcm->card->number, pcm->device);
    if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM,
                    pcm->card, index, &snd_pcm_oss_f_reg,// 实际完成控制设备的fops,即:snd_pcm_oss_f_reg
                    pcm, name) < 0) {
        snd_printk(KERN_ERR "unable to register OSS PCM device %i:%i\n",
               pcm->card->number, pcm->device);
    }
}

snd_register_oss_device(int type, struct snd_card *card, int dev,
                const struct file_operations *f_ops, void *private_data,
                const char *name)
==>int minor = snd_oss_kernel_minor(type, card, dev);                  //minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_PCM1 :SNDRV_MINOR_OSS_PCM));
==> preg->device = dev;                                                 // 我这里minor等于3
==> preg->f_ops = f_ops;
==> snd_oss_minors[minor] = preg;                                       // 放到oss设备数组中,这样在snd_pcm_oss_open时可以打开
==> register_sound_special_device(f_ops, minor, carddev);               // minor>=3
/**
 *    register_sound_special_device - register a special sound node
 *    @fops: File operations for the driver
 *    @unit: Unit number to allocate
 *      @dev: device pointer
 *
 *    Allocate a special sound device by minor number from the sound
 *    subsystem. The allocated number is returned on succes. On failure
 *    a negative error code is returned.
 */
int register_sound_special_device(const struct file_operations *fops, int unit,
                  struct device *dev)
{
    const int chain = unit % SOUND_STEP;    // SOUND_STEP为16,分别代表主设备类型,每个主设备类型下可以追加n个同类型的音频设备.
    int max_unit = 128 + chain;
    const char *name;
    char _name[16];

    switch (chain) {
        case 0:
        name = "mixer";
        break;
        case 1:
        name = "sequencer";
        if (unit >= SOUND_STEP)
            goto __unknown;
        max_unit = unit + 1;
        break;
        case 2:
        name = "midi";
        break;
        case 3:
        name = "dsp";
        break;
        case 4:
        name = "audio";
        break;
        case 8:
        name = "sequencer2";
        if (unit >= SOUND_STEP)
            goto __unknown;
        max_unit = unit + 1;
        break;
        case 9:
        name = "dmmidi";
        break;
        case 10:
        name = "dmfm";
        break;
        case 12:
        name = "adsp";
        break;
        case 13:
        name = "amidi";
        break;
        case 14:
        name = "admmidi";
        break;
        default:
            {
            __unknown:
            sprintf(_name, "unknown%d", chain);
                if (unit >= SOUND_STEP)
                    strcat(_name, "-");
                name = _name;
        }
        break;
    }
    return sound_insert_unit(&chains[chain], fops, -1, unit, max_unit,
                 name, S_IRUSR | S_IWUSR, dev);                         // 将方法集snd_pcm_oss_f_reg注册上去
}

staticint sound_insert_unit(struct sound_unit **list, const structfile_operations *fops, int index, int low, int top, const char *name,umode_t mode, struct device *dev)
{
    struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);
    int r;

    if (!s)
        return -ENOMEM;                                                 // index等于-1,表示动态获取一个可用的设备节点号.
       
    spin_lock(&sound_loader_lock);                                      // 每16个设备为一组,index表示第几组.
    r = __sound_insert_unit(s, list, fops, index, low, top);            // 插入到上面提到的chains[3]中,inode节点的minor设备号
    spin_unlock(&sound_loader_lock);                                    // 从最小值3开始按i*16方式递增,
                                                                        // 即/dev/dsp的节点号为(14,3),
                                                                        // /dev/dsp1的节点号为(14,19),
                                                                        // /dev/dsp2的节点号为(14,35)依次类推[luther.gliethttp].
                                                                        // 最后s->unit_minor=动态获取的一个空闲id
                                                                        // s->unit_fops=snd_pcm_oss_f_reg
    if (r < 0)
        goto fail;
    else if (r < SOUND_STEP)
        sprintf(s->name, "sound/%s", name);
    else
        sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);
                                                                        // 调用device_create广播设备信息到user space,udev创建
                                                                        // 相应的字符设备节点/dev/dsp等[luther.gliethttp].
    device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor),  // MKDEV(SOUND_MAJOR, s->unit_minor)为/dev/dsp设备的
              NULL, s->name+6);                                         // 节点号,主节点号SOUND_MAJOR等于14,子节点minor等于s->unit_minor
    return r;

 fail:
    kfree(s);
    return r;
}

上面snd_pcm_oss_notify中的n_register方法即:snd_pcm_oss_register_minor是在snd_pcm_oss_notify注册时主动执行的,
那在设备注册的时候又是怎么被动的引用n_register方法的呢?下面我们来看看,
先来看看设备注册,
/* audio subsystem */
static struct snd_soc_device TLG_snd_devdata = {
    .machine = &snd_soc_machine_TLG,
    .platform = &ep93xx_soc_platform,
    .codec_dev = &soc_codec_dev_xxxxx,
};

static struct platform_device *TLG_snd_device;
module_init(TLG_init);
static int __init TLG_init(void)                                // 平台audio设备初始化入口
{
    TLG_snd_device = platform_device_alloc("soc-audio", -1);    // 他将被名为"soc-audio"的platform总线下的驱动程序驱动[luther.gliethttp]
    platform_set_drvdata(TLG_snd_device, &TLG_snd_devdata);
    TLG_snd_devdata.dev = &TLG_snd_device->dev;
    ret = platform_device_add(TLG_snd_device);
}

static struct platform_driver soc_driver = {
    .driver        = {
        .name        = "soc-audio",
    },
    .probe        = soc_probe,
    .remove        = soc_remove,
    .suspend    = soc_suspend,
    .resume        = soc_resume,
};
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
// /* TLG audio machine driver */
// static struct snd_soc_machine snd_soc_machine_TLG = {
//     .name = "TLG",
//     .dai_link = TLG_dai,        // /* CPU <--> Codec DAI links  */核心在这里,Digital Audio Interface (DAI)
//     .num_links = ARRAY_SIZE(TLG_dai),
// };
    int ret = 0, i;
    struct snd_soc_device *socdev = platform_get_drvdata(pdev);
    struct snd_soc_machine *machine = socdev->machine;
    struct snd_soc_platform *platform = socdev->platform;
    struct snd_soc_codec_device *codec_dev = socdev->codec_dev;

    if (machine->probe) {               // snd_soc_machine_TLG
        ret = machine->probe(pdev);
        if(ret < 0)
            return ret;
    }

    for (i = 0; i < machine->num_links; i++) {
// TLG_dai
        struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai;
        if (cpu_dai->probe) {
            ret = cpu_dai->probe(pdev);
            if(ret < 0)
                goto cpu_dai_err;
        }
    }

    if (codec_dev->probe) {             // soc_codec_dev_xxxxx
        ret = codec_dev->probe(pdev);   // xxxxx_soc_probe,完成节点创建工作
        if(ret < 0)
            goto cpu_dai_err;
    }
// struct snd_pcm_ops ep93xx_pcm_ops = {
//     .open        = ep93xx_pcm_open,
//     .close        = ep93xx_pcm_close,
//     .ioctl        = snd_pcm_lib_ioctl,
//     .hw_params    = ep93xx_pcm_hw_params,
//     .hw_free    = ep93xx_pcm_hw_free,
//     .prepare    = ep93xx_pcm_prepare,
//     .trigger    = ep93xx_pcm_trigger,
//     .pointer    = ep93xx_pcm_pointer,
//     .mmap        = ep93xx_pcm_mmap,
// };
// struct snd_soc_platform ep93xx_soc_platform = {
//     .name        = "ep93xx-audio",
//     .pcm_ops     = &ep93xx_pcm_ops,
//     .pcm_new    = ep93xx_pcm_new,
//     .pcm_free    = ep93xx_pcm_free_dma_buffers,
// };
    if (platform->probe) {              // ep93xx_soc_platform
        ret = platform->probe(pdev);
        if(ret < 0)
            goto platform_err;
    }
    ......
}
struct snd_soc_codec_device soc_codec_dev_xxxxx中的xxxxx_soc_probe枚举函数
static int xxxxx_soc_probe(struct platform_device *pdev)
{
    snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);   // 创建alsa节点设备 --  major等于160的设备节点
    ret = snd_soc_register_card(socdev);                                // 创建oss节点设备  --  major等于14的/dev/dsp等
}
xxxxx_soc_probe
==> snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
==> soc_new_pcm(socdev, &card->dai_link[i], i);                         // 为每一个DAI数字音频接口流通道注册一个pcm.
/* create a new pcm */
static int soc_new_pcm(struct snd_soc_device *socdev,
    struct snd_soc_dai_link *dai_link, int num)
{
    struct snd_soc_codec *codec = socdev->codec;
    struct snd_soc_codec_dai *codec_dai = dai_link->codec_dai;
    struct snd_soc_cpu_dai *cpu_dai = dai_link->cpu_dai;
    ......
    // 将ep9312开发板音频数据部分控制方法赋值给默认的soc_pcm_ops静态统一结构体[luther.gliethttp]
    // socdev       -- TLG_snd_devdata
    // platform     -- ep93xx_soc_platform
    // pcm_ops      -- ep93xx_pcm_ops
    ret = snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback,
        capture, &pcm);
    ......
// /* 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,
// };
    soc_pcm_ops.mmap = socdev->platform->pcm_ops->mmap;         // 开发板自己的mmap方法
    soc_pcm_ops.pointer = socdev->platform->pcm_ops->pointer;
    soc_pcm_ops.ioctl = socdev->platform->pcm_ops->ioctl;       // 开发板自己的ioctl方法
    soc_pcm_ops.copy = socdev->platform->pcm_ops->copy;
    soc_pcm_ops.silence = socdev->platform->pcm_ops->silence;
    soc_pcm_ops.ack = socdev->platform->pcm_ops->ack;
    soc_pcm_ops.page = socdev->platform->pcm_ops->page;
    if (playback)                                               // 放音通道,见下面[luther.gliethttp]
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops); // 这样stream的ops就直接使用上了与platform平台相关的专有控制函数了.

    if (capture)                                                // 录音通道,见下面
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);

    ret = socdev->platform->pcm_new(codec->card, codec_dai, pcm);
    if (ret < 0) {
        printk(KERN_ERR "asoc: platform pcm constructor failed\n");
        kfree(rtd);
        return ret;
    }

    pcm->private_free = socdev->platform->pcm_free;
    ......
}

/**
 * snd_pcm_set_ops - set the PCM operators
 * @pcm: the pcm instance
 * @direction: stream direction, SNDRV_PCM_STREAM_XXX
 * @ops: the operator table
 *
 * Sets the given PCM operators to the pcm instance.
 */
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops)
{
   struct snd_pcm_str *stream = &pcm->streams[direction];  //有2种值:SNDRV_PCM_STREAM_PLAYBACK(放音)和SNDRV_PCM_STREAM_CAPTURE(录音)
    struct snd_pcm_substream *substream;                    // pcm->streams[]在snd_pcm_new()中创建[luther.gliethttp].
   
    for (substream = stream->substream; substream != NULL; substream = substream->next)
       substream->ops = ops;                               //遍历所有substream流通道,赋予其控制该stream流数据的该ops操作方法集soc_pcm_ops[luther.gliehttp]
}
==> 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,
        .dev_disconnect = snd_pcm_dev_disconnect,
    };
    pcm->device = device;等于codec->pcm_devs++索引值
==> snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops);

int snd_device_new(struct snd_card *card, snd_device_type_t type,
           void *device_data, struct snd_device_ops *ops)
{
    struct snd_device *dev;
    ......
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    ......
    dev->ops = ops;                                         // 上面snd_pcm_new()中static类型的ops方法集,
                                                            // 含有.dev_register = snd_pcm_dev_register
    list_add(&dev->list, &card->devices);    /* add to the head of list */
    return 0;
}
下面是oss设备节点和alsa设备节点创建流程的核心部分[luther.gliethttp].
xxxxx_soc_probe
==> snd_soc_register_card(socdev)
==> snd_card_register(card)
==> snd_device_register_all(card)
/*
 * register all the devices on the card.
 * called from init.c
 */
int snd_device_register_all(struct snd_card *card)
{
    struct snd_device *dev;
    int err;
   
    snd_assert(card != NULL, return -ENXIO);
    list_for_each_entry(dev, &card->devices, list) {        // 注册card设备链表上的所有DAI控制链路的stream流通道[luther.gliethttp].
        if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
            if ((err = dev->ops->dev_register(dev)) < 0)    // 即:snd_pcm_dev_register
                return err;
            dev->state = SNDRV_DEV_REGISTERED;
        }
    }
    return 0;
}
==> dev->ops->dev_register(dev) 即:snd_pcm_dev_register
/*
 * snd_pcm_new
 *     static struct snd_device_ops ops = {
 *         .dev_free = snd_pcm_dev_free,
 *         .dev_register =    snd_pcm_dev_register,
 *         .dev_disconnect = snd_pcm_dev_disconnect,
 *     };
 * ==> snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
 */
static int snd_pcm_dev_register(struct snd_device *device)
{
    char str[16];
    ......
    sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
    或
    sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
    err = snd_register_device_for_dev(devtype, pcm->card,
                          pcm->device,                      // 这里的pcm->device就是snd_pcm_new()函数中codec->pcm_devs++
                          &snd_pcm_f_ops[cidx],             // alsa方法集,包含录音和放音[luther.gliethttp]
                          pcm, str, dev);                   // 注册alsa设备节点
    ......
    list_for_each_entry(notify, &snd_pcm_notify_list, list)
        notify->n_register(pcm);                            // 调用上面介绍的snd_pcm_oss_register_minor注册notifier注册OSS设备节点
    ......
}

int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
                const struct file_operations *f_ops,
                void *private_data,
                const char *name, struct device *device)
{
    ......
    preg->device = dev;
    preg->f_ops = f_ops;                                    // 对应&snd_pcm_f_ops[cidx]这个alsa方法集,包含录音和放音
#ifdef CONFIG_SND_DYNAMIC_MINORS
    minor = snd_find_free_minor();
#else
    minor = snd_kernel_minor(type, card, dev);              // 这里的dev就是snd_pcm_new()函数中codec->pcm_devs++
    if (minor >= 0 && snd_minors[minor])                    // 定义最多256个minor设备#define SNDRV_OS_MINORS 256
        minor = -EBUSY;
#endif
    snd_minors[minor] = preg;                               // 记录到alsa设备维护静态数组中,当open时会查找对应的preg.
    preg->dev = device_create(sound_class, device, MKDEV(major, minor), // uevnt将创建MKDEV(major, minor)节点alsa设备节点
                  private_data, "%s", name);                // 该major在alsa_sound_init中,默认为
                                                            // static int major = CONFIG_SND_MAJOR;
                                                            // #define CONFIG_SND_MAJOR    116    /* standard configuration */
    ......
}

#define CONFIG_SND_MAJOR    116    /* standard configuration */
static int major = CONFIG_SND_MAJOR;
module_init(alsa_sound_init)
alsa_sound_init
==> register_chrdev(major, "alsa", &snd_fops)               // 主设备号为116的所有设备都为alsa设备,节点方法集为snd_fops
static const struct file_operations snd_fops =              // alsa的设备名为pcmC0D1c或pcmC0D1p等,位于/dev/snd/目录下[luther.gliethttp].
{
    .owner =    THIS_MODULE,
    .open =        snd_open
};
snd_open
==> __snd_open(inode, file);
==> __snd_open
    unsigned int minor = iminor(inode);
    mptr = snd_minors[minor];
    file->f_op = fops_get(mptr->f_ops);
    file->f_op->open(inode, file);
const struct file_operations snd_pcm_f_ops[2] = {
    {                                                       // alsa使用到的SNDRV_PCM_STREAM_PLAYBACK放音方法集[luther.gliethttp]
        .owner =        THIS_MODULE,
        .write =        snd_pcm_write,
        .aio_write =        snd_pcm_aio_write,
        .open =            snd_pcm_playback_open,
        .release =        snd_pcm_release,
        .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 =    dummy_get_unmapped_area,
    },
    {                                                       // alsa使用到的SNDRV_PCM_STREAM_CAPTURE录音方法集[luther.gliethttp]
        .owner =        THIS_MODULE,
        .read =            snd_pcm_read,
        .aio_read =        snd_pcm_aio_read,
        .open =            snd_pcm_capture_open,
        .release =        snd_pcm_release,
        .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 =    dummy_get_unmapped_area,
    }
};

至此,/dev/dsp设备节点和alsa设备节点在udev的配合下就按上面简单叙述的流程创建完成了[luther.gliethttp].
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:99549次
    • 积分:1437
    • 等级:
    • 排名:千里之外
    • 原创:8篇
    • 转载:109篇
    • 译文:0篇
    • 评论:47条
    最新评论