8.2 PCI设备扫描过程

Linux内核具备多种PCI的扫描方式,它们之间大同小异。

本节使用传统的扫描方式 执行 pci_legacy_init函数,定义在legacy.c 文件中 :

static int __init pci_legacy_init(void)
{if (!raw_pci_ops) {printk("PCI: System does not support PCI\n");return 0;}if (pcibios_scanned++)return 0;printk("PCI: Probing PCI hardware\n");pci_root_bus = pcibios_scan_root(0);if (pci_root_bus)pci_bus_add_devices(pci_root_bus);pcibios_fixup_peer_bridges();return 0;
}

pci_legacy_init函数

首先 --扫描0号总线, 扫描成功,则把0号总线作为系统的根总线

pci_root_bus = pcibios_scan_root(0);


然后 --把0号总线扫描到的设备加入到一个全局的PCI设备链表

最后 --调用pcibios_fixup_peer_bridges 对BIOS提供的PCI总线进行进一步的扫描

/** Discover remaining PCI buses in case there are peer host bridges.* We use the number of last PCI bus provided by the PCI BIOS.*/
static void __devinit pcibios_fixup_peer_bridges(void)
{int n, devfn;if (pcibios_last_bus <= 0 || pcibios_last_bus >= 0xff)return;DBG("PCI: Peer bridge fixup\n");for (n=0; n <= pcibios_last_bus; n++) {u32 l;if (pci_find_bus(0, n))continue;for (devfn = 0; devfn < 256; devfn += 8) {if (!raw_pci_ops->read(0, n, devfn, PCI_VENDOR_ID, 2, &l) &&l != 0x0000 && l != 0xffff) {DBG("Found device at %02x:%02x [%04x]\n", n, devfn, l);printk(KERN_INFO "PCI: Discovered peer bus %02x\n", n);pci_scan_bus(n, &pci_root_ops, NULL);break;}}}
}

8.2.1 扫描0号总线

扫描 0号总线调用的是pcibios_scan_root 函数,代码如下:

struct pci_bus * __devinit pcibios_scan_root(int busnum)
{struct pci_bus *bus = NULL;dmi_check_system(pciprobe_dmi_table);while ((bus = pci_find_next_bus(bus)) != NULL) {if (bus->number == busnum) {/* Already scanned */return bus;}}printk(KERN_DEBUG "PCI: Probing PCI hardware (bus %02x)\n", busnum);return pci_scan_bus_parented(NULL, busnum, &pci_root_ops, NULL);
}

首先遍历所有的PCI总线,检查指定的总线是否已经扫描过,如果已经扫描,则直接返回。如果尚未扫描,则调用pci_bus_parented函数扫描总线。

8.2.1 扫描总线上的PCI设备

pci_bus_parented函数 的功能是扫描总线上可能接入的256个PCI设备,如果扫描到的PCI设备是 桥设备,还要递归扫描桥设备,把桥设备可能接入的PCI设备扫描出来,代码清单如下:

struct pci_bus * __devinit pci_scan_bus_parented(struct device *parent,int bus, struct pci_ops *ops, void *sysdata)
{struct pci_bus *b;b = pci_create_bus(parent, bus, ops, sysdata);if (b)b->subordinate = pci_scan_child_bus(b);return b;
}

pci_scan_bus_parented 函数可分成两个步骤:

第一步:创建一个总线对象

第二步:调用pci_scan_child_bus 对创建的总线对象进行递归扫描

1、创建一个总线对象

pci_create_bus函数,parent参数为空NULL,说明这条总线没有父设备,是一条根总线

1)pci_create_bus第一部分是创建一个总线对象和一个设备对象,代码如下:

