32、Linux内核中的唤醒源管理与PCI设备驱动开发

Linux内核中的唤醒源管理与PCI设备驱动开发

1. 唤醒源管理

在系统运行过程中,唤醒源管理对于控制设备的电源状态和系统的整体功耗至关重要。唤醒源的激活和停用操作涉及多个步骤,这些步骤会更新唤醒源结构体中的多个字段。

1.1 唤醒源激活步骤
  • 标记唤醒源为活动状态,并增加唤醒源的 active_count 元素。
  • 将唤醒源的 last_time 字段更新为当前时间。
  • 如果 autosleep_enabled 字段为真,则更新唤醒源的 start_prevent_time 字段。
1.2 唤醒源停用步骤
  • 将唤醒源的 active 字段设置为 false
  • 通过将活动状态所花费的时间添加到旧值来更新唤醒源的 total_time 字段。
  • 如果活动状态的持续时间大于旧的 max_time 字段的值,则用该持续时间更新唤醒源的 max_time 字段。
  • 用当前时间更新唤醒源的 last_time 字段,删除唤醒源的定时器,并清除 timer_expires
  • 如果 prevent_sleep_time 字段为真,则更新唤醒源的 prevent_sleep_time 字段。

停用操作可以立即发生(如果 msec == 0 ),也可以在未来 msec 毫秒后调度发生。

1.3 唤醒源与sysfs

可以通过打印 /sys/kernel/debug/wakeup_sources 文件的内容来列出系统中的所有唤醒源,并查看每个唤醒源的统计信息。以下是一些与唤醒源相关的sysfs文件属性及其在 struct wakeup_source 结构中的映射:
| 属性 | 描述 | 映射字段 | 读写属性 |
| ---- | ---- | ---- | ---- |
| wakeup | 其内容决定 device_may_wakeup() 辅助函数的返回值 | - | 读写 |
| wakeup_abort_count | 只读属性 | wakeup->wakeup_count | 只读 |
| wakeup_count | 只读属性 | wakeup->wakeup_count | 只读 |
| wakeup_expire_count | 只读属性 | wakeup->expire_count | 只读 |
| wakeup_active | 只读属性 | wakeup->active | 只读 |
| wakeup_total_time_ms | 以毫秒为单位返回 wakeup->total_time 的值 | wakeup->total_time | 只读 |
| wakeup_max_time_ms | 以毫秒为单位返回 power.wakeup->max_time 的值 | power.wakeup->max_time | 只读 |
| wakeup_last_time_ms | 以毫秒为单位对应 wakeup->last_time 的值 | wakeup->last_time | 只读 |
| wakeup_prevent_sleep_time_ms | 以毫秒为单位映射到 wakeup->prevent_sleep_time 的值 | wakeup->prevent_sleep_time | 只读 |

1.4 IRQF_NO_SUSPEND标志

有些中断需要在整个系统的挂起 - 恢复周期中都能触发,包括设备挂起和恢复的无中断阶段,以及非引导CPU离线和上线的时间。对于这类中断,需要设置 IRQF_NO_SUSPEND 标志。但需要注意的是,该标志虽然有助于在挂起阶段保持中断启用,但不能保证IRQ能将系统从挂起状态唤醒,这种情况下需要使用 enable_irq_wake() ,而这是特定于平台的。同时,应避免混合使用 IRQF_NO_SUSPEND IRQF_SHARED 标志,因为如果一个带有该标志的IRQ被多个用户共享,所有注册该中断的处理程序都会受到影响。

2. PCI设备驱动开发

PCI不仅仅是一种总线,它还是一个标准,定义了计算机不同部分之间的交互方式。多年来,PCI总线已成为设备互连的事实上的标准,几乎每个SoC都对其有原生支持。

2.1 PCI总线的发展

早期,实现PCI标准的第一条总线是PCI总线,它取代了ISA总线,解决了ISA总线的地址限制问题(ISA总线限于24位地址,有时需要使用跳线来路由IRQ等),并采用了32位寻址和无跳线自动检测与配置。后来,PCI Express成为当前的PCI总线家族,它是串行总线,而其前身是并行总线。PCIe在速度、寻址(从32位扩展到64位)和中断管理系统等方面都有改进。

