[TOC]

kernel/time/clockevents.c

clockevents_switch_state - 设置时钟事件设备的运行状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
static int __clockevents_switch_state(struct clock_event_device *dev,
enum clock_event_state state)
{
if (dev->features & CLOCK_EVT_FEAT_DUMMY)
return 0;

/* Transition with new state-specific callbacks */
switch (state) {
case CLOCK_EVT_STATE_DETACHED:
/* The clockevent device is getting replaced. Shut it down. */

case CLOCK_EVT_STATE_SHUTDOWN:
if (dev->set_state_shutdown)
return dev->set_state_shutdown(dev);
return 0;

case CLOCK_EVT_STATE_PERIODIC:
/* Core internal bug */
if (!(dev->features & CLOCK_EVT_FEAT_PERIODIC))
return -ENOSYS;
if (dev->set_state_periodic)
return dev->set_state_periodic(dev);
return 0;

case CLOCK_EVT_STATE_ONESHOT:
/* Core internal bug */
if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT))
return -ENOSYS;
if (dev->set_state_oneshot)
return dev->set_state_oneshot(dev);
return 0;

case CLOCK_EVT_STATE_ONESHOT_STOPPED:
/* Core internal bug */
if (WARN_ONCE(!clockevent_state_oneshot(dev),
"Current state: %d\n",
clockevent_get_state(dev)))
return -EINVAL;

if (dev->set_state_oneshot_stopped)
return dev->set_state_oneshot_stopped(dev);
else
return -ENOSYS;

default:
return -ENOSYS;
}
}

/**
* clockevents_switch_state - 设置时钟事件设备的运行状态
* @dev:设备修改
* @state:新状态
*
* 必须在禁用中断的情况下调用!
*/
void clockevents_switch_state(struct clock_event_device *dev,
enum clock_event_state state)
{
if (clockevent_get_state(dev) != state) {
if (__clockevents_switch_state(dev, state))
return;

clockevent_set_state(dev, state);

/*
* A nsec2cyc multiplicator of 0 is invalid and we'd crash
* on it, so fix it up and emit a warning:
*/
if (clockevent_state_oneshot(dev)) {
if (WARN_ON(!dev->mult))
dev->mult = 1;
}
}
}

clockevents_config Clockevents 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void clockevents_config(struct clock_event_device *dev, u32 freq)
{
u64 sec;

if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT))
return;

/*
* 计算我们可以睡觉的最大秒数。
* 对于可以编程超过 32 位时钟周期的硬件,限制为 10 分钟,
* 因此我们仍然可以获得合理的转换值。
*/
sec = dev->max_delta_ticks;
do_div(sec, freq);
if (!sec)
sec = 1;
else if (sec > 600 && dev->max_delta_ticks > UINT_MAX)
sec = 600;

clockevents_calc_mult_shift(dev, freq, sec);
dev->min_delta_ns = cev_delta2ns(dev->min_delta_ticks, dev, false);
dev->max_delta_ns = cev_delta2ns(dev->max_delta_ticks, dev, true);
}

clockevents_register_device 注册一个时钟事件设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* clockevents_register_device - 注册一个时钟事件设备
* @dev:要注册的设备
*/
void clockevents_register_device(struct clock_event_device *dev)
{
unsigned long flags;

/* 将状态初始化为 DETACHED */
clockevent_set_state(dev, CLOCK_EVT_STATE_DETACHED);

if (!dev->cpumask) {
WARN_ON(num_possible_cpus() > 1);
dev->cpumask = cpumask_of(smp_processor_id());
}

if (dev->cpumask == cpu_all_mask) {
WARN(1, "%s cpumask == cpu_all_mask, using cpu_possible_mask instead\n",
dev->name);
dev->cpumask = cpu_possible_mask;
}

raw_spin_lock_irqsave(&clockevents_lock, flags);
/* 添加设备到全局列表 */
list_add(&dev->list, &clockevent_devices);
/* 检查新注册的设备是否可以用于当前的时钟事件管理 */
tick_check_new_device(dev);
/* 通知系统释放的时钟事件设备,可能用于重新分配或更新设备状态 */
clockevents_notify_released();

raw_spin_unlock_irqrestore(&clockevents_lock, flags);
}
EXPORT_SYMBOL_GPL(clockevents_register_device);

