LK kernel timer

Timers are used to setup one-shot or repeating callbacks in units of milliseconds.

These callbacks happen from interrupt context, so the available APIs are limited to things like signaling an event or unblocking a thread on a wait queue.

It is always safe to reprogram or cancel a timer from its own callback.

1
handler_return a_timer_callback(struct timer *, lk_time_t now, void *arg);
1
2
3
4
void timer_initialize(timer_t *);
void timer_set_oneshot(timer_t *, lk_time_t delay, timer_callback, void *arg);
void timer_set_periodic(timer_t *, lk_time_t period, timer_callback, void *arg);
void timer_cancel(timer_t *);

Timekeeping: lk_time_t
This unsigned type is used by kernel functions involving timers, timeouts, etc, and is in units of milliseconds. The maximum value (defined as INFINITE_TIME) is used to specify a timeout that will never expire. The value 0 passed as a timeout parameter always means ``return immediately if you would have to wait.’’

ERR_TIMED_OUT is the status returned by a function taking a timeout value, if the timeout expires before the requested action can be accomplished.

Safe Actions From IRQ handlers and Timer Callbacks:
(Timer callbacks happen from IRQ context)

A number of these functions take a ‘reschedule’ parameter, which must be false when called from IRQ context. If your interrupt handler takes action which will wake a thread and you want to ensure it wakes now (provided priority is high enough) rather than when the next quantum expires, return INT_RESCHEDULE instead of INT_NO_RESCHEDULE. This will cause the kernel to invoke the scheduler before returning to thread context.

The follow actions are IRQ-safe:

  • Signal an event with event_signal().
  • Reprogram a timer with timer_set_oneshot(), timer_set_periodic(), or timer_cancel().
  • Timer reprogramming is safe even from within that timer’s callback.
  • Using spinlocks.
  • Wake threads on wait queues with wake_queue_wake_one() or wake_queue_wake_all(), provided you hold the thread lock while doing so (useful for building new synchronization primitives).

timer_init

timer_init 函数位于 kernel/timer.c 文件中,主要的作用创建 lk 中的定时器链表和定时器处理函数。

1
2
3
4
5
6
7
8
9
10
11
void timer_init(void)
{
timer_lock = SPIN_LOCK_INITIAL_VALUE;
for (uint i = 0; i < SMP_MAX_CPUS; i++) {
list_initialize(&timers[i].timer_queue);
}
#if !PLATFORM_HAS_DYNAMIC_TIMER
/* register for a periodic timer tick */
platform_set_periodic_timer(timer_tick, NULL, 10); /* 10ms */
#endif
}
  • 每个定时器都存储在 struct timer_t 类型的结构体中:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef struct timer {
    int magic;
    struct list_node node;

    lk_time_t scheduled_time;
    lk_time_t periodic_time;

    timer_callback callback;
    void *arg;
    } timer_t;
    timer_t初始化定义为一个宏:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #define TIMER_INITIAL_VALUE(t) \
    { \
    .magic = TIMER_MAGIC, \
    .node = LIST_INITIAL_CLEARED_VALUE, \
    .scheduled_time = 0, \
    .periodic_time = 0, \
    .callback = NULL, \
    .arg = NULL, \
    }
  • 全局链表 timer_queue 的作用就是存储定时器,定义如下:
    1
    2
    3
    4
    5
    struct timer_state {
    struct list_node timer_queue;
    } __CPU_ALIGN;

    static struct timer_state timers[SMP_MAX_CPUS];
  • 通过platform_set_periodic_timer设定timer每10ms中断一次,中断callback是timer_tick。

timer_tick