struct pci_bus * __devinit pci_create_bus(struct device *parent,int bus, struct pci_ops *ops, void *sysdata)
{int error;struct pci_bus *b;struct device *dev;b = pci_alloc_bus();if (!b)return NULL;//--申请一个dev结构 --dev = kmalloc(sizeof(*dev), GFP_KERNEL);if (!dev){kfree(b);return NULL;}b->sysdata = sysdata;b->ops = ops;//--检查是否被创建if (pci_find_bus(pci_domain_nr(b), bus)) {/* If we already got to this bus through a different bridge, ignore it */pr_debug("PCI: Bus %04x:%02x already known\n", pci_domain_nr(b), bus);goto err_out;}//--创建总线加入PCI总线链表down_write(&pci_bus_sem);list_add_tail(&b->node, &pci_root_buses);up_write(&pci_bus_sem);

PCI总线本身是一个设备,所以除了总线对象外,还要为它创建一个设备对象。总线对象是链接到一个全局的链表头pci_root_buses,这样通过这条链表,可以遍历所有的PCI总线。

2)pci_create_bus函数第二部分执行结构和对象的注册

 //设置Dev结构并登记到系统memset(dev, 0, sizeof(*dev));dev->parent = parent;dev->release = pci_release_bus_bridge_dev;sprintf(dev->bus_id, "pci%04x:%02x", pci_domain_nr(b), bus);error = device_register(dev);if (error)goto dev_reg_err;b->bridge = get_device(dev);b->class_dev.class = &pcibus_class;sprintf(b->class_dev.class_id, "%04x:%02x", pci_domain_nr(b), bus);error = class_device_register(&b->class_dev);if (error)goto class_dev_reg_err;error = class_device_create_file(&b->class_dev, &class_device_attr_cpuaffinity);if (error)goto class_dev_create_file_err;/* Create legacy_io and legacy_mem files for this bus */pci_create_legacy_files(b);error = sysfs_create_link(&b->class_dev.kobj, &b->bridge->kobj, "bridge");if (error)goto sys_create_link_err;b->number = b->secondary = bus;

首先把设备对象注册到系统,这个过程6章已经分析过了。其次四注册PCI总线类 和 sysfs文件系统创建符号链接

3)pci_create_bus函数最后设置PCI总线的资源

 b->resource[0] = &ioport_resource;b->resource[1] = &iomem_resource;return b;

PCI总线资源有两类,

一类是:I/O端口;

另一类:I/O内存;

总线上所有设备的 端口 和 内存 组成一个空间,为了避免冲突,内存设置了全局的数据结构 ioport_resoure 和 iomem_resource,分别保存所有的I/O端口资源 和 所有的I/O内存资源 。

2、扫描总线

现在返回pci_scan_bus_parented函数,当成功创建总线对象后,开始扫描这条总线,调用pci_scan_child_bus

unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus)
{unsigned int devfn, pass, max = bus->secondary;struct pci_dev *dev;pr_debug("PCI: Scanning bus %04x:%02x\n", pci_domain_nr(bus), bus->number);/* Go find them, Rover! *///--扫描总线下面的256个设备for (devfn = 0; devfn < 0x100; devfn += 8)pci_scan_slot(bus, devfn);/** After performing arch-dependent fixup of the bus, look behind* all PCI-to-PCI bridges on this bus.*/pr_debug("PCI: Fixups for bus %04x:%02x\n", pci_domain_nr(bus), bus->number);pcibios_fixup_bus(bus);/*  扫描子总线,分两次扫描,第一次扫描BIOS发现的总线  */for (pass=0; pass < 2; pass++)list_for_each_entry(dev, &bus->devices, bus_list) {if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)max = pci_scan_bridge(bus, dev, max, pass);}/** 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.*/pr_debug("PCI: Bus scan for %04x:%02x returning with max=%02x\n",pci_domain_nr(bus), bus->number, max);return max;
}

扫描PCI总线试过递归过程。每条PCI总线可以配置32个多功能设备,每个多功能设备又可以安装8个子设备,总共就是256个设备。这256个设备中,有的设备可能是PCI桥,每个PCI桥下面又可以介入256设备。通过函数 pci_scan_slot 扫描每个多功能设备的8个子设备,通过pci_scan_bridge函数扫描PCI桥设备。对于桥设备,还要递归调用spi_scan_child_bus函数扫描本 桥设备 下面可能接入的PCI设备。

8.2.3 扫描多功能设备

扫描PCI 多功能设备和扫描 桥设备 有重复的地方,因此本节以扫描多功能设备的函数 pci_scan_slot 为例进行分析,代码如下:

/*** pci_scan_slot - scan a PCI slot on a bus for devices.* @bus: PCI bus to scan* @devfn: slot number to scan (must have zero function.)** Scan a PCI slot on the specified PCI bus for devices, adding* discovered devices to the @bus->devices list.  New devices* will have an empty dev->global_list head.*/
int __devinit pci_scan_slot(struct pci_bus *bus, int devfn)
{int func, nr = 0;int scan_all_fns;scan_all_fns = pcibios_scan_all_fns(bus, devfn);for (func = 0; func < 8; func++, devfn++) {struct pci_dev *dev;dev = pci_scan_single_device(bus, devfn);if (dev) {nr++;/** If this is a single function device,* don't scan past the first function.*/if (!dev->multifunction) {if (func > 0) {dev->multifunction = 1;} else {break;}}} else {if (func == 0 && !scan_all_fns)break;}}return nr;
}