clockevents_init 配置和注册时钟事件设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* clockevents_config_and_register - 配置和注册时钟事件设备
* @dev:要注册的设备
* @freq:时钟频率
* @min_delta:在 oneshot 模式下编程的最小时钟滴答声
* @max_delta:在 oneshot 模式下编程的最大时钟滴答声
*
* min/max_delta 对于不支持 oneshot 模式的设备,可以是 0。
*/
void clockevents_config_and_register(struct clock_event_device *dev,
u32 freq, unsigned long min_delta,
unsigned long max_delta)
{
dev->min_delta_ticks = min_delta;
dev->max_delta_ticks = max_delta;
clockevents_config(dev, freq);
clockevents_register_device(dev);
}
EXPORT_SYMBOL_GPL(clockevents_config_and_register);

clockevents_shutdown 关闭设备并清除next_event

1
2
3
4
5
6
7
8
9
/**
* clockevents_shutdown - 关闭设备并清除next_event
* @dev:设备关机
*/
void clockevents_shutdown(struct clock_event_device *dev)
{
clockevents_switch_state(dev, CLOCK_EVT_STATE_SHUTDOWN);
dev->next_event = KTIME_MAX;
}

clockevents_exchange_device 释放和请求时钟设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* clockevents_exchange_device - 释放和请求时钟设备
* @old:要释放的设备(可以是 NULL)
* @new:要请求的设备(可以为 NULL)
*
* 从各种 tick 函数中调用,同时保持 clockevents_lock 并禁用中断。
*/
void clockevents_exchange_device(struct clock_event_device *old,
struct clock_event_device *new)
{
/*
* 调用方释放一个 clock event device。我们将其排入 released 列表中,并在稍后执行通知添加。
*/
if (old) {
module_put(old->owner);
clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED);
list_move(&old->list, &clockevents_released);
}

if (new) {
BUG_ON(!clockevent_state_detached(new));
clockevents_shutdown(new);
}
}

clockevents_program_event 重新编程 clock event device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* clockevents_program_event - 重新编程 clock event device。
* @dev:设备到程序
* @expires:绝对到期时间(单调时钟)
* @force:如果过期时程序最小延迟无法设置
*
* 成功时返回 0,如果事件过去,则返回 -ETIME。
*/
int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,
bool force)
{
unsigned long long clc;
int64_t delta;
int rc;

if (WARN_ON_ONCE(expires < 0))
return -ETIME;

dev->next_event = expires;

if (clockevent_state_shutdown(dev))
return 0;

/* 我们这里必须处于 ONESHOT 状态*/
WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n",
clockevent_get_state(dev));

/* 可以处理 ktime 的 clockevent 设备的快捷方式。*/
if (dev->features & CLOCK_EVT_FEAT_KTIME)
return dev->set_next_ktime(expires, dev);

/* 计算当前时间与到期时间之间的时间增量 delta(以纳秒为单位 */
delta = ktime_to_ns(ktime_sub(expires, ktime_get()));
if (delta <= 0)
/* 如果增量小于等于零,表示事件已经过期。
* 如果 force 标志为真,则调用 clockevents_program_min_delta
* 设置最小延迟; */
return force ? clockevents_program_min_delta(dev) : -ETIME;

/* 限制时间增量范围 */
delta = min(delta, (int64_t) dev->max_delta_ns);
delta = max(delta, (int64_t) dev->min_delta_ns);

/* 转换为设备周期数 */
clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
/* 调用设备的 set_next_event 方法设置下一个事件 */
rc = dev->set_next_event((unsigned long) clc, dev);

return (rc && force) ? clockevents_program_min_delta(dev) /* 设置最小延迟 */
: rc;
}

