理解linux pci 扫描流程
  JDs12CsXAXHi 2023年11月02日 41 0

本文主要描述linux kernel进行pci总线扫描,linux kernel代码基于版本2.6.37

  1. 域 最先需要说明一个概念--域 百度百科:域之原义指地方的范围,后逐渐演变为数学、生物、科技等学科的某类单位的分类词语。 域英文叫DOMAIN (a set of websites on the Internet which end with the same group of letters,for exemple'.com','org'-------《牛津高阶英汉双解词典》) 出现在本文中,意义要窄一些,认为域是一个使用同一编址方式的范围。 比如: pci域,使用pci地址进行寻址 x86域,使用x86物理地址进行寻址 域有主导者,访问都通过它进行 pci域,主导者是pci root,如果需要访问一个pci设备,就需要通过主导者pci root进行,它能识别的地址是pci地址 x86域,主导者是x86 cpu,如果需要访问一个内存,就需要通过主导者x86 cpu进行,它能识别的地址是x86物理地址 如果引申一下有物理地址,线性地址,逻辑地址,虚拟地址,在另一篇文章中有提到。 本文中将的是pci域,所以所有的寻址都是使用pci地址
  2. pci总线 简单的介绍一下pci,有意愿的话,可以认真看看pci协议,到处都有得下,英文的。 2.1 pci设备 pci有三种设备,有一种叫cardbus没接触过不提,所以认为两种,普通设备和桥设备 普通设备的功能是系统需要的具体功能,具体功能最终也会归到数据传输,数据处理上面去。 如果一个pci设备是这么组成的 pci总线接口 -- 内存 -- 网络数据处理器 -- 网络接口 网络数据通过网络接口进到网络数据处理器,处理后放到pci设备的内存上,再通过pci总线接口将数据发送到pci总线上,于是另一个pci设备就能够获取网络数据,反方向就是发送网络数据 很明显,这个pci设备就是一个pci网卡 类似的 pci总线接口 -- 内存 -- 视频数据处理器 -- 视频接口 一个pci显卡 pci总线接口 -- 内存 -- 音频数据处理器 -- 音频接口 一个pci声卡

桥设备的功能是扩展pci总线,根据pci协议,一个总线上支持32个dev,每个dev支持8中功能,数量不够多,扩展功能是必要的。 命令lspci -t查看拓扑 -[0000:00]-+-00.0 +-02.0 +-1b.0 +-1c.0-[20]-- +-1c.1-[3f]----00.0 +-1d.0 +-1d.1 +-1d.2 +-1d.3 +-1d.7 +-1e.0-[05]----04.0 +-1f.0 +-1f.1 \-1f.2 pci总线0下有多个设备 普通设备00.0, 02.0, 1b.0, 1d.0-7, 1f.0-2 桥设备1c.0, 1c.1, 1e.0 桥1c.0扩展总线20, 1c.1扩展总线3f, 1e.0扩展总线05 总线20下无设备,总线3f下有设备3f:00.0,总线05下有设备05:04.0

2.2 pci配置空间 pci是一种总线,特点是灵活可配置,因为它有一个专门的区域叫配置空间。 配置空间通过io总线访问,x86是0xCF8,0xCFC,一个是控制端口一个是数据端口 命令可查看 sh-4.1# cat /proc/ioports ... 0cf8-0cff : PCI conf1 ... 桥设备和普通设备的配置空间内容不一样 桥设备的配置空间需要说明pci总线的拓扑,内存映射的地址范围等等 普通设备的配置空间需要说明该设备 内存映射的地址范围,pci的中断号等等 讲配置空间的网上有很多很优秀的文章,不贴过来了

