pci_enable_device()

前言

在 PCI 总线下,当 PCIe 设备和 PCIe 驱动匹配后,就会执行驱动的 probe() 函数来初始化设备,以让设备正常运行。
在 probe() 函数中,最先做的事情就是执行 pci_enable_device() 来使能设备。如果设备都无法使能的话,那就直接返回,接下来的工作也就不用做了。

代码解析

pci_enable_device() 的实现在内核 pci 子系统中,所在文件为 drivers/pci/pci.c

/**
 * pci_enable_device - Initialize device before it's used by a driver.
 * @dev: PCI device to be initialized
 *
 *  Initialize device before it's used by a driver. Ask low-level code
 *  to enable I/O and memory. Wake up the device if it was suspended.
 *  Beware, this function can fail.
 *
 *  Note we don't actually enable the device many times if we call
 *  this function repeatedly (we just increment the count).
 */
int pci_enable_device(struct pci_dev *dev)
{
	return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
}

在 pci_enable_device() 中调用 pci_enable_device_flags(),pci_enable_device_flags() 实现如下

static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)
{
	struct pci_dev *bridge;
	int err;
	int i, bars = 0;

	/*
	 * Power state could be unknown at this point, either due to a fresh
	 * boot or a device removal call.  So get the current power state
	 * so that things like MSI message writing will behave as expected
	 * (e.g. if the device really is in D0 at enable time).
	 */
	if (dev->pm_cap) {
		u16 pmcsr;
		pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
		dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
	}

	if (atomic_inc_return(&dev->enable_cnt) > 1)
		return 0;		/* already enabled */

	bridge = pci_upstream_bridge(dev);
	if (bridge)
		pci_enable_bridge(bridge);

	/* only skip sriov related */
	for (i = 0; i <= PCI_ROM_RESOURCE; i++)
		if (dev->resource[i].flags & flags)
			bars |= (1 << i);
	for (i = PCI_BRIDGE_RESOURCES; i < DEVICE_COUNT_RESOURCE; i++)
		if (dev->resource[i].flags & flags)
			bars |= (1 << i);

	err = do_pci_enable_device(dev, bars);
	if (err < 0)
		atomic_dec(&dev->enable_cnt);
	return err;
}

第 15 行调用 pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); 读取电源管理寄存器(起始地址:0x40)的 PMCSR 字段(地址:0x40 + 4),该字段占 3 个字节,但最高 1 字节为保留位,所以有效字节为 2 字节。所以代码中使用 u16 类型的变量 pmcsr 来存储读到的值。

在这里插一句:
PCI 设备使用的基本配置空间由 64 个字节组成,地址范围为 0x00 ~ 0x3F,这 64 个字节是所有 PCI 设备必须支持的。事实上,许多 PCI 设备也仅支持这 64 个配置寄存器。
此外,PCIe 设备还扩展了 0x40 ~ 0xFF 这段配置空间,这段配置空间主要存放一些与 MSI 中断机制和电源管理相关的 Capability 结构。

以下是《PCI EXPRESS 体系结构导读》中对电源管理寄存器的介绍
在这里插入图片描述
在这里插入图片描述
从规范中可以看到,PMCSR 的第 0~1 bits 为 Power State 字段,所以代码中该字段的掩码为 0x3。
pmcsr 与掩码按位与就得到了 Power State,将结果赋值给 dev->current_state。

#define  PCI_PM_CTRL_STATE_MASK	0x0003	/* Current power state (D0 to D3) */
dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);

下面这两行代码应验了 pci_enable_device() 函数的注释: we don’t actually enable the device many times if we call this function repeatedly (we just increment the count).

	if (atomic_inc_return(&dev->enable_cnt) > 1)
		return 0;		/* already enabled */

接下来的三行代码,作用是,使能该 PCIe 设备的上游桥片,upstream 这个概念也是 PCI 规范中出现的。
使能上游桥片,想想也是理所当然,如果上游桥片不工作,那么处于该桥片下面的设备怎么可能正常工作呢。

	bridge = pci_upstream_bridge(dev);
	if (bridge)
		pci_enable_bridge(bridge);

接着获取 bar 掩码,表示使用了哪几个 BAR,对于 PCIe 设备而言,最多有 6 个 BAR 空间,即 BAR0~BAR5。

	/* only skip sriov related */
	for (i = 0; i <= PCI_ROM_RESOURCE; i++)
		if (dev->resource[i].flags & flags)
			bars |= (1 << i);
	for (i = PCI_BRIDGE_RESOURCES; i < DEVICE_COUNT_RESOURCE; i++)
		if (dev->resource[i].flags & flags)
			bars |= (1 << i);

