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
// pci-acpi.c:1360:arch_initcall(acpi_pci_init);acpi_pci_initregister_acpi_bus_type(&acpi_pci_bus);list_add_tail(&type->list, &bus_type_list); // 添加到全局bus_type_list总线链表中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 II之Device Path 中描述了PNP0A3是 PCI Host Bridge, 所以这边用PNP0A03
// subsys_initcall(acpi_init);// 这部分是ACPI的扫描部分,可以快速看下,类似platform这种acpi_initacpi_bus_init();pci_mmcfg_late_init();acpi_scan_init();acpi_pci_root_init();acpi_scan_add_handler_with_hotplug(&pci_root_handler, "pci_root");acpi_pci_link_init();acpi_bus_scan(ACPI_ROOT_OBJECT);acpi_bus_attachacpi_scan_attach_handler // 进行检测handler = acpi_scan_match_handler(hwid->id, &devid);handler->attach(device, devid); // 匹配上边的acpi_pci_root_init,进入attach// 匹配表static const struct acpi_device_id root_device_ids[] = {{"PNP0A03", 0}, // PCIE主桥{"", 0},};static struct acpi_scan_handler pci_root_handler = {.ids = root_device_ids,.attach = acpi_pci_root_add,.detach = acpi_pci_root_remove,.hotplug = {.enabled = true,.scan_dependent = acpi_pci_root_scan_dependent,},};
ACPI主桥分配( 重点 )
acpi_pci_root_add 参考:ACPI PCI Root Bridge Driver
类图参考: acpi_pci.drawio 最好参考acpi的图进行代码阅读
小知识:判断服务器有多少主桥?每个主桥管哪些总线
dmesg 可以看到(acpi_pci_root_add函数打印)出现了:[ 0.181405] ACPI: PCI Root Bridge [PCI0] (domain 0000 [bus 00-1E]) # [PCIn] [bus 00-1E], 那么00就是root的BUS号[ 0.181405] PCI host bridge to bus 0000:00 .. 主桥初始化[ 0.181993] PCI host bridge to bus 0000:00 .. 主桥资源的信息表[ 0.181995] pci_bus 0000:00: root bus resource [bus 00-1E][ 0.181996] pci_bus 0000:00: root bus resource [io 0x0000-0x0cf7 window][ 0.181998] pci_bus 0000:00: root bus resource [io 0x0d00-0xffff window][ 0.181999] pci_bus 0000:00: root bus resource [mem 0x000a0000-0x000bffff window][ 0.182000] pci_bus 0000:00: root bus resource [mem 0x90000000-0xdfffffff window][ 0.182001] pci_bus 0000:00: root bus resource [mem 0xfd000000-0xfe7fffff window].......[ 0.181405] ACPI: PCI Root Bridge [PCI1] (domain 0000 [bus 1F-FF]) # [PCIn] [bus 1F-fe], 那么17就是root的BUS号......可以看到 PC1 已经枚举到bus ff了,那么支持2个主桥
目前所能见到的设备大概只有一个SEG,下边挂了N个主桥,来支持0x00-0xFF的PCI BUS

