
https://github.com/wdfk-prog/linux-study
arch/arm/include/asm/delay.h
该文件的核心原理是提供一个可插拔的、分层的延迟实现机制。它设计了一个抽象的arm_delay_ops
操作函数集, 允许在系统启动时根据可用的硬件资源来选择并注册最优的延迟方法。
- 对于没有精确硬件定时器的旧系统: 它会回退到一种基于"每秒循环数"(
loops_per_jiffy
)的校准循环方法。内核在启动时会测量在一个系统时钟节拍(jiffy)内, CPU能执行多少次空循环, 从而计算出一个近似值。udelay
的实现会根据这个校准值来计算需要执行多少次循环才能达到指定的微秒延迟。 - 对于拥有高精度硬件定时器的新系统(如STM32H750): 系统启动代码(通常是特定于板卡的平台代码)可以使用
register_current_timer_delay
函数来注册一个高精度的定时器(例如ARM Cortex-M内核自带的DWT周期计数器CYCCNT
)。一旦注册成功,arm_delay_ops
中的函数指针就会被更新为指向基于这个硬件定时器的延迟实现。这种实现方式会读取当前定时器计数值, 计算出目标计数值, 然后在一个循环中不断读取定时器直到达到目标值。这种方法比校准循环要精确得多。
定点数算术与常量定义
这部分是为老旧的、基于校准循环的延迟方法准备的。它的目的是在不使用浮点数的情况下, 将一个以微秒为单位的延迟时间delay_us
转换成需要执行的循环次数loops
。
/*
* 宏定义: MAX_UDELAY_MS
* 定义了 udelay() 函数支持的最大延迟时间, 单位为毫秒. 此处为2毫秒 (2000微秒).
* 这个限制是为了防止下面 UDELAY_MULT 乘法运算的结果溢出32位整数.
*/
#define MAX_UDELAY_MS 2
/*
* 宏定义: UDELAY_MULT
* 这是一个精心计算的乘法因子, 用于定点数算术.
* 它的推导基于公式: loops = loops_per_jiffy * HZ * delay_us / 1000000.
* 为了避免浮点数, 此宏将公式中 (HZ / 1000000) 这个分数放大了 2^31 倍.
* 计算结果为: (2^31 / 1000000) * HZ ≈ 2147.483648 * HZ.
* 为了在预处理阶段完成计算, 它被分解为纯整数运算: 2147 * HZ + (483648 * HZ / 1000000).
* UL 后缀确保计算以无符号长整型进行.
*/
#define UDELAY_MULT UL(2147 * HZ + 483648 * HZ / 1000000)
/*
* 宏定义: UDELAY_SHIFT
* 这是与 UDELAY_MULT 配套使用的右移位数.
* 在将延迟时间乘以 UDELAY_MULT 放大后, 需要再右移31位将其缩小回正确的大小.
*/
#define UDELAY_SHIFT 31
核心数据结构与API
#ifndef __ASSEMBLY__ /* 这部分代码仅在C语言中有效, 在汇编代码中无效. */
/*
* delay_timer: 用于注册一个高精度硬件定时器.
*/
struct delay_timer {
/* 一个函数指针, 指向一个能读取当前定时器计数值的函数. */
unsigned long (*read_current_timer)(void);
/* 该定时器的频率, 单位是赫兹(Hz). */
unsigned long freq;
};
/*
* arm_delay_ops: ARM延迟操作的函数指针分派表.
* 这是一个全局结构体, 内核在启动时会根据可用的硬件填充这些指针.
*/
extern struct arm_delay_ops {
void (*delay)(unsigned long); // 一个通用的、基于循环的延迟函数.
void (*const_udelay)(unsigned long); // 用于常量微秒延迟的优化函数.
void (*udelay)(unsigned long); // 用于变量微秒延迟的函数.
unsigned long ticks_per_jiffy; // 在启动时校准出的、每个jiffy的循环次数.
} arm_delay_ops;
/* __delay: 对 arm_delay_ops.delay 的一个简单宏封装. */
#define __delay(n) arm_delay_ops.delay(n)
/*
* 这是一个故意不存在的函数声明.
* 如果你调用 udelay() 时传入一个超限的值, 编译器会在链接阶段报告
* "undefined reference to `__bad_udelay`", 从而强制开发者修正错误.
* 这是一种利用链接器进行编译时检查的技巧.
*/
extern void __bad_udelay(void);
/*
* __udelay 和 __const_udelay: 内部使用的延迟宏.
*/
#define __udelay(n) arm_delay_ops.udelay(n)
#define __const_udelay(n) arm_delay_ops.const_udelay(n)
/*
* udelay: 对外暴露的、最常用的微秒延迟API.
* @n: 需要延迟的微秒数.
*/
#define udelay(n) \
(__builtin_constant_p(n) ? \
/*
* 使用GCC内置函数判断n是否为编译时常量.
* 如果是常量:
*/ \
((n) > (MAX_UDELAY_MS * 1000) ? __bad_udelay() : \
/* 首先, 检查n是否超限 (> 2000us), 如果是, 则调用__bad_udelay()导致链接错误. */ \
__const_udelay((n) * UDELAY_MULT)) : \
/* 如果未超限, 则调用 __const_udelay, 并预先乘以 UDELAY_MULT.
* __const_udelay 的内部实现会负责进行右移 UDELAY_SHIFT 的操作. */ \
__udelay(n))
/*
* 如果n不是常量(是一个变量):
* 直接调用 __udelay(n), 延迟的计算将在运行时在该函数内部完成.
*/
/* 为汇编代码提供的基于循环的延迟函数声明. */
extern void __loop_delay(unsigned long loops);
extern void __loop_udelay(unsigned long usecs);
extern void __loop_const_udelay(unsigned long);
/*
* 延迟定时器的注册接口.
*/
#define ARCH_HAS_READ_CURRENT_TIMER /* 声明此架构支持注册高精度定时器. */
/*
* 驱动程序(如DWT定时器驱动)可以调用此函数,
* 将一个 delay_timer 实例注册到内核中, 从而升级整个系统的延迟实现.
*/
extern void register_current_timer_delay(const struct delay_timer *timer);
#endif /* __ASSEMBLY__ */
arch/arm/lib/delay.c
这个实现的关键在于register_current_timer_delay
函数。它充当一个"延迟引擎"的升级接口。内核中任何能够提供高精度、单调递增计数值的驱动程序(例如, ARM DWT周期计数器驱动, 或STM32的TIM通用定时器驱动), 都可以调用此函数, 将自己的定时器注册为内核的"官方延迟源"。
原理与工作流程:
- 默认状态: 内核启动时,
arm_delay_ops
结构体被初始化为指向一组基于纯软件循环的延迟函数(__loop_delay
,__loop_const_udelay
,__loop_udelay
)。这些函数非常不精确, 其延迟时间会受到CPU频率、缓存状态等多种因素的影响。 - 定时器注册 (
register_current_timer_delay
):- 接收注册: 内核的其他部分(如特定板卡的启动代码)会创建一个
struct delay_timer
实例, 填充其read_current_timer
函数指针和freq
(频率), 然后调用register_current_timer_delay
。 - 精度检查: 函数首先会检查新注册的定时器的分辨率。它计算出该定时器跳动一个周期(
cyc_to_ns
)对应多少纳秒(res
)。如果分辨率太差(例如, 超过1000纳秒), 这个定时器对于实现微秒级延迟就没有意义, 会被直接拒绝。 - 择优录取: 如果新定时器的分辨率比当前已注册的任何定时器都要好(
res < delay_res
), 并且系统尚未完成最终的延迟校准(!delay_calibrated
), 它就会执行切换。 - 执行切换:
a. 将全局的delay_timer
指针指向新注册的定时器。
b. 最关键的一步: 将arm_delay_ops
结构体中的函数指针全部重写, 指向一组新的、基于硬件定时器的延迟函数 (__timer_delay
,__timer_const_udelay
,__timer_udelay
)。
c. 同时, 它会根据定时器的频率精确地计算出loops_per_jiffy
(现在应该叫ticks_per_jiffy
更合适), 并保存在arm_delay_ops.ticks_per_jiffy
中。
- 接收注册: 内核的其他部分(如特定板卡的启动代码)会创建一个
- 新的延迟实现 (
__timer_...
系列函数):__timer_delay
: 这是最底层的实现。它首先通过get_cycles()
(内部会调用delay_timer->read_current_timer()
)读取当前的定时器计数值, 然后进入一个while
循环, 在循环中不断地重新读取计数值并与起始值做差, 直到经过的周期数达到了cycles
参数的要求。cpu_relax()
是一个提示指令, 告诉CPU在等待时可以进入低功耗状态或执行其他超线程, 以提高能效。__timer_const_udelay
和__timer_udelay
: 这两个函数将微秒(usecs
)转换为定时器需要经过的周期数, 然后调用__timer_delay
来执行实际的延迟。它们的计算过程与delay.h
中定义的UDELAY_MULT
和UDELAY_SHIFT
紧密配合, 但现在arm_delay_ops.ticks_per_jiffy
是一个基于硬件定时器频率的精确值, 而不再是粗略的循环校准值。
在STM32H750上的应用:
STM32H750是基于ARM Cortex-M7内核的, 它包含一个名为**DWT(Data Watchpoint and Trace)**的硬件单元, 其中有一个32位的周期计数器CYCCNT
。这个计数器在每个CPU时钟周期都会加1。
- 在系统启动时, 一段特定于ARMv7-M架构的内核代码会检测到DWT的存在, 并将其初始化。
- 然后, 它会创建一个
struct delay_timer
实例, 将其read_current_timer
函数指针设置为一个能读取DWT_CYCCNT
寄存器的函数,freq
则设置为CPU的核心时钟频率。 - 接着, 它会调用
register_current_timer_delay
。由于CYCCNT
的分辨率极高(通常是几百MHz, 对应几纳秒的分辨率), 它几乎肯定会被内核接受为最优的延迟源。 - 因此, 在STM32H750上运行的Linux内核中, 任何对
udelay()
的调用, 最终都会被分派到__timer_udelay
, 转化为对DWT周期计数器的精确等待。这为驱动程序提供了可靠的微秒级延迟能力。
/* 默认的arm_delay_ops, 指向基于软件循环的实现. __ro_after_init 确保其在初始化后是只读的. */
struct arm_delay_ops arm_delay_ops __ro_after_init = {
.delay = __loop_delay,
.const_udelay = __loop_const_udelay,
.udelay = __loop_udelay,
};
static const struct delay_timer *delay_timer; // 全局指针, 指向当前使用的延迟定时器
static bool delay_calibrated; // 标志位, 延迟校准是否已最终完成
static u64 delay_res; // 当前延迟定时器的分辨率(纳秒)
/* read_current_timer: 一个导出的API, 允许内核其他部分读取当前延迟定时器的值. */
int read_current_timer(unsigned long *timer_val)
{
if (!delay_timer) /* 如果还没有注册定时器, 返回错误. */
return -ENXIO;
*timer_val = delay_timer->read_current_timer();
return 0;
}
EXPORT_SYMBOL_GPL(read_current_timer);
/* 将定时器周期数转换为纳秒. (这是一个通用的辅助函数, 未在此文件中直接使用, 但与clocks_calc_mult_shift相关) */
static inline u64 cyc_to_ns(u64 cyc, u32 mult, u32 shift)
{
return (cyc * mult) >> shift;
}
/* __timer_delay: 基于硬件定时器的底层延迟函数. */
static void __timer_delay(unsigned long cycles)
{
cycles_t start = get_cycles(); /* get_cycles()内部会调用 delay_timer->read_current_timer() */
/* 循环等待, 直到经过的定时器周期数达到要求. */
while ((get_cycles() - start) < cycles)
cpu_relax(); /* 在等待时提示CPU可以放松. */
}
/* __timer_const_udelay: 基于定时器的、用于常量参数的udelay实现. */
static void __timer_const_udelay(unsigned long xloops)
{
unsigned long long loops = xloops;
/*
* 将经过定点数乘法(UDELAY_MULT)放大的值, 再乘以精确的每jiffy的ticks数.
* 这里的 'xloops' 实际上是 'usecs * UDELAY_MULT'.
*/
loops *= arm_delay_ops.ticks_per_jiffy;
/* 将结果缩小回正确的ticks数量, 然后调用底层延迟函数. */
__timer_delay(loops >> UDELAY_SHIFT);
}
/* __timer_udelay: 基于定时器的、用于变量参数的udelay实现. */
static void __timer_udelay(unsigned long usecs)
{
/* 直接将微秒数乘以放大因子, 然后调用常量版本的实现. */
__timer_const_udelay(usecs * UDELAY_MULT);
}
/* register_current_timer_delay: 注册一个新的、可能更好的延迟定时器源. */
void __init register_current_timer_delay(const struct delay_timer *timer)
{
u32 new_mult, new_shift;
u64 res;
/* 计算用于将定时器频率转换为纳秒的乘法和移位因子. */
clocks_calc_mult_shift(&new_mult, &new_shift, timer->freq,
NSEC_PER_SEC, 3600);
/* 计算该定时器的分辨率, 即一个周期是多少纳秒. */
res = cyc_to_ns(1ULL, new_mult, new_shift);
/* 如果分辨率太差(>1000ns), 则拒绝此定时器. */
if (res > 1000) {
pr_err("Ignoring delay timer %ps, which has insufficient resolution of %lluns\n",
timer, res);
return;
}
/*
* 如果校准尚未完成, 并且(没有已注册的定时器, 或者新定时器的分辨率更好),
* 则接受这个新定时器.
*/
if (!delay_calibrated && (!delay_res || (res < delay_res))) {
pr_info("Switching to timer-based delay loop, resolution %lluns\n", res);
delay_timer = timer; // 更新全局定时器指针.
lpj_fine = timer->freq / HZ; // 计算精确的lpj.
delay_res = res; // 更新当前最优分辨率.
/* 将精确的lpj保存到arm_delay_ops中, 供udelay计算使用. */
arm_delay_ops.ticks_per_jiffy = lpj_fine;
/* 关键: 重写arm_delay_ops的函数指针, 从__loop_*切换到__timer_*. */
arm_delay_ops.delay = __timer_delay;
arm_delay_ops.const_udelay = __timer_const_udelay;
arm_delay_ops.udelay = __timer_udelay;
} else {
pr_info("Ignoring duplicate/late registration of read_current_timer delay\n");
}
}
/* calibrate_delay_is_known: 在传统的校准循环完成后被调用. */
unsigned long calibrate_delay_is_known(void)
{
delay_calibrated = true; // 标记校准已完成, 不再接受新的定时器注册.
return lpj_fine;
}
/* calibration_delay_done: 另一个标记校准完成的函数. */
void calibration_delay_done(void)
{
delay_calibrated = true;
}