接着就到了 do_pci_enable_device(dev, bars),看下 do_pci_enable_device() 的具体实现

static int do_pci_enable_device(struct pci_dev *dev, int bars)
{
	int err;
	struct pci_dev *bridge;
	u16 cmd;
	u8 pin;

	err = pci_set_power_state(dev, PCI_D0);
	if (err < 0 && err != -EIO)
		return err;

	bridge = pci_upstream_bridge(dev);
	if (bridge)
		pcie_aspm_powersave_config_link(bridge);

	err = pcibios_enable_device(dev, bars);
	if (err < 0)
		return err;
	pci_fixup_device(pci_fixup_enable, dev);

	if (dev->msi_enabled || dev->msix_enabled)
		return 0;

	pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
	if (pin) {
		pci_read_config_word(dev, PCI_COMMAND, &cmd);
		if (cmd & PCI_COMMAND_INTX_DISABLE)
			pci_write_config_word(dev, PCI_COMMAND,
					      cmd & ~PCI_COMMAND_INTX_DISABLE);
	}

	return 0;
}

第 8 行 pci_set_power_state(dev, PCI_D0) 将设备设置为 D0 状态,
pci_set_power_state() 具体实现如下

/**
 * pci_set_power_state - Set the power state of a PCI device
 * @dev: PCI device to handle.
 * @state: PCI power state (D0, D1, D2, D3hot) to put the device into.
 *
 * Transition a device to a new power state, using the platform firmware and/or
 * the device's PCI PM registers.
 *
 * RETURN VALUE:
 * -EINVAL if the requested state is invalid.
 * -EIO if device does not support PCI PM or its PM capabilities register has a
 * wrong version, or device doesn't support the requested state.
 * 0 if the transition is to D1 or D2 but D1 and D2 are not supported.
 * 0 if device already is in the requested state.
 * 0 if the transition is to D3 but D3 is not supported.
 * 0 if device's power state has been successfully changed.
 */
int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
{
	int error;

	/* bound the state we're entering */
	if (state > PCI_D3cold)
		state = PCI_D3cold;
	else if (state < PCI_D0)
		state = PCI_D0;
	else if ((state == PCI_D1 || state == PCI_D2) && pci_no_d1d2(dev))
		/*
		 * If the device or the parent bridge do not support PCI PM,
		 * ignore the request if we're doing anything other than putting
		 * it into D0 (which would only happen on boot).
		 */
		return 0;

	/* Check if we're already there */
	if (dev->current_state == state)
		return 0;

	__pci_start_power_transition(dev, state);

	/* This device is quirked not to be put into D3, so
	   don't put it in D3 */
	if (state >= PCI_D3hot && (dev->dev_flags & PCI_DEV_FLAGS_NO_D3))
		return 0;

	/*
	 * To put device in D3cold, we put device into D3hot in native
	 * way, then put device into D3cold with platform ops
	 */
	error = pci_raw_set_power_state(dev, state > PCI_D3hot ?
					PCI_D3hot : state);

	if (!__pci_complete_power_transition(dev, state))
		error = 0;

	return error;
}

其中 D0、D1、D2、D3cold、D3hot 状态及其转换关系在 PCIe 规范中都有明确规定,

在这里插入图片描述
该段代码调用 pci_raw_set_power_state(dev, state > PCI_D3hot ? PCI_D3hot : state); 将 PCIe 设备设置为 D0 状态,pci_raw_set_power_state() 函数的实现如下,

/**
 * pci_raw_set_power_state - Use PCI PM registers to set the power state of
 *                           given PCI device
 * @dev: PCI device to handle.
 * @state: PCI power state (D0, D1, D2, D3hot) to put the device into.
 *
 * RETURN VALUE:
 * -EINVAL if the requested state is invalid.
 * -EIO if device does not support PCI PM or its PM capabilities register has a
 * wrong version, or device doesn't support the requested state.
 * 0 if device already is in the requested state.
 * 0 if device's power state has been successfully changed.
 */
