当前位置: 首页 > article >正文

PCI 总线学习笔记(三)

PCI 总线学习系列,参考自
技术大牛博客: PCIe 扫盲系列博文连载目录篇
书籍:王齐老师的《PCI Express 体系结构导读》


下面的文章中加入了自己的一些理解和实际使用中遇到的一些场景,供日后查询和回忆使用

阅读本篇文章前,请阅读 PCI 总线学习笔记(二),对 BDF、BAR 空间、Primary、Secondary、Subordinate Bus Number Register、Base&Limit Register 有所了解

1、PCI 总线的枚举

  在 PCI 总线中,系统软件使用深度优先 DFS 算法对 PCI 总线树进行遍历,DFS 算法和广度优先 BFS 算法是遍历树型结构的常用算法。与 BFS 算法相比,DFS 算法的空间复杂度较低,因此绝大多数系统系统在遍历 PCI 总线树时,都使用 DFS 算法而不是 BFS 算法。

  DFS 是搜索算法的一种,其实现机制是沿着一颗树的深度遍历各个节点,并尽可能深地搜索树的分支,DFS 的算法为线性时间复杂度,适合对拓扑结构未知的树进行遍历。在一个处理器系统的初始化阶段,PCI 总线树的拓扑结构是未知的,适合使用 DFS 算法进行遍历。下文以图 2-13 为例,说明系统软件如何使用 DFS 算法,分配 PCI 总线号,并初始化 PCI 桥中的 Primary Bus Number、Secondary Bus Number 和 Subordinate Bus number 寄存器。所谓 DFS 算法是指按照深度优先的原则遍历 PCI 树,其步骤如下:
在这里插入图片描述

  1. HOST 主桥开始扫描 PCI 总线 0 上的设备,即开始使用 BDF(0, x, x) 访问 bus 0 上的设备
  2. HOST 主桥首先发现 PCI 桥 1 ,并将 PCI 桥 1 的 Secondary Bus 命名为 PCI 总线 1。系统软件将初始化 PCI 桥 1 的配置空间,将 PCI 桥 1 的 Primary Bus Number 寄存器赋值为 0,而将 Secondary Bus Number 寄存器赋值为1,即 PCI 桥 1 的上游 PCI 总线号为0,而下游 PCI 总线号为 1

通常这里还会先将 PCI 桥的 Subordinate Bus Number 设置为 0xff。PCI 桥在转发配置空间读写事务时,会判断事务中的 bus 号范围,如果不在范围内,就会丢掉该事务。下面涉及到 PCI 桥的初始化都会先将 Subordinate Bus Number 设置为 0xff,而后再去更新该寄存器。后面就不再赘述了

  1. 扫描 PCI 总线 1,发现 PCI 桥 2,并将 PCI 桥 2 的 Secondary Bus 命名为 PCI 总线 2。系统软件将初始化 PCI 桥 2 的配置空间,将 PCI 桥 2 的 Primary Bus Number 寄存器赋值为 1,而将 Secondary Bus Number 寄存器赋值为 2
  2. 扫描 PCI 总线 2,发现 PCI 桥3,系统软件将 PCI 桥 3 的 Primary Bus Number 寄存器赋值为 2,而将Secondary Bus Number 寄存器赋值为 3
  3. 扫描 PCI 总线 3,没有发现任何 PCI 桥,此时系统软件将 PCI 桥 3 的 Subordinate Bus number 寄存器赋值为 3。系统软件在完成 PCI 总线 3 的扫描后,将回退到 PCI 总线 3 的上一级总线,即 PCI 总线2,继续进行扫描
  4. 在重新扫描 PCI 总线 2 时,系统软件发现 PCI 总线 2 上除了 PCI 桥 3 之外没有发现新的 PCI 桥,而 PCI 桥 3 之下的所有设备已经完成了扫描过程,此时系统软件将 PCI 桥 2 的 Subordinate Bus number 寄存器更新为3。继续回退到 PCI 总线1
  5. PCI 总线 1 上除了 PCI 桥 2 外,没有其他桥片,于是继续回退到 PCI 总线 0,并将 PCI 桥 1 的 Subordinate Bus number 寄存器更新为 3
  6. 在 PCI 总线 0 上,系统软件扫描到 PCI 桥4,则首先将 PCI 桥 4 的 Primary Bus Number 寄存器赋值为 0,而将 Secondary Bus Number 寄存器赋值为4,即 PCI 桥 1 的上游 PCI 总线号为0,而下游PCI 总线号为4
  7. 系统软件发现 PCI 总线 4 上没有任何 PCI 桥,将结束对 PCI 总线 4 的扫描,并将 PCI 桥 4 的Subordinate Bus number 寄存器更新为4,之后回退到 PCI 总线 4 的上游总线,即 PCI 总线 0 继续进行扫描
  8. 系统软件发现在 PCI 总线 0 上的两个桥片 PCI 总线 0 和 PCI 总线 4 都已完成扫描后,将结束对PCI 总线的 DFS 遍历全过程

  从以上算法可以看出,PCI 桥的 Primary Bus 和 Secondary Bus 号的分配在遍历 PCI 总线树的过程中从上向下分配,而 Subordinate Bus 号是从下向上分配的,因为只有确定了一个 PCI 桥之下究竟有多少条 PCI 总线后,才能初始化该 PCI 桥的 Subordinate Bus 号。

  从软件结构上来看,DFS 可以理解为一个递归函数,不断地增加 bus 号,尝试访问当前 bus 上的设备。通常我们会使用 BDF(bus, dev, func) 去访问设备的配置空间

  • 如果访问到当前设备为 PCI 桥设备,则 bus number = bus number + 1,继续遍历下一级 bus
  • 如果访问到当前设备为 PCI Agent 设备,则 dev = dev + 1,继续遍历当前 bus 上的其他设备
  • 如果访问不到当前设备,则返回

