【C语言】linux内核pci_enable_device函数和_PCI_NOP宏

2024-03-22 08:12

本文主要是介绍【C语言】linux内核pci_enable_device函数和_PCI_NOP宏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

pci_enable_device

一、注释

static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)
{struct pci_dev *bridge;int err;int i, bars = 0;/** 此时电源状态可能是未知的,可能是由于新启动或者设备移除调用。* 因此获取当前的电源状态,这样像 MSI 消息写入这样的操作会按预期行为* (比如,如果设备在 enable 时确实位于 D0 状态)。*/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;        /* 已经启用 */// 查找上游桥设备,如果存在则启用它bridge = pci_upstream_bridge(dev);if (bridge)pci_enable_bridge(bridge);// 只跳过和 SR-IOV 相关的资源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);// 实际启用 PCI 设备err = do_pci_enable_device(dev, bars);if (err < 0)atomic_dec(&dev->enable_cnt); // 如果启用失败,则减少引用计数return err;
}/*** pci_enable_device - 在驱动程序使用设备前初始化设备。* @dev: 需要初始化的 PCI 设备**  在驱动程序使用设备前初始化设备。要求底层代码去启用 I/O 和内存。*  如果设备已经进入休眠,则将其唤醒。注意,该函数可能会失败。**  注意如果我们反复调用这个函数,我们不会真正多次启用设备(我们只是增加计数)。*/
int pci_enable_device(struct pci_dev *dev)
{return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
}
EXPORT_SYMBOL(pci_enable_device);

在这段代码中,`pci_enable_device_flags` 是一个静态函数,它接受两个参数:一个指向 PCI 设备的指针和一个标志集合。函数的目的是启用指定的 PCI 设备,但首先会检查设备的电源管理(PM)能力和当前的电源状态,并递增设备的启用计数。如果设备已经启用,函数会立即返回。然后函数检查是否有上游桥设备,若存在则递归地启用桥设备。接下来,它会确定需要启用的资源并最终调用 do_pci_enable_device 函数。

pci_enable_device 函数封装了 pci_enable_device_flags 函数,用于启用 PCI 设备的 I/O 和内存资源。该函数导出了符号 pci_enable_device,使得其他内核模块可以通过符号名引用该函数。 

二、讲解

这段代码是Linux内核中用于启动PCI设备的一部分。下面将对这段代码的关键部分进行讲解。

static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)

这是一个静态函数,其目的是用给定的标志(`flags`)来启动一个PCI设备(`dev`)。

    struct pci_dev *bridge;int err;int i, bars = 0;

定义了指向PCI设备的桥接器的指针`bridge`,一个整型错误码`err`以及两个整型变量`i`和`bars`,其中`bars`用于跟踪需要启用的设备资源条目(BARs)。

    if (dev->pm_cap) {

检查设备(dev)是否支持电源管理(PM)功能。

        u16 pmcsr;pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);}

如果支持,就从设备的配置空间中读取电源管理控制状态寄存器(PMCSR),并更新设备当前的电源状态。

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

使用原子操作增加设备使能计数器`enable_cnt`,如果这不是第一次使能(计数大于1),则表示设备已经使能,函数直接返回0。

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

找到设备的上游桥接器(bridge),如果存在,就启用这个桥接器。

    for (i = 0; i <= PCI_ROM_RESOURCE; i++)...for (i = PCI_BRIDGE_RESOURCES; i < DEVICE_COUNT_RESOURCE; i++)...

这两个循环检查设备的所有资源条目,确定哪些BAR需要被启用,这基于给定的标志和资源的类型。

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

调用`do_pci_enable_device`来实际启用这些资源。如果有错误发生,减少使能计数器并返回错误码。

int pci_enable_device(struct pci_dev *dev)
{return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
}
EXPORT_SYMBOL(pci_enable_device);

这是一个外部可见的函数,用默认的标志(内存和I/O资源)来启用一个PCI设备。

整体而言,这段代码是驱动程序在使用PCI设备之前,对设备进行初始化和启用操作的过程。它确保设备可以响应内存和I/O操作,以及从低功耗状态中唤醒设备。

本函数在drivers\pci\pci.c中定义。

_PCI_NOP宏

一、注释

/** 如果系统没有PCI,很显然这些函数会返回错误。定义这些函数为简单的内联函数,* 以避免在驱动程序中的复杂操作。*/
#define _PCI_NOP(o, s, t) \static inline int pci_##o##_config_##s(struct pci_dev *dev, \int where, t val) \{ return PCIBIOS_FUNC_NOT_SUPPORTED; } // 定义宏_PCI_NOP来构造内联函数,若PCI操作不被支持,则返回错误码#define _PCI_NOP_ALL(o, x)    _PCI_NOP(o, byte, u8 x) \ // 利用_PCI_NOP宏定义读写byte、word、dword三种操作_PCI_NOP(o, word, u16 x) \_PCI_NOP(o, dword, u32 x)_PCI_NOP_ALL(read, *) // 定义所有的read操作,如果系统无法执行PCI读取操作,它们将返回错误
_PCI_NOP_ALL(write,) // 定义所有的write操作,如果系统无法执行PCI写入操作,它们将返回错误

二、讲解

这段代码是用于操作PCI配置空间的宏定义和内联函数,适用于没有PCI支持的系统环境。在这种情况下,对PCI配置空间的读写操作将返回一个错误码,通常是因为PCI功能不被支持(`PCIBIOS_FUNC_NOT_SUPPORTED`)。
代码解释:
1. _PCI_NOP 宏定义: 它用于声明一个静态的内联函数,这个函数对应于PCI读或写操作,并返回一个错误码 PCIBIOS_FUNC_NOT_SUPPORTED。这个宏接收三个参数:操作类型 (o)、数据大小 (s) 和数据类型 (t)。具体来说:
    - o 表示操作类型,可以是 read 或 write。
    - s 表示数据的大小,可以是 byte(8位),`word`(16位)或 dword(32位)。
    - t 表示对应的数据类型,分别是 u8(unsigned 8-bit)、`u16` (unsigned 16-bit)、`u32` (unsigned 32-bit).
2. _PCI_NOP_ALL 宏定义: 它是一个帮助宏,用于针对读和写操作声明所有可能的大小的内联函数,即字节、字和双字。
3. _PCI_NOP_ALL(read, *) 和 _PCI_NOP_ALL(write,): 这两个宏分别为读和写操作创建了三个函数,即针对 byte、`word` 和 dword 大小的数据。对 read 操作的函数,传入变量 val 是一个指针(`*指明取地址),这是因为读操作需要一个指针来存放读到的值。对 write` 操作,`val` 是一个普通变量,因为写操作是将该值写入到PCI配置空间。
4. 函数 pci_##o##_config_##s: 这是一个构建函数名的宏,这里使用了宏的字符串化(##)来组合操作类型和数据大小,创建特定的函数名。例如,`pci_read_config_byte` 或 pci_write_config_dword。
总结一下,这段代码就是为不支持PCI操作的系统提供了一组空操作(no-operation, NOP)函数,以便在该系统的PCI驱动中使用,从而避免了在驱动代码中添加复杂的条件编译指令。当驱动尝试进行PCI配置空间的访问时,这些函数会直接返回一个“功能不支持”的错误码。

这篇关于【C语言】linux内核pci_enable_device函数和_PCI_NOP宏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/834901

相关文章

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信