2.2 PCIe的优势
  • 串行总线技术 :减少了连接设备所需的I/O通道数量,降低了设计复杂度。
  • 增强的中断管理功能 :提供基于消息的中断(MSI)或其扩展版本(MSI - X),增加了PCI设备可处理的中断数量,且不增加延迟。
  • 更高的传输频率和吞吐量 :有Gen1、Gen2、Gen3等不同代际。
2.3 PCI设备的内存映射

PCI设备是内存映射设备,连接到任何PCI总线的设备都会在处理器的地址空间中分配地址范围。这些地址范围在PCI地址域中有不同的含义,包含三种不同类型的内存:控制、数据和状态寄存器,以及通过I/O端口或内存映射进行访问的内存。设备驱动程序或内核会访问这些内存区域来控制特定的PCI设备并与之共享信息。

2.4 PCIe术语
  • 根复合体(RC) :指SoC中的PCIe主机控制器,可在无需CPU干预的情况下访问主内存,也称为主机到PCI桥。
  • 端点(EP) :PCIe设备,由类型为 00h 的配置空间头表示,不会出现在交换机的内部总线上,且没有下游端口。
  • 通道(Lane) :代表一组差分信号对(一对用于发送,一对用于接收)。
  • 链路(Link) :代表两个组件之间的双单工通信通道,为了扩展带宽,链路可以聚合多个通道,用 xN (如 x1 x2 x4 x8 x12 x16 x32 )表示,其中 N 是通道对的数量。
  • 桥(Bridges) :提供与其他总线(如PCI、PCI - X或另一个PCIe总线)的接口,也可以提供与同一总线的接口。例如,PCI - 到 - PCI桥通过创建一个完全独立的二级总线来增加总线上的负载。
  • 交换机(Switches) :提供聚合功能,允许更多设备连接到单个根端口。交换机有一个上游端口和多个下游端口,能够作为数据包路由器,根据地址或其他路由信息(如ID)确定数据包的传输路径。

以下是PCIe相关术语的关系图:

graph LR
    A[根复合体(RC)] --> B[端点(EP)]
    A --> C[桥(Bridges)]
    A --> D[交换机(Switches)]
    D --> E[通道(Lane)]
    D --> F[链路(Link)]
2.5 PCI总线枚举、设备配置和寻址

PCIe相对于PCI的一个显著改进是其点对点的总线拓扑结构,每个设备都有自己的专用总线,即链路。理解PCIe设备的枚举过程需要了解一些基本概念。

在设备的寄存器空间中,通过查看头类型寄存器可以确定设备是端点设备(类型0)还是桥设备(类型1)。桥设备的配置与端点设备不同,在枚举桥设备(类型1)时,软件需要为其分配以下元素:
- 主总线号 :上游总线号。
- 二级/从属总线号 :指定特定PCI桥的下游总线号范围。二级总线号是PCI - 到 - PCI桥下游的直接总线号,从属总线号表示桥下游可到达的所有总线的最高总线号。在枚举的第一阶段,从属总线号字段的值为 0xFF ,随着枚举的进行,该字段将被赋予实际值。

设备标识由以下参数组成:
- 厂商ID :标识设备的制造商。
- 设备ID :标识特定厂商的设备。
- 修订ID :指定设备特定的修订标识符。
- 类代码 :标识设备实现的通用功能。
- 头类型 :定义头的布局。

这些参数可以从设备配置寄存器中读取,内核在枚举总线时会使用这些信息来识别设备。

2.6 总线枚举

在进行PCIe总线枚举之前,需要考虑一些基本限制:
- 系统中最多可以有256条总线(编号为0 - 255),因为用8位来标识它们。
- 每条总线上最多可以有32个设备(编号为0 - 31),因为用5位来标识每个总线上的设备。
- 一个设备最多可以有8个功能(编号为0 - 7),用3位来标识。

所有外部PCIe通道,无论是否源自CPU,都位于PCIe桥后面,因此会获得新的PCIe总线号。配置软件能够在给定系统上枚举最多256条PCI总线,根复合体的总线号始终为0。需要注意的是,在总线枚举过程中,只考虑PCI - 到 - PCI桥的下游端口。

