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的枚举牵扯到了这部分,所以这里也跟踪下代码 4.驱动初始化流程-主桥 - 图1

ACPI初始化主流程

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

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

  1. // subsys_initcall(acpi_init);
  2. // 这部分是ACPI的扫描部分,可以快速看下,类似platform这种
  3. acpi_init
  4. acpi_bus_init();
  5. pci_mmcfg_late_init();
  6. acpi_scan_init();
  7. acpi_pci_root_init();
  8. acpi_scan_add_handler_with_hotplug(&pci_root_handler, "pci_root");
  9. acpi_pci_link_init();
  10. acpi_bus_scan(ACPI_ROOT_OBJECT);
  11. acpi_bus_attach
  12. acpi_scan_attach_handler // 进行检测
  13. handler = acpi_scan_match_handler(hwid->id, &devid);
  14. handler->attach(device, devid); // 匹配上边的acpi_pci_root_init,进入attach
  15. // 匹配表
  16. static const struct acpi_device_id root_device_ids[] = {
  17. {"PNP0A03", 0}, // PCIE主桥
  18. {"", 0},
  19. };
  20. static struct acpi_scan_handler pci_root_handler = {
  21. .ids = root_device_ids,
  22. .attach = acpi_pci_root_add,
  23. .detach = acpi_pci_root_remove,
  24. .hotplug = {
  25. .enabled = true,
  26. .scan_dependent = acpi_pci_root_scan_dependent,
  27. },
  28. };

ACPI主桥分配( 重点

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

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

  1. dmesg 可以看到(acpi_pci_root_add函数打印)出现了:
  2. [ 0.181405] ACPI: PCI Root Bridge [PCI0] (domain 0000 [bus 00-1E]) # [PCIn] [bus 00-1E], 那么00就是root的BUS号
  3. [ 0.181405] PCI host bridge to bus 0000:00 .. 主桥初始化
  4. [ 0.181993] PCI host bridge to bus 0000:00 .. 主桥资源的信息表
  5. [ 0.181995] pci_bus 0000:00: root bus resource [bus 00-1E]
  6. [ 0.181996] pci_bus 0000:00: root bus resource [io 0x0000-0x0cf7 window]
  7. [ 0.181998] pci_bus 0000:00: root bus resource [io 0x0d00-0xffff window]
  8. [ 0.181999] pci_bus 0000:00: root bus resource [mem 0x000a0000-0x000bffff window]
  9. [ 0.182000] pci_bus 0000:00: root bus resource [mem 0x90000000-0xdfffffff window]
  10. [ 0.182001] pci_bus 0000:00: root bus resource [mem 0xfd000000-0xfe7fffff window]
  11. .......
  12. [ 0.181405] ACPI: PCI Root Bridge [PCI1] (domain 0000 [bus 1F-FF]) # [PCIn] [bus 1F-fe], 那么17就是root的BUS号
  13. ......
  14. 可以看到 PC1 已经枚举到bus ff了,那么支持2个主桥

目前所能见到的设备大概只有一个SEG,下边挂了N个主桥,来支持0x00-0xFF的PCI BUS 4.驱动初始化流程-主桥 - 图2

在前边注册到设备后,每检测到一个主桥,会进入 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_integerACPI 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 sizePCI 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. }