Linux ALSA声卡驱动之二:声卡的创建 柔情只为你懂 2022-06-17 10:44 215阅读 0赞 转载自[http://blog.csdn.net/droidphone/article/details/6289712][http_blog.csdn.net_droidphone_article_details_6289712] ## 1. struct snd\_card ## ### 1.1. snd\_card是什么 ### snd\_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd\_card的管理之下,声卡驱动的第一个动作通常就是创建一个snd\_card结构体。正因为如此,本节中,我们也从 struct cnd\_card开始吧。 ### 1.2. snd\_card的定义 ### snd\_card的定义位于改头文件中:include/sound/core.h ![save_snippets.png][] /\* main structure for soundcard \*/ 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 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 \*/ 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 \*/ int free\_on\_last\_close; /\* free in context of file\_release \*/ wait\_queue\_head\_t shutdown\_sleep; struct device \*dev; /\* device assigned to this card \*/ \#ifndef CONFIG\_SYSFS\_DEPRECATED struct device \*card\_dev; /\* cardX object for sysfs \*/ \#endif \#ifdef CONFIG\_PM unsigned int power\_state; /\* power state \*/ struct mutex power\_lock; /\* power lock \*/ wait\_queue\_head\_t power\_sleep; \#endif \#if defined(CONFIG\_SND\_MIXER\_OSS) || defined(CONFIG\_SND\_MIXER\_OSS\_MODULE) 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.1. 第一步,创建snd\_card的一个实例 ### ![save_snippets.png][] struct snd\_card \*card; int err; .... err = snd\_card\_create(index, id, THIS\_MODULE, 0, &card); * index 一个整数值,该声卡的编号 * id 字符串,声卡的标识符 * 第四个参数 该参数决定在创建snd\_card实例时,需要同时额外分配的私有数据的大小,该数据的指针最终会赋值给snd\_card的private\_data数据成员 * card 返回所创建的snd\_card实例的指针 ### 2.1.2. 第二步,创建声卡的芯片专用数据 ### 声卡的专用数据主要用于存放该声卡的一些资源信息,例如中断资源、io资源、dma资源等。可以有两种创建方法: * 通过上一步中snd\_card\_create()中的第四个参数,让snd\_card\_create自己创建 ![save_snippets.png][] // struct mychip 用于保存专用数据 err = snd\_card\_create(index, id, THIS\_MODULE, sizeof(struct mychip), &card); // 从private\_data中取出 struct mychip \*chip = card->private\_data; * 自己创建: ![save_snippets.png][] struct mychip \{ struct snd\_card \*card; .... \}; struct snd\_card \*card; struct mychip \*chip; chip = kzalloc(sizeof(\*chip), GFP\_KERNEL); ...... err = snd\_card\_create(index\[dev\], id\[dev\], THIS\_MODULE, 0, &card); // 专用数据记录snd\_card实例 chip->card = card; ..... 然后,把芯片的专有数据注册为声卡的一个低阶设备: ![save_snippets.png][] static int snd\_mychip\_dev\_free(struct snd\_device \*device) \{ return snd\_mychip\_free(device->device\_data); \} static struct snd\_device\_ops ops = \{ .dev\_free = snd\_mychip\_dev\_free, \}; .... snd\_device\_new(card, SNDRV\_DEV\_LOWLEVEL, chip, &ops); 注册为低阶设备主要是为了当声卡被注销时,芯片专用数据所占用的内存可以被自动地释放。 ### 2.1.3. 第三步,设置Driver的ID和名字 ### ![save_snippets.png][] strcpy(card->driver, "My Chip"); strcpy(card->shortname, "My Own Chip 123"); sprintf(card->longname, "%s at 0x%lx irq %i", card->shortname, chip->ioport, chip->irq); snd\_card的driver字段保存着芯片的ID字符串,user空间的alsa-lib会使用到该字符串,所以必须要保证该ID的唯一性。shortname字段更多地用于打印信息,longname字段则会出现在/proc/asound/cards中。 ### 2.1.4. 第四步,创建声卡的功能部件(逻辑设备),例如PCM,Mixer,MIDI等 ### 这时候可以创建声卡的各种功能部件了,还记得开头的snd\_card结构体的devices字段吗?每一种部件的创建最终会调用snd\_device\_new()来生成一个snd\_device实例,并把该实例链接到snd\_card的devices链表中。 通常,alsa-driver的已经提供了一些常用的部件的创建函数,而不必直接调用snd\_device\_new(),比如: PCM ---- snd\_pcm\_new() RAWMIDI -- snd\_rawmidi\_new() CONTROL -- snd\_ctl\_create() TIMER -- snd\_timer\_new() INFO -- snd\_card\_proc\_new() JACK -- snd\_jack\_new() ### 2.1.5. 第五步,注册声卡 ### ![save_snippets.png][] err = snd\_card\_register(card); if (err < 0) \{ snd\_card\_free(card); return err; \} ### 2.2. 一个实际的例子 ### 我把/sound/arm/pxa2xx-ac97.c的部分代码贴上来: ![save_snippets.png][] static int \_\_devinit 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; \} (1) ret = snd\_card\_create(SNDRV\_DEFAULT\_IDX1, SNDRV\_DEFAULT\_STR1, THIS\_MODULE, 0, &card); if (ret < 0) goto err; card->dev = &dev->dev; (3) strncpy(card->driver, dev->dev.driver->name, sizeof(card->driver)); (4) ret = pxa2xx\_pcm\_new(card, &pxa2xx\_ac97\_pcm\_client, &pxa2xx\_ac97\_pcm); if (ret) goto err; (2) ret = pxa2xx\_ac97\_hw\_probe(dev); if (ret) goto err; (4) 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; (3) 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\]); snd\_card\_set\_dev(card, &dev->dev); (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; \} static int \_\_devexit pxa2xx\_ac97\_remove(struct platform\_device \*dev) \{ struct snd\_card \*card = platform\_get\_drvdata(dev); if (card) \{ snd\_card\_free(card); platform\_set\_drvdata(dev, NULL); pxa2xx\_ac97\_hw\_remove(dev); \} return 0; \} static struct platform\_driver pxa2xx\_ac97\_driver = \{ .probe = pxa2xx\_ac97\_probe, .remove = \_\_devexit\_p(pxa2xx\_ac97\_remove), .driver = \{ .name = "pxa2xx-ac97", .owner = THIS\_MODULE, \#ifdef CONFIG\_PM .pm = &pxa2xx\_ac97\_pm\_ops, \#endif \}, \}; static int \_\_init pxa2xx\_ac97\_init(void) \{ return platform\_driver\_register(&pxa2xx\_ac97\_driver); \} static void \_\_exit pxa2xx\_ac97\_exit(void) \{ platform\_driver\_unregister(&pxa2xx\_ac97\_driver); \} module\_init(pxa2xx\_ac97\_init); module\_exit(pxa2xx\_ac97\_exit); MODULE\_AUTHOR("Nicolas Pitre"); MODULE\_DESCRIPTION("AC97 driver for the Intel PXA2xx chip"); 驱动程序通常由probe回调函数开始,对一下2.1中的步骤,是否有相似之处? 经过以上的创建步骤之后,声卡的逻辑结构如下图所示: ![0_13014670816zCV.gif][] 图 2.2.1 声卡的软件逻辑结构 下面的章节里我们分别讨论一下snd\_card\_create()和snd\_card\_register()这两个函数。 ## 3. snd\_card\_create() ## snd\_card\_create()在/sound/core/init.c中定义。 ![save_snippets.png][] /\*\* \* snd\_card\_create - create and initialize a soundcard structure \* @idx: card index (address) \[0 ... (SNDRV\_CARDS-1)\] \* @xid: card identification (ASCII string) \* @module: top level module for locking \* @extra\_size: allocate this extra size after the main soundcard structure \* @card\_ret: the pointer to store the created card instance \* \* Creates and initializes a soundcard structure. \* \* The function allocates snd\_card instance via kzalloc with the given \* space for the driver to use freely. The allocated struct is stored \* in the given card\_ret pointer. \* \* Returns zero if successful or a negative error code. \*/ int snd\_card\_create(int idx, const char \*xid, struct module \*module, int extra\_size, struct snd\_card \*\*card\_ret) 首先,根据extra\_size参数的大小分配内存,该内存区可以作为芯片的专有数据使用(见前面的介绍): ![save_snippets.png][] card = kzalloc(sizeof(\*card) + extra\_size, GFP\_KERNEL); if (!card) return -ENOMEM; 拷贝声卡的ID字符串: ![save_snippets.png][] if (xid) strlcpy(card->id, xid, sizeof(card->id)); 如果传入的声卡编号为-1,自动分配一个索引编号: ![save_snippets.png][] if (idx < 0) \{ for (idx2 = 0; idx2 < SNDRV\_CARDS; idx2++) /\* idx == -1 == 0xffff means: take any free slot \*/ if (~snd\_cards\_lock & idx & 1<<idx2) \{ if (module\_slot\_match(module, idx2)) \{ idx = idx2; break; \} \} \} if (idx < 0) \{ for (idx2 = 0; idx2 < SNDRV\_CARDS; idx2++) /\* idx == -1 == 0xffff means: take any free slot \*/ if (~snd\_cards\_lock & idx & 1<<idx2) \{ if (!slots\[idx2\] || !\*slots\[idx2\]) \{ idx = idx2; break; \} \} \} 初始化snd\_card结构中必要的字段: ![save_snippets.png][] 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); init\_waitqueue\_head(&card->shutdown\_sleep); \#ifdef CONFIG\_PM mutex\_init(&card->power\_lock); init\_waitqueue\_head(&card->power\_sleep); \#endif 建立逻辑设备:Control ![save_snippets.png][] /\* the control interface cannot be accessed from the user space until \*/ /\* snd\_cards\_bitmask and snd\_cards are set with snd\_card\_register \*/ err = snd\_ctl\_create(card); 建立proc文件中的info节点:通常就是/proc/asound/card0 ![save_snippets_01.png][] err = snd\_info\_card\_create(card); 把第一步分配的内存指针放入private\_data字段中: ![save_snippets_01.png][] if (extra\_size > 0) card->private\_data = (char \*)card + sizeof(struct snd\_card); ## 4. snd\_card\_register() ## snd\_card\_create()在/sound/core/init.c中定义。 ![save_snippets.png][] /\*\* \* snd\_card\_register - register the soundcard \* @card: soundcard structure \* \* This function registers all the devices assigned to the soundcard. \* Until calling this, the ALSA control interface is blocked from the \* external accesses. Thus, you should call this function at the end \* of the initialization of the card. \* \* Returns zero otherwise a negative error code if the registrain failed. \*/ int snd\_card\_register(struct snd\_card \*card) 首先,创建sysfs下的设备: ![save_snippets.png][] if (!card->card\_dev) \{ card->card\_dev = device\_create(sound\_class, card->dev, MKDEV(0, 0), card, "card%i", card->number); if (IS\_ERR(card->card\_dev)) card->card\_dev = NULL; \} 其中,sound\_class是在/sound/sound\_core.c中创建的: ![save_snippets.png][] static char \*sound\_devnode(struct device \*dev, mode\_t \*mode) \{ if (MAJOR(dev->devt) == SOUND\_MAJOR) return NULL; return kasprintf(GFP\_KERNEL, "snd/%s", dev\_name(dev)); \} static int \_\_init init\_soundcore(void) \{ int rc; rc = init\_oss\_soundcore(); if (rc) return rc; sound\_class = class\_create(THIS\_MODULE, "sound"); if (IS\_ERR(sound\_class)) \{ cleanup\_oss\_soundcore(); return PTR\_ERR(sound\_class); \} sound\_class->devnode = sound\_devnode; return 0; \} 由此可见,声卡的class将会出现在文件系统的/sys/class/sound/下面,并且,sound\_devnode()也决定了相应的设备节点也将会出现在/dev/snd/下面。 接下来的步骤,通过snd\_device\_register\_all()注册所有挂在该声卡下的逻辑设备,snd\_device\_register\_all()实际上是通过snd\_card的devices链表,遍历所有的snd\_device,并且调用snd\_device的ops->dev\_register()来实现各自设备的注册的。 ![save_snippets.png][] if ((err = snd\_device\_register\_all(card)) < 0) return err; 最后就是建立一些相应的proc和sysfs下的文件或属性节点,代码就不贴了。 至此,整个声卡完成了建立过程。 [http_blog.csdn.net_droidphone_article_details_6289712]: http://blog.csdn.net/droidphone/article/details/6289712 [save_snippets.png]: /images/20220617/d6ca3292d6544f528269532cb336acde.png [0_13014670816zCV.gif]: /images/20220617/89ee7238c6a34f328cd343b0a542501f.png [save_snippets_01.png]: /images/20220617/2d9618838ef8459d92e0020160556e8d.png
还没有评论,来说两句吧...