2. 时钟框架概述:Linux内核通用时钟框架(CCF)的架构、核心数据结构与时钟提供者/使用者模型

2.1 CCF 设计目标与分层架构

Linux 内核通用时钟框架(Common Clock Framework, CCF)旨在统一管理 SoC 中种类繁多的时钟源(晶振、PLL、分频器、多路选择器、门控等),为时钟提供者(Clock Provider)和时钟使用者(Clock Consumer)提供一套标准化的操作接口。CCF 采用分层设计,从上到下依次为:

  1. 时钟消费者 API 层:供设备驱动调用,如 clk_get()、clk_prepare_enable()、clk_set_rate()。
  2. 时钟核心框架层:维护时钟树拓扑、处理速率传播、实现通用操作逻辑。
  3. 时钟硬件抽象层(struct clk_hw):将底层硬件寄存器操作封装为回调函数。
  4. 时钟提供者驱动层:由具体 SoC 厂商实现,注册到 CCF 中。
调优视角: 理解分层架构有助于在性能调优时快速定位瓶颈——消费者 API 调用失败通常检查设备树节点;速率不符合预期则需排查核心框架的速率传播逻辑或提供者驱动回调。
CCF 分层架构与数据流 时钟消费者 API 层 clk_get / clk_prepare_enable / clk_set_rate / clk_put 时钟核心框架层 时钟树维护 / 速率传播 / 通用操作调度 时钟硬件抽象层 (struct clk_hw) 回调函数:recalc_rate / round_rate / set_rate / enable / disable 时钟提供者驱动层 厂商实现:寄存器读写 / PLL配置 / 分频器控制 数据流方向:消费者 → 核心框架 → 硬件抽象 → 提供者驱动 → 硬件寄存器

2.2 核心数据结构

CCF 围绕三个核心结构体构建时钟树:

数据结构 所在文件 核心作用 关键字段
struct clk_core drivers/clk/clk.c 时钟树内部节点,维护拓扑关系和速率传播 name, parent, children, rate, flags
struct clk_hw include/linux/clk-provider.h 硬件抽象层,连接核心框架与提供者驱动 core, init (包含回调函数表)
struct clk include/linux/clk.h 消费者句柄,通过 clk_get() 获得 core, dev_id, con_id
关键关系: struct clk 是消费者看到的“指针”,指向 struct clk_core;struct clk_core 通过 struct clk_hw 调用提供者驱动的回调函数。三者形成“句柄 → 核心节点 → 硬件操作”的链式访问。

2.3 时钟提供者模型

时钟提供者(Clock Provider)是指能够产生或变换时钟信号的硬件模块,如晶振、PLL、分频器、MUX、门控等。提供者驱动的注册流程如下:

  1. 定义 struct clk_hw 实例,并填充 struct clk_ops 回调表。
  2. 调用 clk_hw_register() 系列函数(如 clk_hw_register_fixed_rate()、clk_hw_register_gate())注册到 CCF。
  3. 在设备树中通过 #clock-cells 属性声明提供者输出时钟的数量。

设备树中典型的时钟提供者节点示例:

osc24M: osc24M {
    compatible = "fixed-clock";
    #clock-cells = <0>;          /* 单输出时钟 */
    clock-frequency = <24000000>; /* 24MHz */
};

pll_cpu: pll_cpu {
    compatible = "allwinner,sun50i-a64-pll-clk";
    reg = <0x01c20000 0x4>;
    #clock-cells = <0>;
    clocks = <&osc24M>;
    clock-output-names = "pll-cpu";
};

2.4 时钟使用者模型

时钟使用者(Clock Consumer)是依赖时钟信号工作的设备模块,如 CPU、GPU、DMA 控制器、外设接口等。使用者通过设备树的 clocks 属性引用提供者,并在驱动中调用 CCF API 获取和操作时钟。

设备树中时钟使用者节点示例:

uart0: serial@1c28000 {
    compatible = "snps,dw-apb-uart";
    reg = <0x01c28000 0x400>;
    interrupts = <0 0 4>;
    clocks = <&osc24M>;          /* 引用提供者 */
    clock-names = "baudclk";      /* 时钟名称 */
};

驱动中获取和使用时钟的典型代码:

struct clk *baudclk;

baudclk = devm_clk_get(&pdev->dev, "baudclk");
if (IS_ERR(baudclk))
    return PTR_ERR(baudclk);

clk_prepare_enable(baudclk);
clk_set_rate(baudclk, 115200 * 16);  /* 设置波特率时钟 */
/* ... 使用外设 ... */
clk_disable_unprepare(baudclk);
时钟提供者 / 使用者模型与设备树绑定 时钟提供者 (Clock Provider) osc24M: fixed-clock #clock-cells = <0> pll_cpu: sun50i-pll clocks = <&osc24M> clk_mux: clock-mux clocks = <&pll_cpu &osc24M> 时钟使用者 (Clock Consumer) uart0: dw-apb-uart clocks = <&osc24M> cpu0: cpu clocks = <&pll_cpu> dma: dma-controller clocks = <&clk_mux> 设备树中 clocks 属性建立提供者→使用者的引用关系;CCF 据此构建时钟树拓扑

2.5 时钟树拓扑与速率传播

CCF 在内部维护一棵以 struct clk_core 为节点的时钟树。当使用者调用 clk_set_rate() 时,核心框架执行以下传播逻辑:

  • 向上传播:如果当前时钟无法直接达到目标频率,框架会尝试调整父时钟的速率(通过 clk_ops->round_rate 和 clk_ops->set_rate)。
  • 向下传播:父时钟速率变化后,框架遍历子时钟列表,调用 clk_ops->recalc_rate 更新子时钟的速率。
  • 标志控制:CLK_SET_RATE_PARENT 标志决定是否允许向父时钟传播速率请求;CLK_IGNORE_UNUSED 防止框架在初始化时关闭未使用的时钟。
故障定位要点: 速率传播失败是时钟调优中最常见的问题。检查设备树中是否遗漏 assigned-clock-rates 属性,或提供者驱动是否实现了 round_rate 回调。使用 clk_summary 调试文件系统接口可以实时查看时钟树状态。

2.6 调试接口与性能调优切入点

CCF 提供多个调试接口,对性能调优和故障定位至关重要:

调试接口 路径 提供信息 调优用途
clk_summary /sys/kernel/debug/clk/clk_summary 所有时钟的 enable 计数、prepare 计数、当前速率、父时钟 确认时钟是否被正确使能,速率是否符合预期
clk_dump /sys/kernel/debug/clk/clk_dump 时钟树的完整 JSON 转储 分析时钟树拓扑结构,排查父子关系错误
trace_clk tracepoint: clk_set_rate / clk_enable 时钟操作的实时跟踪事件 定位频繁开关或速率切换导致的性能抖动
性能调优建议: 对于 CPU 和 GPU 等对延迟敏感的时钟,避免在热路径中频繁调用 clk_set_rate();使用 clk_round_rate() 预先验证目标频率是否可达;在设备树中通过 assigned-clock-rates 预设启动频率,减少运行时调整。