timer_tick 函数的作用则是遍历 timer_queue 来处理其中注册的定时器回调函数,代码如下:

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
/* called at interrupt time to process any pending timers */
static enum handler_return timer_tick(void *arg, lk_time_t now)
{
timer_t *timer;
enum handler_return ret = INT_NO_RESCHEDULE;

DEBUG_ASSERT(arch_ints_disabled());

THREAD_STATS_INC(timer_ints);
// KEVLOG_TIMER_TICK(); // enable only if necessary

uint cpu = arch_curr_cpu_num();

LTRACEF("cpu %u now %u, sp %p\n", cpu, now, __GET_FRAME());

spin_lock(&timer_lock);

for (;;) {
/* see if there's an event to process */
timer = list_peek_head_type(&timers[cpu].timer_queue, timer_t, node);
if (likely(timer == 0))
break;
LTRACEF("next item on timer queue %p at %u now %u (%p, arg %p)\n", timer, timer->scheduled_time, now, timer->callback, timer->arg);
if (likely(TIME_LT(now, timer->scheduled_time)))
break;

/* process it */
LTRACEF("timer %p\n", timer);
DEBUG_ASSERT(timer && timer->magic == TIMER_MAGIC);
list_delete(&timer->node);

/* we pulled it off the list, release the list lock to handle it */
spin_unlock(&timer_lock);

LTRACEF("dequeued timer %p, scheduled %u periodic %u\n", timer, timer->scheduled_time, timer->periodic_time);

THREAD_STATS_INC(timers);

bool periodic = timer->periodic_time > 0;

LTRACEF("timer %p firing callback %p, arg %p\n", timer, timer->callback, timer->arg);
KEVLOG_TIMER_CALL(timer->callback, timer->arg);
if (timer->callback(timer, now, timer->arg) == INT_RESCHEDULE)
ret = INT_RESCHEDULE;

/* it may have been requeued or periodic, grab the lock so we can safely inspect it */
spin_lock(&timer_lock);

/* if it was a periodic timer and it hasn't been requeued
* by the callback put it back in the list
*/
if (periodic && !list_in_list(&timer->node) && timer->periodic_time > 0) {
LTRACEF("periodic timer, period %u\n", timer->periodic_time);
timer->scheduled_time = now + timer->periodic_time;
insert_timer_in_queue(cpu, timer);
}
}

#if PLATFORM_HAS_DYNAMIC_TIMER
/* reset the timer to the next event */
timer = list_peek_head_type(&timers[cpu].timer_queue, timer_t, node);
if (timer) {
/* has to be the case or it would have fired already */
DEBUG_ASSERT(TIME_GT(timer->scheduled_time, now));

lk_time_t delay = timer->scheduled_time - now;

LTRACEF("setting new timer for %u msecs for event %p\n", (uint)delay, timer);
platform_set_oneshot_timer(timer_tick, NULL, delay);
}

/* we're done manipulating the timer queue */
spin_unlock(&timer_lock);
#else
/* release the timer lock before calling the tick handler */
spin_unlock(&timer_lock);

/* let the scheduler have a shot to do quantum expiration, etc */
/* in case of dynamic timer, the scheduler will set up a periodic timer */
if (thread_timer_tick() == INT_RESCHEDULE)
ret = INT_RESCHEDULE;
#endif

return ret;
}

timer_tick 是个死循环,会从变量timer_queue 中是否有到期的时间:

1
2
/* see if there's an event to process */
timer = list_peek_head_type(&timers[cpu].timer_queue, timer_t, node);

如果timer为空或者还未到期,则跳出循环:

1
2
3
4
5
if (likely(timer == 0))
break;
LTRACEF("next item on timer queue %p at %u now %u (%p, arg %p)\n", timer, timer->scheduled_time, now, timer->callback, timer->arg);
if (likely(TIME_LT(now, timer->scheduled_time)))
break;

如果有的话,就从timer_queue 中删除:

1
list_delete(&timer->node);

并调用这个timer到期时间需要执行的callback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* we pulled it off the list, release the list lock to handle it */
spin_unlock(&timer_lock);

LTRACEF("dequeued timer %p, scheduled %u periodic %u\n", timer, timer->scheduled_time, timer->periodic_time);

THREAD_STATS_INC(timers);

bool periodic = timer->periodic_time > 0;

LTRACEF("timer %p firing callback %p, arg %p\n", timer, timer->callback, timer->arg);
KEVLOG_TIMER_CALL(timer->callback, timer->arg);
if (timer->callback(timer, now, timer->arg) == INT_RESCHEDULE)
ret = INT_RESCHEDULE;

/* it may have been requeued or periodic, grab the lock so we can safely inspect it */
spin_lock(&timer_lock);

如果这个timer是periodic类型的额,那么再次插入到timer_queue中:

1
2
3
4
5
6
7
8
/* if it was a periodic timer and it hasn't been requeued
- by the callback put it back in the list
*/
if (periodic && !list_in_list(&timer->node) && timer->periodic_time > 0) {
LTRACEF("periodic timer, period %u\n", timer->periodic_time);
timer->scheduled_time = now + timer->periodic_time;
insert_timer_in_queue(cpu, timer);
}

platform

在timer中,调用的platform层的函数分为:

  • platform_set_periodic_timer
  • platform_set_oneshot_timer
  • platform_stop_timer
    两种。

这两种类型通过一个宏区分:
PLATFORM_HAS_DYNAMIC_TIMER

1
2
3
4
5
#if PLATFORM_HAS_DYNAMIC_TIMER
调用platform_set_oneshot_timer、platform_stop_timer

#if !PLATFORM_HAS_DYNAMIC_TIMER
调用platform_set_periodic_timer

这三个函数在lk/dev/timer中对应的平台下实现
例1:platform为lk/dev/timer/arm_generic。
其中宏PLATFORM_HAS_DYNAMIC_TIMER定义在arm_generic/rules.mk中:

1
2
3
4
5
GLOBAL_DEFINES += \
PLATFORM_HAS_DYNAMIC_TIMER=1