static int pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state)
{
	u16 pmcsr;
	bool need_restore = false;

	/* Check if we're already there */
	if (dev->current_state == state)
		return 0;

	if (!dev->pm_cap)
		return -EIO;

	if (state < PCI_D0 || state > PCI_D3hot)
		return -EINVAL;

	/* Validate current state:
	 * Can enter D0 from any state, but if we can only go deeper
	 * to sleep if we're already in a low power state
	 */
	if (state != PCI_D0 && dev->current_state <= PCI_D3cold
	    && dev->current_state > state) {
		dev_err(&dev->dev, "invalid power transition (from state %d to %d)\n",
			dev->current_state, state);
		return -EINVAL;
	}

	/* check if this device supports the desired state */
	if ((state == PCI_D1 && !dev->d1_support)
	   || (state == PCI_D2 && !dev->d2_support))
		return -EIO;

	pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);

	/* If we're (effectively) in D3, force entire word to 0.
	 * This doesn't affect PME_Status, disables PME_En, and
	 * sets PowerState to 0.
	 */
	switch (dev->current_state) {
	case PCI_D0:
	case PCI_D1:
	case PCI_D2:
		pmcsr &= ~PCI_PM_CTRL_STATE_MASK;
		pmcsr |= state;
		break;
	case PCI_D3hot:
	case PCI_D3cold:
	case PCI_UNKNOWN: /* Boot-up */
		if ((pmcsr & PCI_PM_CTRL_STATE_MASK) == PCI_D3hot
		 && !(pmcsr & PCI_PM_CTRL_NO_SOFT_RESET))
			need_restore = true;
		/* Fall-through: force to D0 */
	default:
		pmcsr = 0;
		break;
	}

	/* enter specified state */
	pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, pmcsr);

	/* Mandatory power management transition delays */
	/* see PCI PM 1.1 5.6.1 table 18 */
	if (state == PCI_D3hot || dev->current_state == PCI_D3hot)
		pci_dev_d3_sleep(dev);
	else if (state == PCI_D2 || dev->current_state == PCI_D2)
		udelay(PCI_PM_D2_DELAY);

	pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
	dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
	if (dev->current_state != state && printk_ratelimit())
		dev_info(&dev->dev, "Refused to change power state, currently in D%d\n",
			 dev->current_state);

	/*
	 * According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT
	 * INTERFACE SPECIFICATION, REV. 1.2", a device transitioning
	 * from D3hot to D0 _may_ perform an internal reset, thereby
	 * going to "D0 Uninitialized" rather than "D0 Initialized".
	 * For example, at least some versions of the 3c905B and the
	 * 3c556B exhibit this behaviour.
	 *
	 * At least some laptop BIOSen (e.g. the Thinkpad T21) leave
	 * devices in a D3hot state at boot.  Consequently, we need to
	 * restore at least the BARs so that the device will be
	 * accessible to its driver.
	 */
	if (need_restore)
		pci_restore_bars(dev);

	if (dev->bus->self)
		pcie_aspm_pm_state_change(dev->bus->self);

	return 0;
}

第 45 行读取设备当前的 pmcsr 寄存器值,
第 55、56 行将该寄存器中的 state 字段设置为指定值(D0 = 0),
第 71 行将 pmcsr 写入设备的配置空间,
完成 PCIe 设备的使能工作

总结

可以看到,驱动代码是 PCIe 规范的具体实现,
如果你没看过规范,那么当你看到如下宏定义时

#define PCI_D0		((pci_power_t __force) 0)
#define PCI_D1		((pci_power_t __force) 1)
#define PCI_D2		((pci_power_t __force) 2)
#define PCI_D3hot	((pci_power_t __force) 3)
#define PCI_D3cold	((pci_power_t __force) 4)
#define PCI_UNKNOWN	((pci_power_t __force) 5)
#define PCI_POWER_ERROR	((pci_power_t __force) -1)

你又怎会明白这些 D0、D1、D2、D3hot、D3cold 是什么意思?
没看过规范,你又怎会明白 pmcsr 变量是什么意思?
没看过规范,你又怎会知道 Power State 的掩码要设置为 0x3

#define  PCI_PM_CTRL_STATE_MASK	0x0003

甚至,驱动和内核代码中,常常可以见到直接以规范的章节号、表格号作为注释,如

/* see PCI PM 1.1 5.6.1 table 18 */
/* According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT
 * INTERFACE SPECIFICATION, REV. 1.2" ... */ 

意思很明显,作者觉得三言两语也解释不清这段代码的作用,就直接贴出来了规范的章节号,请读者自己去看规范吧。

下面这个例子更具有说服力,

