当PCIe设备插入服务器后,内核会自动进行设备的探测和识别。这个过程通常包括以下几个步骤:
-
PCIe总线枚举:当PCIe设备插入服务器时,PCIe总线会自动进行枚举,将每个设备的信息存储在PCIe配置空间中。
-
ACPI设备枚举:内核会通过ACPI(高级配置和电源管理接口)协议来识别PCIe设备。ACPI是一种标准的系统硬件抽象层,它提供了一种与设备无关的方法来控制硬件。
-
PCIe驱动加载:内核会根据设备的PCIe ID(厂商ID和设备ID)来加载相应的驱动程序。驱动程序通常是以模块的形式加载到内核中的。一旦驱动程序被加载,内核就会调用驱动程序的probe函数来初始化设备。
-
系统日志输出:内核会将设备的信息输出到系统日志中,以便管理员进行诊断和调试。
下面是内核调用栈,展示了内核如何识别PCIe设备的过程:
-
pcie_bus_add_device():PCIe总线枚举函数,用于将PCIe设备添加到总线上。
-
acpi_scan_add_device():ACPI设备枚举函数,用于根据设备的ACPI路径来识别设备。
-
pci_device_add():PCIe设备添加函数,用于将设备添加到PCIe总线上。
-
pci_scan_device():PCIe设备扫描函数,用于扫描设备的PCIe配置空间,获取设备的厂商ID和设备ID。
-
pci_match_device():PCIe设备匹配函数,用于根据设备的PCIe ID来查找相应的驱动程序。
-
pci_device_probe():PCIe设备探测函数,用于调用驱动程序的probe函数来初始化设备。
-
driver_probe_device():驱动程序探测函数,用于初始化驱动程序并调用probe函数来初始化设备。
-
device_initialize():设备初始化函数,用于初始化设备的属性和状态。
-
sysfs_create_link():创建sysfs链接函数,用于将设备添加到sysfs文件系统中。
-
dev_set_name():设置设备名称函数,用于设置设备的名称。
-
driver_sysfs_add():驱动程序添加函数,用于将驱动程序添加到sysfs文件系统中。
-
printk():系统日志输出函数,用于将设备的信息输出到系统日志中。
内核识别PCIe设备的过程是一个比较复杂的过程,需要涉及到多个函数和模块的协同工作。不同的内核版本和硬件平台可能会有一些差异,但是整个流程大致是相同的。
函数调用栈:
解释:
subsys_initcall()是Linux内核中用于注册子系统初始化函数的宏定义,pcic_init是一个函数名,表示子系统初始化函数的入口地址。具体来说,subsys_initcall()宏定义会将pcic_init函数注册到内核子系统初始化序列中,当内核启动时,该函数会被自动调用,用于初始化PCI子系统。
PCI子系统是Linux内核中的一个重要子系统,负责管理PCI和PCIe设备。在PCI子系统初始化时,需要进行一系列的初始化操作,例如创建PCI总线、扫描PCI设备、初始化PCI设备和驱动程序、注册PCI中断等等。因此,内核会在启动时自动调用PCI子系统初始化函数,以确保PCI子系统正确地初始化。
subsys_initcall()宏定义是用于将初始化函数注册到内核子系统初始化序列中的,其中subsys表示子系统名称,initcall表示初始化函数类型。在内核启动时,内核会按照子系统初始化序列的顺序依次调用各个子系统的初始化函数,以完成整个内核的初始化。
/*
* Main entry point from the PCI subsystem.
*/
static int __init pcic_init(void)
{
struct linux_pcic *pcic;
/*
* PCIC should be initialized at start of the timer.
* So, here we report the presence of PCIC and do some magic passes.
*/
if(!pcic0_up)
return 0;
pcic = &pcic0;
/*
* Switch off IOTLB translation.
*/
writeb(PCI_DVMA_CONTROL_IOTLB_DISABLE,
pcic->pcic_regs+PCI_DVMA_CONTROL);
/*
* Increase mapped size for PCI memory space (DMA access).
* Should be done in that order (size first, address second).
* Why we couldn't set up 4GB and forget about it? XXX
*/
writel(0xF0000000UL, pcic->pcic_regs+PCI_SIZE_0);
writel(0+PCI_BASE_ADDRESS_SPACE_MEMORY,
pcic->pcic_regs+PCI_BASE_ADDRESS_0);
pcic_pbm_scan_bus(pcic);
return 0;
}
这是Linux内核中的一个PCI子系统初始化函数,名为pcic_init()
。该函数是PCI子系统的入口函数,用于初始化PCI控制器。具体来说,该函数会进行以下操作:
- 检查PCI控制器是否存在,如果不存在则直接返回。
- 获取PCI控制器的结构体指针,并关闭PCI控制器的IOTLB转换。
- 增加PCI内存空间的映射大小。
- 扫描PCI总线,探测PCI设备并加载相应的驱动程序。
该函数主要是针对Sun Sparc体系结构的PCI控制器进行初始化的,其中包括关闭IOTLB转换和增加PCI内存空间的映射大小等操作。在最后,该函数会调用pcic_pbm_scan_bus()
函数来扫描PCI总线并探测PCI设备。在探测到PCI设备后,PCI子系统会自动加载相应的驱动程序,从而完成PCI设备的初始化和驱动加载操作。
总之,pcic_init()
函数是Linux内核中一个非常重要的PCI子系统初始化函数,用于初始化PCI控制器并扫描PCI总线,是PCI子系统的入口函数。
static void __init pcic_pbm_scan_bus(struct linux_pcic *pcic)
{
struct linux_pbm_info *pbm = &pcic->pbm;
pbm->pci_bus = pci_scan_bus(pbm->pci_first_busno, &pcic_ops, pbm);
if (!pbm->pci_bus)
return;
#if 0 /* deadwood transplanted from sparc64 */
pci_fill_in_pbm_cookies(pbm->pci_bus, pbm, pbm->prom_node);
pci_record_assignments(pbm, pbm->pci_bus);
pci_assign_unassigned(pbm, pbm->pci_bus);
pci_fixup_irq(pbm, pbm->pci_bus);
#endif
pci_bus_add_devices(pbm->pci_bus);
}
这是Linux内核中用于PCI总线扫描的函数,名为pcic_pbm_scan_bus()
。该函数的作用是扫描PCI总线,探测PCI设备并加载相应的驱动程序。具体来说,该函数会进行以下操作:
- 获取PCI总线信息结构体指针,并调用
pci_scan_bus()
函数扫描PCI总线,探测PCI设备并加载相应的驱动程序。 - 如果扫描失败,则直接返回。
- 调用
pci_bus_add_devices()
函数将PCI设备添加到总线上。
在PCI总线扫描过程中,PCI子系统会自动探测PCI设备并加载相应的驱动程序。在驱动程序中,可以使用pci_register_driver()
函数将驱动程序注册到PCI子系统中,从而使得驱动程序能够正确地探测和管理PCI设备。在探测到PCI设备后,PCI子系统会自动加载相应的驱动程序,从而完成PCI设备的初始化和驱动加载操作。
pcic_pbm_scan_bus()
函数是Linux内核中一个用于PCI总线扫描的函数,用于探测PCI设备并加载相应的驱动程序,是PCI子系统的重要组成部分。
/**
* pci_scan_child_bus_extend() - Scan devices below a bus
* @bus: Bus to scan for devices
* @available_buses: Total number of buses available (%0 does not try to
* extend beyond the minimal)
*
* Scans devices below @bus including subordinate buses. Returns new
* subordinate number including all the found devices. Passing
* @available_buses causes the remaining bus space to be distributed
* equally between hotplug-capable bridges to allow future extension of the
* hierarchy.
*/
static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus,
unsigned int available_buses)
{
unsigned int used_buses, normal_bridges = 0, hotplug_bridges = 0;
unsigned int start = bus->busn_res.start;
unsigned int devfn, fn, cmax, max = start;
struct pci_dev *dev;
int nr_devs;
dev_dbg(&bus->dev, "scanning bus\n");
/* Go find them, Rover! */
for (devfn = 0; devfn < 256; devfn += 8) {
nr_devs = pci_scan_slot(bus, devfn);
/*
* The Jailhouse hypervisor may pass individual functions of a
* multi-function device to a guest without passing function 0.
* Look for them as well.
*/
if (jailhouse_paravirt() && nr_devs == 0) {
for (fn = 1; fn < 8; fn++) {
dev = pci_scan_single_device(bus, devfn + fn);
if (dev)
dev->multifunction = 1;
}
}
}
/* Reserve buses for SR-IOV capability */
used_buses = pci_iov_bus_range(bus);
max += used_buses;
/*
* After performing arch-dependent fixup of the bus, look behind
* all PCI-to-PCI bridges on this bus.
*/
if (!bus->is_added) {
dev_dbg(&bus->dev, "fixups for bus\n");
pcibios_fixup_bus(bus);
bus->is_added = 1;
}
/*
* Calculate how many hotplug bridges and normal bridges there
* are on this bus. We will distribute the additional available
* buses between hotplug bridges.
*/
for_each_pci_bridge(dev, bus) {
if (dev->is_hotplug_bridge)
hotplug_bridges++;
else
normal_bridges++;
}
/*
* Scan bridges that are already configured. We don't touch them
* unless they are misconfigured (which will be done in the second
* scan below).
*/
for_each_pci_bridge(dev, bus) {
cmax = max;
max = pci_scan_bridge_extend(bus, dev, max, 0, 0);
/*
* Reserve one bus for each bridge now to avoid extending
* hotplug bridges too much during the second scan below.
*/
used_buses++;
if (cmax - max > 1)
used_buses += cmax - max - 1;
}
/* Scan bridges that need to be reconfigured */
for_each_pci_bridge(dev, bus) {
unsigned int buses = 0;
if (!hotplug_bridges && normal_bridges == 1) {
/*
* There is only one bridge on the bus (upstream
* port) so it gets all available buses which it
* can then distribute to the possible hotplug
* bridges below.
*/
buses = available_buses;
} else if (dev->is_hotplug_bridge) {
/*
* Distribute the extra buses between hotplug
* bridges if any.
*/
buses = available_buses / hotplug_bridges;
buses = min(buses, available_buses - used_buses + 1);
}
cmax = max;
max = pci_scan_bridge_extend(bus, dev, cmax, buses, 1);
/* One bus is already accounted so don't add it again */
if (max - cmax > 1)
used_buses += max - cmax - 1;
}
/*
* Make sure a hotplug bridge has at least the minimum requested
* number of buses but allow it to grow up to the maximum available
* bus number of there is room.
*/
if (bus->self && bus->self->is_hotplug_bridge) {
used_buses = max_t(unsigned int, available_buses,
pci_hotplug_bus_size - 1);
if (max - start < used_buses) {
max = start + used_buses;
/* Do not allocate more buses than we have room left */
if (max > bus->busn_res.end)
max = bus->busn_res.end;
dev_dbg(&bus->dev, "%pR extended by %#02x\n",
&bus->busn_res, max - start);
}
}
/*
* We've scanned the bus and so we know all about what's on
* the other side of any bridges that may be on this bus plus
* any devices.
*
* Return how far we've got finding sub-buses.
*/
dev_dbg(&bus->dev, "bus scan returning with max=%02x\n", max);
return max;
}
这段代码是用于扫描PCI子总线的函数。它接受两个参数:PCI总线对象和可用总线数。它首先迭代PCI总线下的所有设备,并扫描它们。然后,它为SR-IOV能力保留总线。接下来,它计算总线上的热插拔桥和普通桥的数量,并在第一次扫描中扫描已配置的桥。它还分配了额外的可用总线,以便将其分配给热插拔桥。在第二次扫描中,它扫描需要重新配置的桥,并将额外的总线分配给热插拔桥。最后,它确保热插拔桥至少拥有最小数量的总线,并返回扫描到的最大总线号。