20.PCI代码导读-3.驱动初始化流程-准备工作-《计算机知识》

admin 2025-11-02 22:25:37 系统网络 来源:ZONE.CI 全球网 0 阅读模式
  • 关于PCI的配置
  • 第一阶段初始化
    • pure_initcall系列初始化
      • 入口参数-early_param(复习)
    • postcore_initcall系列初始化
      • sys/class/pci注册
      • pci_bus注册和/sys/bus/pci/初始化
    • arch_initcall系列(重点)
      • pci_arch_init(重点)
      • pci_direct_probe 详解
        • CONF1和CONF2
        • PCIE的IO端口和配置空间访问
        • 校验主桥过程
        • arch_init详解
      • pci_direct_init详解
    • subsys_initcall 系列
      • pci_slot_init 创建了/sys/bus/pci/slots/ 目录
      • pci_subsys_init(重点-第三部分详解)
    • x86默认初始化接口
  • 第一阶段初始化内核打印

    关于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那些事之PCI》P23
    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 BIOS,2.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 N5400和Omnibook 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 位:总线编号。 // bus15: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空间中0xCF8和0xCFC留给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 ID和Vendor 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=1pci_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
    01-shell脚本介绍-《shell脚本》 系统网络

    01-shell脚本介绍-《shell脚本》

    一、shell脚本是什么二、为什么要学shell,而不是其他计算机语言三、学习这门课程的优势四、学了能干什么五、学习什么内容六、学习的技巧七、成长路径八、学习环
    评论:0   参与:  18