static void pci_flr_wait(struct pci_dev *dev)
{
	int delay = 1, timeout = 60000;
	u32 id;

	/*
	 * Per PCIe r3.1, sec 6.6.2, a device must complete an FLR within
	 * 100ms, but may silently discard requests while the FLR is in
	 * progress.  Wait 100ms before trying to access the device.
	 */
	msleep(100);

	/*
	 * After 100ms, the device should not silently discard config
	 * requests, but it may still indicate that it needs more time by
	 * responding to them with CRS completions.  The Root Port will
	 * generally synthesize ~0 data to complete the read (except when
	 * CRS SV is enabled and the read was for the Vendor ID; in that
	 * case it synthesizes 0x0001 data).
	 *
	 * Wait for the device to return a non-CRS completion.  Read the
	 * Command register instead of Vendor ID so we don't have to
	 * contend with the CRS SV value.
	 */
	pci_read_config_dword(dev, PCI_COMMAND, &id);
	while (id == ~0) {
		if (delay > timeout) {
			dev_warn(&dev->dev, "not ready %dms after FLR; giving up\n",
				 100 + delay - 1);
			return;
		}

		if (delay > 1000)
			dev_info(&dev->dev, "not ready %dms after FLR; waiting\n",
				 100 + delay - 1);

		msleep(delay);
		delay *= 2;
		pci_read_config_dword(dev, PCI_COMMAND, &id);
	}

	if (delay > 1000)
		dev_info(&dev->dev, "ready %dms after FLR\n", 100 + delay - 1);
}

第 11 行,直接来了个 msleep(100);,还好作者写了注释:根据 PCIe r3.1,第 6.6.2 节,设备必须在 100ms 内完成一个 FLR。不过,即便有这个注释,你要想真正弄明白为什么要睡眠 100ms,还是要去阅读 PCIe 3.1 规范的 6.6.2 节才行。

所以,借用《Linux 那些事儿之我是 U 盘》中的一句话:
从协议中来,到协议中去。

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

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

相关文章

MES系统是什么?MES系统的功能有哪些?

在现代制造业的快速发展中&#xff0c;所有规模的企业都面临着类似的挑战&#xff1a;如何提高生产效率、确保产品质量、减少浪费、降低成本&#xff0c;同时迅速响应市场变化。而在这个过程中&#xff0c;传统企业管理往往有以下几个典型痛点&#xff1a; 纸质文件堆叠如山&a…

框架面试题

文章目录 1. spring中的bean是线程安全的吗2. 事务的实现--AOP3. 项目中用到的AOP4.spring中事务的失效场景5. Bean的生命周期6.spring中的循环引用问题7. springMVC的执行流程8. springboot自动装配原理9. 常见注解10 Mybatis11 Mybatis一二级缓存 1. spring中的bean是线程安全…

约束-练习题

练习1 已经存在数据库test04_emp&#xff0c;两张表emp2和dept2 CREATE DATABASE test04_emp; use test04_emp; CREATE TABLE emp2( id INT, emp_name VARCHAR(15) ); CREATE TABLE dept2( id INT, dept_name VARCHAR(15) );题目: 向表emp2的id列中添加PRIMARY KEY约束向表d…

前端---css 的介绍

1. css 的定义 css(Cascading Style Sheet)层叠样式表&#xff0c;它是用来美化页面的一种语言。 没有使用css的效果图 使用css的效果图 2. css 的作用 美化界面, 比如: 设置标签文字大小、颜色、字体加粗等样式。控制页面布局, 比如: 设置浮动、定位等样式。 3. css 的基本语…

【2024 行人重识别最新进展】ReID3D:首个关注激光雷达行人 ReID 的工作!

【2024 行人重识别最新进展】ReID3D&#xff1a;首个关注激光雷达行人 ReID 的工作&#xff01; 摘要&#xff1a;数据集&#xff1a;方法模型&#xff1a;多任务预训练&#xff1a;ReID Network&#xff1a; 实验结果&#xff1a;结论&#xff1a; 来源&#xff1a;Arxiv 2023…

[Linux] MySQL数据库之事务

一、事务的概念 事务就是一组数据库操作序列&#xff08;包含一个或者多个 SQL 操作命令&#xff09;&#xff0c;事务会把所有 操作看作是一个不可分割的整体向数据库系统提交或撤消操作&#xff0c;所有操作要么都执行&#xff0c;要么都不执行。 事务是一种机制、一个操作序…

Java 基础学习(十七)多线程高级

1 多线程并发安全&#xff08;续&#xff09; 1.1 synchronized方法 1.1.1 synchronized方法 与同步代码块不同&#xff0c;同步方法将子线程要访问的代码放到一个方法中&#xff0c;在该方法的名称前面加上关键字synchronized即可&#xff0c;这里默认的锁为this&#xff0…