2、实战

  Linux 源码过于复杂,不易于初学者了解。uboot 是一个很好的切口,代码很少、精炼,更容易理解。

  以 uboot 源码为例:

/*
 * u-boot-2021.07/drivers/pci/pci.c
 */
 
pci_hose_scan()
--> pci_hose_scan_bus()
  --> pciauto_config_device()
    --> pciauto_setup_device
    --> pci_hose_scan_bus
    --> pciauto_postscan_setup_bridge

从上面的代码逻辑看,递归函数就是 pci_hose_scan_bus

int pci_hose_scan_bus(struct pci_controller *hose, int bus)
{
	unsigned int sub_bus, found_multi = 0;
	......
	sub_bus = bus;
	
	/*
	 * 使用 BDF 遍历当前 bus 上的设备
	 */
	for (dev =  PCI_BDF(bus,0,0);
	     dev <  PCI_BDF(bus, PCI_MAX_PCI_DEVICES - 1,
				PCI_MAX_PCI_FUNCTIONS - 1);
	     dev += PCI_BDF(0, 0, 1)) {
		......
		/*
		 * 尝试读取当前设备配置空间中的 PCI_HEADER_TYPE、PCI_VENDOR_ID 参数
		 * 来判断当前设备是否存在
		 */
		pci_hose_read_config_byte(hose, dev, PCI_HEADER_TYPE, &header_type);

		pci_hose_read_config_word(hose, dev, PCI_VENDOR_ID, &vendor);

		/* 设备不存在 */
		if (vendor == 0xffff || vendor == 0x0000)
			continue;
		......
		/*
		 * 代码走到这里,说明当前设备(bus, dev, func)是存在的
		 * 接下来就是对当前设备进行分析、配置
		 */
		sub_bus = max((unsigned int)pciauto_config_device(hose, dev),
			      sub_bus);
	}
	
	return sub_bus;
}

pciauto_config_device 函数分析如下:

/*
 * HJF: Changed this to return int. I think this is required
 * to get the correct result when scanning bridges
 */
