ALSA声卡驱动--2:声卡的创建

"本文转载自:[DroidPhone]的Linux ALSA声卡驱动之二:声卡的创建"

1.snd_card结构体

1.1 snd_card是什么

  snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd_card的管理之下,声卡驱动的第一个动作通常就是创建一个snd_card结构体。

1.2 snd_card的定义

snd_card的定义位于该头文件中:include/sound/core.h

struct snd_card {
        int number;                        /* number of soundcard (index to
                                                                snd_cards) */

        char id[16];                        /* id string of this card */
        char driver[16];                /* driver name */
        char shortname[32];                /* short name of this soundcard */
        char longname[80];                /* name of this soundcard */
        char irq_descr[32];                /* Interrupt description */
        char mixername[80];                /* mixer name */
        char components[128];                /* card components delimited with
                                                                space */
        struct module *module;                /* top-level module */

        void *private_data;                /* private data for soundcard */
        void (*private_free) (struct snd_card *card); /* callback for freeing of
                                                                private data */
        struct list_head devices;        /* devices */

        struct device ctl_dev;                /* control device */
        unsigned int last_numid;        /* last used numeric ID */
        struct rw_semaphore controls_rwsem;        /* controls list lock */
        rwlock_t ctl_files_rwlock;        /* ctl_files list lock */
        int controls_count;                /* count of all controls */
        int user_ctl_count;                /* count of all user controls */
        struct list_head controls;        /* all controls for this card */
        struct list_head ctl_files;        /* active control files */

        struct snd_info_entry *proc_root;        /* root for soundcard specific files */
        struct snd_info_entry *proc_id;        /* the card id */
        struct proc_dir_entry *proc_root_link;        /* number link to real id */

        struct list_head files_list;        /* all files associated to this card */
        struct snd_shutdown_f_ops *s_f_ops; /* file operations in the shutdown
                                                                state */
        spinlock_t files_lock;                /* lock the files for this card */
        int shutdown;                        /* this card is going down */
        struct completion *release_completion;
        struct device *dev;                /* device assigned to this card */
        struct device card_dev;                /* cardX object for sysfs */
        const struct attribute_group *dev_groups[4]; /* assigned sysfs attr */
        bool registered;                /* card_dev is registered? */
        wait_queue_head_t remove_sleep;

#ifdef CONFIG_PM
        unsigned int power_state;        /* power state */
        wait_queue_head_t power_sleep;
#endif

#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
        struct snd_mixer_oss *mixer_oss;
        int mixer_oss_change_count;
#endif
};

  • struct list_head devices:记录该声卡下所有逻辑设备的链表;
  • struct list_head controls:记录该声卡下所有的控制单元的链表;
  • void *private_data:声卡的私有数据,可以在创建声卡时通过参数指定数据的大小。

2.声卡的建立流程

2.1 第一步,创建snd_card的一个实例

  • /sound/core/init.c
int snd_card_new(struct device *parent, int idx, const char *xid,
          struct module *module, int extra_size,
          struct snd_card **card_ret)

  • parent: the parent device object;
  • idx:一个整数数值,该声卡的编号;
  • xid:字符串,声卡的标示符;
  • module: top level module for locking;
  • extra_size: 该参数决定在建立snd_card实例时,须要同时额外分配数据的大小,该数据的指针最终会赋值给snd_card的private_data数据成员;
  • card_ret: 返回所建立的snd_card实例指针。

2.2 第二步,建立声卡芯片专用数据

if (extra_size > 0)
    card->private_data = (char *)card + sizeof(struct snd_card);

2.3 第三步,设置Driver的ID和名字

  • /sound/pci/hda/hda_intel.c
strcpy(card->driver, "HDA-Intel");
strlcpy(card->shortname, driver_short_names[chip->driver_type],
   sizeof(card->shortname));
snprintf(card->longname, sizeof(card->longname),
    "%s at 0x%lx irq %i",
    card->shortname, bus->addr, bus->irq);

  snd_card的driver字段保存着芯片的ID字符串,user空间的alsa-lib会使用到该字符串,因此必需要保证该ID的惟一性,shortname字段更多的用于打印信息,longname字段会出如今/proc/asound/cards/中。

 0 [PCH            ]: HDA-Intel - HDA Intel PCH
                      HDA Intel PCH at 0xbf210000 irq 128

