目录

前言

1.枚举过程

1.1 acpi_pci_root_add

1.2 pci_acpi_scan_root(枚举开始)

1.3 acpi_pci_root_create

1.4 pci_scan_child_bus(枚举执行的重点函数)

1.5 pci_scan_slot(pci_scan_single_device才是做事的)

1.6 pci_scan_device

1.7 pci_device_add

1.8 pci_scan_bridge

2.总结


前言

在旧版本的内核(以2.6.16版本为例)中系统会调用pci_legacy_init(内核加载等级为4)中调用
pcibios_scan_root来完成对于PCI设备的枚举过程,但是对于现在的x86架构的SOC来说由于ACPI机制的普遍支持,所以对于PCI设备枚举的整个过程就移交至ACPI中来完成。在此我们只基于新机制下的PCI设备枚举的过程进行分析。

注:本文档分析的PCI总线驱动框架基于4.4版本内核

1.枚举过程

由于在ACPI机制在x86平台的广泛应用,所以对于PCI设备探测枚举这个过程也放到了这里来实现,由于PCI设备的枚举只是ACPI要实现的一个功能之一,所以在此我们只对ACPI机制中PCI设备探测这部分进行分析。

我们先来看一个整体的函数流程:

这些函数向我们描述了ACPI初始化中PCI的相关操作,主要可分为两个部分:

第一部分:acpi_pci_root_init完成PCI设备的相关操作(包括PCI主桥,PCI桥、PCI设备的枚举,配置空间的设置,总线号的分配等);

第二部分:acpi_pci_link_init完成PCI中断的相关操作,在此不做具体分析。

1.1 acpi_pci_root_add

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节点进行遍历。

1.2 pci_acpi_scan_root(枚举开始)

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信息的一些接口函数的集合(这其中就包括对配置空间的读写方法)

1.3 acpi_pci_root_create

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进行遍历。

1.4 pci_scan_child_bus(枚举执行的重点函数)

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。

1.5 pci_scan_slot(pci_scan_single_device才是做事的)

这个函数中真正干活的函数是调用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总线的设备链表上去,然后注册设备。一个个分析。

1.6 pci_scan_device

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 IDpci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);  //读取subsystem device IDpci_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, &region);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, &region);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, &region);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, &region);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结构填充数据而已。

1.7 pci_device_add

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);       //添加kobjectWARN_ON(ret < 0);
}

函数分析:本函数所做的事情没什么好说的,就是对pci_dev做一些最后的填充,修正后,添加到当前PCI总线的设备链表中,并且调用device_add函数注册相应的kobject。

1.8 pci_scan_bridge

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 BUSif (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总线号)返回。

2.总结

至此,PCI总线的枚举过程执行完毕,经历了上述这些函数,我们已经得到了PCI总线下的所有设备的信息,并将它们注册为了pci_dev结构体,这时pci总线上的设备信息已经准备完毕,现在就是等待后续将要注册的PCI驱动与他们进行匹配。梳理了整个过程之后,我们可以了解到,BIOS的枚举:初始化所有PCI设备的配置空间,系统的枚举:依据BIOS枚举后的配置空间信息生成pci_dev设备结构体。

本文转自PCI总线驱动代码梳理(三)--PCI设备的枚举

Linux源码阅读——PCI总线驱动代码(三)PCI设备枚举过程相关推荐

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

    目录 一.前言 二.概述 三.整体流程 四.PCI相关入口函数 4.1 pcibus_class_init 4.2 pci_driver_init 4.3 pci_arch_init 4.4 pci_ ...

  2. Soul 网关源码阅读(二)代码初步运行

    Soul 源码阅读(二)代码初步运行 简介     基于上篇:Soul 源码阅读(一) 概览,这部分跑一下Soul网关的示例 过程记录     现在我们可以根据地图,稍微探索一下周边,摸一摸      ...

  3. Linux内核基础——Linux源码阅读工具Source Insight4.0

    Linux内核源码阅读工具--source insight4.0 Source insight4.0工具的使用入门 一.Souce insight建立工程.导入源码 二.遍历所有源码文件建立符号索引 ...

  4. NJ4X源码阅读分析笔记系列(三)—— nj4x-ts深入分析

    NJ4X源码阅读分析笔记系列(三)-- nj4x-ts深入分析 一.系统的工作流程图(模块级) 其工作流程如下(以行情获取为例): 应用端向Application Server发起连接 应用服务器调用 ...

  5. linux源码阅读笔记 fork函数

    在阅读源码的过程中,发现找不到fork函数的定义.后来在linux/init/main.c中找到了这样一条语句 static inline _syscall0(int,fork) 原来这里就是fork ...

  6. linux源码阅读神器,Ubuntu下安装LXR Linux源代码阅读利器

    1.安装apache2 sudo apt-get install apache2 2.安装lxr sudo apt-get install lxr 3. 在/etc/apache2/httpd.con ...

  7. surefire 拉起 junit 单元测试类 源码阅读(一)

    根据surefire 拉起Junit单元测试类 输出的报错日志 跟踪执行过程: 日志1: java.lang.reflect.InvocationTargetExceptionat sun.refle ...

  8. Soul网关源码阅读(十)自定义简单插件编写

    Soul网关源码阅读(十)自定义简单插件编写 简介     综合前面所分析的插件处理流程相关知识,此次我们来编写自定义的插件:统计请求在插件链中的经历时长 编写准备     首先我们先探究一下,一个P ...

  9. Soul网关源码阅读(九)插件配置加载初探

    Soul网关源码阅读(九)插件配置加载初探 简介     今日来探索一下插件的初始化,及相关的配置的加载 源码Debug 插件初始化     首先来到我们非常熟悉的插件链调用的类: SoulWebHa ...

最新文章

  1. LeetCode 876——链表的中间结点
  2. 【CSS练习】常用的CSS字段
  3. Java 类的生命周期详解
  4. 015_面向对象_异常,包和Object类
  5. COM_ASET check in CRM Middleware inbound scenario
  6. redis的那种目录结构能新建么_Serverless 解惑——函数计算如何访问 Redis 数据库...
  7. java memorystream 包_存储在MemoryStream中的裁剪图像中心
  8. 自动化专业学python如何_如何系统有效学习 Python 自动化测试?
  9. Python使用for循环打印直角三角形
  10. RHEL7和RHEL6的主要变化
  11. vscode如何关闭Pylint警告或错误提示
  12. R 语言的安装(详细教程)
  13. websocket实现多房间聊天室
  14. HTML之表格与表单
  15. 【算法练习】CodeVs1391 伊吹萃香(分层图最短路)
  16. Java中如何使用方法?
  17. 清理 Windows 系统 DNS 缓存文件
  18. 迈德威视相机调用( 基于 Windows 系统 + VS2017 + OpenCV 3.x.x )
  19. 35. Python数据类型之字典
  20. MYSQL相关知识总结

热门文章

  1. 我们计划招收300位数据分析爱好者,免费攻读R语言数据分析
  2. 尝试用程序记录QQ密码
  3. 解决windows server 2008 R2安装到D盘的问题
  4. 1146 -table 'performance_schema.session_variables' donesn't exist解决方案
  5. Log4j日志配置详解(Log4j2)
  6. CAD二次开发 ZOOM 居中视野
  7. 【跟学C++】C++队列——queue类(Study13)
  8. Android使用FFmpeg开发播发器(一)编译FFmpeg
  9. RPG游戏《黑暗之光》流程介绍与代码分析之(十四):角色技能系统的实现
  10. FAPI专题-3:5G nFAPI接口 - 中文规范-3- 协议栈、消息格式