在掌握了驱动开发的基础后,深入内核子系统的定制与调试,将使我们具备系统级的分析与问题解决能力。本部分将重点解析时钟(Clock)管理、DMA/IOMMU及GIC/中断控制器等核心机制。
时钟管理框架 (Clock Framework)
在嵌入式系统中,时钟是设备的生命线,它直接决定了设备的性能与功耗表现。Linux内核的时钟框架负责管理SoC内部复杂的时钟树,为嵌入式系统开发提供统一的抽象接口。
一个典型的时钟体系包含以下部分:
- 时钟源(Clock Source):如晶振或锁相环(PLL),用于产生基础频率。
- 时钟树(Clock Tree):时钟源经过分频器、选择器、多路复用器等,形成复杂的派生关系网。
- 时钟消费者(Clock Consumer):如I2C、显示控制器等设备驱动,需要向内核申请并使用时钟。
- 时钟提供者(Clock Provider):通常由芯片厂商提供的底层驱动,负责配置PLL和分频器等硬件。
在设备驱动中,我们使用 clk_* 系列API来操作时钟,例如在省电模式下降低频率,或在需要高性能时提升频率。
驱动代码示例:获取与操作时钟
// 获取时钟
struct clk *clk;
clk = devm_clk_get(&pdev->dev, "clk_name"); // "clk_name" 对应设备树中的时钟命名
// 使能时钟
clk_prepare_enable(clk); // 准备并使能时钟
// ... 设备工作 ...
clk_disable_unprepare(clk); // 禁用并释放时钟
// 设置频率
long rate = clk_set_rate(clk, 100000000); // 设置为 100MHz
设备树 (DTS) 配置示例
在设备树中,通过 clocks 属性声明驱动所需的时钟。
my_i2c_controller {
// 引用时钟控制器节点,以及时钟 ID
clocks = <&cru SCLK_I2C0>;
clock-names = "i2c_clk"; // 驱动中 devm_clk_get(&dev, “i2c_clk”) 使用的名称
status = "okay";
};
DMA 与 IOMMU
当涉及大量或高速数据传输时(如网络、存储、显示),若由CPU负责数据搬运将成为系统瓶颈。DMA (直接内存访问) 技术允许外设直接与内存交换数据,解放CPU。而IOMMU则解决了DMA过程中的地址映射与安全问题。
DMA核心概念:
- 地址映射:外设操作的是总线地址,而CPU使用虚拟地址。DMA API的核心职责之一就是完成这两种地址间的正确转换。
- 缓存一致性:DMA操作期间,数据在设备与CPU缓存中可能不一致。DMA API需要负责在适当时机刷写或无效化缓存,确保数据同步。
驱动代码示例:DMA 内存映射
// DMA映射
dma_addr_t bus_addr;
void *cpu_addr = buffer;
size_t size = 512;
// 将 CPU 虚拟地址映射为 DMA 总线地址
bus_addr = dma_map_single(dev, cpu_addr, size, DMA_TO_DEVICE);
// 将 bus_addr 写入硬件 DMA 寄存器...
// ... 数据传输 ...
// 传输完成后,必须解除映射以保证缓存一致性
dma_unmap_single(dev, bus_addr, size, DMA_TO_DEVICE);
IOMMU (输入输出内存管理单元):
IOMMU的功能类似于CPU的MMU,它负责将外设发出的总线地址翻译为系统的物理地址。其主要作用包括:
- 内存保护:防止恶意或故障设备访问非授权内存区域。
- 提供连续地址视图:将物理上不连续的内存页映射为连续的总线地址,简化设备驱动开发。
例如,RK3568等芯片集成了IOMMU,以支持更复杂的DMA场景。在驱动开发中,只要正确使用了标准的DMA API,内核通常会自动处理底层IOMMU的配置,这涉及到底层的内存管理与地址映射机制。
|