pci_scan_slot函数从 0 号设备开始进行扫描,如果发现是单功能设备,不再继续扫描,如果发现是多功能设备,则进行8次扫描

8.2.4 扫描单个设备

扫描单个设备调用 pci_scan_single_device 函数,输入参数是 总线结构 和 设备功能号,代码:

pci_scan_single_device(struct pci_bus *bus, int devfn)
{struct pci_dev *dev;dev = pci_scan_device(bus, devfn);if (!dev)return NULL;pci_device_add(dev, bus);pci_scan_msi_device(dev);return dev;
}

pci_scan_single_device 函数调用 pci_scan_device 扫描设备,扫描成功后把设备加入总线的设备链表。 最后的pci_scan_msi_device函数是检查设备的MSI能力MSI和 设备的中断有关。

8.2.5 扫描设备信息

扫描PCI设备 通过读取PCI设备的配置空间完成,这部分原理在 第3章介绍过。扫描设备的代码pci_scan_device 函数:

pci_scan_device(struct pci_bus *bus, int devfn)
{struct pci_dev *dev;u32 l;u8 hdr_type;int delay = 1;//--读PCI设备制造商的ID--if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))return NULL;/* some broken boards return 0 or ~0 if a slot is empty: */if (l == 0xffffffff || l == 0x00000000 ||l == 0x0000ffff || l == 0xffff0000)return NULL;/* Configuration request Retry Status */
/** --处理需要重复读配置信息的情况--  **/while (l == 0xffff0001) {msleep(delay);delay *= 2;if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))return NULL;/* Card hasn't responded in 60 seconds?  Must be stuck. */if (delay > 60 * 1000) {printk(KERN_WARNING "Device %04x:%02x:%02x.%d not ""responding\n", pci_domain_nr(bus),bus->number, PCI_SLOT(devfn),PCI_FUNC(devfn));return NULL;}}

pci_scan_device函数第一部分读PCI设备制造商的ID,所有制造商都要分配厂商的ID号,从ID号就可以获得设备厂商信息。

这部分代码要处理异常情况,某些设备可能返回重试状态,这种情况要延迟一段时间,再次尝试读制造商的ID,如果延迟时间超过60秒,还没有读到ID,则返回失败。

----pci_scan_device函数第二部分为设备分配一个PCI设备结构,然后根据设备配置空间读取的信息对设备进行赋值

/*** --- 读PCI设备的类型 --- ***/  if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type))return NULL;
/*** --- 申请一个PCI设备结构 --- ***/dev = kzalloc(sizeof(struct pci_dev), GFP_KERNEL);if (!dev)return NULL;
/*** --- 设置PCI设备的参数,包括类型、制造商、是否多功能 --- ***/dev->bus = bus;dev->sysdata = bus->sysdata;dev->dev.parent = bus->bridge;dev->dev.bus = &pci_bus_type;dev->devfn = devfn;dev->hdr_type = hdr_type & 0x7f;dev->multifunction = !!(hdr_type & 0x80);dev->vendor = l & 0xffff;dev->device = (l >> 16) & 0xffff;dev->cfg_size = pci_cfg_space_size(dev);dev->error_state = pci_channel_io_normal;/* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer)set this higher, assuming the system even supports it.  */
/*** --- 设置设备的dma 地址掩码 --- ***/dev->dma_mask = 0xffffffff;if (pci_setup_device(dev) < 0) {kfree(dev);return NULL;}return dev;
}

此时,只读取配置空间的制造商ID 和头部信息(HEADER_TYPE),信息的进一步读取在函数pci_setup_device中完成,这个函数同时设置PCI设备的信息:

