关于PCI的配置

  1. #
  2. # Bus options (PCI etc.)
  3. #
  4. CONFIG_PCI=y
  5. CONFIG_PCI_DIRECT=y
  6. CONFIG_PCI_MMCONFIG=y
  7. CONFIG_PCI_XEN=y
  8. CONFIG_PCI_DOMAINS=y
  9. # CONFIG_PCI_CNB20LE_QUIRK is not set
  10. CONFIG_PCIEPORTBUS=y
  11. CONFIG_HOTPLUG_PCI_PCIE=y
  12. CONFIG_PCIEAER=y
  13. # CONFIG_PCIE_ECRC is not set
  14. # CONFIG_PCIEAER_INJECT is not set
  15. CONFIG_PCIEASPM=y
  16. CONFIG_PCIEASPM_DEBUG=y
  17. CONFIG_PCIEASPM_DEFAULT=y
  18. # CONFIG_PCIEASPM_POWERSAVE is not set
  19. # CONFIG_PCIEASPM_POWER_SUPERSAVE is not set
  20. # CONFIG_PCIEASPM_PERFORMANCE is not set
  21. CONFIG_PCIE_PME=y
  22. CONFIG_PCIE_DPC=y
  23. CONFIG_PCIE_PTM=y
  24. CONFIG_PCI_BUS_ADDR_T_64BIT=y
  25. CONFIG_PCI_MSI=y
  26. CONFIG_PCI_MSI_IRQ_DOMAIN=y
  27. CONFIG_PCI_QUIRKS=y
  28. # CONFIG_PCI_DEBUG is not set
  29. CONFIG_PCI_REALLOC_ENABLE_AUTO=y
  30. CONFIG_PCI_STUB=m
  31. CONFIG_XEN_PCIDEV_FRONTEND=m
  32. CONFIG_PCI_ATS=y
  33. CONFIG_PCI_LOCKLESS_CONFIG=y
  34. CONFIG_PCI_IOV=y
  35. CONFIG_PCI_PRI=y
  36. CONFIG_PCI_PASID=y
  37. CONFIG_PCI_LABEL=y
  38. CONFIG_PCI_HYPERV=m
  39. CONFIG_HOTPLUG_PCI=y
  40. CONFIG_HOTPLUG_PCI_ACPI=y
  41. CONFIG_HOTPLUG_PCI_ACPI_IBM=m
  42. CONFIG_HOTPLUG_PCI_CPCI=y
  43. CONFIG_HOTPLUG_PCI_CPCI_ZT5550=m
  44. CONFIG_HOTPLUG_PCI_CPCI_GENERIC=m
  45. CONFIG_HOTPLUG_PCI_SHPC=m
  46. #
  47. # DesignWare PCI Core Support
  48. #
  49. CONFIG_PCIE_DW=y
  50. CONFIG_PCIE_DW_HOST=y
  51. CONFIG_PCIE_DW_PLAT=y
  52. #
  53. # PCI host controller drivers
  54. #
  55. CONFIG_VMD=m
  56. #
  57. # PCI Endpoint
  58. #
  59. CONFIG_PCI_ENDPOINT=y
  60. CONFIG_PCI_ENDPOINT_CONFIGFS=y
  61. # CONFIG_PCI_EPF_TEST is not set

第一阶段初始化

pure_initcall系列初始化

入口参数-early_param(复习)

  1. pci.c:6564:pure_initcall(pci_realloc_setup_params);
  2. pci_realloc_setup_params 这个函数是GRUB引导时可以传入一些PCI的配置参数,
  3. 这里先不深入研究,可以参考《Linux那些事之PCIP23
  4. early_param("pci", pci_setup);
  5. pci_setup(char *str)

ubuntu 修改命令行参数方式:
/etc/default/grub

GRUB_CMDLINE_LINUX=
修改完后,sudo update-grub && sudo update-grub2
最后在 /boot/grub/grub.cfg中表现出来: linux /boot/vmlinuz-…… root=xxxxx

