天天看點

/dev/dsp與alsa架構下裝置節點打開和建立簡易流程

淺析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=

{

    .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].

[email protected]:~$ 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;

    ......

    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

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方法的呢?下面我們來看看,

先來看看裝置注冊,

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,

};

static int soc_probe(struct platform_device *pdev)

{

//

// static struct snd_soc_machine snd_soc_machine_TLG = {

//     .name = "TLG",

//     .dai_link = TLG_dai,        // 核心在這裡,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.

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);

    ......

//

// 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;

    ......

}

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);   

    return 0;

}

下面是oss裝置節點和alsa裝置節點建立流程的核心部分[luther.gliethttp].

xxxxx_soc_probe

==> snd_soc_register_card(socdev)

==> snd_card_register(card)

==> snd_device_register_all(card)

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

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   

    ......

}

#define CONFIG_SND_MAJOR    116   

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].

繼續閱讀