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);
	}
	......
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/959312.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

麒麟操作系统基础知识保姆级教程(二十一)进入单用户模式

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 在咱们运维工作中&#xff0c;服务器的密码长度&#xff0c;密码复杂度&#xff0c;修改时间&#xff0c;超时时间&#xff0c;用户权限管理&#xff0c;root直接远程连接&#xff0c;普通用户su到r…

ARM64平台Flutter环境搭建

ARM64平台Flutter环境搭建 Flutter简介问题背景搭建步骤1. 安装ARM64 Android Studio2. 安装Oracle的JDK3. 安装 Dart和 Flutter 开发插件4. 安装 Android SDK5. 安装 Flutter SDK6. 同意 Android 条款7. 运行 Flutter 示例项目8. 修正 aapt2 报错9. 修正 CMake 报错10. 修正 N…

OpenCV:高通滤波之索贝尔、沙尔和拉普拉斯

目录 简述 什么是高通滤波&#xff1f; 高通滤波的概念 应用场景 索贝尔算子 算子公式 实现代码 特点 沙尔算子 算子公式 实现代码 特点 拉普拉斯算子 算子公式 实现代码 特点 高通滤波器的对比与应用场景 相关阅读 OpenCV&#xff1a;图像滤波、卷积与卷积核…

VS企业版和专业版的区别

网上查询vs分析dump文件&#xff0c;查找托管内存泄露&#xff0c;需要使用“调试托管内存”功能&#xff0c;当前安装的vs2022 专用版找不到这个选项&#xff0c;vs2015是ok的&#xff0c;比较版本发现2022是专业版&#xff0c;2015是企业版。网上搜索专业版和企业版差异如下&…

ESP8266 NodeMCU与WS2812灯带:实现多种花样变换

在现代电子创意项目中&#xff0c;LED灯带的应用已经变得极为广泛。通过结合ESP8266 NodeMCU的强大处理能力和FastLED库的高效功能&#xff0c;我们可以轻松实现多达100种灯带变换效果。本文将详细介绍如何使用Arduino IDE编程&#xff0c;实现从基础到高级的灯光效果&#xff…

技术 · 创作 · 生活 | 我的 2024 全面复盘

目录 &#x1f31f;2024年度总结&#xff1a;回顾、成长与突破&#x1f31f;&#x1f680; 一、技术成长与突破 &#x1f680;&#x1f517; 1. 深入区块链与智能合约&#x1f50d; 2. 探索新兴技术 ✍️ 二、创作与博客历程 ✍️&#x1f4d6; 1. 内容创作的演变&#x1f3c6;…

嵌入式MCU面试笔记2

目录 串口通信 概论 原理 配置 HAL库代码 1. 初始化函数 2. 数据发送和接收函数 3. 中断和DMA函数 4. 中断服务函数 串口通信 概论 我们知道&#xff0c;通信桥接了两个设备之间的交流。一个经典的例子就是使用串口通信交换上位机和单片机之间的数据。 比较常见的串…

LeetCode --- 433周赛

题目列表 3427. 变长子数组求和 3428. 最多 K 个元素的子序列的最值之和 3429. 粉刷房子 IV 3430. 最多 K 个元素的子数组的最值之和 一、变长子数组求和 题意要求我们能快速算出 n u m s [ s t a r t . . . i ] nums[start...i] nums[start...i] 这段区间和&#xff0c;其中…

EasyNVR免费版已发布!EasyNVR接入海康NVR大华NVR宇视NVR天地伟业NVR接入各种IPC摄像机工业监控家庭监控

EasyNVR不用多说了&#xff0c;驰名已久&#xff01;之前一直是收费的&#xff0c;不管多少个摄像机接入都是收费的&#xff0c;这就导致&#xff0c;很多个人用户&#xff0c;或者说是家庭用户&#xff0c;家里就那么两三个摄像机&#xff0c;想通过EasyNVR接入NAS系统&#x…

任务一:Android逆向

首先我使用了一个叫objection的东西。 列出了他所有的活动界面,列出来之后在慢慢筛选。 然后用了一个命令,就是可以跳到这个活动界面的命令。 我就确定了这个活动界面的位置,然后我就采取了objection的另一种栈追踪。 追踪到了这个地方。 先打印出具有特征参数的值,在…

