alsa是音频最重要的框架,没有之一。接下来一个月时间在总结工作知识的主线上。单开个音频支线讲解alsa。说实话,alsa这块我也不是很精通。只在过去的项目中增加 一路substream实现低延时。我打算从下面四个方面去学习alsa。
1.alsa官网。网址如下,大家也可以学习后在评论里 交流,互相进步。如果想偷懒也可以直接看我的总结。
https://www.alsa-project.org/wiki/Main_Page
2.阅读在工作项目中alsa的源码。
3.整理网上各类免费的alsa资源。
4.向开发驱动的同事沟通请教。
写PCI驱动的基本流程
定义PCI ID表 (其它类型的驱动应该也有类似的结构体,比如I2S。后续探究一下)
定义probe回调函数
定义remove回调函数
创建struct pci_driver类型的结构体指向上面的三个元素
定义init 函数,在函数中调用pci_register_driver() 来注册上面定义的pci_driver结构体
定义exit函数来执行pci_unregister_driver()
简单总结一下上面的步骤,其实就两部分:1.定义钩子函数。2.将新定义的模块(结构体+函数)加入/移出框架
下面是个示例代码,重点关注snd_mychip_probe函数和snd_mychip_remove。这两个函数怎么暴露到alsa框架调用呢?
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/initval.h>
/* module parameters (see "Module Parameters") */
/* SNDRV_CARDS: maximum number of cards supported by this module */
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
/* definition of the chip-specific record */
struct mychip {
struct snd_card *card;
/* the rest of the implementation will be in section
* "PCI Resource Management"
*/
};
/* chip-specific destructor
* (see "PCI Resource Management")
*/
static int snd_mychip_free(struct mychip *chip)
{
.... /* will be implemented later... */
}
/* component-destructor
* (see "Management of Cards and Components")
*/
static int snd_mychip_dev_free(struct snd_device *device)
{
return snd_mychip_free(device->device_data);
}
/* chip-specific constructor
* (see "Management of Cards and Components")
*/
static int snd_mychip_create(struct snd_card *card,
struct pci_dev *pci,
struct mychip **rchip)
{
struct mychip *chip;
int err;
static const struct snd_device_ops ops = {
.dev_free = snd_mychip_dev_free,
};
*rchip = NULL;
/* check PCI availability here
* (see "PCI Resource Management")
*/
....
/* allocate a chip-specific data with zero filled */
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (chip == NULL)
return -ENOMEM;
chip->card = card;
/* rest of initialization here; will be implemented
* later, see "PCI Resource Management"
*/
....
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
if (err < 0) {
snd_mychip_free(chip);
return err;
}
*rchip = chip;
return 0;
}
// 对外接口
/* constructor -- see "Driver Constructor" sub-section */
static int snd_mychip_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct mychip *chip;
int err;
/* (1) */
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
/* (2) */
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
/* (3) */
err = snd_mychip_create(card, pci, &chip);
if (err < 0)
goto error;
/* (4) */
strcpy(card->driver, "My Chip");
strcpy(card->shortname, "My Own Chip 123");
sprintf(card->longname, "%s at 0x%lx irq %i",
card->shortname, chip->port, chip->irq);
/* (5) */
.... /* implemented later */
/* (6) */
err = snd_card_register(card);
if (err < 0)
goto error;
/* (7) */
pci_set_drvdata(pci, card);
dev++;
return 0;
error:
snd_card_free(card);
return err;
}
/* destructor -- see the "Destructor" sub-section */
static void snd_mychip_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
}
驱动的构造函数
PCI驱动的真正构造函数时probe回调函数。probe回调函数和所有被probe调用的函数都不可以用__init作为前缀名。因为所有的PCI设备都可能是个热插拔设备。
在probe回调函数中,遵循下面的基本流程。
1)检查和增加device索引。(一个card可以支持多个devcie)
static int dev;
....
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
2)创建声卡实例。这个函数是alsa框架提供的。创建alsa的声卡类型。
struct snd_card *card;
int err;
....
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
3)创建核心组件(与实际业务关联的结构体),在实例中,分配了PCI资源。
struct mychip *chip;
....
err = snd_mychip_create(card, pci, &chip);
if (err < 0)
goto error;
发生异常时,probe函数需要处理异常。在这个示例中,包含一个错误处理逻辑在函数末尾。
error:
snd_card_free(card);
return err;
4)设置驱动的ID和名字。(这里的结构体是通过alsa框架函数获取的。实现了类似将驱动注册到alsa框架的效果)
strcpy(card->driver, "My Chip");
strcpy(card->shortname, "My Own Chip 123");
sprintf(card->longname, "%s at 0x%lx irq %i",
card->shortname, chip->port, chip->irq);
驱动的字段包含芯片的ID字段。alsa-lib的配置器会使用这个字段。这个字段尽量做到简单和唯一。即使是同一个驱动也可以使用不同的驱动ID。通过不同的ID来区别不同功能的芯片。shortname 字段是个更加冗余的名字。而longname 字段包含了显示在 /proc/asound/cards的信息。
5)创建其它的组件,包括mixer,MIDI,以及其他的接口。如果需要有proc信息的功能,也可以定义在这里。
6)将PCI驱动注册到alsa框架
err = snd_card_register(card);
if (err < 0)
goto error;
7)把驱动特定数据设置到card结构体
pci_set_drvdata(pci, card);
dev++;
return 0;
在上面的代码中,card的数据被保存下来了。这个指针会在回调函数中使用。
析构函数
remove回调函数就是析构函数,它会释放card实例。ALSA中间层会自动释放所有绑定的组件。
比较常见的方法就是调用snd_card_free()函数。
static void snd_mychip_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
}
上述代码的前提是card指针被设置到PCI的驱动数据中。
从声卡card角度看,初始化共进行了这些操作。
snd_card_new 分配声卡
snd_card_register 注册声卡
pci_set_drvdata 绑定声卡
这些都是alsa-core的接口,也就是我们的驱动通过这些接口与alsa框架建立链接。
snd_device_new()来生成一个snd_device实例,并把该实例链接到snd_card的devices链表中