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_init
register_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_init
acpi_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_attach
acpi_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 == 0
try_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 Bridge
root->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_bus
if(bus){ // 不考虑
...
} else { // this branch
info->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_info
pci_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_bridge
pci_create_root_bus // 创建host bridge
pci_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_info
setup_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_resources
pci_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.c
enum 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_settings
if (!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获取MPS
pcie_capability_read_word(dev, PCI_EXP_DEVCTL, &ctl);
return 128 << ((ctl & PCI_EXP_DEVCTL_PAYLOAD) >> 5); // MPS配置
pcie_write_mps(dev, mps); // 根据参数配置mps
if (pcie_bus_config == PCIE_BUS_PERFORMANCE) { // 最优化的MPS配置
mps = 128 << dev->pcie_mpss;
}
rc = pcie_set_mps(dev, mps); // 配置Device Control Register的MPS
pcie_write_mrrs(dev); // PCIE_BUS_PERFORMANCE 才支持
mrrs = pcie_get_mps(dev); // mrrs = Device Control Register的MPS
while (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;
}