第二章 2. PCI驱动的基本流程
大纲
PCI声卡驱动的最简流程如下:
定义PCI ID表(请看PCI条目部分,此部分在第四章:《PCI资源管理》中)。
创建probe()回调。
创建remove()回调。
创建一个包含上面三个指针的pci_driver结构体。
创建一个init()函数,只调用pci_register_driver()来注册pci_driver表来定义上面几项。
创建一个exit()函数来调用pci_unregister_driver()函数。
完整的代码例子
代码实例如下。目前有一部分是空着的,在后续的章节中会添加上。snd_mychip_probe()函数中的注释在接下来的章节中会详细解释。
例子:2.1. PCI驱动的基本流 - 示例
/* constructor -- see "Constructor" sub-section */
static int __devinit snd_mychip_probe(struct pci_dev * pci, const struct pci_device_is * pci_id)
{
static int dev;
static snd_card * card;
struct muychip * chip;
int err;
/* (1) */
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
/* (2) */
err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
if (err < 0)
return err;
/* (3) */
err = snd_mychip_create(card, pci, &chip);
if (err < 0) {
snd_card_free(card);
return err;
}
/* (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->ioport, chip->irq);
/* (5) */
.... /* implemented later */
/* (6) */
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
return err;
}
/* (7) */
pci_set_drvdata(pci, card);
dev++;
return 0;
}
/* destructor -- see the "Destructor" sub-section */
static void __devexit snd_mychip_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
pci_set_drvdata(pci, NULL);
}
构造函数
真正的PCI驱动构造函数是probe回调。probe回调函数和其他在probe回调函数中被调用的构造函数组件应该使用__devinit的前缀。你不能使用__init前缀,因为任何的PCI设备都可能是热插拔的设备。
在probe回调函数中,下面这些组合经常被用到。
1)检查并且增加设备索引
static int dev;
....
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
enabel[dev]是模块选项。
每次probe回调函数被调用时,都会检查设备的可用性。如果不可用,只增加设备索引并且返回。dev在稍后会自加(见step7)。
2)创建一个卡实例
struct snd_card * card;
int err;
....
err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
细节方面见章节:《卡和组件的管理》
3)创建一个主组件
在这个部分中,PCI资源会被分配。
struct mychip * chip;
....
err = snd_mychip_create(card, pci, &chip);
if (err < 0) {
snd_card_free(card);
return err;
}
细节方面会在章节:《PCI资源管理》中会有描写。
4)设置驱动ID和名称字符串
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);
代码中的"card->driver"部分保存着最简的芯片ID字符串。这个是供alsa-lib的配置使用的,故要保持它简单但唯一。即使相同的驱动也可以有不同的驱动ID,以区别每个不同类型芯片的功能。
"card->shortname"部分用来显示更详细的名称。"card->longname"部分保存着/proc/asound/cards节点中显示的信息。
5)创建其他组件,例如mixer,MIDI等
这里你会定义基本的组件,像PCM,mixer(e.g.AC97),MIDI(e.g.MPU-401),和其他的接口。同样,如果你想要一个proc文件(见第12章:Proc接口),也在这里定义。
6)注册一个卡实例
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
return err;
}
这个也会在:《卡和组件管理》章节中解释。
7)设置PCI驱动数据并且返回零
pci_set_drvdata(pci, card);
dev++;
return 0;
上面的代码中,卡的记录被保存了。这个指针也会在remove回调函数和电源管理回调函数中使用。
析构函数
析构函数,remove回调,单纯移除卡的实例。然后ALSA中间层会自动释放所有的附加组件;
它会定义如下:
static void __devexit snd_mychip_remove(struct pci_dev *pci)
{
snd_card_free(pci_get_drvdata(pci));
pci_set_drvdata(pci, NULL);
}
上面的代码假定了卡的指针被设置到了PCI驱动数据中。
头文件
对于上面例子,至少需要如下的头文件。
#include
#include
#include
#include
#include
最后的那项只有在模块选项被定义在了源文件中时是需要的。如果代码被分散保存到几个文件夹下,那么没有模块选项的文件夹就用不到他们。
除了这些头文件,你会需要<linux/interrupt.h>用来中断处理,和<asm/io.h>用于I/O访问。如果你使用mdelay()或udelay()函数,你也会需要导入<linux/delay.h>。
ALSA接口,像PCM和控制APIs是在其他的<sound/xxx.h>头文件中定义的。它们必须要被放在<sound/core.h>后面导入。