4.2 snd_card 的构建
上一小节,我们看了硬件的简图,现在要看它们如何被组织成软件视图。snd_card的框架简图基本就是一个软件的视图。下面我们去了解它们的组织过程,以及各个模块的作用。
4.2.1 azx_probe
这部分涉及到代码,都在 sound/pci/hda和sound/hda下。之所以有两个目录,是因为hda其实也可以连接到其它总线上。在pc上一般都是用pci总线来连接的,我们也以这部分代码来展开。我们首先看hda_intel.c中的代码,虽说命名是intel,其实其它厂商的实现也在这儿,源于hda是统一标准,大部分的接口其实是通用的。但是文不尽意,同样的文字不是所有的人理解都一致,或者源于一些厂商的特殊处理,所以你在目录下可以看到很多对一些芯片做特殊处理的代码。
另外看到很多结构和函数是已azx开头的,这个是因为hda最开始的设计代号是Azalia,后来改成了hda。
我们从hda_intel.c中的最下方开始:
static struct pci_driver azx_driver = {
.name = KBUILD_MODNAME,
.id_table = azx_ids,
.probe = azx_probe,
.remove = azx_remove,
.shutdown = azx_shutdown,
.driver = {
.pm = AZX_PM_OPS,
},
};
module_pci_driver(azx_driver);
挂载在pci总线的controller属于一个pci设备,所以这里就需要提供了一个pci_driver。原理简单的讲一下,pci总线自检的时候会列出所有连接到总线上的设备,包括controller,但自检的代码并不会关心它是干什么用的,而是为它们各自生成一个pci_dev,然后通过devcie_add加到系统,这时候会遍历pci总线上已经挂载的驱动,查找合适的驱动,然后调用它的probe函数。另外这个过程也可以反过来,注册drive的时候,也会去查询当前总线上挂载的设备。
这里简单总结下我理解的driver和device。以pci总线为例,其上可以连接众多设备,pci的drive有义务找出所有的设备,但这时候它不会做进一步的处理,而是以pci_dev的结构对这些设备做统一的处理。而这些设备功能各异,就需要写出自己的driver,这些driver也会挂载到pci的driver列表中,它们有统一的接口,以便系统一调用。这里的pci_driver就是一个统一的结构,在pci总线找到controller设备的时候,会去调用probe函数。
另外后续我们看到的codec设备也是这样的处理方式,由controller找到设备,再去寻找它们的驱动程序,看到那部分代码,这个原理应该会理解的更完整一些。
这里我们从azx_probe看起:
static int azx_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
struct snd_card *card;
struct hda_intel *hda;
struct azx *chip;
bool schedule_probe;
int err;
//针对特殊设备的处理,有些设备是controller设备,但并不挂载codec。比如MSI TRX40,貌似是通过连接usb来播放声音的。它们的驱动将以别的形式处理。
if (pci_match_id(driver_denylist, pci)) {
dev_info(&pci->dev, "Skipping the device on the denylist\n");
return -ENODEV;
}
//控制设备个数,比如做多支持8个controller。
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev]) {
dev++;
return -ENOENT;
}
// 也是针对不同设备的特殊处理
/*
* st