在前边注册到设备后,每检测到一个主桥,会进入 acpi_pci_root_add中,进行初始化。
又因为支持了多个主桥,所以这个函数可能会进入多次。
// 这部分是acpi_pci_root部分,需要看下acpi_pci_root_add // 分配一个acpi_pci_root,并对其进行初始化,一般情况下仅含有一个HOST桥。root = kzalloc(sizeof(struct acpi_pci_root), GFP_KERNEL); // 分配acpi_pci_root// 下边这部分与BIOS相关:获取ROOT的主设备号acpi_evaluate_integer(&segment); // 从BIOS里边获取SEG信息, 就是pci的domain == 0try_get_root_bridge_busnr(&root->secondary); // 获取domain下边的总线个数// 内核打印定位(!!!关键点调试用):// dmesg输出: ACPI: PCI Root Bridge [PCI0] (domain 0000 [bus 00-7f])#define ACPI_PCI_ROOT_CLASS "pci_bridge"#define ACPI_PCI_ROOT_DEVICE_NAME "PCI Root Bridge"strcpy(acpi_device_name(device), ACPI_PCI_ROOT_DEVICE_NAME);strcpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS);pr_info(PREFIX "%s [%s] (domain %04x %pR)..."); // !!! 打印了对应主桥的SEG和所管理的BUS号:root->segment和&root->secondary// Scan the Root Bridgeroot->bus = pci_acpi_scan_root(root); // PCI总线初始化的入口函数 !!! 重点pci_bus_add_devices(root->bus);
小知识: acpi_evaluate_integer的作用: 内核ACPI函数API之acpi_evaluate_integer 和 ACPI PCI Root Bridge Driver
主桥的创建-1: pci_acpi_scan_root
pci_acpi_scan_root(root); // 枚举PCI设备// number of PCI domain, busnum 都从BIOS获取int domain = root->segment; // 注:这个函数给我们最大疑惑是:domain是多少?busnum是多少? 大部分情况下,domain=0(也就是SEG),根据打印可以看到int busnum = root->secondary.start; // 设备给每一个domain下的主桥分配一段总线域,总共簇成了0x00-0xFF 可以看内核中的打印(也许支持多个主桥)。bus = pci_find_bus(domain, busnum); // domain=0,busnum=0, qemu模拟没找到// 通过pci_find_bus查找HOST Bridge对应的segment,bus num有没有被注册,如果注册了就更新一下信息,// 没有注册则调用acpi_pci_root_create创建,该函数中有两个比较重要,一个是pci_create_root_busif(bus){ // 不考虑...} else { // this branchinfo->sd.domain = domain;info->sd.node = node;info->sd.companion = root->device;acpi_pci_root_create(重点)// 设置整条链路的maxpayload配置(专栏 max payload讲述)list_for_each_entry(child, &bus->children, node)pcie_bus_configure_settings(child);
acpi_pci_root_create(重点)ops->init_info(info); // acpi_pci_root_ops的 pci_acpi_root_init_infopci_acpi_root_init_info(info) // CONFIG_PCI_MMCONFIG=y,ECAM初始化// 设备资源的获取ops->prepare_resources(info); // acpi_pci_root_ops的 pci_acpi_root_prepare_resources 资源扫描pci_acpi_root_prepare_resources(info); // 从ACPI获取所有设备资源信息pci_acpi_root_add_resources(info); // 添加acpi 资源pci_add_resource(&info->resources, &root->secondary);// 创建host_bridgepci_create_root_bus // 创建host bridgepci_register_host_bridge(bridge); // 创建root bus ******************* 也是重点// 扫描child bus ** 子设备创建pci_scan_child_bus(bus); // 查看下一章节
MCFG的资源分配:init_info函数
unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 |PCI_PROBE_MMCONF;ops->init_info(info)pci_acpi_root_init_infosetup_mcfg_map(ci);pci_mmconfig_insert(dev, seg, info->start_bus, info->end_bus,root->mcfg_addr);// 分配MCFG的 struct pci_mmcfg_region !!! 这里只分配主桥管理的BUS总线的资源,因为1个主桥并不一定全部0x00-0XFF的PCI总线cfg = pci_mmconfig_alloc(seg, start, end, addr);pci_mmcfg_check_reserved(dev, cfg, 0); // 检测主桥对MMC的预留信息(非重点)insert_resource_conflict(&iomem_resource, &cfg->res); // __insert_resource ( 非重点)pci_mmcfg_arch_map(cfg)cfg->virt = mcfg_ioremap(cfg); // !!! 映射mcfg空间list_add_sorted(cfg); // !!! pci_mmcfg_list 存放了全局所有主桥下的ECAM配置// 启动打印: PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0x80000000-0x8fffffff] (base 0x80000000)// PCI: MMCONFIG at [mem 0x80000000-0x8fffffff] reserved in E820 这两个地址就是前边看的acpi的地址raw_pci_ext_ops = &pci_mmcfg; // 这就是访问PCI 扩展空间的ECAM方式。
资源的扫描过程
每个PCI设备在BAR中描述自己需要占用多少地址空间,bios通过所有设备的这些信息构建一张address map,
描述系统中资源的分配情况,然后在合理的将地址空间配置给每个PCI设备。
pci_acpi_scan_root 下函数的类图类图
其中,segment=0, secondary为 [bus 00-ff]
在资源扫描过程有以下几部分:
static struct acpi_pci_root_ops acpi_pci_root_ops = {.pci_ops = &pci_root_ops,.init_info = pci_acpi_root_init_info,.release_info = pci_acpi_root_release_info,.prepare_resources = pci_acpi_root_prepare_resources,};if (ops->init_info && ops->init_info(info)) // pci_acpi_root_init_info(info) MCFG的资源管理goto out_release_info;if (ops->prepare_resources)ret = ops->prepare_resources(info); // pci_acpi_root_prepare_resources(info) PCI的资源管理else......pci_acpi_root_add_resources(info);
**
资源管理接口
从ACPI中获取所有设备的资源信息,并添加到链表中
ops->prepare_resources(info);pci_acpi_root_prepare_resourcespci_acpi_root_add_resources(info);pci_add_resource(&info->resources, &root->secondary);

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