/*** pci_setup_device - fill in class and map information of a device* @dev: the device structure to fill** Initialize the device structure with information about the device's * vendor,class,memory and IO-space addresses,IRQ lines etc.* Called at initialisation of the PCI subsystem and by CardBus services.* Returns 0 on success and -1 if unknown type of device (not normal, bridge* or CardBus).*/
static int pci_setup_device(struct pci_dev * dev)
{u32 class;sprintf(pci_name(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);class >>= 8;                   /* upper 3 bytes */dev->class = class;class >>= 8;pr_debug("PCI: Found %s [%04x/%04x] %06x %02x\n", pci_name(dev),dev->vendor, dev->device, class, dev->hdr_type);/* "Unknown power state" */dev->current_state = PCI_UNKNOWN;/* Early fixups, before probing the BARs */pci_fixup_device(pci_fixup_early, dev);class = dev->class >> 8;

pci_setup_device函数第一部分是读设备类的信息:

------高24位:class信息

------低8位  :revision信息,并根据读取的信息设置PCI设备

pci_setup_device函数第二部分根据设备类型读需要的信息

 switch (dev->hdr_type) {         /* header type */case PCI_HEADER_TYPE_NORMAL:           /* standard header */if (class == PCI_CLASS_BRIDGE_PCI)goto bad;
/*** ------   读中断信息   ------ ***/pci_read_irq(dev);
/*** ------   读配置空间的资源信息 6条信息  ------ ***/pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
/*** ------   读子系统厂商的ID   ------ ***/pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
/*** ------   读子系统的ID   ------ ***/pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);break;case PCI_HEADER_TYPE_BRIDGE:            /* bridge header */if (class != PCI_CLASS_BRIDGE_PCI)goto bad;/* The PCI-to-PCI bridge spec requires that subtractivedecoding (i.e. transparent) bridge must have programminginterface code of 0x01. */ pci_read_irq(dev);dev->transparent = ((dev->class & 0xff) == 1);pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);break;case PCI_HEADER_TYPE_CARDBUS:          /* CardBus bridge header */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:                    /* unknown header */printk(KERN_ERR "PCI: device %s has unknown header type %02x, ignoring.\n",pci_name(dev), dev->hdr_type);return -1;bad:printk(KERN_ERR "PCI: %s: class %x doesn't match header type %02x. Ignoring class.\n",pci_name(dev), class, dev->hdr_type);dev->class = PCI_CLASS_NOT_DEFINED;}/* We found a fine healthy device, go go go... */return 0;
}

设备有三种类型:通常的 PCI设备 、 PCI桥设备 和 CARDBUS设备。

每种设备都要读中断信息资源信息。PCI设备的配置空间提供两种资源,一种是I/O端口,另一种是I/O内存。

普通设备可以提供 个资源信息,桥设备 只有 2 个资源信息。

普通操作系统扫描PCI总线,目的是获得PCI设备的信息,然后为每个设备分配一个PCI设备结构。PCI总线扫描到设备之后,需要设备加载正确的驱动,这部分在67章中。

补充:

内核启动PCI总线枚举的过程中,跟踪到底层扫描总线上每个设备,都是通过每个设备的vendor ID来确定设备的有无,函数pci_bus_read_config_dword没有找到实现,只是找到了EXPORT_SYMBOL(pci_bus_read_config_dword)。

access.c 中

#define PCI_OP_WRITE(size,type,len) \
int pci_bus_write_config_##size \(struct pci_bus *bus, unsigned int devfn, int pos, type value) \
{                                   \int res;                           \unsigned long flags;                       \if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;  \spin_lock_irqsave(&pci_lock, flags);               \res = bus->ops->write(bus, devfn, pos, len, value);     \spin_unlock_irqrestore(&pci_lock, flags);          \return res;                            \
}

PCI_OP_READ(byte, u8, 1)
PCI_OP_READ(word, u16, 2)
PCI_OP_READ(dword, u32, 4)
PCI_OP_WRITE(byte, u8, 1)
PCI_OP_WRITE(word, u16, 2)
PCI_OP_WRITE(dword, u32, 4)

EXPORT_SYMBOL(pci_bus_read_config_byte);
EXPORT_SYMBOL(pci_bus_read_config_word);
EXPORT_SYMBOL(pci_bus_read_config_dword);
EXPORT_SYMBOL(pci_bus_write_config_byte);
EXPORT_SYMBOL(pci_bus_write_config_word);
EXPORT_SYMBOL(pci_bus_write_config_dword);

------看PCI_OP_READ宏定义,##是宏定义中用来字符串替换的,也就是将宏定义穿进来的参数字符串原封不动替换

这样也就有了函数pci_bus_read_config_byte/word/dword 等