2.3 pci内存映射空间 考虑一个pci声卡 pci总线接口 -- 内存 -- 音频数据处理器 -- 音频接口 音频数据处理器处理完的数据存放到pci设备的内存中,如何在pci总线上访问到这块内存从而获取其中的音频数据呢? 前面说pci配置空间说明内存映射空间地址范围,即将这块内存映射到pci总线域的哪块地址,这样在pci总线域中访问这块地址就是访问这块内存 内存映射空间一般有一个或者多个,看一个真实的显卡 00:02.0 VGA compatible controller: Intel Corporation 82945G/GZ Integrated Graphics Controller (rev 02) (prog-if 00 [VGA controller]) Subsystem: Hewlett-Packard Company Device 3010 Flags: bus master, fast devsel, latency 0, IRQ 16 Memory at e0400000 (32-bit, non-prefetchable) [size=512K] I/O ports at 20c0 [size=8] Memory at d0000000 (32-bit, prefetchable) [size=256M] Memory at e0480000 (32-bit, non-prefetchable) [size=256K] Expansion ROM at Kernel driver in use: i915 Kernel modules: i915 这里面的信息都是从配置空间获取的,可以看到有4个内存映射空间 Memory at e0400000 (32-bit, non-prefetchable) [size=512K] I/O ports at 20c0 [size=8] Memory at d0000000 (32-bit, prefetchable) [size=256M] Memory at e0480000 (32-bit, non-prefetchable) [size=256K] 只是第二个有些特殊是io port的映射区间,另外的三个都是真实的内存映射区间,第三个对应的就是显卡上的256MB显存

  1. 扫描过程 经过前面内容,可以猜想pci设备的初始化流程应该是 a. 通过io总线配置pci配置空间 b. 通过io总线读取pci配置空间信息,获取内存映射空间的映射地址信息 c. 通过读写内存映射空间实现数据传输 看上去显得很简单,但是简单的说就是这么简单 如果只有一个总线,下面又只挂了一个pci设备,那么我认为上面的流程及足够了,上面的流程也是pci驱动初始化pci设备的基本流程。 由于大量的pci桥和pci普通设备有可能出现在pci总线上,所以实际情况比这个复杂 3.1 BIOS pci 扫描 最先进行pci扫描的是x86上的BIOS,扫描的结果是各个pci普通设备和pci桥都被配置完成,但是这种配置有可能是不能用的。 原因需要回到域的概念,简单的考虑,x86系统中存在两个域,x86域和pci域,由于x86系统的特殊性,两个域被设计成对等映射的关系。 也就是说,虽然x86域中使用x86地址进行访问,pci域中使用pci地址进行访问,但是两者对同一个实体的地址是相等的。这样带来的问题是两个域的地址不能重合,需要分开。 命令cat /proc/iomem可查看 00000000-00000fff : reserved 00001000-0009fbff : System RAM 0009fc00-0009ffff : reserved 000a0000-000bffff : Video RAM area 000c0000-000c7fff : Video ROM 000cd400-000e7fff : pnp 00:0e 000e8000-000fffff : reserved 000f0000-000fffff : System ROM 00100000-7f7c82ff : System RAM 00400000-007aca75 : Kernel code 007aca76-00a1cf6f : Kernel data 00aa8000-00c2b8bf : Kernel bss 7f7c8300-7fffffff : reserved 80000000-801fffff : PCI Bus 0000:20 80200000-803fffff : PCI Bus 0000:20 80400000-805fffff : PCI Bus 0000:3f 80600000-806fffff : PCI Bus 0000:05 80600000-8061ffff : 0000:05:04.0 d0000000-dfffffff : 0000:00:02.0 e0400000-e047ffff : 0000:00:02.0 e0480000-e04bffff : 0000:00:02.0 e04c0000-e04c3fff : 0000:00:1b.0 e04c0000-e04c3fff : ICH HD audio e04c4000-e04c43ff : 0000:00:1d.7 e04c4000-e04c43ff : ehci_hcd e0500000-e07fffff : PCI Bus 0000:3f e0500000-e050ffff : 0000:3f:00.0 e0500000-e050ffff : tg3 e0800000-e0afffff : PCI Bus 0000:05 e0a00000-e0a000ff : 0000:05:04.0 e0a00000-e0a000ff : r8169 f0000000-f3ffffff : reserved f0000000-f3ffffff : pnp 00:0e f0000000-f1ffffff : PCI MMCONFIG 0000 [bus 00-1f] fec00000-fed3ffff : reserved fec00000-fec003ff : IOAPIC 0 fec01000-fed3ffff : pnp 00:0e fed45000-ffffffff : reserved fee00000-fee00fff : Local APIC 后面一段基本都是pci设备,前面一段基本都是ram,两者没有重合部分 回到BIOS配置可能不可用的问题,如果linux kernel将系统内存也安排到了BIOS配置pci的地址区域,出现重合,系统就乱了。