clockevents_program_event 重新编程 clock event device

  • 它的核心作用是:接收一个未来的、绝对的到期时间expires,计算出从“现在”到这个未来时间点的时间差,并将这个时间差转换为底层硬件定时器可以理解的“周期计数值(cycle/count)”,然后调用硬件驱动提供的回调函数,将这个计数值编程到硬件中,以使硬件在精确的时刻触发一次中断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* clockevents_program_event - 重新编程时钟事件设备。
* @dev: 需要被编程的设备。
* @expires: 绝对的到期时间(基于monotonic时钟)。
* @force: 如果expires无法被设置,是否强制编程一个最小延迟。
*
* 返回值: 成功时返回0,当事件已在过去时返回-ETIME。
*/
int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,
bool force)
{
/* clc: "cycles", 64位无符号整型,用于存储转换后的硬件周期计数值。*/
unsigned long long clc;
/* delta: 64位有符号整型,用于存储从现在到expires的纳秒级时间差。*/
int64_t delta;
/* rc: 用于存储驱动回调函数的返回值。*/
int rc;

/* 这是一个健壮性检查。到期时间不应为负数。如果为负,则触发一次性警告并返回错误。*/
if (WARN_ON_ONCE(expires < 0))
return -ETIME;

/* 将目标到期时间保存到设备结构体的next_event字段中。*/
dev->next_event = expires;

/* 如果设备当前处于“关闭”状态,则无需编程,直接返回成功。*/
if (clockevent_state_shutdown(dev))
return 0;

/* 我们在这里必须处于ONESHOT状态。这是一个断言,如果不是,则打印警告。*/
WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n",
clockevent_get_state(dev));

/* 对那些能直接处理ktime的硬件设备,提供一条快速路径。*/
if (dev->features & CLOCK_EVT_FEAT_KTIME)
/* 直接调用驱动提供的set_next_ktime回调,将绝对到期时间传递给它。*/
return dev->set_next_ktime(expires, dev);

/*
* --- 常规路径:手动进行时间转换 ---
*/
/* 计算从现在(ktime_get())到目标时间expires之间的时间差(单位:纳秒)。*/
delta = ktime_to_ns(ktime_sub(expires, ktime_get()));

/* 如果delta小于等于0,说明expires已经是一个过去或现在的时刻。*/
if (delta <= 0)
/* 如果force为true,则尝试用最小延迟编程一次中断;否则返回-ETIME错误。*/
return force ? clockevents_program_min_delta(dev) : -ETIME;

/* 将delta限制在硬件支持的最大和最小延迟范围内。*/
delta = min(delta, (int64_t) dev->max_delta_ns);
delta = max(delta, (int64_t) dev->min_delta_ns);

/*
* 核心转换:使用预先计算好的乘数(mult)和移位数(shift),
* 将纳秒级的时间差delta,转换为硬件定时器需要的周期计数值clc。
*/
clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
/* 调用驱动提供的set_next_event回调,将计算出的周期数clc编程到硬件中。*/
rc = dev->set_next_event((unsigned long) clc, dev);

/* 如果驱动回调返回错误,并且force为true,则再次尝试以最小延迟编程。*/
return (rc && force) ? clockevents_program_min_delta(dev) : rc;
}

Clockevents 跨CPU解绑与替换 (__clockevents_try_unbind, clockevents_unbind)

本代码片段是 Linux 内核 clockevents(时钟事件)子系统中实现硬件定时器与 CPU 解绑的核心逻辑,特别针对多核(SMP)环境设计。其主要功能是通过跨 CPU 调用(IPI - Inter-Processor Interrupt),安全地在指定的 CPU 上执行解绑操作。clockevents_unbind 负责发起请求,__clockevents_unbind 是在目标 CPU 上执行的函数,而 __clockevents_try_unbind 则是实际执行解绑或替换逻辑的内部核心。

实现原理分析