PCI总线---PCI设备扫描过程相关推荐

  1. Linux PCI总线-PCI空间

    Linux PCI总线-PCI空间 2. PCI 总线地址空间映射 2.1 X86平台地址空间映射 2.2 龙芯平台地址空间映射 2.3 X86配置空间寄存器 2.4 龙芯平台配置空间寄存器 3. P ...

  2. 四、PCI总线上的数据传输过程

    本节所给的时序图主要表示总线以32位方式执行有关操作时,相应信号之间的关系.在具体图示中,当以信号以虚线画出时,则表示没有设备驱动它,但若此虚线处在基准位置时,仍然可表示它具有一个稳定的值:当三态信号 ...

  3. linux内核静态添加sdio设备,Linux下sdio设备扫描过程

    前言 本文基于Linux version 3.10.52版本代码分析sdio设备的扫描过程,同时选择sdio wifi设备作为分析对象,在分析过程中,附带上sdio的协议内容,帮助初学人员学习sdio ...

  4. UEFI中的PCI设备扫描及分配Mem/Io空间过程

    最近在调试解决PCI设备相关的问题,然后对UEFI下面的这部分实现有了初步的了解,为了防止后面慢慢的又忘记了,所以还是决定将最近的收获都记录起来,各位读者如果发现哪里记录或者总结的不对,欢迎留言指正. ...

  5. Linux源码阅读——PCI总线驱动代码(三)PCI设备枚举过程

    目录 前言 1.枚举过程 1.1 acpi_pci_root_add 1.2 pci_acpi_scan_root(枚举开始) 1.3 acpi_pci_root_create 1.4 pci_sca ...

  6. linux设备驱动之PCI总线概述

    文章目录 总线概念 PCI总线 PCI总线体系结构 PCI设备寻址 PCI寻址 配置寄存器 总线概念 总线是一种传输信号的信道:总线是连接一个或多个半导体的电气连线.总线由电气接口和编程接口组成,对于 ...

  7. Kernel PCI总线框架

    2019独角兽企业重金招聘Python工程师标准>>>     1,PCI总线介绍 在PC时代的早期,外部设备通过ISA总线接入计算机.ISA总线只有24根地址线,因此其上的外部设备 ...

  8. PCI总线的桥与配置(一)

    在PCI体系结构中,含有两类桥片,一个是HOST主桥,另一个是PCI桥.在每一个PCI设备中(包括PCI桥)都含有一个配置空间.这个配置空间由HOST主桥管理,而PCI桥可以转发来自HOST主桥的配置 ...

  9. PCI总线的桥与配置(二)

    PCI桥与PCI设备的配置空间 PCI设备都有独立的配置空间,HOST主桥通过配置读写总线事务访问这段空间.PCI总线规定了三种类型的PCI配置空间,分别是PCI Agent设备使用的配置空间,PCI ...

最新文章

  1. 操作系统:基本分页存储管理方式
  2. case class到底啥用
  3. CDATA and comment
  4. java 动态代理
  5. linux c头文件#include<sys/types.h>和#include<fcntl.h>头文件总结
  6. 七年级计算机上教学计划,初一教学计划模板锦集5篇
  7. 小程序文本高度左对齐问题
  8. CHD4B1(hadoop-0.23)实现NameNode HA安装配置
  9. [图:知识竞赛题库PPT制作] 为上海棒约翰餐饮管理有限公司定制的的知识竞赛题目及展示界面-PPT格式-双屏展示。
  10. 11款免费而强大的PCB设计软件 还用什么AD PADS?
  11. Halo博客搭建及配套小程序使用教程
  12. 条形码类型及常见条形码
  13. SAP中生产返工中单独的作业返冲处理分析测试
  14. c语言解三色旗问题加注释,三色旗问题(Three
  15. python耗时方法_Python中统计函数运行耗时的方法
  16. docker容器网络配置之容器间的链接(默认桥接网络下的links)
  17. oracle 创建索引和视图
  18. 李宏毅language课程Speach Recongition
  19. 如何查杀stopped进程
  20. Android应用启动之从Launcher拉起APP(三)

热门文章

  1. Caliburn.Micro 杰的入门教程3,事件和参数
  2. “bang” in JavaScript
  3. 1.3 创建弧形轴网
  4. 笔记本电脑卡顿问题原因
  5. 【unity3d】如何学习unity3d
  6. 20220425二次型复习
  7. Vysor 安装教程
  8. 网站访问量等数据统计
  9. 奥利给!2020年10月程序员工资最新统计,涨了!!!
  10. java的意思和含义,2022年最新