3.2 kernel扫描 既然BIOS配置可能不可用,那么只能kernel自己重新扫一次,合理安排pci地址空间和x86空间的地址了。 内核有个配置选项与此相关 CONFIG_PCI_BIOS CONFIG_PCI_MMCONFIG CONFIG_PCI_DIRECT 网上有相关说明,不贴了 3.2.1 扫描流程 cat System.map | grep pci | grep __initcall ffffffff8170c818 t __initcall_pci_reboot_init1 ffffffff8170c8c8 t __initcall_pcibus_class_init2 ffffffff8170c8d0 t __initcall_pci_driver_init2 ffffffff8170c918 t __initcall_acpi_pci_init3 ffffffff8170c940 t __initcall_pci_arch_init3 ffffffff8170c9b8 t __initcall_pci_slot_init4 ffffffff8170c9d0 t __initcall_acpi_pci_root_init4 ffffffff8170c9d8 t __initcall_acpi_pci_link_init4 ffffffff8170ca48 t __initcall_pci_subsys_init4 ffffffff8170caf0 t __initcall_pcibios_assign_resources5 ffffffff8170cb18 t __initcall_pci_apply_final_quirks5s ffffffff8170cb28 t __initcall_pci_iommu_initrootfs ffffffff8170ced0 t __initcall_pci_proc_init6 ffffffff8170ced8 t __initcall_pcie_portdrv_init6 ffffffff8170cfa8 t __initcall_serial8250_pci_init6 ffffffff8170cfd0 t __initcall_hsu_pci_init6 ffffffff8170d010 t __initcall_ide_scan_pcibus6 ffffffff8170d410 t __initcall_pci_resource_alignment_sysfs_init7 ffffffff8170d418 t __initcall_pci_sysfs_init7 ffffffff8170d440 t __initcall_pci_mmcfg_late_insert_resources7 这些都是在kernel启动过程中会执行的pci相关函数,有些明显不是通用的可以忽略 整个扫描流程分为两部分 扫描设备 和 分配资源 3.2.1.1 扫描设备 从主桥开始扫 pcibios_scan_root(0)

pci_scan_bus_parented() pci_scan_child_bus() for (devfn = 0; devfn < 0x100; devfn += 8) pci_scan_slot(bus, devfn); 前面有说一个总线下支持32个dev,每个dev支持8个fn,这里就是扫描总线下所有的dev 所有的dev有普通pci设备,还有pci桥 pci桥会扩展出另一个总线号,其下可能挂有其他设备,所以还需要扫描找到的pci桥 pci_scan_single_device() pci_scan_device() pci_setup_device() pci_read_irq() pci_read_bases() __pci_read_base() pci_size() 获取pci设备信息,中断号,内存映射区域大小和地址等等 pci_device_add() list_add_tail(&dev->bus_list, &bus->devices) 扫到的设备全存放在 bus->devices链表中,后面会有很多扫描依赖这个链表 常见的比如: list_for_each_entry(dev, &bus->devices, bus_list) 就是循环该bus下所有pci设备 pci_scan_bridge() pci_find_bus() pci_add_new_bus() 桥扩展出来的总线给一个新的总线号 pci_scan_child_bus() 递归扫描新扫到的pci桥 这样是扫描到桥就往深处走,称深度优先扫描,将所有的pci设备扫描完毕,并获取内存映射区域信息

3.2.1.2 分配资源 从主桥开始 __pci_bus_assign_resources()

pbus_assign_resources_sorted() __dev_sort_resources() pdev_sort_resources() pci_resource_alignment() resource_alignment() resource_size() 将bus下所有dev所需要占用的资源都放入一个链表上--head __assign_resources_sorted() assign_requested_resources_sorted() resource_size() pci_assign_resource() pci_resource_alignment() __pci_assign_resource() pci_bus_alloc_resource()

### allocate a resource from a parent bus

从bus所含资源中给链表head上所注明资源分配空间 >>> allocate_resource() >>> find_resource() 在制定范围内寻找合适的空间[min, max] min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM; max = PCIBIOS_MAX_MEM_32; 匹配方式可看resource_clip(),移动最小匹配 >>> __request_resource() 分配 >>> request_resource_conflict() ### request and reserve an I/O or memory resource >>> __request_resource() >>> pci_update_resource() >>> pci_resource_bar() 更新配置空间 >>> adjust_resources_sorted() >>>

__pci_bus_assign_resources()

recursion

pci_setup_bridge() 更新桥的配置空间,桥所包含地址范围 __pci_setup_bridge() pci_setup_bridge_io() res = bus->resource[0] pcibios_resource_to_bus(bridge, &region, res)

### write to io base and limit
    >>> pci_setup_bridge_mmio()
        >>>  res = bus->resource[1]
        >>> pcibios_resource_to_bus(bridge, ®ion, res)
         ### write to mem base and limit
    >>> pci_setup_bridge_mmio_pref()
         >>> res = bus->resource[2]
        >>>  pcibios_resource_to_bus(bridge, ®ion, res)
        ###  write to pref mem base and limit

下面只是列出较具体流程,代码就不贴了 3.2.2 pci_reboot_init