此代码段的实现原理是 SMP 环境下安全修改 per-CPU 硬件资源的典范,其核心技术是 远程过程调用状态驱动的原子替换

  1. 跨CPU执行的必要性 (smp_call_function_single): clockevents_unbind 的核心是调用 smp_call_function_single。这是因为用于内核节拍(tick)的 clock_event_device 通常是 per-CPU 的硬件资源(例如,每个核心独立的 Local APIC 定时器或 ARM architected timer)。对这些硬件的任何操作,如停止、重新编程或禁用中断,都必须在它们所属的那个 CPU 核心上执行,直接操作其他核心的定时器硬件是不可能或不安全的。smp_call_function_single 通过向目标 cpu 发送一个 IPI,强制该 CPU 中断当前工作,转而执行指定的函数(__clockevents_unbind),从而实现了在正确的“地点”执行关键代码。

  2. 两阶段解绑与替换 (__clockevents_try_unbind): 此函数体现了对不同设备状态的精确处理:

    • 快速路径: 如果设备已经是 detached(分离)状态,意味着它当前未被使用。此时可以直接从全局链表中移除(list_del_init),操作非常迅速。
    • 慢速路径 (替换): 如果设备正被当前 CPU 用于内核节拍 (ced == per_cpu(tick_cpu_device, cpu).evtdev),直接移除是不行的,会导致系统失去心跳。此时,函数返回 -EAGAIN,通知上层调用者(__clockevents_unbind)必须执行一个更复杂的操作——clockevents_replaceclockevents_replace 会原子地在系统中寻找一个合适的替代定时器,将内核节拍切换到新定时器上,然后才安全地将旧设备置于 detached 状态。
    • 繁忙状态: 如果设备正在被使用,但不是用于当前 CPU 的内核节拍(例如,它可能是一个共享的定时器,或被用于 hrtimers),则返回 -EBUSY,表示当前无法解绑。

特定场景分析:单核、无MMU的STM32H750平台

硬件交互

在 STM32H750 平台上,一个 clock_event_device 实例通常代表 ARMv7-M 架构的 SysTick 定时器,或者是 STM32 的某个通用定时器(TIM)。当 clockevents_unbind 被调用试图解绑 SysTick 时:

  • __clockevents_unbind 会在 STM32H750 的唯一核心上被执行。
  • __clockevents_try_unbind 会发现 SysTick 正被用作内核节拍,返回 -EAGAIN
  • clockevents_replace 会被调用。它会尝试寻找系统中的其他可用定时器(例如,一个配置为时钟事件模式的 TIM)。如果找到,它会停止并禁用 SysTick 的中断,然后启动并使能新 TIM 的中断来接管内核节拍。如果找不到替代者,操作将失败。

单核环境影响

这组函数虽然为 SMP 设计,但在单核系统上依然能够正确工作,只是其行为被极大地简化了:

  • smp_call_function_single 的退化: 在单核配置下,smp_call_function_single 不会发送任何 IPI。它会退化为一个简单的本地函数调用。内核会通过禁用本地中断来模拟一个“原子”的执行环境,然后直接调用 __clockevents_unbind 函数。所有复杂的跨核同步和等待逻辑都被绕过。
  • CPU ID: smp_processor_id() 将始终返回 0。

因此,在 STM32H750 上,这个流程变成了一个纯粹的本地操作:clockevents_unbind 通过一个简单的函数调用(带有中断屏蔽)来执行 __clockevents_unbind,后者再根据设备状态决定是直接移除还是进行替换。

无MMU影响

本代码片段的功能完全位于内核核心调度和时间管理层,其所有操作——包括链表操作、per-CPU 变量访问、函数调用——都与内存管理单元(MMU)无关。它在内核的平坦地址空间中工作,不依赖任何虚拟内存机制,因此在无 MMU 的 STM32H750 系统上可以无差别地正确执行。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// __clockevents_try_unbind: 在持有锁的情况下,尝试解绑一个时钟事件设备。
// @ced: 指向待解绑的时钟事件设备(clock_event_device)的指针。
// @cpu: 目标CPU的ID。
// 返回值: 0 表示成功解绑;-EAGAIN 表示需要执行替换操作;-EBUSY 表示设备正忙。
/*
* 该函数必须在持有 clockevents_mutex 和 clockevents_lock 的情况下被调用。
*/
static int __clockevents_try_unbind(struct clock_event_device *ced, int cpu)
{
// 快速路径:检查设备是否处于“分离”(detached)状态。
if (clockevent_state_detached(ced)) {
// 如果设备未使用,直接从全局设备链表中移除它。
list_del_init(&ced->list);
return 0; // 返回成功。
}

// 检查该设备是否正是当前目标CPU正在使用的内核节拍(tick)设备。
// 如果是,则不能直接移除,必须先找到替代者。返回 -EAGAIN 表示“请重试”(并执行替换)。
// 如果不是,则意味着设备可能被用于其他目的(如高精度定时器),正处于繁忙状态,返回 -EBUSY。
return ced == per_cpu(tick_cpu_device, cpu).evtdev ? -EAGAIN : -EBUSY;
}

