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设备驱动的开发。
超级会员免费看
91

被折叠的 条评论
为什么被折叠?