**
mps和mrrs (这部分在mps专栏分析)
参考: PCIe学习笔记之Max payload size 和 PCI Express Base_r5_1.pdf 中 7.5.3章节(PCIE CAP)
struct pci_bus *child;list_for_each_entry(child, &bus->children, node)pcie_bus_configure_settings(child); // drivers/pci/probe.cenum pcie_bus_config_types {PCIE_BUS_TUNE_OFF, /* don't touch MPS at all */PCIE_BUS_DEFAULT, /* ensure MPS matches upstream bridge */PCIE_BUS_SAFE, /* use largest MPS boot-time devices support */PCIE_BUS_PERFORMANCE, /* use MPS and MRRS for best performance */PCIE_BUS_PEER2PEER, /* set MPS = 128 for all devices */};enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_DEFAULT;pcie_bus_configure_settingsif (!pci_is_pcie(bus->self)) return; // 只有PCIE支持maxpayload,因为这个配置在PCIE里边pcie_bus_configure_set(bus->self, &smpss);pci_walk_bus(bus, pcie_bus_configure_set, &smpss);
重点函数1
static int pcie_bus_configure_set(struct pci_dev *dev, void *data){int mps, orig_mps;if (!pci_is_pcie(dev)) // PCIE才只是MPS调整return 0;if (pcie_bus_config == PCIE_BUS_TUNE_OFF ||pcie_bus_config == PCIE_BUS_DEFAULT) // 这两种配置不进行设置return 0;mps = 128 << *(u8 *)data;orig_mps = pcie_get_mps(dev); // 从Device Control Register获取MPSpcie_capability_read_word(dev, PCI_EXP_DEVCTL, &ctl);return 128 << ((ctl & PCI_EXP_DEVCTL_PAYLOAD) >> 5); // MPS配置pcie_write_mps(dev, mps); // 根据参数配置mpsif (pcie_bus_config == PCIE_BUS_PERFORMANCE) { // 最优化的MPS配置mps = 128 << dev->pcie_mpss;}rc = pcie_set_mps(dev, mps); // 配置Device Control Register的MPSpcie_write_mrrs(dev); // PCIE_BUS_PERFORMANCE 才支持mrrs = pcie_get_mps(dev); // mrrs = Device Control Register的MPSwhile (mrrs != pcie_get_readrq(dev) && mrrs >= 128) { // 从Device Control Register获取MRRS,往最高性能配置MRRS,失败后依次降低rc = pcie_set_readrq(dev, mrrs);if (!rc)break;dev_warn(&dev->dev, "Failed attempting to set the MRRS\n");mrrs /= 2;}dev_info(&dev->dev, "Max Payload Size set to %4d/%4d (was %4d), Max Read Rq %4d\n",pcie_get_mps(dev), 128 << dev->pcie_mpss,orig_mps, pcie_get_readrq(dev));return 0;}