参考:Linux内核引导参数简介
与PCI相关的命令行参数:
如果在grub文件kernel那一行添加有“pci=”这样的东东,在调用那些入口函数之前,就必须得先调用一个pci_setup函数来解析这部分内核参数。pci_setup函数在drivers/pci/pci.c里有定义

  1. [PCI]
  2. pci=option[,option...]
  3. off
  4. [IA-32]不检测PCI总线,也就是关闭所有PCI设备。
  5. bios
  6. [IA-32]强制使用PCI BIOS而不是直接访问硬件,这表示内核完全信任BIOS(大多数情况下它并不可信)。仅在你的机器有一个不标准的PCI host bridge的时候才用。
  7. nobios
  8. [IA-32]强制直接访问硬件而不使用PCI BIOS2.6.13之后这是默认值。如果你确定在内核引导时的崩溃是由BIOS所致就可以使用它。
  9. conf1
  10. [IA-32]强制硬件设备使用PCI Configuration Mechanism 1访问PCI Memory以与内核中的驱动程序进行通信。
  11. conf2
  12. [IA-32]强制硬件设备使用PCI Configuration Mechanism 2访问PCI Memory以与内核中的驱动程序进行通信。
  13. nommconf
  14. [IA-32,X86_64]禁止为 PCI Configuration 使用 MMCONFIG 表。
  15. nomsi
  16. [MSI]如果启用了PCI_MSI内核配置选项,那么可以使用这个参数在系统范围内禁用MSI中断。
  17. nosort
  18. [IA-32]不在检测阶段根据PCI BIOS给出的顺序对PCI设备进行排序。进行这样的排序是为了以与早期内核兼容的方式获取设备序号。
  19. biosirq
  20. [IA-32]使用PCI BIOS调用来获取中断路由表。这些调用在不少机器上都有缺陷,会导致系统在使用过程中挂起。但是在某些机器上却是唯一获取中断路由表的手段。如果内核无法分配IRQ或者发现了第二个PCI总线,就可以尝试使用这个选项解决问题。
  21. rom
  22. [IA-32]为扩展ROM分配地址空间。使用此选项要小心,因为某些设备在ROM与其它资源之间共享地址×××。
  23. pirqaddr=0xAAAAA
  24. [IA-32]指定物理地址位于F0000h-100000h范围之外的PIRQ表(通常由BIOS产生)的物理地址。
  25. lastbus=N
  26. [IA-32]扫描所有总线,直到第N个总线。如果内核找不到第二条总线的时候,你就需要使用这个选项明确告诉它。
  27. assign-busses
  28. [IA-32]总是使用你自己指定的PCI总线号(而不是firmware提供的)。
  29. usepirqmask
  30. [IA-32]优先使用可能存在于BIOS $PIR表中的IRQ掩码。某些有缺陷的BIOS需要这个选项,特别是在HP Pavilion N5400Omnibook XE3笔记本上。如果启用了ACPI IRQ路由的话,将不会考虑这个选项的设置。
  31. noacpi
  32. [IA-32]不为IRQ路由或者PCI扫描使用ACPI
  33. routeirq
  34. 为所有PCI设备执行IRQ路由。这个通常在pci_enable_device()中执行,所有这是一个解决不调用此函数的bug驱动程序的临时解决方法。
  35. bfsort
  36. 按照宽度优先的顺序对PCI设备进行排序。进行这样的排序是为了以与2.4内核兼容的方式获取设备序号。
  37. nobfsort
  38. 不按照宽度优先的顺序对PCI设备进行排序。
  39. cbiosize=nn[KMG]
  40. CardBus bridge IO 窗口接受的固定长度的总线空间(bus space),默认值是256字节。
  41. cbmemsize=nn[KMG]
  42. CardBus bridge memory 窗口接受的固定长度的总线空间(bus space),默认值是64MB
  43. # max payload配置选择,默认是PCIE_BUS_DEFAULT
  44. pcie_bus_config = PCIE_BUS_TUNE_OFF;
  45. pcie_bus_config = PCIE_BUS_SAFE;
  46. pcie_bus_config = PCIE_BUS_PERFORMANCE;
  47. pcie_bus_config = PCIE_BUS_PEER2PEER;

postcore_initcall系列初始化