// __clockevents_unbind: 在目标CPU上实际执行解绑操作的函数。
// @arg: 一个void指针,指向包含解绑信息的 ce_unbind 结构体。
/*
* 这是一个通过SMP函数调用(IPI)在指定CPU上执行的函数。
*/
static void __clockevents_unbind(void *arg)
{
struct ce_unbind *cu = arg; // 将void指针转换为 ce_unbind 结构体指针。
int res;

// 获取本地CPU的 clockevents_lock 锁(这是一个原始自旋锁)。
raw_spin_lock(&clockevents_lock);
// 尝试解绑。
res = __clockevents_try_unbind(cu->ce, smp_processor_id());
// 如果 __clockevents_try_unbind 返回 -EAGAIN,说明需要执行替换操作。
if (res == -EAGAIN)
// clockevents_replace 会寻找一个新的定时器来接管内核节拍,然后才将旧设备置为分离状态。
res = clockevents_replace(cu->ce);
// 将操作结果存入传入的结构体中,以便发起者可以获取。
cu->res = res;
// 释放锁。
raw_spin_unlock(&clockevents_lock);
}

// clockevents_unbind: 发起一个对指定CPU上时钟事件设备的解绑请求。
// @ced: 指向待解绑的时钟事件设备的指针。
// @cpu: 目标CPU的ID。
// 返回值: 解绑操作的最终结果。
/*
* 该函数在调用时必须持有 clockevents_mutex 互斥锁。
*/
static int clockevents_unbind(struct clock_event_device *ced, int cpu)
{
// 定义一个 ce_unbind 结构体在栈上,用于传递参数和接收返回值。
struct ce_unbind cu = { .ce = ced, .res = -ENODEV };

// 发起一个SMP调用:请求在目标CPU(cpu)上执行 __clockevents_unbind 函数,
// 参数为 cu 的地址,最后的 '1' 表示这是一个同步调用,即本函数会等待目标CPU执行完毕。
smp_call_function_single(cpu, __clockevents_unbind, &cu, 1);
// 返回从目标CPU上获取的操作结果。
return cu.res;
}

Clockevents Sysfs 接口初始化 (clockevents_init_sysfs, tick_init_sysfs)

本代码片段的核心功能是为 Linux 内核的 clockevents(时钟事件)子系统创建一套 sysfs 接口。它通过标准的 Linux 设备模型,在 /sys/bus/clockevents/ 目录下注册一个总线,并为系统中的每个 CPU(以及可能的广播通道)创建一个对应的逻辑设备。这些逻辑设备下又包含属性文件(如 current_device),允许用户空间程序查看当前哪个硬件定时器正被用于该 CPU 的内核节拍(tick),并提供了在特定条件下手动解绑定时器的调试能力。

实现原理分析

