Linux源码阅读——PCI总线驱动代码(一)整体框架_pci_device_probe

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

一、前言

二、概述

三、整体流程

四、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等方法。

四、PCI相关入口函数

当我们编译Linux内核源码后我们可以得到一个System.map文件该文件梳理了驱动调用initcall宏的顺序通过该文件我们可以找到第一个与PCI总线有关的入口函数。

4.1 pcibus_class_init

位置/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目录如下图所示。


4.2 pci_driver_init

位置/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目录

4.3 pci_arch_init

位置/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配置空间的读写方法。

4.4 pci_slot_init

位置/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的文件夹。

4.5 acpi_init

位置/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设备的枚举操作也是在此完成的。

4.6 pci_subsys_init

位置/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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“Linux源码阅读——PCI总线驱动代码(一)整体框架_pci_device_probe” 的相关文章