int pciauto_config_device(struct pci_controller *hose, pci_dev_t dev)
{
	......
	unsigned short class;
	
    /* 读取设备配置空间 PCI_CLASS_DEVICE 寄存器 */
	pci_hose_read_config_word(hose, dev, PCI_CLASS_DEVICE, &class);
	
	/* 判断当前设备类型,PCI 桥还是 PCI Agent 设备 */
	switch (class) {
	case PCI_CLASS_BRIDGE_PCI:               
	/*
	 * 如果是 PCI 桥设备
	 */ 
	 
	/* 配置当前设备,主要是配置 BAR 空间地址 */
	pciauto_setup_device(hose, dev, 2, pci_mem,
			     pci_prefetch, pci_io);

	/* bus number = bus number + 1 */
	hose->current_busno++;
	/* 
	 * 配置当前设备,主要是配置 Primary、Secondary、Subordinate Bus Number
	 * 以及 Memory Base
	 */
	pciauto_prescan_setup_bridge(hose, dev, hose->current_busno);

	/* 递归遍历下一级 bus */
	n = pci_hose_scan_bus(hose, hose->current_busno);

	/* 
	 * 配置当前设备,主要是更新 Subordinate Bus Number
	 * 以及 Memory Limit Base
	 */
	sub_bus = max((unsigned int)n, sub_bus);
	pciauto_postscan_setup_bridge(hose, dev, sub_bus);

	sub_bus = hose->current_busno;
	break;

	case PCI_CLASS_PROCESSOR_POWERPC: /* an agent or end-point */
		/*
	 	 * 如果是 PCI Agent 设备
	 	 */ 
		debug("PCI AutoConfig: Found PowerPC device\n");

	default:
		/* 配置当前设备,主要是配置 BAR 空间地址 */
		pciauto_setup_device(hose, dev, 6, pci_mem,
				     pci_prefetch, pci_io);
		break;
	}

	return sub_bus;
}

pciauto_prescan_setup_bridgepciauto_postscan_setup_bridge 函数分析如下:

void pciauto_prescan_setup_bridge(struct pci_controller *hose,
					 pci_dev_t dev, int sub_bus)
{
	......
	/* Configure bus number registers */
	pci_hose_write_config_byte(hose, dev, PCI_PRIMARY_BUS,
				   PCI_BUS(dev) - hose->first_busno);
	pci_hose_write_config_byte(hose, dev, PCI_SECONDARY_BUS,
				   sub_bus - hose->first_busno);
	pci_hose_write_config_byte(hose, dev, PCI_SUBORDINATE_BUS, 0xff);

	if (pci_mem) {
		/* Round memory allocator to 1MB boundary */
		pciauto_region_align(pci_mem, 0x100000);

		/* Set up memory and I/O filter limits, assume 32-bit I/O space */
		pci_hose_write_config_word(hose, dev, PCI_MEMORY_BASE,
					(pci_mem->bus_lower & 0xfff00000) >> 16);

		cmdstat |= PCI_COMMAND_MEMORY;
	}
	......
}

void pciauto_postscan_setup_bridge(struct pci_controller *hose,
					  pci_dev_t dev, int sub_bus)
{
	......
	/* Configure bus number registers */
	pci_hose_write_config_byte(hose, dev, PCI_SUBORDINATE_BUS,
				   sub_bus - hose->first_busno);
	
	if (pci_mem) {
		/* Round memory allocator to 1MB boundary */
		pciauto_region_align(pci_mem, 0x100000);

		pci_hose_write_config_word(hose, dev, PCI_MEMORY_LIMIT,
				(pci_mem->bus_lower - 1) >> 16);
	}
	......
}

http://www.kler.cn/a/518078.html

相关文章:

  • Vue3笔记——(五)路由
  • Kubernetes v1.28.0安装dashboard v2.6.1(k8s图形化操作界面)
  • kettle经验篇:分享两个小白常见问题
  • 免费获得Photoshop等设计软件的机会
  • CF 420A.Start Up(Java实现)
  • 14-6-2C++的list
  • 基于 AI Coding 「RTC + STT」 Web Demo
  • 多层 RNN原理以及实现
  • Underwater 系列coding记录
  • Golang Gin系列-8:单元测试与调试技术
  • Gin 应用并注册 pprof
  • Jenkins pipeline共享库的最佳实践
  • 全面指南:使用JMeter进行性能压测与性能优化(中间件压测、数据库压测、分布式集群压测、调优)
  • LogicFlow 一款流程图编辑框架
  • SQL Server 建立每日自动log备份的维护计划
  • 基于 STM32 的智能农业温室控制系统设计
  • StarRocks常用命令
  • 24_游戏启动逻辑梳理总结
  • C语言初阶牛客网刷题——HJ76 尼科彻斯定理【难度:简单】
  • Class ‘ZipArchive‘ not found