短视频矩阵系统的崛起和影响

近年来&#xff0c;短视频矩阵系统已经成为了社交媒体中的一股新势力。这个新兴的社交媒体形式以其独特的魅力和吸引力&#xff0c;迅速吸引了大量的用户。这个系统简单来说就是将海量短视频整合在一个平台上&#xff0c;使用户可以方便地观看和分享好玩有趣的短视频。 短视频…

测试员有必要转测试开发吗?

为什么很多公司都在招测试开发&#xff1f; 质量保证和软件稳定性&#xff1a;测试开发人员在软件开发生命周期中扮演着关键的角色&#xff0c;他们负责编写和执行测试代码&#xff0c;以确保软件的质量和稳定性。他们可以帮助发现和修复潜在的问题和缺陷&#xff0c;提高软件…

短视频矩阵系统源码是如何运作的?

在当今数字化时代&#xff0c;短视频已经成为人们日常生活中的重要娱乐方式。而为了更好地满足用户需求以及提升使用体验&#xff0c;短视频平台需要依靠一个强大而高效的短视频矩阵系统。那么&#xff0c;这个系统又是如何运作的呢&#xff1f; 首先&#xff0c;短视频矩阵系…

[机器人-1]:开源MIT Min cheetah机械狗设计(一):系统方案及硬件解析

目录 一、MIT Min cheetah机械狗系统设计 1.1 指标需求 1.2 系统框图 1&#xff09;腿部硬件构成&#xff1a;机械狗每条腿 (共四条腿&#xff09; 2&#xff09; 数据通信转换板部分 2 STM32 * 2 CAN * (4 * 3)&#xff1a;FOC算法 3&#xff09;UP board 计算机板卡硬件…

【C++】explicit关键字

explicit关键字通常用于声明构造&#xff0c;是为了防止编译器进行隐式类型转换。 当加上explicit关键字后&#xff0c;只能显示的调用构造函数

Android---Kotlin 学习009

继承 在 java 里如果一个类没有被 final 关键字修饰&#xff0c;那么它都是可以被继承的。而在 kotlin 中&#xff0c;类默认都是封闭的&#xff0c;要让某个类开放继承&#xff0c;必须使用 open 关键字修饰它&#xff0c;否则会编译报错。此外在子类中&#xff0c;如果要复写…

09-为Java开疆拓土的ZGC深度剖析

文章目录 引言ZGC出现背景STW带来的问题手机系统(Android) 显示卡顿证券交易系统实时性要求大数据平台(Hadoop集群性能) 垃圾回收器的发展 ZGC介绍ZGC中JVM内存布局和设计为什么这么设计&#xff1f; ZGC支持NUMA&#xff08;了解即可&#xff09; ZGC的核心概念指针着色技术&a…

智能优化算法应用:基于人工蜂鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于人工蜂鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于人工蜂鸟算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工蜂鸟算法4.实验参数设定5.算法结果6.…

Redis源码精读:字符串

文章目录 前言代码位置核心类型SDS结构获取sds字符串的元数据的宏获取字符串长度重新设置sds长度创建字符串感悟最后 前言 Redis中实现了sds&#xff08;simple dynamic string&#xff09;这种字符串&#xff0c;它比c语言标准库的char*字符串更加实用 代码位置 src/sdc.h …

Ascon加解密算法分析

参数定义 加密架构图 整个过程是在处理320bits的数据&#xff0c;所以在最开始需要对原始的数据进行一个初始化&#xff0c;获得320bits的数据块&#xff0c; 图里看到的pa和pb都是在做置换&#xff08;对320bits的数据进行一个置换&#xff09; 置换&#xff08;Permutation&…

不可能得到的最短骰子序列

说在前面 &#x1f388;不知道大家对于算法的学习是一个怎样的心态呢&#xff1f;为了面试还是因为兴趣&#xff1f;不管是出于什么原因&#xff0c;算法学习需要持续保持。 题目描述 给你一个长度为 n 的整数数组 rolls 和一个整数 k 。你扔一个 k 面的骰子 n 次&#xff0c;…

Vue 封装echarts饼状图(Pie)组件

目的&#xff1a;减少重复代码&#xff0c;便于维护 效果显示&#xff1a; 组件代码 <template><div class"ldw-data-content-box"><div class"ldw-chilren-box"><div class"title"><div>{{ title }}</div>…

【隐私保护】使用Python从文本中删除个人信息:第一部分

自我介绍 做一个简单介绍&#xff0c;酒架年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【…