PCI枚举过程基于深度优先搜索(DFS)算法,通常从一个已知节点(在PCI枚举中为根复合体)开始,沿着每个分支尽可能深入地探索(实际上是寻找桥),然后回溯。当找到一个桥时,配置软件会为其下游端口分配一个总线号,该总线号至少比桥所在的总线号大1,然后继续在新总线上寻找新的桥。

枚举后的设备使用BDF格式(Bus - Device - Function)进行标识,使用三个字节的十六进制表示(不带 0x )。例如, 00:01:03 表示总线 0x00 上设备 0x01 的功能 0x03

以下是一个PCIe总线枚举过程的示例步骤:
| 步骤 | 描述 |
| ---- | ---- |
| A、B | 根复合体内部操作,根复合体托管总线0,并带有两个桥(提供两条总线 00:00:00 00:01:00 ) |
| C | 总线1上有一个交换机,其内部总线编号为2 |
| D | 找到交换机的第一个下游桥,其总线编号为3,后面是一个端点 |
| E | 找到交换机的第二个下游桥,其总线编号为4,后面有一个设备 |
| F | 步骤B中找到的虚拟桥的总线编号为5,后面有一个交换机 |
| G | 3端口交换机的第一个下游虚拟桥的总线编号为7,后面是一个端点 |
| H | 3端口交换机的第二个下游虚拟桥的总线编号为8,后面有一个PCIe - 到 - PCI桥 |

Linux内核中的唤醒源管理与PCI设备驱动开发

2.7 PCIe总线枚举示例拓扑分析

下面结合一个具体的拓扑图来详细分析PCIe总线的枚举过程。在分析之前,我们需要牢记以下四个重要原则:
1. PCI - 到 - PCI桥通过创建一个完全独立的二级总线来增加总线上的负载,因此每个桥的下游端口都是一条新的总线,其总线号至少比桥所在的总线号大1。
2. 交换机的下游端口是一种虚拟的PCI - PCI(P2P)桥,它将内部总线连接到代表其输出的PCI Express链路的总线。
3. CPU通过主机到PCI桥连接到根复合体,该桥代表根复合体中的上游桥。
4. 在总线枚举过程中,只考虑PCI - 到 - PCI桥的下游端口。

以下是该拓扑图中枚举过程的详细步骤:
| 步骤 | 详细描述 |
| ---- | ---- |
| A、B | 根复合体内部操作,根复合体托管总线0,并带有两个桥,分别提供总线 00:00:00 00:01:00 。这里 00:00 是一个虚拟桥,其下游端口必然是一条总线,根据规则,它被分配编号1(因为桥所在的总线号是0),随后总线1开始被枚举。 |
| C | 总线1上有一个交换机,这个交换机有一个上游虚拟桥提供其内部总线,还有两个下游虚拟桥暴露其输出总线。交换机的内部总线被赋予编号2。 |
| D | 找到交换机的第一个下游桥,其总线被分配编号3,该总线后面是一个端点(没有下游端口)。根据深度优先搜索(DFS)算法的原则,我们到达了这个分支的叶节点,此时可以开始回溯。 |
| E | 找到交换机中与步骤D中桥为兄弟关系的虚拟桥,其总线被分配编号4,后面有一个设备。之后可以再次进行回溯操作。 |
| F | 步骤B中找到的虚拟桥被分配总线编号5,该总线后面有一个交换机。这个交换机有一个上游虚拟桥实现其内部总线(编号为6),还有3个下游虚拟桥代表其外部总线,这是一个3端口交换机。 |
| G | 3端口交换机的第一个下游虚拟桥被找到,其总线被赋予编号7,后面是一个端点。如果用BDF格式标识这个端点的功能0,就是 07:00:00 。根据DFS算法,我们到达了这个分支的底部,开始回溯。 |
| H | 3端口交换机的第二个下游虚拟桥被找到,其总线被分配编号8,后面有一个PCIe - 到 - PCI桥。 |

以下是该枚举过程的流程图:

graph TD
    A[根复合体(总线0)] --> B[桥1(00:00:00)]
    A --> C[桥2(00:01:00)]
    B --> D[总线1]
    D --> E[交换机(内部总线2)]
    E --> F[下游桥1(总线3,端点)]
    E --> G[下游桥2(总线4,设备)]
    C --> H[总线5]
    H --> I[3端口交换机(内部总线6)]
    I --> J[下游桥3(总线7,端点)]
    I --> K[下游桥4(总线8,PCIe - 到 - PCI桥)]