sys/class/pci注册

  1. // probe.c:108:postcore_initcall(pcibus_class_init);
  2. // driver/pci/probe.c
  3. // 注册 /sys/class/pci_bus接口
  4. static struct class pcibus_class = {
  5. .name = "pci_bus",
  6. .dev_release = &release_pcibus_dev,
  7. .dev_groups = pcibus_groups,
  8. };
  9. static int __init pcibus_class_init(void)
  10. {
  11. return class_register(&pcibus_class); // /sys/class/pci_bus
  12. }
  13. postcore_initcall(pcibus_class_init);

pci_bus注册和/sys/bus/pci/初始化

  1. // pci-driver.c:1681:postcore_initcall(pci_driver_init);
  2. // driver/pci/pci-driver.c
  3. // 注册PCI BUS,所以/sys/bus/pci目录存在,且子目录device和driver目录存在
  4. struct bus_type pci_bus_type = {
  5. .name = "pci",
  6. .match = pci_bus_match,
  7. .uevent = pci_uevent,
  8. .probe = pci_device_probe,
  9. .remove = pci_device_remove,
  10. .shutdown = pci_device_shutdown,
  11. .dev_groups = pci_dev_groups,
  12. .bus_groups = pci_bus_groups,
  13. .drv_groups = pci_drv_groups,
  14. .pm = PCI_PM_OPS_PTR,
  15. .num_vf = pci_bus_num_vf,
  16. .dma_configure = pci_dma_configure,
  17. };
  18. pci_driver_init
  19. bus_register(&pci_bus_type); // /sys/bus/pci/

arch_initcall系列(重点)

注:DMI信息参考《Linux那些事之PCI》P42中详解,不考虑这些相关函数

pci_arch_init(重点)

X86开始扫描检测PCI设备

  1. // init.c:45:arch_initcall(pci_arch_init);
  2. pci_arch_init
  3. if (!(pci_probe & PCI_PROBE_NOEARLY))
  4. pci_mmcfg_early_init(); // 这里会初始化ECAM,参考下节配置空间访问 详解
  5. // 《Linux那些事之PCI》P5中描述了三种PCI access mode,
  6. // 内核中CONFIG_PCI_DIRECT这个宏有配直接Direct去访问
  7. pci_direct_probe(); // 判断是否为PCI桥设备,下边详解
  8. if (x86_init.pci.arch_init && !x86_init.pci.arch_init()) // 函数没实现,哈哈
  9. return 0;
  10. // pci_pcbios_init(); // CONFIG_PCI_BIOS--不配置,不用看
  11. pci_direct_init(type); // raw_pci_ops = raw_pci_ext_ops 预留读写pci配置空间的接口

pci_direct_probe 详解

  1. pci_probe & PCI_PROBE_CONF1 # 判断,什么是CONF1和CONF2
  2. request_region(0xCF8, 8, "PCI conf1") # 为什么使用0xCF8
  3. pci_check_type1() # 检测type1
  4. raw_pci_ops = &pci_direct_conf1; # 初始化pci配置空间操作接口

CONF1和CONF2

《Linux那些事之PCI》P36-P43 这部分讲解很详细了

  1. // 相关宏配置:arch/x86/include/asm/pci_x86.h
  2. unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 |
  3. PCI_PROBE_MMCONF; // 其中PCI_PROBE_CONF1和PCI_PROBE_CONF2
  4. // 这里不考虑主动传入命令行参数情况

这些配置在《Linux那些事之PCI》P27说过,其他都

PCI设备的访问方式有BIOS、Direct的conf1和conf2(兼容老主板)、MMConfig PCI_PROBE_BIOS对应了BIOS方式, PCI_PROBE_MMCONF对应了MMConfig方式,这好理解,看名字就知道了, 不好理解的是PCI_PROBE_CONF1和PCI_PROBE_CONF2都对应了Direct方式:

因为曾经有过两种PCI Configuration Mechanism, 内核要想不通过BIOS直接去访问设备的话,也必须得对应有两种访问方式,即这里的conf1和conf2(也是TYPE1和TYPE2)。

Type2主要是在PCI发展的少年时期,某些主桥用过,现在一般都不会再用了,但是为了兼容一些老的主板,conf2还是保留了下来。

PCIE的IO端口和配置空间访问
  1. request_region(0xCF8, 8, "PCI conf1")