MODULE_SRCS += \
$(LOCAL_DIR)/arm_generic_timer.c

arm_generic/arm_generic_timer.c文件里实现了定时器的底层操作函数,并实现了以下几个与外部调用相关的函数:

platform_set_oneshot_timer

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
status_t platform_set_oneshot_timer(platform_timer_callback callback, void *arg, lk_time_t interval)
{
uint64_t cntpct_interval = lk_time_to_cntpct(interval);

ASSERT(arg == NULL);

t_callback = callback;
if (cntpct_interval <= INT_MAX)
write_cntp_tval(cntpct_interval);
else
write_cntp_cval(read_cntpct() + cntpct_interval);
write_cntp_ctl(1);

return 0;
}

1.将interval的单位为ms的延时时间转换为要设置的定时器计数的寄存器值。
2.设置中断服务程序中要执行的callback函数。
3.将要设置的计数器的值写入寄存器。
4.开启定时器。

platform_stop_timer

代码如下:

1
2
3
4
void platform_stop_timer(void)
{
write_cntp_ctl(0);
}

1.关闭定时器。

platform_tick

代码如下:

1
2
3
4
5
6
7
8
9
static enum handler_return platform_tick(void *arg)
{
write_cntp_ctl(0);
if (t_callback) {
return t_callback(arg, current_time());
} else {
return INT_NO_RESCHEDULE;
}
}

1.关闭定时器。
2.执行中断处理程序。

arm_generic_timer_init
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
void arm_generic_timer_init(int irq, uint32_t freq_override)
{
uint32_t cntfrq;

if (freq_override == 0) {
cntfrq = read_cntfrq();

if (!cntfrq) {
TRACEF("Failed to initialize timer, frequency is 0\n");
return;
}
} else {
cntfrq = freq_override;
}

#if LOCAL_TRACE
LTRACEF("Test min cntfrq\n");
arm_generic_timer_init_conversion_factors(1);
test_time_conversions(1);
LTRACEF("Test max cntfrq\n");
arm_generic_timer_init_conversion_factors(~0);
test_time_conversions(~0);
LTRACEF("Set actual cntfrq\n");
#endif
arm_generic_timer_init_conversion_factors(cntfrq);
test_time_conversions(cntfrq);

LTRACEF("register irq %d on cpu %d\n", irq, arch_curr_cpu_num());
register_int_handler(irq, &platform_tick, NULL);
unmask_interrupt(irq);

timer_irq = irq;
}

其中注册中断服务程序:

1
2
3
4
register_int_handler(irq, &platform_tick, NULL);
unmask_interrupt(irq);

timer_irq = irq;

例2:platform为lk/dev/timer/or1k_ticktimer。
其中宏PLATFORM_HAS_DYNAMIC_TIMER在or1k_ticktimer/rules.mk中被屏蔽掉,表示不定义:

1
2
3
4
5
#GLOBAL_DEFINES += \
# PLATFORM_HAS_DYNAMIC_TIMER=1

MODULE_SRCS += \
$(LOCAL_DIR)/or1k_ticktimer.c

or1k_ticktimer/or1k_ticktimer.c文件里实现了定时器的底层操作函数,并实现了以下几个与外部调用相关的函数:

platform_set_periodic_timer

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval)
{
LTRACEF("cb %p, arg %p, interval %ld\n", callback, arg, interval);

uint32_t ttmr = (uint64_t)timer_freq * interval / 1000;
LTRACEF("count 0x%x\n", ttmr);

timer_cb = callback;
timer_arg = arg;

/* disable timer before doing changes */
mtspr(OR1K_SPR_TICK_TTMR_ADDR, 0);
/* reset timer counter */
mtspr(OR1K_SPR_TICK_TTCR_ADDR, 0);
/* enable timer with given interval in 'restart' mode */
ttmr = OR1K_SPR_TICK_TTMR_MODE_SET(ttmr | OR1K_SPR_TICK_TTMR_IE_MASK,
OR1K_SPR_TICK_TTMR_MODE_RESTART);
mtspr(OR1K_SPR_TICK_TTMR_ADDR, ttmr);

return NO_ERROR;
}

1.根据interval计算定时器计数器需要设置的初始值ttmr。
2.设置中断服务程序中要执行的callback函数。
3.关闭定时器并配置定时器参数。
4.启动定时器。

platform_tick

代码如下:

1
2
3
4
5
6
7
8
9
10
enum handler_return platform_tick(void)
{
ticks++;

/* clear pending interrupt flag */
mtspr(OR1K_SPR_TICK_TTMR_ADDR,
mfspr(OR1K_SPR_TICK_TTMR_ADDR) & ~(OR1K_SPR_TICK_TTMR_IP_MASK));

return timer_cb(timer_arg, ticks * 10);
}

1.清除中断标记位。
2.执行callback函数。