此代码是 Linux 内核“一切皆文件”思想的典型体现,其实现原理基于对内核设备模型的深度集成。

  1. 设备模型抽象: 代码并未直接创建文件,而是将 clockevents 子系统的内部组件抽象为设备模型的标准元素:

    • 总线 (bus_type): clockevents_subsys 定义了一个名为 “clockevents” 的总线类型。这会在 sysfs 中创建 /sys/bus/clockevents/ 目录,作为所有相关设备的容器。
    • 设备 (device): 系统中的每个 CPU 核心都被视为一个独立的“节拍设备”,代码为此定义了一个 per-CPUstruct device 实例 (tick_percpu_dev)。在初始化时,tick_init_sysfs 函数会遍历所有 CPU,为每一个注册一个设备实例,从而在 sysfs 中创建出如 /sys/bus/clockevents/devices/clockevent0, /sys/bus/clockevents/devices/clockevent1 等目录。
    • 属性 (device_attribute): DEVICE_ATTR_RO(current_device)DEVICE_ATTR_WO(unbind_device) 这两个宏定义了设备的属性,它们分别对应 sysfs 中的一个文件。当用户读写这些文件时,内核会调用其对应的 _show_store 函数(如 current_device_show)。
  2. 两阶段解绑的同步技巧 (unbind_device_store): 解绑设备的操作展示了一种复杂的同步策略。它首先在持有轻量级的 raw_spin_lock_irq 的情况下,调用 __clockevents_try_unbind 尝试快速解绑。自旋锁下禁止睡眠,因此 __clockevents_try_unbind 如果遇到需要等待或可能导致睡眠的情况,会立即返回 -EAGAINunbind_device_store 捕捉到这个返回值后,会释放自旋锁,并调用允许睡眠的 clockevents_unbind 函数来完成这个慢速路径的操作。在此期间,它持有 clockevents_mutex 互斥锁,以防止目标设备 ce 在此过程中被销毁。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#ifdef CONFIG_SYSFS // 仅在内核配置了SYSFS文件系统支持时,以下代码才会被编译。
// 定义一个名为 "clockevents" 的总线(bus)类型。
// 这将在 /sys/bus/ 目录下创建一个名为 "clockevents" 的子目录。
static const struct bus_type clockevents_subsys = {
.name = "clockevents",
.dev_name = "clockevent", // 设备的基础名称
};

// 为每个CPU定义一个独立的 device 结构体实例。
static DEFINE_PER_CPU(struct device, tick_percpu_dev);
// 声明一个辅助函数,用于从通用的 device 结构体指针找到对应的 tick_device 结构体。
static struct tick_device *tick_get_tick_dev(struct device *dev);

// "current_device" sysfs 属性文件的 "show" (读) 操作实现函数。
// @dev: 指向此属性所属的设备结构体。
// @attr: 指向设备属性结构体。
// @buf: 用于存储输出字符串的缓冲区。
// 返回值: 写入缓冲区的字节数。
static ssize_t current_device_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tick_device *td;
ssize_t count = 0;

// 获取保护 clockevents 子系统的自旋锁,并禁用中断。
raw_spin_lock_irq(&clockevents_lock);
// 根据 dev 指针找到对应的 tick_device。
td = tick_get_tick_dev(dev);
// 如果 tick_device 存在并且已经关联了一个时钟事件设备(evtdev)。
if (td && td->evtdev)
// 使用 sysfs_emit 格式化输出当前时钟事件设备的名称到缓冲区。
count = sysfs_emit(buf, "%s\n", td->evtdev->name);
// 释放自旋锁,并恢复中断。
raw_spin_unlock_irq(&clockevents_lock);
return count;
}
// 使用宏定义一个只读的 "current_device" 设备属性。
static DEVICE_ATTR_RO(current_device);

// "unbind_device" sysfs 属性文件的 "store" (写) 操作实现函数。
// @dev: 指向此属性所属的设备结构体。
// @attr: 指向设备属性结构体。
// @buf: 指向用户空间写入的数据。
// @count: 写入数据的字节数。
// 返回值: 成功则为写入的字节数,失败则为负数错误码。
static ssize_t unbind_device_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
char name[CS_NAME_LEN]; // 缓冲区,用于存储从用户输入中提取的设备名。
ssize_t ret = sysfs_get_uname(buf, name, count); // 从buf中解析出设备名。
struct clock_event_device *ce = NULL, *iter;

if (ret < 0)
return ret;