X86使用0xCF8(bus+device+),0xCFC作为IO端口来访问PCI,对PCI主桥校验

  1. baiy@ubuntu:output$ sudo cat /proc/ioports | grep PCI
  2. 0000-0cf7 : PCI Bus 0000:00
  3. 0cf8-0cff : PCI conf1 # 访问基础配置空间
  4. 0d00-feff : PCI Bus 0000:00
  5. 2000-3fff : PCI Bus 0000:02
  6. 4000-4fff : PCI Bus 0000:03
  7. 5000-5fff : PCI Bus 0000:0b
  8. 6000-6fff : PCI Bus 0000:13
  9. 7000-7fff : PCI Bus 0000:1b
  10. 8000-8fff : PCI Bus 0000:04
  11. 9000-9fff : PCI Bus 0000:0c
  12. a000-afff : PCI Bus 0000:14
  13. b000-bfff : PCI Bus 0000:1c
  14. c000-cfff : PCI Bus 0000:05
  15. d000-dfff : PCI Bus 0000:0d
  16. e000-efff : PCI Bus 0000:15
  17. baiy@ubuntu:output$

这就是PCI基础配置空间读写的真实方法:

  1. inno@DEV-005:~$ sudo cat /proc/ioports | grep "PCI conf"
  2. 0cf8-0cff : PCI conf1
  3. // arch/x86/direct.c
  4. #define PCI_CONF1_ADDRESS(bus, devfn, reg) \
  5. (0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \
  6. | (devfn << 8) | (reg & 0xFC))
  7. outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8); // 配置地址
  8. u32 value = inl(0xCFC); // 读取配置

深入PCI与PCIe之二:软件篇 中关于CONFIG_ADDRESS描述:
CONFIG_ADDRESS寄存器格式:
31 位:Enabled位。
23:16 位:总线编号。 // bus
15:11 位:设备编号。 // devfn[7:3]
10: 8 位:功能编号。 // devfn[2:0]
7: 2 位:配置空间寄存器编号。 // 配置空间偏移地址, 注:因为是32位端口,所以4字节访问。
1: 0 位:恒为“00”。这是因为CF8h、CFCh端口是32位端口。

看到这里有个疑问:配置空间寄存器编号 只有8bit,所以只能访问到 0-255的配置空间,如何访问PCI设备的拓展配置空间?下一章会描述ECAM机制

