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

21 篇文章 237 订阅

目录

一、前言

二、概述

三、整体流程

四、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

  • 11
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
Linux内核中包含了大量的PCI驱动程序,这些驱动程序的功能各不相同,但都是用于支持PCI设备的工作。下面我们来详细分析一些典型的PCI驱动程序。 1. e1000e驱动程序 e1000e驱动程序是用于Intel网卡的驱动程序,它支持Intel 82563/6/7, 82571/2/3/4/7/8/9, or 82583 NICs。e1000e驱动程序采用DMA总线传输机制,能够提供高性能的网络传输。 2. ahci驱动程序 ahci驱动程序是用于SATA硬盘控制器的驱动程序,它支持AHCI(Advanced Host Controller Interface)标准,能够提供高速稳定的数据传输,支持NCQ(Native Command Queuing)和Hot Plug等特性。 3. igb驱动程序 igb驱动程序是用于Intel Gigabit以太网卡的驱动程序,它支持Intel 82575/6, 82580, I350, I210/1, and I211 NICs。igb驱动程序采用DMA总线传输机制,能够提供高性能的网络传输。 4. nvme驱动程序 nvme驱动程序是用于NVMe(NVM Express)SSD固态硬盘的驱动程序,它支持PCIe接口,能够提供高速稳定的数据传输,支持多队列和命令集等特性。 5. mlx4驱动程序 mlx4驱动程序是用于Mellanox Connect-IB/Connect-X Infiniband和以太网适配器的驱动程序,它采用DMA总线传输机制,支持InfiniBand和Ethernet网络通信协议,能够提供高性能的网络传输。 总之,Linux内核中的PCI驱动程序功能丰富,能够支持各种类型的PCI设备,为系统的性能和稳定性提供了重要的保障。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸鱼弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值