2.4 第四步,创建声卡的功能部件(逻辑设备),例如PCM,Mixer,MIDI等

  这时候可以创建声卡的各种功能部件了,还记得开头的snd_card结构体的devices字段吗?每一种部件的创建最终会调用snd_device_new()来生成一个snd_device实例,并把该实例链接到snd_card的devices链表中。

通常,alsa-driver的已经提供了一些常用的部件的创建函数,而不必直接调用snd_device_new(),比如:

PCM     --- snd_pcm_new()       --- /sound/core/pcm.c
RAWMIDI --- snd_rawmidi_new()   --- /sound/core/rawmidi.c
CONTROL --- snd_ctl_create()    --- /sound/core/control.c
TIMER   --- snd_timer_new()     --- /sound/core/timer.c
JACK    --- snd_jack_new()      --- /sound/core/jack.c
...

2.5 第五步,注册声卡

  • /sound/pci/hda/hda_intel.c
err = snd_card_register(chip->card);
if (err < 0)
   goto out_free;
...
out_free:
   snd_card_free(card);
   return err;

3.声卡建立实例分析

  • /sound/arm/pxa2xx-ac97.c

驱动程序通常由probe回调函数开始:

static int pxa2xx_ac97_probe(struct platform_device *dev)
{
   struct snd_card *card;
   struct snd_ac97_bus *ac97_bus;
   struct snd_ac97_template ac97_template;
   int ret;
   pxa2xx_audio_ops_t *pdata = dev->dev.platform_data;

   if (dev->id >= 0) {
      dev_err(&dev->dev, "PXA2xx has only one AC97 port.\n");
      ret = -ENXIO;
      goto err_dev;
   }
   // step 1: 创建声卡
   ret = snd_card_new(&dev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
            THIS_MODULE, 0, &card);
   if (ret < 0)
      goto err;

   //step 3:设置Driver的ID
   strlcpy(card->driver, dev->dev.driver->name, sizeof(card->driver));
   // snd_pcm_new():创建pcm设备
   ret = pxa2xx_pcm_new(card, &pxa2xx_ac97_pcm_client, &pxa2xx_ac97_pcm);
   if (ret)
      goto err;
   //setp 2
   ret = pxa2xx_ac97_hw_probe(dev);
   if (ret)
      goto err;
   //step 4:创建一个 AC97 总线组件 
   ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus);
   if (ret)
      goto err_remove;
   memset(&ac97_template, 0, sizeof(ac97_template));
   ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97);
   if (ret)
      goto err_remove;
   //step 3:设置Driver的名字
   snprintf(card->shortname, sizeof(card->shortname),
       "%s", snd_ac97_get_short_name(pxa2xx_ac97_ac97));
   snprintf(card->longname, sizeof(card->longname),
       "%s (%s)", dev->dev.driver->name, card->mixername);

   if (pdata && pdata->codec_pdata[0])
      snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata[0]);
   //step 5:注册声卡
   ret = snd_card_register(card);
   if (ret == 0) {
      platform_set_drvdata(dev, card);
      return 0;
   }

err_remove:
   pxa2xx_ac97_hw_remove(dev);
err:
   if (card)
      snd_card_free(card);
err_dev:
   return ret;
}

经过以上的创建步骤之后,声卡的逻辑结构如下图所示:

  • 声卡的软件逻辑结构
5b21325ebaab5f632c2795e51775912f_f92708c1-9e33-4686-8a2d-2c12d923d3d2.png

4.主要方法解析

4.1 snd_card_new

  • /sound/core/init.c
int snd_card_new(struct device *parent, int idx, const char *xid,
                    struct module *module, int extra_size,
                    struct snd_card **card_ret)
{
        struct snd_card *card;
        int err;

        if (snd_BUG_ON(!card_ret))
                return -EINVAL;
        *card_ret = NULL;

