20.PCI代码导读-4.驱动初始化流程-主桥-《计算机知识》

admin 2025-11-02 22:25:33 系统网络 来源:ZONE.CI 全球网 0 阅读模式
  • arch_initcall
    • acpi_pci_init(了解)
  • subsys_initcall
    • 重点描述
    • acpi_init(重点)
      • ACPI初始化主流程
      • ACPI主桥分配( 重点
        • 主桥的创建-1: pci_acpi_scan_root
        • MCFG的资源分配:init_info函数
        • 资源的扫描过程
        • 资源管理接口
      • mps和mrrs (这部分在mps专栏分析)
        • 重点函数1

    ACPI下的PCI初始化(重点)

    参考:PCI总线的初始化

    这里有一个ACPI(Advanced Configuration and Power Interface),我的理解是ACPI提供了电源、硬件和固件的接口。这里只关注软件角度的ACPI的结构——在屏蔽了硬件细节的同时,提供了一系列系统资源,包括:

    • ACPI寄存器
    • ACPI BIOS
    • ACPI Tables

    arch_initcall

    acpi_pci_init(了解)

    注:这部分虽然也在arch_initcall,但在pci 的 arch_initicall之后

    注册一个全局的acpi_pci_bus总线,并添加到ACPI的全局总线链表中:bus_type_list

    1. // pci-acpi.c:1360:arch_initcall(acpi_pci_init);
    2. acpi_pci_init
    3. register_acpi_bus_type(&acpi_pci_bus);
    4. list_add_tail(&type->list, &bus_type_list); // 添加到全局bus_type_list总线链表中
    5. pci_set_platform_pm(&acpi_pci_platform_pm); // 设置全局的pci相关pm管理接口

    subsys_initcall

    重点描述

    • 从ACPI获取UEFI中给PCI设备分配得资源表信息
    • ECAM地址获取及ioremap
    • 初始化PCI设备resouce信息(下节)
    • 系统启动过程中得打印分析

    acpi_init(重点)

    因为PCI的枚举牵扯到了这部分,所以这里也跟踪下代码

    ACPI初始化主流程

    APCI关于PCI主桥设备的总线的匹配(了解)

    EDK%20II之Device%20Path%20中描述了PNP0A3是%20PCI%20Host%20Bridge,%20%20所以这边用PNP0A03

    //%20subsys_initcall(acpi_init);//%20这部分是ACPI的扫描部分,可以快速看下,类似platform这种acpi_init%20%20%20%20acpi_bus_init();%20%20%20%20pci_mmcfg_late_init();%20%20%20%20acpi_scan_init();%20%20%20%20%20%20%20%20acpi_pci_root_init();%20%20%20%20%20%20%20%20%20%20%20%20acpi_scan_add_handler_with_hotplug(&pci_root_handler,%20"pci_root");%20%20%20%20%20%20%20%20acpi_pci_link_init();%20%20%20%20%20%20%20%20acpi_bus_scan(ACPI_ROOT_OBJECT);%20%20%20%20%20%20%20%20%20%20%20%20acpi_bus_attach%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20acpi_scan_attach_handler%20//%20进行检测%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20handler%20=%20acpi_scan_match_handler(hwid->id,%20&devid);%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20handler->attach(device,%20devid);%20//%20匹配上边的acpi_pci_root_init,进入attach//%20匹配表static%20const%20struct%20acpi_device_id%20root_device_ids[]%20=%20{%20%20%20%20{"PNP0A03",%200},%20%20%20//%20PCIE主桥%20%20%20%20{"",%200},};static%20struct%20acpi_scan_handler%20pci_root_handler%20=%20{%20%20%20%20.ids%20=%20root_device_ids,%20%20%20%20.attach%20=%20acpi_pci_root_add,%20%20%20%20.detach%20=%20acpi_pci_root_remove,%20%20%20%20.hotplug%20=%20{%20%20%20%20%20%20%20%20.enabled%20=%20true,%20%20%20%20%20%20%20%20.scan_dependent%20=%20acpi_pci_root_scan_dependent,%20%20%20%20},};

    %20

    ACPI主桥分配(%20重点%20)

    acpi_pci_root_add%20%20%20参考:ACPI%20PCI%20Root%20Bridge%20Driver类图参考:%20acpi_pci.drawio%20%20最好参考acpi的图进行代码阅读

    小知识:判断服务器有多少主桥?每个主桥管哪些总线

    dmesg%20可以看到(acpi_pci_root_add函数打印)出现了:[%20%20%20%200.181405]%20ACPI:%20PCI%20Root%20Bridge%20[PCI0]%20(domain%200000%20[bus%2000-1E])%20%20#%20[PCIn]%20[bus%2000-1E],%20那么00就是root的BUS号[%20%20%20%200.181405]%20PCI%20host%20bridge%20to%20bus%200000:00%20%20..%20主桥初始化[%20%20%20%200.181993]%20PCI%20host%20bridge%20to%20bus%200000:00%20%20..%20主桥资源的信息表%20%20%20%20[%20%20%20%200.181995]%20pci_bus%200000:00:%20root%20bus%20resource%20[bus%2000-1E][%20%20%20%200.181996]%20pci_bus%200000:00:%20root%20bus%20resource%20[io%20%200x0000-0x0cf7%20window][%20%20%20%200.181998]%20pci_bus%200000:00:%20root%20bus%20resource%20[io%20%200x0d00-0xffff%20window][%20%20%20%200.181999]%20pci_bus%200000:00:%20root%20bus%20resource%20[mem%200x000a0000-0x000bffff%20window][%20%20%20%200.182000]%20pci_bus%200000:00:%20root%20bus%20resource%20[mem%200x90000000-0xdfffffff%20window][%20%20%20%200.182001]%20pci_bus%200000:00:%20root%20bus%20resource%20[mem%200xfd000000-0xfe7fffff%20window].......[%20%20%20%200.181405]%20ACPI:%20PCI%20Root%20Bridge%20[PCI1]%20(domain%200000%20[bus%201F-FF])%20%20#%20[PCIn]%20[bus%201F-fe],%20那么17就是root的BUS号......可以看到%20%20PC1%20已经枚举到bus%20ff了,那么支持2个主桥

    目前所能见到的设备大概只有一个SEG,下边挂了N个主桥,来支持0x00-0xFF的PCI%20BUS

    在前边注册到设备后,每检测到一个主桥,会进入 acpi_pci_root_add中,进行初始化。又因为支持了多个主桥,所以这个函数可能会进入多次。

    1. // 这部分是acpi_pci_root部分,需要看下
    2. acpi_pci_root_add // 分配一个acpi_pci_root,并对其进行初始化,一般情况下仅含有一个HOST桥。
    3. root = kzalloc(sizeof(struct acpi_pci_root), GFP_KERNEL); // 分配acpi_pci_root
    4. // 下边这部分与BIOS相关:获取ROOT的主设备号
    5. acpi_evaluate_integer(&segment); // 从BIOS里边获取SEG信息, 就是pci的domain == 0
    6. try_get_root_bridge_busnr(&root->secondary); // 获取domain下边的总线个数
    7. // 内核打印定位(!!!关键点调试用):
    8. // dmesg输出: ACPI: PCI Root Bridge [PCI0] (domain 0000 [bus 00-7f])
    9. #define ACPI_PCI_ROOT_CLASS "pci_bridge"
    10. #define ACPI_PCI_ROOT_DEVICE_NAME "PCI Root Bridge"
    11. strcpy(acpi_device_name(device), ACPI_PCI_ROOT_DEVICE_NAME);
    12. strcpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS);
    13. pr_info(PREFIX "%s [%s] (domain %04x %pR)..."); // !!! 打印了对应主桥的SEG和所管理的BUS号:root->segment和&root->secondary
    14. // Scan the Root Bridge
    15. root->bus = pci_acpi_scan_root(root); // PCI总线初始化的入口函数 !!! 重点
    16. pci_bus_add_devices(root->bus);

    小知识: acpi_evaluate_integer的作用: 内核ACPI函数API之acpi_evaluate_integer 和 ACPI PCI Root Bridge Driver

    **

    主桥的创建-1: pci_acpi_scan_root
    1. pci_acpi_scan_root(root); // 枚举PCI设备
    2. // number of PCI domain, busnum 都从BIOS获取
    3. int domain = root->segment; // 注:这个函数给我们最大疑惑是:domain是多少?busnum是多少? 大部分情况下,domain=0(也就是SEG),根据打印可以看到
    4. int busnum = root->secondary.start; // 设备给每一个domain下的主桥分配一段总线域,总共簇成了0x00-0xFF 可以看内核中的打印(也许支持多个主桥)。
    5. bus = pci_find_bus(domain, busnum); // domain=0,busnum=0, qemu模拟没找到
    6. // 通过pci_find_bus查找HOST Bridge对应的segment,bus num有没有被注册,如果注册了就更新一下信息,
    7. // 没有注册则调用acpi_pci_root_create创建,该函数中有两个比较重要,一个是pci_create_root_bus
    8. if(bus){ // 不考虑
    9. ...
    10. } else { // this branch
    11. info->sd.domain = domain;
    12. info->sd.node = node;
    13. info->sd.companion = root->device;
    14. acpi_pci_root_create(重点)
    15. // 设置整条链路的maxpayload配置(专栏 max payload讲述)
    16. list_for_each_entry(child, &bus->children, node)
    17. pcie_bus_configure_settings(child);
    1. acpi_pci_root_create(重点)
    2. ops->init_info(info); // acpi_pci_root_ops的 pci_acpi_root_init_info
    3. pci_acpi_root_init_info(info) // CONFIG_PCI_MMCONFIG=y,ECAM初始化
    4. // 设备资源的获取
    5. ops->prepare_resources(info); // acpi_pci_root_ops的 pci_acpi_root_prepare_resources 资源扫描
    6. pci_acpi_root_prepare_resources(info); // 从ACPI获取所有设备资源信息
    7. pci_acpi_root_add_resources(info); // 添加acpi 资源
    8. pci_add_resource(&info->resources, &root->secondary);
    9. // 创建host_bridge
    10. pci_create_root_bus // 创建host bridge
    11. pci_register_host_bridge(bridge); // 创建root bus ******************* 也是重点
    12. // 扫描child bus ** 子设备创建
    13. pci_scan_child_bus(bus); // 查看下一章节

    MCFG的资源分配:init_info函数
    1. unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 |
    2. PCI_PROBE_MMCONF;
    3. ops->init_info(info)
    4. pci_acpi_root_init_info
    5. setup_mcfg_map(ci);
    6. pci_mmconfig_insert(dev, seg, info->start_bus, info->end_bus,
    7. root->mcfg_addr);
    8. // 分配MCFG的 struct pci_mmcfg_region !!! 这里只分配主桥管理的BUS总线的资源,因为1个主桥并不一定全部0x00-0XFF的PCI总线
    9. cfg = pci_mmconfig_alloc(seg, start, end, addr);
    10. pci_mmcfg_check_reserved(dev, cfg, 0); // 检测主桥对MMC的预留信息(非重点)
    11. insert_resource_conflict(&iomem_resource, &cfg->res); // __insert_resource ( 非重点)
    12. pci_mmcfg_arch_map(cfg)
    13. cfg->virt = mcfg_ioremap(cfg); // !!! 映射mcfg空间
    14. list_add_sorted(cfg); // !!! pci_mmcfg_list 存放了全局所有主桥下的ECAM配置
    15. // 启动打印: PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0x80000000-0x8fffffff] (base 0x80000000)
    16. // PCI: MMCONFIG at [mem 0x80000000-0x8fffffff] reserved in E820 这两个地址就是前边看的acpi的地址
    17. raw_pci_ext_ops = &pci_mmcfg; // 这就是访问PCI 扩展空间的ECAM方式。

    资源的扫描过程

    每个PCI设备在BAR中描述自己需要占用多少地址空间,bios通过所有设备的这些信息构建一张address map,描述系统中资源的分配情况,然后在合理的将地址空间配置给每个PCI设备

    pci_acpi_scan_root 下函数的类图类图image.png其中,segment=0, secondary为 [bus 00-ff]

    在资源扫描过程有以下几部分:

    1. static struct acpi_pci_root_ops acpi_pci_root_ops = {
    2. .pci_ops = &pci_root_ops,
    3. .init_info = pci_acpi_root_init_info,
    4. .release_info = pci_acpi_root_release_info,
    5. .prepare_resources = pci_acpi_root_prepare_resources,
    6. };
    7. if (ops->init_info && ops->init_info(info)) // pci_acpi_root_init_info(info) MCFG的资源管理
    8. goto out_release_info;
    9. if (ops->prepare_resources)
    10. ret = ops->prepare_resources(info); // pci_acpi_root_prepare_resources(info) PCI的资源管理
    11. else
    12. ......
    13. pci_acpi_root_add_resources(info);

    **

    资源管理接口

    从ACPI中获取所有设备的资源信息,并添加到链表中

    1. ops->prepare_resources(info);
    2. pci_acpi_root_prepare_resources
    3. pci_acpi_root_add_resources(info);
    4. pci_add_resource(&info->resources, &root->secondary);

    image.png**pci_create_root_bus 主桥的分配

    1. pci_create_root_bus // 重点:分配主桥
    2. struct pci_host_bridge *bridge // 主桥描述结构体
    3. pci_alloc_host_bridge(0); // 分配主桥对象 struct pci_host_bridge *bridge;
    4. list_splice_init(resources, &bridge->windows); //
    5. pci_register_host_bridge // 注册主桥
    6. dev_set_name(&bridge->dev, "pci%04x:%02x", pci_domain_nr(bus),
    7. bridge->busnr); // 这就是主桥编号是: 0000:00的来源
    8. # 打印当前主桥的内容
    9. 设置当前主桥在PCI总线域的地址区范围(重点,后期设备初始化需要靠这个来确定存储区地址),注:这里明显windows offset==0
    10. [ 0.125794] PCI host bridge to bus 0000:00
    11. [ 0.125797] pci_bus 0000:00: root bus resource [io 0x0000-0x0cf7 window]
    12. [ 0.125798] pci_bus 0000:00: root bus resource [io 0x0d00-0xffff window]
    13. [ 0.125799] pci_bus 0000:00: root bus resource [mem 0x000a0000-0x000bffff window]
    14. [ 0.125800] pci_bus 0000:00: root bus resource [mem 0xbf800000-0xfeafffff window]
    15. [ 0.125801] pci_bus 0000:00: root bus resource [bus 00-3f]

    image.png**

    mps和mrrs (这部分在mps专栏分析)

    参考: PCIe学习笔记之Max payload size 和 PCI Express Base_r5_1.pdf 中 7.5.3章节(PCIE CAP)

    1. struct pci_bus *child;
    2. list_for_each_entry(child, &bus->children, node)
    3. pcie_bus_configure_settings(child); // drivers/pci/probe.c
    4. enum pcie_bus_config_types {
    5. PCIE_BUS_TUNE_OFF, /* don't touch MPS at all */
    6. PCIE_BUS_DEFAULT, /* ensure MPS matches upstream bridge */
    7. PCIE_BUS_SAFE, /* use largest MPS boot-time devices support */
    8. PCIE_BUS_PERFORMANCE, /* use MPS and MRRS for best performance */
    9. PCIE_BUS_PEER2PEER, /* set MPS = 128 for all devices */
    10. };
    11. enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_DEFAULT;
    12. pcie_bus_configure_settings
    13. if (!pci_is_pcie(bus->self)) return; // 只有PCIE支持maxpayload,因为这个配置在PCIE里边
    14. pcie_bus_configure_set(bus->self, &smpss);
    15. pci_walk_bus(bus, pcie_bus_configure_set, &smpss);

    重点函数1
    1. static int pcie_bus_configure_set(struct pci_dev *dev, void *data)
    2. {
    3. int mps, orig_mps;
    4. if (!pci_is_pcie(dev)) // PCIE才只是MPS调整
    5. return 0;
    6. if (pcie_bus_config == PCIE_BUS_TUNE_OFF ||
    7. pcie_bus_config == PCIE_BUS_DEFAULT) // 这两种配置不进行设置
    8. return 0;
    9. mps = 128 << *(u8 *)data;
    10. orig_mps = pcie_get_mps(dev); // 从Device Control Register获取MPS
    11. pcie_capability_read_word(dev, PCI_EXP_DEVCTL, &ctl);
    12. return 128 << ((ctl & PCI_EXP_DEVCTL_PAYLOAD) >> 5); // MPS配置
    13. pcie_write_mps(dev, mps); // 根据参数配置mps
    14. if (pcie_bus_config == PCIE_BUS_PERFORMANCE) { // 最优化的MPS配置
    15. mps = 128 << dev->pcie_mpss;
    16. }
    17. rc = pcie_set_mps(dev, mps); // 配置Device Control Register的MPS
    18. pcie_write_mrrs(dev); // PCIE_BUS_PERFORMANCE 才支持
    19. mrrs = pcie_get_mps(dev); // mrrs = Device Control Register的MPS
    20. while (mrrs != pcie_get_readrq(dev) && mrrs >= 128) { // 从Device Control Register获取MRRS,往最高性能配置MRRS,失败后依次降低
    21. rc = pcie_set_readrq(dev, mrrs);
    22. if (!rc)
    23. break;
    24. dev_warn(&dev->dev, "Failed attempting to set the MRRS\n");
    25. mrrs /= 2;
    26. }
    27. dev_info(&dev->dev, "Max Payload Size set to %4d/%4d (was %4d), Max Read Rq %4d\n",
    28. pcie_get_mps(dev), 128 << dev->pcie_mpss,
    29. orig_mps, pcie_get_readrq(dev));
    30. return 0;
    31. }
    01-shell脚本介绍-《shell脚本》 系统网络

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

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