PCI子系统的关键概念包括:PCI设备、pci插槽、PCI域、PCI总线、PCI桥、热插拔控制器、
PCI设备探测流程
linux启动过程中会调用PCI核心的探测函数进行PCI设备的探测,并建立起相关的拓扑关系以及记录每个探测到的设备的信息;探测采用递归的方式从根控制器开始逐级往下探测,遇到桥控制器则继续探测隐藏在它下面的设备,依次类推。
PCI探测的接口函数包括:pci_scan_root_bus、pci_scan_bus
在追踪PCI设备探测的流程之前先看一下pci_sys_data这个数据结构,这个数据结构记录了PCI探测过程中需要的各种信息,没有这个数据结构,PCI探测将无法实现。另外对于每个PCI控制器都有这样一个实例(显而易见,不同的控制器相关信息也不同)。
struct pci_sys_data {
#ifdef CONFIG_PCI_DOMAINS
int domain; //对于服务器来说由于有相当多的PCI设备,所以需要通过分域来区别不同的设备
#endif
struct list_head node;
int busnr; /* primary bus number */
u64 mem_offset; /* bus->cpu memory mapping offset */
unsigned long io_offset; /* bus->cpu IO mapping offset */
struct pci_bus *bus; /* PCI bus */
struct list_head resources; /* root bus resources (apertures) */
/* Bridge swizzling */
u8 (*swizzle)(struct pci_dev *, u8 *);
/* IRQ mapping */
int (*map_irq)(const struct pci_dev *, u8, u8);
struct hw_pci *hw;
void *private_data; /* platform controller private data */
}
而pci_sys_data中的信息又通过启动时初始化由hw_pci来获取,这个地方不知道为什么要转换一次?先不管这么多,直接看hw_pci这个结构体,到底都有哪些成员。
struct hw_pci {
#ifdef CONFIG_PCI_DOMAINS
int domain;
#endif
struct list_head buses;
int nr_controllers;
int (*setup)(int nr, struct pci_sys_data *);
struct pci_bus *(*scan)(int nr, struct pci_sys_data *);
void (*preinit)(void);
void (*postinit)(void);
u8 (*swizzle)(struct pci_dev *dev, u8 *pin);
int (*map_irq)(const struct pci_dev *dev, u8 slot, u8 pin);
}
看起来和pci_sys_data很类似,又仔细看了下代码才发现hw_pci和pci_sys_data的区别。hw_pci记录的是系统中总的和PCI相关的信息,而pci_sys_data是针对每个控制器都有一个,记录的是单个控制器的信息。
看下hw_pci的成员吧,知道了它就知道了pci_sys_data。
domain:因为PCI规范允许单个系统拥有高达256个总线,所以总线编号是8位。但对于大型系统而言,这是不够的,所以,引入了域的概念,每个PCI域可以拥有最多256个总线,每个总线上可支持32个设备,而每个设备上最多可有8种功能。
buses:所以探测的总线都链接到这个双向链表中
nr_controllers:系统已知的控制器数量
setup:主要是获取以及申请PCI总线的地址空间
scan:扫描总线以探测设备和获取设备信息
preinit:初始化PCI控制器的硬件,使软件可以在后面顺利的通过控制器探测设备
postinit:大多数情况下都不需要它,既然设备什么的都探测到了,也就不需要再去初始化了吧
swizzle和map_irq:这里涉及到PCI中断机制,PCI规范在硬件上规定了4根INT管脚,PCI设备的每种功能都可以任意连接到4根INT管脚中的一根,当有多个功能连接到一根INT管脚上时采用线与的方式。同时设备的每种功能还有中断线,用于在控制器那边区别到底是哪种功能设备产生了中断,该值需要在设备探测过程中进行初始化。swizzle将PCI设备的中断管脚一级一级路由到根控制器的中断管脚上,对于大多数PCI桥来说中断管脚路由遵循(((pin - 1) + slot) % 4) + 1的规律。调用swizzle之后得到了远端的PCI设备映射到根控制器的中断管脚,可以根据这个信息给PCI设备分配主机可以识别的虚拟中断线,然后把分配的中断线写入PCI设备的配置空间。
瞧,中断线在这里配置:
中断在桥设备上的路由一般遵循以下规律:
了解了hw_pci这个东东,我们再来看驱动核心具体是如何初始化PCI设备列表的。我们的解读以ARM平台作为分析的目标,在ARM平台上调用驱动核心进行设备探测的API是pci_common_init这个函数。
void __init pci_common_init(struct hw_pci *hw)
{
struct pci_sys_data *sys;
INIT_LIST_HEAD(&hw->buses);
pci_add_flags(PCI_REASSIGN_ALL_RSRC);
//初始化PCI控制器硬件,后续的探测才能正常开展
if (hw->preinit)
hw->preinit();
//设备探测的任务都在这个不起眼的函数里面,所谓小身材大作用是也
pcibios_init_hw(hw);
if (hw->postinit)
hw->postinit();
//这里开始配置总线上的每个设备的中断线了
pci_fixup_irqs(pcibios_swizzle, pcibios_map_irq);
list_for_each_entry(sys, &hw->buses, node) {
struct pci_bus *bus = sys->bus;
if (!pci_has_flag(PCI_PROBE_ONLY)) {
/*
* Size the bridge windows.
*/
pci_bus_size_bridges(bus);
/*
* Assign resources.
*/
pci_bus_assign_resources(bus);
/*
* Enable bridges
*/
pci_enable_bridges(bus);
}
/*
* Tell drivers about devices found.
*/
pci_bus_add_devices(bus);
}
}