目录
一、前言
二、概述
三、整体流程
四、PCI相关入口函数
4.1 pcibus_class_init
4.2 pci_driver_init
4.3 pci_arch_init
4.4 pci_slot_init
4.5 acpi_init
4.6 pci_subsys_init
在做项目时,遇到了系统中有个PCIe设备桥的MMIO空间异常问题,通过BIOS启动日志看没什么异常,OS启动日志打印BAR14 BAR15异常。这究竟是BIOS阶段出现了问题?还是OS阶段出现了问题?在搞热插拔时,也经常需要关注OS中PCI设备遍历加载过程,因此,借机会学习下PCI的代码。阅读源码无从下口,在网上先搜几篇文章看下,了解个大概,在梳理下代码逻辑。
PCI总线的驱动代码结构稍稍同于SPI、IIC、platform这种常规总线,首先一个不同点就在于PCI总线的代码框架具有多个入口函数,像SPI这种总线往往只会使用一个或两个initcall,但是PCI总线的驱动框架却使用了许多不同等级的initcall函数入口,下面我们就按照顺序依次来进行梳理。
注:本文档分析的代码是以x86为硬件平台并使用了ACPI机制,所以与powerPC等其他平所实现的PCI总线驱动略有不同本文档分析的内核版本为 Linux-4.4.185
系统对于PCI的初始化大体分为4个阶段:
- BIOS对于PCI设备的初次枚举
- PCI设备系统枚举的前期准备(文件系统相关目录的建立,访问方法的初始化)
- PCI设备的系统阶段的枚举(pci_dev的建立)
- PCI设备驱动的初始化(driver的建立)
下面我们对这4个阶段的执行函数做一个map图谱:
其中较复杂的过程是配置空间方法的建立以及设备的枚举这两个阶段,这两个阶段在另外两篇文章中描述,在此不列出详细执行过程,至此PCI相关的数据结构都已经建立完成,相关初始化完成,但这些初始化的设备是指本身就连接的PCI设备,即系统上电前就挂接在PCI总线上的设备,除此之外还有一部分设备属于即插即用的设备,他们是在系统上电初始化完毕后才加入到总线中来的,
请注意
- 对于x86架构的CPU来说pci_dev的建立是系统扫描出来生成的(如果由于BIOS扫描失败,系统找不到设备,一般需要自己建立);
- pci_dre是我们需要完成的驱动节点,实现probe等方法。
当我们编译Linux内核源码后我们可以得到一个System.map文件,该文件梳理了驱动调用initcall宏的顺序,通过该文件我们可以找到第一个与PCI总线有关的入口函数。
位置:/drivers/pci/probe.c
static struct class pcibus_class = {
.name = "pci_bus",
.dev_release = &release_pcibus_dev, //资源
.dev_groups = pcibus_groups,
};
static int __init pcibus_class_init(void)
{
return class_register(&pcibus_class);
}
postcore_initcall(pcibus_class_init);
函数分析:该函数的执行也很简单,就是调用了一个class_register函数注册了一个pcibus_class结构体,并且执行的等级为2(共7个等级),该函数的作用就是注册一个PCI_BUS类,并且在/sys/class这个目录下生成一个pci_bus目录如下图所示。
位置:/drivers/pci/pci-driver.c
struct bus_type pci_bus_type = {
.name = "pci",
.match = pci_bus_match, //匹配函数
.uevent = pci_uevent, //用户事件
.probe = pci_device_probe, //匹配后的执行函数
.remove = pci_device_remove, //退出函数
.shutdown = pci_device_shutdown,
.dev_groups = pci_dev_groups,
.bus_groups = pci_bus_groups,
.drv_groups = pci_drv_groups,
.pm = PCI_PM_OPS_PTR, //电源管理相关
};
EXPORT_SYMBOL(pci_bus_type);
static int __init pci_driver_init(void)
{
return bus_register(&pci_bus_type);
}
postcore_initcall(pci_driver_init);
函数分析:这个是第二个与PCI总线有关的启动函数,该函数与pcibus_class_init类似,也只完成了一个结构体的注册,启动等级为2,作用是在/sys/bus下注册一个pci目录并且完成该目录下子目录的创建(device目录和driver目录)
位置:/arch/x86/pci/init.c
static __init int pci_arch_init(void)
{
#ifdef CONFIG_PCI_DIRECT //CONFIG_PCI_DIRECT此选项打开
int type = 0;
type = pci_direct_probe(); //config1方法配置
#endif
if (!(pci_probe & PCI_PROBE_NOEARLY))
pci_mmcfg_early_init(); //MMconfig方法配置
if (x86_init.pci.arch_init && !x86_init.pci.arch_init())
return 0;
#ifdef CONFIG_PCI_BIOS //未执行
pci_pcbios_init();
#endif
#ifdef CONFIG_PCI_DIRECT //
pci_direct_init(type);
#endif
......
return 0;
}
arch_initcall(pci_arch_init);
函数分析:该函数内部通过条件编译来确定内部函数的执行,我们可以通过.config文件得到源码定义的所有宏定义,经查得出CONFIG_PCI_DIRECT定义而CONFIG_PCI_BIOS未定义,该函数的功能是设置整个PCI配置空间的读写方法。
位置:/drivers/pci/slot.c
static int pci_slot_init(void)
{
struct kset *pci_bus_kset;
pci_bus_kset = bus_get_kset(&pci_bus_type); //先前注册了一个pci kobject,在这里获取它。
pci_slots_kset = kset_create_and_add("slots", NULL,
&pci_bus_kset->kobj); //创建一个slots目录
if (!pci_slots_kset) {
printk(KERN_ERR "PCI: Slot initialization failure\n");
return -ENOMEM;
}
return 0;
}
函数分析:此函数的作用是在/sys/bus/pci目录下创建一个子目录slot,表示插槽,该目录存放的主要是当前硬件板上的PCI/PCIE的插槽节点,当该板上无引出的PCI/PCIE插槽时,那么该文件夹为空。在本函数中,主要的核心调用函数为kset_create_and_add,这个函数在pci bus下创建了一个名字为slots的文件夹。
位置:/drivers/acpi/bus.c
static int __init acpi_init(void)
{
int result;
。。。。。。
pci_mmcfg_late_init(); //pci_mmcfg的第二次配置
acpi_scan_init(); //acpi设备扫描(包括PCI设备)
acpi_ec_init();
acpi_debugfs_init();
acpi_sleep_proc_init();
acpi_wakeup_device_init();
return 0;
}
函数分析:在以前的X86架构的系统中,系统会调用pcibios_scan_root的方式来枚举PCI总线,所以在查阅资料的时候经常会遇见该函数,但现在ACPI机制在x86架构系统中的普及,所以,对于该枚举部分代码的实现也有了改变,该函数是ACPI的初始化函数,启动等级为4,抛开pci_mmcfg_late_init()不看(该函数为pci_mmcfg的第二次配置,由于之前我们已完成了该方法的相关配置,所以此函数并不执行什么操作就返回了,除非你前面的配置有问题),ACPI初始化工作第一个重要的工作就对设备的探测,而我们所最为关心的PCI设备的枚举操作,也是在此完成的。
位置:/arch/x86/pci/legacy.c
static int __init pci_subsys_init(void)
{
if (x86_init.pci.init())
pci_legacy_init(); //不执行
pcibios_fixup_peer_bridges();
x86_init.pci.init_irq();
pcibios_init(); //调用pcibios_resource_survey
return 0;
}
void __init pcibios_resource_survey(void)
{
struct pci_bus *bus;
DBG("PCI: Allocating resources\n");
list_for_each_entry(bus, &pci_root_buses, node)
pcibios_allocate_bus_resources(bus); //为PCI桥分配地址空间
//为PCI设备分配地址空间(BIOS中以启用的)
list_for_each_entry(bus, &pci_root_buses, node)
pcibios_allocate_resources(bus, 0);
//为PCI设备分配地址空间(其他PCI设备)
list_for_each_entry(bus, &pci_root_buses, node)
pcibios_allocate_resources(bus, 1);
e820_reserve_resources_late();
ioapic_insert_resources();
}
函数分析:本函数主要是调用pcibios_init中的pcibios_resource_survey函数去检查这些pci_bus结构中resource的合法性。并从上级总线的资源地址空间中分配出一些空间为我们当前的PCI设备。
本文转载自 PCI总线驱动代码梳理(一)--整体框架_SangDeYG的博客-CSDN博客_pci_arch_init