2.8 Linux内核PCI子系统和数据结构

Linux内核为PCI设备驱动开发提供了一系列的API和核心功能,这些功能使得驱动开发者可以方便地编写可靠的PCI设备驱动。内核会对PCI总线的各种机制进行抽象和隐藏,通过一组简化的API来实现对PCI设备的操作。

对于驱动开发者来说,无论使用的是哪种PCI总线家族(如PCI、PCIe等),大部分操作都是透明的。内核会处理不同总线之间的差异,开发者只需要使用内核提供的API即可完成设备的初始化、配置和数据传输等操作。

2.9 PCI和直接内存访问(DMA)

PCI设备通常支持直接内存访问(DMA),这是一种允许设备直接与系统内存进行数据传输的机制,而无需CPU的干预。DMA可以显著提高数据传输的效率,特别是在大量数据需要快速传输的场景下。

在PCI设备驱动开发中,需要正确配置和管理DMA操作。这包括分配DMA缓冲区、设置DMA控制器的参数、处理DMA传输的完成中断等。内核提供了一系列的API来帮助开发者完成这些任务,例如 dma_map_single() 用于映射单个DMA缓冲区, dma_unmap_single() 用于取消映射等。

以下是一个简单的PCI设备DMA操作的流程:
1. 初始化DMA :在设备初始化阶段,分配DMA缓冲区,并使用 dma_map_single() 将其映射到DMA地址空间。
2. 配置DMA控制器 :设置DMA控制器的源地址、目标地址、传输长度等参数。
3. 启动DMA传输 :向DMA控制器发送启动命令,开始数据传输。
4. 处理DMA完成中断 :当DMA传输完成时,会触发中断,在中断处理程序中处理传输结果,并使用 dma_unmap_single() 取消缓冲区的映射。

2.10 技术要求

在进行PCI设备驱动开发之前,开发者需要具备以下知识和技能:
- Linux内存管理和内存映射 :了解Linux内核的内存管理机制,包括物理内存和虚拟内存的映射关系,以及如何进行内存分配和释放。
- 中断和锁的概念 :熟悉Linux内核中的中断处理机制和锁机制,能够正确处理中断和避免并发访问的问题。
- Linux内核版本 :建议使用Linux内核v4.19.X版本的源代码,可从 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/refs/tags 获取。

总结

本文详细介绍了Linux内核中的唤醒源管理和PCI设备驱动开发的相关知识。在唤醒源管理方面,我们了解了唤醒源的激活和停用步骤、sysfs接口的使用以及 IRQF_NO_SUSPEND 标志的注意事项。在PCI设备驱动开发方面,我们探讨了PCI总线的发展、PCIe的优势、PCI设备的内存映射、PCIe相关术语、总线枚举过程以及Linux内核PCI子系统和DMA操作等内容。通过掌握这些知识,开发者可以更好地进行系统的电源管理和PCI设备驱动的开发。

基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究(Matlab代码实现)内容概要:本文围绕“基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究”,介绍了利用Matlab代码实现配电网可靠性的仿真分析方法。重点采用序贯蒙特卡洛模拟法对配电网进行长时间段的状态抽样统计,通过模拟系统元件的故障修复过程,评估配电网的关键可靠性指标,如系统停电频率、停电持续时间、负荷点可靠性等。该方法能够有效处理复杂网络结构设备时序特性,提升评估精度,适用于含分布式电、电动汽车等新型负荷接入的现代配电网。文中提供了完整的Matlab实现代码案例分析,便于复现和扩展应用。; 适合人群:具备电力系统基础知识和Matlab编程能力的高校研究生、科研人员及电力行业技术人员,尤其适合从事配电网规划、运行可靠性分析相关工作的人员; 使用场景及目标:①掌握序贯蒙特卡洛模拟法在电力系统可靠性评估中的基本原理实现流程;②学习如何通过Matlab构建配电网仿真模型并进行状态转移模拟;③应用于含新能接入的复杂配电网可靠性定量评估优化设计; 阅读建议:建议结合文中提供的Matlab代码逐段调试运行,理解状态抽样、故障判断、修复逻辑及指标统计的具体实现方式,同时可扩展至不同网络结构或加入更多不确定性因素进行深化研究。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值