深入理解Linux系统中的唤醒源管理与PCI设备驱动
1. 唤醒源管理
在Linux系统中,唤醒源管理对于系统的电源管理至关重要。唤醒源的激活和停用涉及一系列特定的步骤。
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 毫秒后安排执行(如果 msec 不为零)。
以下是唤醒源激活和停用步骤的表格总结:
| 操作 | 步骤 |
| — | — |
| 激活 | 1. 标记唤醒源为活动,增加 active_count
2. 更新 last_time 为当前时间
3. 若 autosleep_enabled 为真,更新 start_prevent_time |
| 停用 | 1. 设置 active 为 false
2. 更新 total_time
3. 若活动时长大于旧 max_time ,更新 max_time
4. 更新 last_time ,删除定时器,清除 timer_expires
5. 若 prevent_sleep_time 为真,更新 prevent_sleep_time |
1.3 唤醒源与sysfs
为了调试目的,可以通过打印 /sys/kernel/debug/wakeup_sources 文件的内容来列出系统中的所有唤醒源。该文件还报告每个唤醒源的统计信息。以下是一些与唤醒源相关的sysfs文件属性:
- wakeup :可读写属性,其内容决定了 device_may_wakeup() 辅助函数的返回值。
- wakeup_abort_count 和 wakeup_count :只读属性,指向 wakeup->wakeup_count 字段。
- wakeup_expire_count :映射到 wakeup->expire_count 字段。
- wakeup_active :只读,映射到 wakeup->active 元素。
- wakeup_total_time_ms :只读属性,返回 wakeup->total_time 值,单位为毫秒。
- wakeup_max_time_ms :返回 power.wakeup->max_time 值,单位为毫秒。
- wakeup_last_time_ms :只读属性,对应 wakeup->last_time 值,单位为毫秒。
- wakeup_prevent_sleep_time_ms :只读,映射到 wakeup->prevent_sleep_time 值,单位为毫秒。
2. IRQF_NO_SUSPEND标志
在系统的电源管理中, IRQF_NO_SUSPEND 标志起着重要作用。有些中断需要在整个系统的挂起 - 恢复周期中都能触发,例如定时器中断。对于这类中断,必须设置该标志。
不过,需要注意的是,该标志虽然有助于在挂起阶段保持中断启用,但并不能保证中断能将系统从挂起状态唤醒。要实现这一点,需要使用 enable_irq_wake() 函数,而该函数是特定于平台的。此外,如果一个带有该标志的中断被多个用户共享,那么所有用户都会受到影响,因此应避免将 IRQF_NO_SUSPEND 和 IRQF_SHARED 标志混合使用。
3. PCI设备驱动概述
PCI不仅仅是一种总线,它还是一个标准,定义了计算机不同部分之间的交互方式。多年来,PCI总线已成为设备互连的事实上的标准,几乎每个SoC都对其提供原生支持。随着对速度的需求不断增加,PCI总线也发展出了不同的版本和代际。
3.1 PCI总线的发展
- PCI总线 :早期实现PCI标准的总线,取代了ISA总线。它具有32位寻址和无跳线自动检测与配置功能,改善了ISA总线的地址限制(ISA总线限于24位寻址,有时需要使用跳线来路由IRQ等)。
- PCI Express(PCIe) :当前的PCI总线家族,是一种串行总线,而其前身是并行总线。除了速度提升外,PCIe还将其前身的32位寻址扩展到64位,并在中断管理系统方面有多项改进。PCIe分为不同的代际,如Gen1、Gen2、Gen3等。
3.2 PCIe的优势
与传统PCI总线相比,PCIe具有以下优势:
- 串行总线技术 :减少了连接设备所需的I/O通道数量,降低了设计复杂度。
- 增强的中断管理 :提供基于消息的中断(MSI)或其扩展版本(MSI - X),增加了PCI设备可处理的中断数量,同时不增加延迟。
- 更高的传输频率和吞吐量 :不同代际的PCIe在速度上不断提升。
以下是PCI和PCIe的对比表格:
| 特性 | PCI | PCIe |
| — | — | — |
| 总线类型 | 并行 | 串行 |
| 寻址 | 32位 | 64位 |
| 中断管理 | 传统 | MSI/MSI - X |
| 速度 | 相对较低 | 不断提升 |
3.3 PCI设备的特点
PCI设备是一种内存映射设备。连接到任何PCI总线的设备会在处理器的地址空间中被分配地址范围。这些地址范围在PCI地址域中有不同的含义,包含三种不同类型的内存:控制、数据和状态寄存器,以及根据访问方式(I/O端口或内存映射)划分的内存。设备驱动程序或内核将访问这些内存区域来控制连接在PCI总线上的特定设备并与之共享信息。
4. PCIe术语
在深入了解PCIe之前,需要熟悉一些相关术语:
- 根复合体(Root Complex, RC) :指SoC中的PCIe主机控制器,它可以在不经过CPU干预的情况下访问主内存,其他设备可利用这一特性访问主内存,也被称为主机到PCI桥。
- 端点(Endpoint, EP) :PCIe设备,由类型为 00h 的配置空间头表示,不会出现在交换机的内部总线上,且没有下游端口。
- 通道(Lane) :代表一组差分信号对(一对用于发送,一对用于接收)。
- 链路(Link) :表示两个组件之间的双单工通信通道。为了扩展带宽,链路可以聚合多个通道,用 xN (如 x1 、 x2 、 x4 、 x8 、 x12 、 x16 和 x32 )表示,其中 N 是通道对的数量。
- 桥(Bridges) :提供与其他总线(如PCI或PCI - X,甚至另一个PCIe总线)的接口,也可以提供与同一总线的接口。例如,PCI到PCI桥通过创建一个完全独立的二级总线来增加总线上的负载。
- 交换机(Switches) :提供聚合功能,允许更多设备连接到单个根端口。交换机有一个上游端口和多个下游端口,能够像数据包路由器一样工作,根据数据包的地址或其他路由信息(如ID)识别数据包的传输路径。
以下是PCIe术语的mermaid流程图:
graph LR
A[PCIe系统] --> B[根复合体(RC)]
A --> C[端点(EP)]
A --> D[桥(Bridges)]
A --> E[交换机(Switches)]
D --> F[PCI到PCI桥]
E --> G[上游端口]
E --> H[下游端口]
B --> I[访问主内存]
C --> J[无下游端口]
F --> K[创建二级总线]
H --> L[虚拟PCI - PCI桥]
5. PCI总线枚举、设备配置和寻址
PCIe相对于PCI的一个显著改进是其点对点的总线拓扑结构,每个设备都位于自己的专用总线上,在PCIe术语中称为链路。理解PCIe设备的枚举过程需要一些基础知识。
5.1 设备类型识别
当查看设备的寄存器空间(在头类型寄存器中)时,可以判断其是类型0还是类型1的寄存器空间。通常,类型0表示端点设备,类型1表示桥设备。软件需要识别与之通信的是端点设备还是桥设备,因为桥设备的配置与端点设备不同。
5.2 桥设备配置
在桥设备(类型1)枚举期间,软件需要为其分配以下元素:
- 主总线号(Primary Bus Number) :上游总线号。
- 二级/从属总线号(Secondary/Subordinate Bus Number) :给出特定PCI桥下游总线号的范围。二级总线号是PCI - PCI桥紧下游的总线号,而从属总线号表示该桥下游所有可到达总线的最高总线号。在枚举的第一阶段,从属总线号字段被赋予值 0xFF (因为255是最高总线号),随着枚举的进行,该字段将被赋予该桥实际能到达的下游总线的最高编号。
5.3 设备识别
在PCI子系统中,设备识别由以下参数组成:
- 厂商ID(Vendor ID) :识别设备的制造商。
- 设备ID(Device ID) :识别特定厂商的设备。
- 修订ID(Revision ID) :指定设备特定的修订标识符。
- 类代码(Class Code) :识别设备实现的通用功能。
- 头类型(Header Type) :定义头的布局。
所有这些参数都可以从设备配置寄存器中读取,内核在枚举总线时会利用这些信息来识别设备。
5.4 总线枚举
在进行PCIe总线枚举之前,需要考虑一些基本限制:
- 系统中最多可以有256条总线(编号为0 - 255),因为用8位来识别它们。
- 每条总线上最多可以有32个设备(编号为0 - 31),因为用5位来识别它们。
- 一个设备最多可以有8个功能(编号为0 - 7),用3位来识别它们。
PCI枚举过程基于深度优先搜索(DFS)算法,通常从一个已知节点(在PCI枚举中为根复合体)开始,沿着每个分支尽可能深入地探索(实际上是寻找桥),然后回溯。当找到一个桥时,配置软件会为其分配一个至少比该桥所在总线号大1的编号,然后在新总线上继续寻找新的桥,依此类推。
枚举后的设备使用BDF格式(Bus - Device - Function)进行标识,使用三个字节(十六进制表示,不包含 0x ),例如 00:01:03 表示总线 0x00 上设备 0x01 的功能 0x03 。这种表示法有助于在给定拓扑中快速定位设备。如果使用双字节表示法,则表示功能被省略或不重要,例如 XX:YY 。
以下是一个简单的PCIe总线枚举步骤示例:
1. 根复合体(总线0)中有两个桥( 00:00:00 和 00:01:00 )。
2. 00:00 作为一个(虚拟)桥,其下游端口被分配为总线1,然后对总线1进行枚举。
3. 总线1上有一个交换机,其内部总线被编号为2。
4. 交换机的第一个下游桥的总线被编号为3,后面是一个端点设备。
5. 回溯后,交换机的第二个下游桥的总线被编号为4,后面有一个设备。
6. 00:01 桥的下游总线被编号为5,该总线上有一个交换机,其内部总线被编号为6,有三个下游桥。
7. 第一个下游桥的总线被编号为7,后面是一个端点设备。
8. 回溯后,第二个下游桥的总线被编号为8,后面有一个PCIe到PCI桥。
通过以上步骤,我们可以看到PCIe设备的枚举过程是一个复杂但有序的过程,它确保了系统能够正确识别和配置所有连接的PCIe设备。
深入理解Linux系统中的唤醒源管理与PCI设备驱动
6. PCI总线枚举示例分析
为了更清晰地理解PCI总线枚举过程,我们结合之前提到的示例拓扑图进行详细分析。
在这个拓扑图中,标准化枚举逻辑从步骤C开始。以下是对各个步骤的详细解释:
| 步骤 | 描述 |
|---|---|
| A - B | 根复合体内部,主机总线编号为0,有两个桥 00:00:00 和 00:01:00 。 |
| C | 总线1上有一个交换机,其内部总线编号为2。 |
| D | 交换机的第一个下游桥被找到,其总线编号为3,后面是一个端点设备,根据DFS算法,到达此分支的叶节点,开始回溯。 |
| E | 交换机中与步骤D中桥为兄弟关系的虚拟桥轮到被处理,其总线编号为4,后面有一个设备,再次进行回溯。 |
| F | 步骤B中找到的虚拟桥被分配总线编号5,该总线上有一个交换机,其内部总线编号为6,有三个下游桥。 |
| G | 3 - 端口交换机的第一个下游虚拟桥的总线编号为7,后面是一个端点设备,到达分支底部,开始回溯。 |
| H | 3 - 端口交换机的第二个下游虚拟桥的总线编号为8,后面有一个PCIe到PCI桥。 |
这个枚举过程严格遵循深度优先搜索(DFS)算法,确保了系统能够全面且有序地识别和配置所有PCIe设备。
以下是该枚举过程的mermaid流程图:
graph LR
A[根复合体(总线0)] --> B[桥00:00:00]
A --> C[桥00:01:00]
B --> D[总线1]
D --> E[交换机(内部总线2)]
E --> F[下游桥1(总线3)]
F --> G[端点设备]
E --> H[下游桥2(总线4)]
H --> I[设备]
C --> J[总线5]
J --> K[交换机(内部总线6)]
K --> L[下游桥1(总线7)]
L --> M[端点设备]
K --> N[下游桥2(总线8)]
N --> O[PCIe到PCI桥]
7. Linux内核PCI子系统与数据结构
在Linux内核中,PCI子系统提供了一系列的数据结构和API来管理PCI设备。这些数据结构和API抽象了底层的PCI总线操作,使得驱动开发者可以更方便地编写PCI设备驱动。
以下是一些重要的数据结构和对应的功能:
| 数据结构 | 功能 |
|---|---|
struct pci_dev | 表示一个PCI设备,包含设备的各种信息,如设备ID、厂商ID、资源信息等。 |
struct pci_driver | 表示一个PCI设备驱动,包含驱动的名称、设备ID表、探测和移除函数等。 |
struct pci_bus | 表示一个PCI总线,包含总线的编号、父总线、子设备等信息。 |
驱动开发者可以通过这些数据结构和相关的API来完成PCI设备的注册、初始化、资源分配等操作。例如,使用 pci_register_driver() 函数来注册一个PCI设备驱动,使用 pci_enable_device() 函数来启用一个PCI设备。
8. PCI与直接内存访问(DMA)
PCI设备通常支持直接内存访问(DMA)功能,这允许设备直接与系统内存进行数据传输,而无需CPU的干预,从而提高了数据传输的效率。
在Linux内核中,使用DMA需要进行以下几个步骤:
- DMA映射 :使用
dma_map_single()或dma_map_sg()函数将设备的物理地址映射到内核的虚拟地址空间。 - DMA传输 :配置设备的DMA控制器,设置传输的源地址、目的地址和传输长度等参数,然后启动DMA传输。
- DMA完成处理 :在DMA传输完成后,使用
dma_unmap_single()或dma_unmap_sg()函数解除映射,并处理传输结果。
以下是一个简单的DMA传输示例代码:
#include <linux/pci.h>
#include <linux/dma-mapping.h>
static int my_pci_driver_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
struct device *device = &dev->dev;
dma_addr_t dma_handle;
void *buffer;
int ret;
// 分配内存
buffer = dma_alloc_coherent(device, PAGE_SIZE, &dma_handle, GFP_KERNEL);
if (!buffer) {
dev_err(device, "Failed to allocate DMA buffer\n");
return -ENOMEM;
}
// 配置设备的DMA控制器
// ...
// 启动DMA传输
// ...
// 等待DMA传输完成
// ...
// 解除映射
dma_free_coherent(device, PAGE_SIZE, buffer, dma_handle);
return 0;
}
static struct pci_device_id my_pci_device_table[] = {
{ PCI_DEVICE(VENDOR_ID, DEVICE_ID), },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, my_pci_device_table);
static struct pci_driver my_pci_driver = {
.name = "my_pci_driver",
.id_table = my_pci_device_table,
.probe = my_pci_driver_probe,
// .remove = my_pci_driver_remove,
};
static int __init my_pci_driver_init(void)
{
return pci_register_driver(&my_pci_driver);
}
static void __exit my_pci_driver_exit(void)
{
pci_unregister_driver(&my_pci_driver);
}
module_init(my_pci_driver_init);
module_exit(my_pci_driver_exit);
9. 技术要求总结
开发PCI设备驱动需要具备一定的技术基础,以下是详细的技术要求:
- Linux内存管理和内存映射 :需要了解Linux内核的内存管理机制,包括物理内存和虚拟内存的映射关系,以及如何进行内存分配和释放。
- 中断和锁机制 :熟悉Linux内核中的中断处理和锁机制,确保驱动在多线程环境下的正确性和稳定性。
- Linux内核版本 :建议使用Linux kernel v4.19.X版本的源代码,可以从 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/refs/tags 下载。
通过掌握以上技术要求,驱动开发者可以更好地理解和开发PCI设备驱动。
10. 总结
本文深入探讨了Linux系统中的唤醒源管理和PCI设备驱动相关内容。在唤醒源管理方面,我们了解了唤醒源的激活和停用步骤,以及如何通过sysfs接口查看唤醒源的统计信息。同时,介绍了 IRQF_NO_SUSPEND 标志的作用和使用注意事项。
在PCI设备驱动方面,我们回顾了PCI总线的发展历程,分析了PCIe的优势和相关术语,详细阐述了PCI总线的枚举过程、设备配置和寻址方法。此外,还介绍了Linux内核PCI子系统的数据结构和API,以及PCI设备的DMA功能和使用方法。
通过对这些内容的学习,我们可以更好地理解Linux系统中的电源管理和PCI设备驱动开发,为进一步的系统优化和设备驱动开发打下坚实的基础。
Linux唤醒源与PCI驱动详解
超级会员免费看
1498

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