黑龙江锅包肉:酸甜香酥的东北经典

黑龙江锅包肉:酸甜香酥的东北经典 黑龙江锅包肉,作为东北菜的代表之一,尤其在黑龙江省哈尔滨市享有极高的声誉。这道美食不仅承载着丰富的历史文化内涵,更以其鲜明的地域特色,成为了黑龙江省乃至整个东北地区的标志性菜肴。 历史渊源 锅包肉的历史可以追溯到清朝光绪年间,其…

[JavaScript] ES6及以后版本的新特性

文章目录 箭头函数&#xff08;Arrow Functions&#xff09;为什么需要箭头函数&#xff1f;箭头函数的完整语法箭头函数中的 this实用场景 解构赋值&#xff08;Destructuring Assignment&#xff09;为什么需要解构赋值&#xff1f;数组解构赋值的完整用法对象解构赋值的完整…

ipad和macbook同步zotero文献附件失败的解决办法

背景&#xff1a;我所有的文献及其附件pdf都是在台式机&#xff08;windows系统&#xff09;&#xff0c;想要把这些文献同步到云上&#xff0c;然后再从云上同步到平板和其他笔记本电脑比如macbook。文献同步虽已成功&#xff0c;但文献附件都无法打开。 平板报错如下&#xf…

element tbas增加下拉框

使用Tabs 标签页的label插槽&#xff0c;嵌入Dropdown 下拉菜单&#xff0c;实现Tabs 标签页增加下拉切换功能 Tabs 标签页 tab-click"事件"&#xff08;这个事件当中到拥有下拉框的tab里时&#xff0c;可以存一下Dropdown 第一个菜单的id&#xff0c;实现点击到拥有…

环境变量配置与问题解决

目录 方法 配置了还是运行不了想要的东西 解决方案 为什么 解决方案 方法 方法一&#xff1a;此电脑右击-属性-相关链接-高级系统设置-环境变量&#xff08;N&#xff09;-系统变量里面找到Path-三个确定】 方法二&#xff1a;winr cmd 黑框输入sysdm.cpl&#xff0c;后面…

【C++】详细讲解继承(下)

本篇来继续说说继承。上篇可移步至【C】详细讲解继承&#xff08;上&#xff09; 1.继承与友元 友元关系不能继承 &#xff0c;也就是说基类友元不能访问派⽣类私有和保护成员。 class Student;//前置声明class Same //基类 { public:friend void Fun(const Same& p, con…

联想电脑怎么设置u盘启动_联想电脑设置u盘启动方法(支持新旧机型)

有很多网友问联想电脑怎么设置u盘启动&#xff0c;联想电脑设置u盘启动的方法有两种&#xff0c;一是通过bios进行设置。二是通过快捷方式启动进入u盘启动。但需要注意有两种引导模式是&#xff0c;一种是uefi引导&#xff0c;一种是传统的leacy引导&#xff0c;所以需要注意制…

算法|牛客网华为机试53-62C++

牛客网华为机试 上篇&#xff1a;算法|牛客网华为机试41-52C 文章目录 HJ53 杨辉三角的变形HJ54 表达式求值HJ55 挑7HJ56 完全数计算HJ57 高精度整数加法HJ58 输入n个整数&#xff0c;输出其中最小的k个HJ59 找出字符串中第一个只出现一次的字符HJ60 查找组成一个偶数最接近的两…

消息队列篇--通信协议篇--TCP和UDP(3次握手和4次挥手,与Socket和webSocket的概念区别等)

1、TCP和UDP概述 TCP&#xff08;传输控制协议&#xff0c;Transmission Control Protocol&#xff09;和UDP&#xff08;用户数据报协议&#xff0c;User Datagram Protocol&#xff09;都算是最底层的通信协议&#xff0c;它们位于OSI模型的传输层。*传输层的主要职责是确保…

springboot基于Spring Boot的智慧养老服务系统的设计与实现

系统介绍&#xff1a; 智慧养老服务系统是一种运用现代科技手段&#xff0c;整合各类养老资源&#xff0c;为老年人提供全方位、个性化服务的综合性平台。该系统通过智能化设备、大数据分析、云计算等技术&#xff0c;实现对老年人健康状况、生活需求的实时监控与精准匹配&…