注:本文档分析的PCI总线驱动框架基于4.4版本内核
在旧版本的内核(以2.6.16版本为例)中系统会调用pci_legacy_init(内核加载等级为4)中调用
pcibios_scan_root来完成对于PCI设备的枚举过程,但是对于现在的x86架构的SOC来说由于ACPI机制的普遍支持,所以对于PCI设备枚举的整个过程就移交至ACPI中来完成。在此我们只基于新机制下的PCI设备枚举的过程进行分析。
由于在ACPI机制在x86平台的广泛应用,所以对于PCI设备探测枚举这个过程也放到了这里来实现,由于PCI设备的枚举只是ACPI要实现的一个功能之一,所以在此我们只对ACPI机制中PCI设备探测这部分进行分析。
我们先来看一个整体的函数流程:
这些函数向我们描述了ACPI初始化中PCI的相关操作,主要可分为两个部分、第一部分:acpi_pci_root_init完成PCI设备的相关操作(包括PCI主桥,PCI桥、PCI设备的枚举,配置空间的设置,总线号的分配等),第二部分:acpi_pci_link_init完成PCI中断的相关操作,在此不做具体分析。
static int acpi_pci_root_add(struct acpi_device *device,
const struct acpi_device_id *not_used)
{
......
negotiate_os_control(root, &no_aspm);
root->bus = pci_acpi_scan_root(root);
//错误检验
if (!root->bus) {
dev_err(&device->dev,
"Bus %04x:%02x not present in PCI namespace\n",
root->segment, (unsigned int)root->secondary.start);
device->driver_data = NULL;
result = -ENODEV;
goto remove_dmar;
}
......
}
函数分析:该函数通过ACPI表中的_SEG和_BBN参数获得HOST主桥的Segment和Bus号,创建acpi_pci_root结构(表示HOST主桥信息),在本系统中,由于只有一个HOST主桥,所以acpi_pci_root_add只调用一次,acpi_pci_root也就一个。最终,在完成数据结构的创建以及一些初始化后,就调用pci_acpi_scan_root函数对这条主桥下的PCI节点进行遍历。
struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
{
int domain = root->segment;
int busnum = root->secondary.start;
int node = pci_acpi_root_get_node(root);
struct pci_bus *bus;
......
bus = pci_find_bus(domain, busnum);
if (bus) { //如果当前总线存在就不会进行create,直接就返回
struct pci_sysdata sd = {
.domain = domain,
.node = node,
.companion = root->device
};
memcpy(bus->sysdata, &sd, sizeof(sd));
} else {
struct pci_root_info *info;
info = kzalloc_node(sizeof(*info), GFP_KERNEL, node); //数据结构实体化
if (!info)
dev_err(&root->device->dev,
"pci_bus %04x:%02x: ignored (out of memory)\n",
domain, busnum);
else {
info->sd.domain = domain;
info->sd.node = node;
info->sd.companion = root->device;
bus = acpi_pci_root_create(root, &acpi_pci_root_ops,
&info->common, &info->sd);
}
}
......
return bus;
}
函数分析:这个函数先调用pci_find_bus去判断当前总线号是否已经存在,如果存在就退出,如果不存在就调用acpi_pci_root_create函数去对这条PCI总线进行遍历,在这里我们需注意一个结构就是acpi_pci_root_ops,它是新版本的内核提供给我们对于PCI信息的一些接口函数的集合(这其中就包括对配置空间的读写方法)
struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root,
struct acpi_pci_root_ops *ops,
struct acpi_pci_root_info *info,
void *sysdata)
{
int ret, busnum = root->secondary.start;
struct acpi_device *device = root->device;
int node = acpi_get_node(device->handle);
struct pci_bus *bus;
...... //此段代码都对资源的设置初始化和添加操作。
bus = pci_create_root_bus(NULL, busnum, ops->pci_ops,
sysdata, &info->resources); //创建host桥的数据结构
if (!bus)
goto out_release_info;
pci_scan_child_bus(bus); //遍历host桥产生的子总线,从bus 0 开始
pci_set_host_bridge_release(to_pci_host_bridge(bus->bridge),
acpi_pci_root_release_info, info);
if (node != NUMA_NO_NODE)
dev_printk(KERN_DEBUG, &bus->dev, "on NUMA node %d\n", node);
return bus;
out_release_info:
__acpi_pci_root_release_info(info);
return NULL;
}
函数分析:在进入此函数时,系统首先会对这个root的信息进行一些初始化和添加操作,之后将这些resources、读写方法、总线号等数据传入pci_create_root_bus这个函数,通过这个函数返回一个总线结构pci_bus(注意:pci_create_root_bus返回的pci总线是总线号为0的总线,即直连HOST桥的总线),最后将得到的结构体送入pci_scan_child_bus开始从总线0进行遍历。
unsigned int pci_scan_child_bus(struct pci_bus *bus)
{
unsigned int devfn, pass, max = bus->busn_res.start;
struct pci_dev *dev;
dev_dbg(&bus->dev, "scanning bus\n");
/* Go find them, Rover! */
for (devfn = 0; devfn < 0x100; devfn += 8)
pci_scan_slot(bus, devfn);
/* Reserve buses for SR-IOV capability. */
max += pci_iov_bus_range(bus);
if (!bus->is_added) {
dev_dbg(&bus->dev, "fixups for bus\n");
pcibios_fixup_bus(bus);
bus->is_added = 1;
}
//为了兼容x86和其他不使用BIOS的架构的CPU,执行两次
for (pass = 0; pass < 2; pass++)
list_for_each_entry(dev, &bus->devices, bus_list) {
if (pci_is_bridge(dev)) //如果是总线上有桥设备就递归调用
max = pci_scan_bridge(bus, dev, max, pass);
}
dev_dbg(&bus->dev, "bus scan returning with max=%02x\n", max);
return max;
}
函数分析:我们先来说明一下这个函数整体的实现过程:通过for循环先遍历当前总线上的每一个PCI设备(包括PCI桥设备),遍历后将其注册为pci_dev结构体,之后通过递归调用来进入当前总线的下一级子总线继续完成上述过程,直到某条PCI总线上无PCI桥设备,返回最大的总线号。在这个函数中,一上来就是探测总线0上的所有设备,这里通过一个for循环来实现(为什么是0x100,回答:0x100即256,由于一条PCI总线最多可以有32个设备,而每个设备最多能有8个功能,故32*8=256,一条总线最多有256个逻辑设备,那就执行256次把它们一个不漏的全部枚举一次,去发现他们GO),在本函数中,使用pci_scan_slot来遍历探测总线上的每一个设备,使用pci_scan_bridge递归调用pci_scan_child_bus进入下一级总线。先分析pci_scan_slot。
这个函数中真正干活的函数是调用pci_scan_single_device函数实现的,这里就不贴代码了,直接进入pci_scan_single_device函数去。
struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
{
struct pci_dev *dev;
......
dev = pci_scan_device(bus, devfn); //pci_dev的建立
if (!dev)
return NULL;
pci_device_add(dev, bus); //pci_dev的注册
return dev;
}
函数分析:在pci_scan_single_device函数进一步调用了两个函数pci_scan_device函数和pci_device_add函数,先说说这两个函数是干啥的,首先pci_scan_device函数完成的事情是依据BIOS遍历的结果去填充pci_dev结构,而pci_device_add函数的工作就简单了,把这个结构体加到当前PCI总线的设备链表上去,然后注册设备。一个个分析。
static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
{
struct pci_dev *dev;
u32 l;
//读取该PCI设备的厂商ID和设备ID,如果连这两个ID都读取无效,就直接返回
if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))
return NULL;
dev = pci_alloc_dev(bus); //创建一个pci_dev结构
if (!dev)
return NULL;
dev->devfn = devfn; //把刚刚获取的两个ID填充至结构体(就是靠这两个ID去找驱动的)
dev->vendor = l & 0xffff;
dev->device = (l >> 16) & 0xffff;
pci_set_of_node(dev);
if (pci_setup_device(dev)) { //进一步填充pci_dev结构,本函数重点
pci_bus_put(dev->bus);
kfree(dev);
return NULL;
}
return dev;
}
函数分析:这个函数做了哪些事情,我们已在代码中列举了,下面直接看这个函数的重点pci_setup_device(dev)
int pci_setup_device(struct pci_dev *dev)
{
u32 class;
u16 cmd;
u8 hdr_type;
int pos = 0;
struct pci_bus_region region;
struct resource *res;
if (pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type)) //读取头信息
return -EIO;
dev->sysdata = dev->bus->sysdata;
dev->dev.parent = dev->bus->bridge;
dev->dev.bus = &pci_bus_type;
dev->hdr_type = hdr_type & 0x7f; //依据头信息,决定这个设备属于哪类PCI设备
dev->multifunction = !!(hdr_type & 0x80);
dev->error_state = pci_channel_io_normal;
set_pcie_port_type(dev);
pci_dev_assign_slot(dev);
dev->dma_mask = 0xffffffff;
//设置设备名字 主桥号(一般为0):PCI总线号:PCI设备号.PCI设备功能号
dev_set_name(&dev->dev, "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
dev->bus->number, PCI_SLOT(dev->devfn),
PCI_FUNC(dev->devfn));
pci_read_config_dword(dev, PCI_CLASS_REVISION, &class);
dev->revision = class & 0xff;
dev->class = class >> 8; /* upper 3 bytes */
dev_printk(KERN_DEBUG, &dev->dev, "[%04x:%04x] type %02x class %#08x\n",
dev->vendor, dev->device, dev->hdr_type, dev->class);
/* need to have dev->class ready */
dev->cfg_size = pci_cfg_space_size(dev);
/* "Unknown power state" */
dev->current_state = PCI_UNKNOWN;
pci_msi_setup_pci_dev(dev);
/* 做一个早期的检查 */
pci_fixup_device(pci_fixup_early, dev);
/* device class may be changed after fixup */
class = dev->class >> 8;
if (dev->non_compliant_bars) {
pci_read_config_word(dev, PCI_COMMAND, &cmd);
if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
dev_info(&dev->dev, "device has non-compliant BARs; disabling IO/MEM decoding\n");
cmd &= ~PCI_COMMAND_IO;
cmd &= ~PCI_COMMAND_MEMORY;
pci_write_config_word(dev, PCI_COMMAND, cmd);
}
}
*********************************************************************************
switch (dev->hdr_type) { /* header type */
case PCI_HEADER_TYPE_NORMAL: //标准配置空间的类型
if (class == PCI_CLASS_BRIDGE_PCI) //桥设备(0604)
goto bad;
pci_read_irq(dev); //读取中断
//读取BAR寄存器中所存的基地址以及这些区间的大小
pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
//读取subsystem vendor ID
pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
//读取subsystem device ID
pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);
if (class == PCI_CLASS_STORAGE_IDE) { //IDE控制器设备(0101)
u8 progif;
pci_read_config_byte(dev, PCI_CLASS_PROG, &progif);
if ((progif & 1) == 0) { //不同的编程接口
region.start = 0x1F0;
region.end = 0x1F7;
res = &dev->resource[0];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
dev_info(&dev->dev, "legacy IDE quirk: reg 0x10: %pR\n",
res);
region.start = 0x3F6;
region.end = 0x3F6;
res = &dev->resource[1];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
dev_info(&dev->dev, "legacy IDE quirk: reg 0x14: %pR\n",
res);
}
if ((progif & 4) == 0) {
region.start = 0x170;
region.end = 0x177;
res = &dev->resource[2];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
dev_info(&dev->dev, "legacy IDE quirk: reg 0x18: %pR\n",
res);
region.start = 0x376;
region.end = 0x376;
res = &dev->resource[3];
res->flags = LEGACY_IO_RESOURCE;
pcibios_bus_to_resource(dev->bus, res, ®ion);
dev_info(&dev->dev, "legacy IDE quirk: reg 0x1c: %pR\n",
res);
}
}
break;
case PCI_HEADER_TYPE_BRIDGE: // PCI桥配置空间的类型
if (class != PCI_CLASS_BRIDGE_PCI)
goto bad;
pci_read_irq(dev);
dev->transparent = ((dev->class & 0xff) == 1);
pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);
set_pcie_hotplug_bridge(dev);
pos = pci_find_capability(dev, PCI_CAP_ID_SSVID);
if (pos) {
pci_read_config_word(dev, pos + PCI_SSVID_VENDOR_ID, &dev->subsystem_vendor);
pci_read_config_word(dev, pos + PCI_SSVID_DEVICE_ID, &dev->subsystem_device);
}
break;
case PCI_HEADER_TYPE_CARDBUS: // CardBus桥配置空间的类型
if (class != PCI_CLASS_BRIDGE_CARDBUS)
goto bad;
pci_read_irq(dev);
pci_read_bases(dev, 1, 0);
pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device);
break;
default: //不知道什么类型,错误
dev_err(&dev->dev, "unknown header type %02x, ignoring device\n",
dev->hdr_type);
return -EIO;
bad:
dev_err(&dev->dev, "ignoring class %#08x (doesn't match header type %02x)\n",
dev->class, dev->hdr_type);
dev->class = PCI_CLASS_NOT_DEFINED << 8;
}
/* We found a fine healthy device, go go go... */
return 0;
}
函数分析:这个函数好长,我们先总体说明该的功能,即:继续初始化pci_dev结构体的版本号,类型,存储空间,中断线等问题,在代码中,我用一条线将函数分成两个部分。在上半部就是一些常规的操作(从配置空间读取一些信息例如:配置空间类型,设备类型等,将读取到的信息填充到设备结构体,做一些早期的参数修正)。在下半部中函数通过switch语句将设备的配置空间分为三类即:标准类型(6个BAR)、桥类型(2个BAR)、CardBus桥类型(1个BAR),当一个设备在准备遍历的时候会依据该设备配置空间的信息来确定这是个什么设备(例如:读取配置空间的Class code寄存器,得到一个值0x0101表示这是一种大容量存储控制器且使用的是IDE控制器,查表可得)依据不同的设备类型,来获取配置空间的信息填充pci_dev结构。由此我们可知,本函数看似很长,其实内部做的更多的只是依据不同类型的设备去为pci_dev结构填充数据而已。
void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
{
int ret;
pci_configure_device(dev);
device_initialize(&dev->dev);
dev->dev.release = pci_release_dev;
set_dev_node(&dev->dev, pcibus_to_node(bus));
dev->dev.dma_mask = &dev->dma_mask;
dev->dev.dma_parms = &dev->dma_parms;
dev->dev.coherent_dma_mask = 0xffffffffull;
pci_dma_configure(dev);
pci_set_dma_max_seg_size(dev, 65536);
pci_set_dma_seg_boundary(dev, 0xffffffff);
pci_fixup_device(pci_fixup_header, dev);
pci_reassigndev_resource_alignment(dev);
dev->state_saved = false;
pci_init_capabilities(dev);
down_write(&pci_bus_sem);
list_add_tail(&dev->bus_list, &bus->devices); //将该设备加入到总线的设备链表中
up_write(&pci_bus_sem);
ret = pcibios_add_device(dev);
WARN_ON(ret < 0);
pci_set_msi_domain(dev);
dev->match_driver = false;
ret = device_add(&dev->dev); //添加kobject
WARN_ON(ret < 0);
}
函数分析:本函数所做的事情没什么好说的,就是对pci_dev做一些最后的填充,修正后,添加到当前PCI总线的设备链表中,并且调用device_add函数注册相应的kobject。
int pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max, int pass)
{
struct pci_bus *child;
int is_cardbus = (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS);
u32 buses, i, j = 0;
u16 bctl;
u8 primary, secondary, subordinate;
int broken = 0;
......
if ((secondary || subordinate) && !pcibios_assign_all_busses() &&
!is_cardbus && !broken) {
unsigned int cmax;
if (pass)
goto out;
child = pci_find_bus(pci_domain_nr(bus), secondary); //检查该bus是否存在
if (!child) {
child = pci_add_new_bus(bus, dev, secondary);
if (!child)
goto out;
child->primary = primary;
pci_bus_insert_busn_res(child, secondary, subordinate);
child->bridge_ctl = bctl;
}
cmax = pci_scan_child_bus(child); //递归调用,进入到下一级PCI BUS
if (cmax > subordinate)
dev_warn(&dev->dev, "bridge has subordinate %02x but max busn %02x\n",
subordinate, cmax);
/* subordinate should equal child->busn_res.end */
if (subordinate > max)
max = subordinate;
} else {
......
}
sprintf(child->name,
(is_cardbus ? "PCI CardBus %04x:%02x" : "PCI Bus %04x:%02x"),
pci_domain_nr(bus), child->number);
......
&bus->busn_res);
}
bus = bus->parent;
}
out:
pci_write_config_word(dev, PCI_BRIDGE_CONTROL, bctl);
return max;
}
函数分析:本函数调用了两次(通过for循环),原因是:在不同架构的对于PCI设备的枚举实现是不同的,例如在x86架构中有BIOS为我们提前去遍历一遍PCI设备,但是在ARM或者powerPC中uboot是没有这种操作的,所以为了兼容这两种情况,这里就执行两次对应于两种不同的情况,当pci_scan_slot函数执行完了后,我们就得到了一个当前PCI总线的设备链表,在执行pci_scan_bridge函数前,会遍历这个设备链表,如果存在PCI桥设备,就调用pci_scan_bridge函数,而在本函数内部会再次调用pci_scan_child_bus函数,去遍历子PCI总线设备(注意:这时的BUS就已经不是PCI BUS 0了)就是通过这种一级一级的递归调用,在遍历总PCI总线下的每一条PCI子总线。直到某条PCI子总线下无PCI桥设备,就停止递归,并修改subbordinate参数,(最大PCI总线号)返回。
至此,PCI总线的枚举过程执行完毕,经历了上述这些函数,我们已经得到了PCI总线下的所有设备的信息,并将它们注册为了pci_dev结构体,这时pci总线上的设备信息已经准备完毕,现在就是等待后续将要注册的PCI驱动与他们进行匹配。梳理了整个过程之后,我们可以了解到,BIOS的枚举:初始化所有PCI设备的配置空间,系统的枚举:依据BIOS枚举后的配置空间信息生成pci_dev设备结构体。