        if (extra_size < 0)
                extra_size = 0;
        //根据extra_size大小分配内存,该内存区可做为芯片专有数据使用
        card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
        if (!card)
                return -ENOMEM;
        //把第一步分配的内存指针放入private_data字段中:
        if (extra_size > 0)
                card->private_data = (char *)card + sizeof(struct snd_card);
        //拷贝声卡ID字符串
        if (xid)
                strlcpy(card->id, xid, sizeof(card->id));
        err = 0;
        mutex_lock(&snd_card_mutex);
        //若是传入的声卡编号为-1,自动分配一个索引编号
        if (idx < 0) /* first check the matching module-name slot */
                idx = get_slot_from_bitmask(idx, module_slot_match, module);
        if (idx < 0) /* if not matched, assign an empty slot */
                idx = get_slot_from_bitmask(idx, check_empty_slot, module);
        if (idx < 0)
                err = -ENODEV;
        else if (idx < snd_ecards_limit) {
                if (test_bit(idx, snd_cards_lock))
                        err = -EBUSY;        /* invalid */
        } else if (idx >= SNDRV_CARDS)
                err = -ENODEV;
        if (err < 0) {
                mutex_unlock(&snd_card_mutex);
                dev_err(parent, "cannot find the slot for index %d (range 0-%i), error: %d\n",
                         idx, snd_ecards_limit - 1, err);
                kfree(card);
                return err;
        }
        set_bit(idx, snd_cards_lock);                /* lock it */
        if (idx >= snd_ecards_limit)
                snd_ecards_limit = idx + 1; /* increase the limit */
        mutex_unlock(&snd_card_mutex);
        //初始化snd_card中必要的字段
        card->dev = parent;
        card->number = idx;
        card->module = module;
        INIT_LIST_HEAD(&card->devices);
        init_rwsem(&card->controls_rwsem);
        rwlock_init(&card->ctl_files_rwlock);
        INIT_LIST_HEAD(&card->controls);
        INIT_LIST_HEAD(&card->ctl_files);
        spin_lock_init(&card->files_lock);
        INIT_LIST_HEAD(&card->files_list);
#ifdef CONFIG_PM
        init_waitqueue_head(&card->power_sleep);
#endif
        init_waitqueue_head(&card->remove_sleep);

        device_initialize(&card->card_dev);
        card->card_dev.parent = parent;
        card->card_dev.class = sound_class;
        card->card_dev.release = release_card_device;
        card->card_dev.groups = card->dev_groups;
        card->dev_groups[0] = &card_dev_attr_group;
        err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);
        if (err < 0)
                goto __error;

        snprintf(card->irq_descr, sizeof(card->irq_descr), "%s:%s",
                 dev_driver_string(card->dev), dev_name(&card->card_dev));

        /* the control interface cannot be accessed from the user space until */
        /* snd_cards_bitmask and snd_cards are set with snd_card_register */
        //创建逻辑设备:control
        err = snd_ctl_create(card);
        if (err < 0) {
                dev_err(parent, "unable to register control minors\n");
                goto __error;
        }
        //创建proc文件中的info节点,一般就是/proc/asound/card0/
        err = snd_info_card_create(card);
        if (err < 0) {
                dev_err(parent, "unable to create card info\n");
                goto __error_ctl;
        }
        *card_ret = card;
        return 0;

      __error_ctl:
        snd_device_free_all(card);
      __error:
        put_device(&card->card_dev);
          return err;
}

4.2 snd_card_register

  • /sound/core/init.c
int snd_card_register(struct snd_card *card)
{
        int err;

        if (snd_BUG_ON(!card))
                return -EINVAL;
        //首先建立sysfs下的设备
        if (!card->registered) {
                err = device_add(&card->card_dev);
                if (err < 0)
                        return err;
                card->registered = true;
        }

//经过snd_device_register_all()注册全部挂在该声卡下的逻辑设备,snd_device_register_all()其实是经过snd_card的device链表,遍历全部snd_device,而且调用snd_device的ops->dev_register()来实现各自设备的注册的
        if ((err = snd_device_register_all(card)) < 0)
                return err;
        mutex_lock(&snd_card_mutex);
        if (snd_cards[card->number]) {
                /* already registered */
                mutex_unlock(&snd_card_mutex);
                return snd_info_card_register(card); /* register pending info */
        }
        if (*card->id) {
                /* make a unique id name from the given string */
                char tmpid[sizeof(card->id)];
                memcpy(tmpid, card->id, sizeof(card->id));
                snd_card_set_id_no_lock(card, tmpid, tmpid);
        } else {
                /* create an id from either shortname or longname */
                const char *src;
                src = *card->shortname ? card->shortname : card->longname;
                snd_card_set_id_no_lock(card, src,
                                        retrieve_id_from_card_name(src));
        }
        snd_cards[card->number] = card;
        mutex_unlock(&snd_card_mutex);
        init_info_for_card(card);
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
        if (snd_mixer_oss_notify_callback)
                snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
#endif
        return 0;
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容