参考资料:
1) 《Linux内核源代码情景分析》
2) Linux内核源代码(2.6.32)。
本文只讨论比较简单的软硬件配置场景。
系统中的第一条PCI总线(即主PCI总线),挂在“宿主—PCI桥”上。
CPU通过“宿主——PCI桥”就可以访问主PCI总线了。
PC机中通常只有一个“宿主—PCI桥”。但是,通过引入其他类型的PCI桥,可以将更多的总线(可以是PCI总线,也可以是ISA总线)连接到主PCI总线上来。这样一来,系统中就可以有多条总线存在了。
下层PCI总线也可以进一步通过PCI桥,将更下一层的PCI总线连接进来。
在上层总线看来,PCI桥也是连接到本总线上的一个设备。
主PCI总线的编号是0,其他的pci总线编号则从1开始依次递增。每一条PCI总线,可以挂接32个PCI总线接口芯片。每个PCI设备(注意,这里的设备概念后面需要进一步解释)都是通过一个PCI总线接口芯片连接到PCI主线上。
这样的话,每条PCI总线,最大支持32个设备。设备编号0~31
设备可以固化在主板上,也可以做成一个PCI接口卡,通过一个PCI插槽连接到系统中。
这样的话,每个PCI插槽对应一个PCI总线接口芯片。
上面说了,这里提到的设备的概念需要进一步解释。这里就来说说吧。
PCI总线上的一个设备,可以包含1~8个功能,编号是0~7。每个功能称为一个逻辑设备。
有些设备可能只包含一个逻辑设备。例如,一块网卡,上面可能就一个逻辑设备。
在Linux内核角度来看,这些逻辑设备才是最终的设备。
这样的话,每条PCI总线,最大支持32*8个逻辑设备,即256个逻辑设备。
通过lspci命令,可以查看系统中的所有pci设备(逻辑设备)。
下面是此命令的输出结果的一行,显示了一块网卡的信息:1号总线,1号设备,0号功能。
01:01.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+ (rev 10)
下面看看Linux内核中pci相关的部分代码。
内核启用过程中,会依次调用pci_driver_init与pci_subsys_init函数。
前者向内核注册pci总线类型,后者完成设备的探测与添加。
前者内容很短,但要完全理解,水可不浅。
本文主要看看后者。
我们这里只看最简单的情况,因此忽略掉那几个条件编译项。这样的话,我们从pci_legacy_init函数看起。
- int __init pci_subsys_init(void)
- {
- #ifdef CONFIG_X86_NUMAQ
- pci_numaq_init();
- #endif
- #ifdef CONFIG_ACPI
- pci_acpi_init();
- #endif
- #ifdef CONFIG_X86_VISWS
- pci_visws_init();
- #endif
- pci_legacy_init();
- pcibios_fixup_peer_bridges();
- pcibios_irq_init();
- pcibios_init();
- return 0;
- }
- subsys_initcall(pci_subsys_init);
下面是pci_legacy_init的代码,实际关键的代码就是
调用pcibios_scan_root(0)扫描0号pci总线,
调用pci_bus_add_devices(pci_root_bus),将0号pci总线上扫描到的设备添加到系统中。
- static int __init pci_legacy_init(void)
- {
- if (!raw_pci_ops) {
- printk("PCI: System does not support PCI\n");
- return 0;
- }
- if (pcibios_scanned++)
- return 0;
- printk("PCI: Probing PCI hardware\n");
- pci_root_bus = pcibios_scan_root(0);
- if (pci_root_bus)
- pci_bus_add_devices(pci_root_bus);
- return 0;
- }
再看pcibios_scan_root函数。
while结构应该是在遍历已经探测到的总线。首次执行里,自然什么都遍历不到。
因此,最终进入 pci_scan_bus_parented,进而进入pci_scan_child_bus去扫描总线上的设备(这里只关注核心部分,因此跳得有点快^_^)。
- struct pci_bus * __devinit pcibios_scan_root(int busnum)
- {
- struct pci_bus *bus = NULL;
- struct pci_sysdata *sd;
- while ((bus = pci_find_next_bus(bus)) != NULL) {
- if (bus->number == busnum) {
- /* Already scanned */
- return bus;
- }
- }
- /* Allocate per-root-bus (not per bus) arch-specific data.
- * TODO: leak; this memory is never freed.
- * It's arguable whether it's worth the trouble to care.
- */
- sd = kzalloc(sizeof(*sd), GFP_KERNEL);
- if (!sd) {
- printk(KERN_ERR "PCI: OOM, not probing PCI bus %02x\n", busnum);
- return NULL;
- }
- sd->node = get_mp_bus_to_node(busnum);
- printk(KERN_DEBUG "PCI: Probing PCI hardware (bus %02x)\n", busnum);
- bus = pci_scan_bus_parented(NULL, busnum, &pci_root_ops, sd);
- if (!bus)
- kfree(sd);
- return bus;
- }
pci_scan_child_bus的核心代码如下:
- for (devfn = 0; devfn < 0x100; devfn += 8)
- pci_scan_slot(bus, devfn);
可见共扫描32个插槽,每个插槽扫描8个功能,即8个逻辑设备。
这里的devfn,是devicefunction的缩写,即“设备功能”的意思。
相当于是设备号与功能号的组合,高位是设备号,低3-bit是功能号。
再来看pci_scan_slot函数。先扫描出一个逻辑设备,如果是多功能设备,再扫描出其余的逻辑设备。
针对扫描出的每一个逻辑设备,创建一个struct pci_dev结构,挂入所属的pci总线(每条总线由一个struct pci_bus结构表示)的devices链表中。
具体实现,看pci_scan_single_device函数的实现就知道了。
struct pci_dev结构中,有一个devfn字段,其值就来自pci_scan_single_device传入的参数devfn + fn。
因此,他是设备号与功能号的组合:高位是设备号,低3-bit是功能号。
- int pci_scan_slot(struct pci_bus *bus, int devfn)
- {
- int fn, nr = 0;
- struct pci_dev *dev;
- dev = pci_scan_single_device(bus, devfn);
- if (dev && !dev->is_added)/* new device? */
- nr++;
- if (dev && dev->multifunction) {
- for (fn = 1; fn < 8; fn++) {
- dev = pci_scan_single_device(bus, devfn + fn);
- if (dev) {
- if (!dev->is_added)
- nr++;
- dev->multifunction = 1;
- }
- }
- }
- /* only one slot has pcie device */
- if (bus->self && nr)
- pcie_aspm_init_link_state(bus->self);
- return nr;
- }
好了,逻辑设备扫描完了,每一个逻辑设备也都挂入了所属的pci总线的devices链表中了。
再回到pci_legacy_init函数来看,扫描完逻辑设备,调用pci_bus_add_devices(pci_root_bus)将逻辑设备添加到系统中。
pci_bus_add_devices的核心是调用pci_bus_add_device函数。
接下来的核心调用链是:
device_add --) bus_probe_device --) device_attach --) bus_for_each_drv --) __device_attach --) driver_probe_device --) really_probe --) dev->bus->probe(即pci_bus_type.probe,也即pci_device_probe)
pci_device_probe最终会调用相应的pci设备驱动(由struct pci_driver结构表示)的probe函数。
总之,从上层来说,device_add函数,用于向系统添加一个设备,此函数会尝试将被添加的逻辑设备与系统中的驱动程序进行一次匹配。
另外,当一个pci驱动被加载到内核中时,也会将系统中尚未匹配驱动程序的逻辑设备与此驱动进行一次匹配尝试(调用链pci_register_driver --) __pci_register_driver --) driver_register --) bus_add_driver --) driver_attach --) bus_for_each_dev --) __driver_attach --) driver_probe_device)。