dmi_check_system(pci_reboot_dmi_table) 只有apple相关产品会做一些修正,不关心

3.2.3 pcibus_class_init

class_register(&pcibus_class) release_pcibus_dev() pci_bus_remove_resources() 注册pci class

3.2.4 pci_driver_init()

bus_register(&pci_bus_type) bus_register(&pci_bus_type) pci_bus_match() pci_match_device() pci_match_id() pci_match_one_device()

### id->vendor == dev->vendor, id->device == dev->device
    >>> pci_uevent()
        >>> add_uevent_var()
        ### add key value string to the environment buffer
    >>> pci_device_probe()
        >>> pci_match_device()
        >>> pci_call_probe()
            >>> local_pci_probe()
                >>> ddi->drv->probe(ddi->dev, ddi->id)
    >>> pci_device_remove()
        >>> drv->remove(pci_dev)
    >>> pci_device_shutdown()
        >>> drv->shutdown(pci_dev)
        >>> pci_msi_shutdown(pci_dev)
        >>> pci_msix_shutdown(pci_dev)
    >>> pci_dev_attrs
    >>> pci_bus_attrs
        >>> bus_rescan_store()
            >>> b = pci_find_next_bus()
            >>> pci_rescan_bus(b)

注册pci驱动,但是这里注意 pci_rescan_bus(b)在pci hotplug中是很有用的,另一篇文章有提到

3.2.5 acpi_pci_init() 从ACPI获取信息,ACPI还是很复杂的,可以简单看作一堆表示系统相关信息的表

3.2.6 pci_arch_init()

pci_direct_probe() request_region(0xCF8, 8, "PCI conf1") 这里就是保留io总线访问配置空间的资源,0xCF8 0xCFC pci_mmcfg_early_init() __pci_mmcfg_init() acpi_sfi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg)

### mmconfig address is coming from ACPI table

MMCONFIG对于访问pcie设备配置空间很有作用,因为PCI的配置空间是256,而PCIE的配置空间是4K,多出的部分io总线访问不了,需要mmconfig进行访问 >>> pci_parse_mcfg() >>> pci_mmconfig_add() >>> pci_mmcfg_reject_broken() >>> is_mmconf_reserved() >>> pci_mmcfg_arch_init()

x86_init.pci.arch_init() pci_acpi_init()

### about pci irq

pci_pcbios_init() >>> pci_find_bios()

### find BIOS32 0xe0000 - 0xfffff

pci_direct_init()

###  raw_pci_ops = &pci_direct_conf1

dmi_check_pciprobe() dmi_check_skip_isa_align()

3.2.7 pci_slot_init()

pci_slots_kset = kset_create_and_add("slots", NULL, &pci_bus_kset->kobj)

3.2.8 acpi_pci_root_init()

acpi_hest_init() pci_acpi_crs_quirks() acpi_bus_register_driver(&acpi_pci_root_driver)

about irq

3.2.9 acpi_pci_link_init()

acpi_bus_register_driver(&acpi_pci_link_driver)

about irq

3.2.10 pci_subsys_init()

pci_legacy_init() pci_root_bus = pcibios_scan_root(0) bus = pci_scan_bus_parented(NULL, busnum, &pci_root_ops, sd) b = pci_create_bus(parent, bus, ops, sysdata) b->subordinate = pci_scan_child_bus(b)

### ...
>>> pci_bus_add_devices(pci_root_bus)
    >>> pci_bus_add_device()
    ### insert newly discovered PCI devices
    >>> pci_bus_add_child()
    ### add a child bus

pcibios_fixup_peer_bridges() x86_init.pci.init_irq() pcibios_init() pcibios_resource_survey() pcibios_allocate_bus_resources()

### for PCI_BRIDGE_RESOURCES, for bridge
        >>> pci_claim_resource()
            >>> pci_find_parent_resource()
            >>> request_resource_conflict()
                >>> __request_resource()
        >>> pcibios_allocate_bus_resources()
        ### recursion
    >>> pcibios_allocate_resources()
    ### from  PCI_STD_RESOURCES to PCI_STD_RESOURCE_END
        >>>  pci_claim_resource()
    >>>  e820_reserve_resources_late()
    >>> ioapic_insert_resources()
        >>> insert_resource(&iomem_resource, ioapic_resources)

3.2.11 pcibios_assign_resources()

pci_assign_unassigned_resources() __pci_bus_size_bridges() __pci_bus_size_bridges() pci_bridge_check_ranges() pbus_size_io() pbus_size_mem() __pci_bus_assign_resources() pbus_assign_resources_sorted() __pci_bus_assign_resources() pci_setup_bridge() __pci_setup_bridge() pci_setup_bridge_io() pci_setup_bridge_mmio() pci_setup_bridge_mmio_pref() pci_enable_bridges() pci_bus_dump_resources()