校验主桥过程
  1. // IO空间中0xCF80xCFC留给PCI使用,0xCF8作为地址信息,0xCFC数据交互
  2. request_region(0xCF8, 8, "PCI conf1") // 申请8字节的IO端口资源
  3. // 这里肯定是CONF1,所以不考虑其他情况
  4. pci_check_type1() // 读取HOST主桥的配置空间,看是否可以读取出来 -书上详解,这里不再重复
  5. //??? 其实这段代码很有悬念,感觉就像检查下端口是否好用,和检测下北桥
  6. // 对这句话的官方解释如下:
  7. // 问题:https://www.cs.helsinki.fi/linux/linux-kernel/2003-01/0553.html
  8. // 回复:https://www.cs.helsinki.fi/linux/linux-kernel/2003-01/1060.html
  9. // "It is trying to verify that the PCI northbridge does *NOT* respond to this (byte-wide) reference".
  10. outb(0x01, 0xCFB); // 前边说过, CONFIG_ADDRESS[1:0]永远都为2b00,瞬间打脸,这里只测试下北桥,没有意义
  11. tmp = inl(0xCF8); // 保存0xCF8的状态
  12. // 同上,也是迷之代码
  13. outl(0x80000000, 0xCF8);
  14. 判断inl(0xCF8) == 0x80000000
  15. // ******重点:测试HOST-bridge是否存在*******
  16. /*
  17. * 在决定使用直接硬件访问机制之前,我们尝试进行一些琐碎的检查,以确保它至少_似乎正常运行
  18. * 我们只测试总线00是否包含主桥
  19. */
  20. pci_sanity_check // pci_direct_conf1接口尝试读取总线0上所有设备的 device IDVendor ID
  21. for (devfn = 0; devfn < 0x100; devfn++) {
  22. pci_direct_conf1->read(); // 遍历bus0下所有设备,寻找VGA/HOST_BRIDGE ,肯定是要能找到的。
  23. raw_pci_ops = &pci_direct_conf1;
  24. port_cf9_safe = true;
  25. return 1; // 从这里返回
  26. release_region(0xCF8, 8); // 释放8字节IO端口资源, 基本上不会走到这里,因为肯定有主桥,且永久占用
  • inb 从I/O端口读取一个字节(BYTE, HALF-WORD) ;
  • outb 向I/O端口写入一个字节(BYTE, HALF-WORD) ;
  • inw 从I/O端口读取一个字(WORD,即两个字节) ;
  • outw 向I/O端口写入一个字(WORD,即两个字节) ;
  • inl 从I/O端口读取双字(即四个字节) ;
  • outl 向I/O端口写入双字(即四个字节) ;

这里细节太多了,《Linux那些事之PCI》P36-P43 讲解很详细

总结:读取了下X86的IO空间,然后检测可以读取HOST主桥的设备信息, 这里也留了操作PCI的接口函数。

SMBIOS(System Management BIOS)主板厂商使用 DMI(Desktop Management Interface)进行同步,这里不看这部分

arch_init详解
  1. if (x86_init.pci.arch_init && !x86_init.pci.arch_init())
  2. return 0;
  3. 竟然有没实现相关方法,开不开心
  4. // arch/x86/kernel/x86_init.c
  5. struct x86_init_ops x86_init __initdata = {
  6. .pci = {
  7. .init = x86_default_pci_init,
  8. .init_irq = x86_default_pci_init_irq,
  9. .fixup_irqs = x86_default_pci_fixup_irqs,
  10. },
  11. }

pci_direct_init详解

正常情况下:type = pci_direct_probe();
返回的配置WORK=1,type=1
pci_direct_init(int type)

  1. raw_pci_ops = &pci_direct_conf1;
  2. port_cf9_safe = true;
  3. raw_pci_ops = &pci_direct_conf1; // 给全局变量OPS赋值
  4. raw_pci_ext_ops = &pci_direct_conf1; // TBD,这里ext指的是0x40-0xff还是0x100-0xfff ???
  5. const struct pci_raw_ops pci_direct_conf1 = { // 以后配置空间访问就看这哥们的了
  6. .read = pci_conf1_read,
  7. .write = pci_conf1_write,
  8. };

subsys_initcall 系列

pci_slot_init 创建了/sys/bus/pci/slots/ 目录

代码也就创建了/sys/bus/pci/slots/目录和初始化相关方法

  1. [baiy@server_202 pci]$ tree /sys/bus/pci/slots/
  2. /sys/bus/pci/slots/
  3. pci_bus_kset = bus_get_kset(&pci_bus_type);
  4. pci_slots_kset = kset_create_and_add("slots", NULL,

pci_subsys_init(重点-第三部分详解)
  1. x86_init.pci.init() // ACPI PCI初始化-后边详解
  2. // 注:旧版本调用pci_legacy_init(),由于X86都使用了ACPI,所以不研究
  3. pcibios_fixup_peer_bridges();
  4. x86_init.pci.init_irq(); // ACPI PCI INIT初始化
  5. pcibios_init();

pci_legacy_init其实是以前旧版本的PCI检测程序,资料较多,可惜现在都用了ACPI部分

x86默认初始化接口

  1. x86_init.pci.init() // 前边掉的是x86_init.pci.arch_init(),现在有这个初始化
  2. // arch/x86/kernel/x86_init.c
  3. struct x86_init_ops x86_init __initdata = {
  4. .pci = {
  5. .init = x86_default_pci_init,
  6. .init_irq = x86_default_pci_init_irq,
  7. .fixup_irqs = x86_default_pci_fixup_irqs,
  8. },
  9. }
  10. #ifdef CONFIG_PCI
  11. # ifdef CONFIG_ACPI
  12. # define x86_default_pci_init pci_acpi_init
  13. # else
  14. # define x86_default_pci_init pci_legacy_init
  15. # endif
  16. # define x86_default_pci_init_irq pcibios_irq_init
  17. # define x86_default_pci_fixup_irqs pcibios_fixup_irqs
  18. #endif

第一阶段初始化内核打印

其实就在 linux-git\arch\x86\pci\direct.c : pci_direct_init 打印了一句

  1. [ 0.149685] PCI: Using configuration type 1 for base access