ret = -ENODEV; // 默认返回“无此设备”错误。
// 获取互斥锁,用于保护可能引起睡眠的解绑操作。
mutex_lock(&clockevents_mutex);
// 获取自旋锁,用于保护对 clockevent_devices 链表的遍历。
raw_spin_lock_irq(&clockevents_lock);
// 遍历全局的时钟事件设备链表。
list_for_each_entry(iter, &clockevent_devices, list) {
// 比较名称以找到目标设备。
if (!strcmp(iter->name, name)) {
// 尝试在持有自旋锁的情况下进行解绑(快速路径)。
ret = __clockevents_try_unbind(iter, dev->id);
ce = iter; // 保存找到的设备指针。
break;
}
}
// 释放自旋锁。
raw_spin_unlock_irq(&clockevents_lock);

// 此时仍然持有 clockevents_mutex 互斥锁,所以 ce 指针是安全的。
// 如果快速路径解绑失败并返回 -EAGAIN,说明需要执行可能睡眠的慢速路径。
if (ret == -EAGAIN)
ret = clockevents_unbind(ce, dev->id);
// 释放互斥锁。
mutex_unlock(&clockevents_mutex);
// 如果操作成功(ret=0),则返回用户写入的字节数,否则返回错误码。
return ret ? ret : count;
}
// 使用宏定义一个只写的 "unbind_device" 设备属性。
static DEVICE_ATTR_WO(unbind_device);

#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST // 如果配置了广播时钟事件。
// 为广播时钟事件定义一个全局的 device 结构体。
static struct device tick_bc_dev = {
.init_name = "broadcast",
.id = 0, // ID 为0,但通过与 per-cpu 设备的指针比较来区分。
.bus = &clockevents_subsys, // 关联到 clockevents 总线。
};

// 辅助函数,根据 dev 指针找到对应的 tick_device。
static struct tick_device *tick_get_tick_dev(struct device *dev)
{
// 如果是广播设备,则获取广播 tick_device;否则,获取与 dev->id 对应的 per-cpu tick_device。
return dev == &tick_bc_dev ? tick_get_broadcast_device() :
&per_cpu(tick_cpu_device, dev->id);
}

// 初始化广播时钟事件的 sysfs 接口。
static __init int tick_broadcast_init_sysfs(void)
{
int err = device_register(&tick_bc_dev); // 注册广播设备。

if (!err)
// 为广播设备创建 "current_device" 属性文件。
err = device_create_file(&tick_bc_dev, &dev_attr_current_device);
return err;
}
#else // 如果没有配置广播时钟事件。
// 辅助函数,直接获取与 dev->id 对应的 per-cpu tick_device。
static struct tick_device *tick_get_tick_dev(struct device *dev)
{
return &per_cpu(tick_cpu_device, dev->id);
}
// 定义一个空的内联函数作为存根。
static inline int tick_broadcast_init_sysfs(void) { return 0; }
#endif

// 初始化 per-cpu tick 设备的 sysfs 接口。
static int __init tick_init_sysfs(void)
{
int cpu;

// 遍历系统中所有可能存在的CPU。
for_each_possible_cpu(cpu) {
// 获取该CPU对应的 per-cpu device 结构体指针。
struct device *dev = &per_cpu(tick_percpu_dev, cpu);
int err;

dev->id = cpu; // 设置设备的ID为CPU号。
dev->bus = &clockevents_subsys; // 关联到 clockevents 总线。
// 注册这个 per-cpu 设备。
err = device_register(dev);
if (!err)
// 为设备创建 "current_device" 属性文件。
err = device_create_file(dev, &dev_attr_current_device);
if (!err)
// 为设备创建 "unbind_device" 属性文件。
err = device_create_file(dev, &dev_attr_unbind_device);
if (err)
return err; // 如果任何一步失败,则返回错误。
}
// 初始化广播设备的 sysfs 接口。
return tick_broadcast_init_sysfs();
}

// clockevents 子系统 sysfs 接口的总初始化函数。
static int __init clockevents_init_sysfs(void)
{
// 注册 "clockevents" 总线。
int err = subsys_system_register(&clockevents_subsys, NULL);

if (!err)
// 如果总线注册成功,则继续初始化 tick 设备的 sysfs 接口。
err = tick_init_sysfs();
return err;
}
// 将 clockevents_init_sysfs 注册为一个设备初始化调用,在内核启动的相应阶段执行。
device_initcall(clockevents_init_sysfs);
#endif /* SYSFS */