一、pci主桥acpi框架
1、匹配DSDT中(PCI0)调用.attach
kernel调用acpi_init执行,
acpi_init->acpi_scan_init->acpi_bus_scan->acpi_bus_check_add->acpi_scan_init_hotplug->acpi_scan_match_handler->acpi_scan_handler_matching
static bool acpi_scan_handler_matching(struct acpi_scan_handler *handler,
const char *idstr,
const struct acpi_device_id **matchid)
{
...
for (devid = handler->ids; devid->id[0]; devid++)
if (!strcmp((char *)devid->id, idstr)) {
if (matchid)
*matchid = devid;
return true;
...
}
这个函数会比较biso是否传递PNP0A03 字符串,而这个字符串代表的是pcie的root。
如果bios传递了这个字符串(Name (_CID, EisaId ("PNP0A03")在DSDT中有描述)。则
int acpi_bus_scan(acpi_handle handle)
{
...
if (device) {
acpi_bus_attach(device);
return 0;
}
...
}
acpi_bus_scan 中调用acpi_bus_attach->acpi_scan_attach_handler,主要代码:
static int acpi_scan_attach_handler(struct acpi_device *device)
{
...
ret = handler->attach(device, devid);
...
return ret;
}
这个函数最终调用handler->attach,对于pcie root则acpi_pci_root_add
这个函数中就会扫描主桥。下面是主桥设备注册的数据结构。
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,
},
}
2、acpi_pci_root_init流程
(1)acpi_hest_init()
在acpi下对HEST表的支持,这个表可以包含硬件错误源。
(2)pci_acpi_crs_quirks
X86下实现为解析获取dmi信息,当匹配某些信息(板卡名称、厂商、固件版本号等)后,确定是否使用DSDT中_CRS下的资源以及是否忽略_SEG方法(returns a device’s PCI Segment Group number)。前提是使用UEFI作为固件并且支持acpi。
(3)注册struct acpi_scan_handler
acpi_scan_add_handler_with_hotplug(&pci_root_handler, "pci_root");
将struct acpi_scan_handler添加到&acpi_scan_handlers_list链表中。并使能为热插拔设备。当在DSDT中找到 Name (_CID, EisaId ("PNP0A03") 描述,系统调用.attach函数,即acpi_pci_root_add函数。(acpi框架已实现)
3、acpi_pci_root_add流程
(1)解析DSDT方法
检测_SEG控制方法,如不存在,则假定所有pci总线段位段组0. 检测_CRS方法,try_get_root_bridge_busnr 获取_CRS资源列表,这里主要获取下游bus号范围。
_CRS中包含总线范围资源
评估_CBA,获取pci主桥的配置基地址。
评估_BBN(base bus number),来获取总线号。如找到为[_BBN-0xFF] ,否则默认为 [0-0xFF]。
(2)negotiate_os_control
判断主桥设备是否支持aspm、run_wake、以及热插拔事件。
pci_acpi_scan_root(与架构相关,须在mips下实现)。(必须在尝试绑定根设备之前执行此操作,因为在进行此调用之前不会创建PCI名称空间(因此根桥的pci_dev不存在)。
X86架构下pci_acpi_scan_root函数的分析:
首先检测系统对pci组段的支持情况。
acpi的_PXM 表示device所在的numa id,这里通过评估_PXM方法获取node号。
为结构struct pci_root_info申请空间,填充struct pci_sysdata sd结构成员
检测pci主桥总线的0000:00设备。由于pci总线还没有初始化,在这里显然找不到。
这里主要分析未创建。首先将root资源插入资源列表。调用probe_pci_root_info函数。在这个函数中,调用acpi_dev_get_resources函数。通过acpi_walk_resources评估_CRS控制方法。获取_CRS中描述的资源信息。
这里从使用crs获取信息和不使用两方面分析:
- 使用_CRS中获取的资源
在add_resources中、调用validate_resources确认获取到的IO/MEM资源是正确的。遍历_CRS中的资源,将IO和MEM资源分别插入iomem_resource和ioport_resource中,如出现冲突,则释放要插入的资源。最后将_CRS中的资源插入资源列表。
- 不使用_CRS中获取的资源
释放从_CRS获取到的资源。调用x86_pci_root_bus_resources函数。在这个函数中,遍历pci_root_infos,查找bus为0的总线信息。找不到则使用默认资源设置。否则资源列表,将IO和MEM资源分别插入iomem_resource和ioport_resource中。
接下来判断是否支持MMCFG。不支持则执行pci扫描的整个流程。创建根节点。扫描主桥下的所有pci设备。
二、pci在acpi下枚举和普通枚举对比
1、ACPI热插拔扫描
|->acpi_bus_scan(ACPI_ROOT_OBJECT)
|->acpi_bus_scan@drivers/acpi/scan.c
acpi_bus_check_add检测root
|->acpi_walk_namespace@drivers/acpi/acpica/nsxfeval.c
|->acpi_ns_walk_namespace@drivers/acpi/acpica/nswalk.c
|->acpi_bus_device_attach(即post_order_visit)
|->acpi_scan_attach_handler
|->acpi_scan_match_handler
|->handler->attach(即acpi_pci_root_add)
|->pci_acpi_scan_root@arch/x86/pci/probe.c
|->probe_pci_root_info
|->pci_scan_child_bus@drivers/pci/probe.c
|->pci_scan_slot
2. 传统扫描
subsys_initcall(pcibios_init)@arch/mips/pci/pci.c
|->pcibios_init
|->pcibios_scanbus@arch/mips/pci/pci.c
|->pci_scan_root_bus
|->pci_scan_root_bus@drivers/pci/probe.c
|->pci_create_root_bus
|->pci_scan_child_bus
|->pci_scan_slot
(1)获取域、node号
目前平台忽略pci段组的概念,在3a+7a环境下,默认pci段组为0.在acpi中,段组的概念通过seg对象来获取。
acpi模式下,可通过_PXM来获取node的概念。
pxm = acpi_get_pxm(handle);
return acpi_map_pxm_to_node(pxm);
(2)适配acpi新增字段
mips架构下对acpi没有支持,所以在pci控制器结构中添加了acpi_device的概念。
struct pci_controller {
...
#ifdef CONFIG_ACPI
struct acpi_device *companion;
#endif
};
使用companion成员来记录acpi_device结构成员。传递给pci_create_root_bus接口来创建0号总线及pci主桥。
(3)资源获取流程
调用接口acpi_dev_get_resources获取主桥设备的_CRS对象下描述的io/mem资源。将获取到的资源插入到资源列表中,以便后续传递给pci_create_root_bus来创建0号总线及pci主桥。
获取到的资源需要进行资源检查:1、是否获取到io/mem资源(DSDT中是否有描述)2、获取到的io/mem资源是否合理。
如果没有在DSDT中获取到资源描述或检测到获取到的资源不符合逻辑,则使用默认资源。
2、主桥初始化添加代码
1、pci_domain_nr函数返回bus->sysdata(controller)的index数据。
2、pcibios_root_bridge_prepare为主桥设备设置固件节点(struct fwnode_handle *fwnode)。
3、pcibios_add_bus(b)
->acpi_pci_slot_enumerate --- 遍历名称空间,检查每个名称空间中存在的节点句柄是否有相关联的插槽,如果有,则创建PCI插槽(在/sys/bus/pci/slots/ 目录中显示)。(物理插槽与pci总线对应关系)
->acpiphp_enumerate_slots --- 为给定的总线枚举pci插槽
三、扫描过程问题
(1)parent NULL...return ------------
[ 0.265625] pci 0000:00:16.0: [0014:7a0b] type 00 class 0x088000
[ 0.265625] pci 0000:00:16.0: reg 0x10: [mem 0x4b1a7000-0x4b1a7fff 64bit]
[ 0.265625] pci 0000:00:16.0: reg 0x18: [mem 0x4a000000-0x4affffff 64bit]
[ 0.265625] pci 0000:00:17.0: [0014:7a0c] type 00 class 0x060100
问题原因:pci主桥下设备在DSDT中没有描述_ADR对象时,则在扫描过程会出现Unable to get handle打印。属正常现象。
(2)调用acpi_find_child_device失败
问题问题原因,由于在pci扫描过程没有为device->device_rh->fwnode赋值,导致在使用ACPI_COMPANION宏获取acpi dev时获取总为NULL。后续在创建主桥过程,在struct pci_controller中添加acpi_device字段,并将deivce赋值。解决此问题,在pci扫描过程会检测DSDT中_ADD的描述,如发现,则会调用这个函数来查找设备。
(3)PCI枚举问题
1)卡死问题
。。。。
[ 0.148437] CPU#1 finished, CP0_ST=d400cce1
[ 0.152343] Enable clock for CPU#2
[ 0.152343] Booting CPU#2...
[ 0.152343] Primary instruction cache 64kB, VIPT, 4-way, linesize 64 bytes.
[ 0.152343] Primary data cache 64kB, 4-way, VIPT, no aliases, linesize 64 bytes
2)bar空间为NULL
以上问题都是在移植过程,由于x86和mips架构代码及数据结构不同导致。
调用pci标准接口pci_create_root_bus来创建0号总线及主桥设备时,传递的参数void *sysdata,mips下需将pci_controller结构传递,且在调用pci_bus_assign_resources函数时会使用pci_controller的成员io_resource以及mem_resource成员,所以必须对其赋值。