3.3 kernel扫描关键的四个函数 3.3.1 pci_scan_child_bus()

pci_scan_slot()

scan a PCI slot on a bus for devices

>>> pci_scan_single_device()
    >>>  pci_get_slot()
    ### check devfn is exist or not under bus
    >>>  pci_scan_device()
    ### check VID and DID to decide devfn is exist or not
        >>> alloc_pci_dev()
        >>> pci_setup_device()
            >>> pci_fixup_device(pci_fixup_early, dev)
            ### do early fixup here
            >>> pci_read_bases()
            ### read pci bar 0 - 5
                >>> __pci_read_base()
                ### read a pci bar for region and size
    >>>  pci_device_add()
        >>>  pci_fixup_device(pci_fixup_header, dev)
        >>> list_add_tail(&dev->bus_list, &bus->devices)
        ### scan all devices under one bus, then add them (dev->bus_list) to bus->devices

pci_iov_bus_range() pcibios_fixup_bus() pci_read_bridge_bases() pci_bus_remove_resources(child) pci_read_bridge_io(child) pci_read_bridge_mmio(child) pci_read_bridge_mmio_pref(child) pci_bus_add_resource() pci_scan_bridge()

find a bridge, then want to find bus under the bridge in system.

>>> pci_find_bus()
### find the bus
    >>> pci_do_find_bus()
        >>> pci_do_find_bus()
        ### recursion
>>> pci_add_new_bus()
### do not find the bus, so add a new one
>>> pci_scan_child_bus()
###  recursion
>>>  pci_fixup_parent_subordinate_busnr()

3.3.2 pci_bus_add_devices()

pci_bus_add_device() pci_bus_add_devices()

recursion

pci_bus_add_child()

3.3.3 __pci_bus_size_bridges()

if it is bridge, recursion

__pci_bus_size_bridges()

for bridge

pci_bridge_check_ranges()

check support 64bit or not

pbus_size_io()

calc io resource of all devices under this bus

pbus_size_mem()

calc mem resource of all devices under this bus

3.3.4 __pci_bus_assign_resources()

pbus_assign_resources_sorted() __dev_sort_resources() pdev_sort_resources() pci_resource_alignment() resource_alignment() resource_size() __assign_resources_sorted() assign_requested_resources_sorted() resource_size() pci_assign_resource() pci_resource_alignment() __pci_assign_resource() pci_bus_alloc_resource()

### allocate a resource from a parent bus
                    >>> allocate_resource()
                        >>> find_resource()
                        >>> __request_resource()
                >>> request_resource_conflict()
                    ### request and reserve an I/O or memory resource
                    >>> __request_resource()
                >>> pci_update_resource()
                    >>> pci_resource_bar()
    >>> adjust_resources_sorted()
        >>>

__pci_bus_assign_resources()

recursion

pci_setup_bridge() __pci_setup_bridge() pci_setup_bridge_io() res = bus->resource[0] pcibios_resource_to_bus(bridge, &region, res)

### write to io base and limit
    >>> pci_setup_bridge_mmio()
        >>>  res = bus->resource[1]
        >>> pcibios_resource_to_bus(bridge, ®ion, res)
         ### write to mem base and limit
    >>> pci_setup_bridge_mmio_pref()
         >>> res = bus->resource[2]
        >>>  pcibios_resource_to_bus(bridge, ®ion, res)
        ###  write to pref mem base and limit
  1. 总结 linux pci 扫描流程 a. BIOS先扫描,但是扫描的结果可能不能直接用。 b. linux kernel扫描分两步, 扫描设备和分配资源。 c. 扫描设备会从主桥开始,按照深度优先方式扫描所有设备,如果遇到一个桥设备则扩展一个总线号,并对该总线进行扫描。 d. 扫描会获取pci总线拓扑和各个设备所需要的资源信息。 e. 分配资源也从主桥开始,按照深度优先方式扫描所有设备,并按需为设备分配资源和更新配置信息。 f. 桥根据其下设备所分配的资源更新配置范围,以供pci总线路由。 g. x86域的地址资源和pci域的地址资源是对等映射,所以分配需考虑到两者不能冲突。
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  anLrwkgbyYZS   2023年12月30日   28   0   0 i++iosi++ioscici
  anLrwkgbyYZS   2023年12月30日   34   0   0 ideciciMaxideMax
JDs12CsXAXHi
作者其他文章 更多