目录
1.简介
2.定时器策略 - PTIMER_POLICY
3.定时器结构 - struct ptimer_state
4.创建定时器 - ptimer_init()
5.设置时钟周期
5.1.按固定值设置时钟周期 - ptimer_set_period()
5.2.按频率设置时钟周期 - ptimer_set_freq()
5.3.从已有时钟获取周期 - ptimer_set_period_from_clock()
6.定时器接口
6.1.获取计数值 - ptimer_get_count()
6.2.设置计数值 - ptimer_set_count()
1.简介
ptimer 是一个减法定时器,其相关接口及作用如下:
使用 ptimer_get_count() 和 ptimer_set_count() 接口获取/设置定时时间;
使用 ptimer_set_period() 或 ptimer_set_freq() 设置时钟周期;
使用 ptimer_run() 使能定时器后,其值按设定的周期递减,递减至 0 时触发回调函数,此时可以控制定时器载入一个新的值并继续递减,或者直接停止(one-shot timer);
ptime 拥有一组基于事务的接口(transaction-based API)用于修改 ptimer 的状态:
执行状态修改操作前必须执行 ptimer_transaction_begin(),然后进行相应的操作;
最后执行 ptimer_transaction_commit(),commit 调用后,会在本次事务的所有的操作完成后修改定时器的状态,并在条件满足时调用回调函数;
有关状态修改函数的完整列表和回调函数的详细含义,可以参考 ptimer_init() 的相关描述;
2.定时器策略 - PTIMER_POLICY
定时器可以设置多种策略控制定时器在超时或启动时的行为
// include/hw/ptimer.h
#define PTIMER_POLICY_LEGACY 0
#define PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD (1 << 0)
#define PTIMER_POLICY_CONTINUOUS_TRIGGER (1 << 1)
#define PTIMER_POLICY_NO_IMMEDIATE_TRIGGER (1 << 2)
#define PTIMER_POLICY_NO_IMMEDIATE_RELOAD (1 << 3)
#define PTIMER_POLICY_NO_COUNTER_ROUND_DOWN (1 << 4)
#define PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT (1 << 5)
3.定时器结构 - struct ptimer_state
基本的定时时长、回调函数、以及绑定的 QEMU 定时器信息:
// hw/core/ptimer.c
struct ptimer_state
{
uint8_t enabled; /* 0 = disabled, 1 = periodic, 2 = oneshot. */
uint64_t limit;
uint64_t delta;
uint32_t period_frac;
int64_t period; // 一个Tick为多少ns,20HHz晶振,1Tick为50ns
int64_t last_event;
int64_t next_event;
uint8_t policy_mask;
QEMUTimer *timer;
ptimer_cb callback;
void *callback_opaque;
bool in_transaction;
bool need_reload;
};
4.创建定时器 - ptimer_init()
为 ptimer_state 分配内存,调用 timer_new_ns() 创建定时器,初始化回调函数、策略等
// hw/core/ptimer.c
ptimer_state *ptimer_init(ptimer_cb callback, void *callback_opaque,
uint8_t policy_mask)
{
ptimer_state *s;
/* The callback function is mandatory. */
assert(callback);
s = g_new0(ptimer_state, 1);
s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ptimer_tick, s);
s->policy_mask = policy_mask;
s->callback = callback;
s->callback_opaque = callback_opaque;
...
return s;
}
-------------------------------------------------------------------------
// include/qemu/timer.h
static inline QEMUTimer *timer_new_ns(QEMUClockType type, QEMUTimerCB *cb,
void *opaque)
{
return timer_new(type, SCALE_NS, cb, opaque);
}
5.设置时钟周期
设置 1ns 包含多少个 Tick(1s = 1000 000 000ns),若晶振频率为 20MHz(20*1000*1000 个 Tick 为 1s),则 1ns 包含 1*1000*1000*1000ns/20*1000*1000 = 50 个 Tick
5.1.按固定值设置时钟周期 - ptimer_set_period()
ptimer_set_period() 设置定时时长 period:
// hw/core/ptimer.c
void ptimer_set_period(ptimer_state *s, int64_t period)
{
...
s->delta = ptimer_get_count(s);
s->period = period;
s->period_frac = 0;
if (s->enabled) {
s->need_reload = true;
}
}
5.2.按频率设置时钟周期 - ptimer_set_freq()
ptimer_set_freq() 设置定时器的工作频率:
// hw/core/ptimer.c
void ptimer_set_freq(ptimer_state *s, uint32_t freq)
{
...
s->delta = ptimer_get_count(s);
s->period = 1000000000ll / freq;
s->period_frac = (1000000000ll << 32) / freq;
if (s->enabled) {
s->need_reload = true;
}
}
5.3.从已有时钟获取周期 - ptimer_set_period_from_clock()
ptimer_set_period_from_clock() 获取定时器的时钟频率:
// hw/core/ptimer.c
void ptimer_set_period_from_clock(ptimer_state *s, const Clock *clk,
unsigned int divisor)
{
uint64_t raw_period = clock_get(clk);
|--> return clk->period; // 1ns包含的Tick数量
uint64_t period_frac;
assert(s->in_transaction);
s->delta = ptimer_get_count(s);
s->period = extract64(raw_period, 32, 32);
period_frac = extract64(raw_period, 0, 32);
s->period *= divisor;
period_frac *= divisor;
s->period += extract64(period_frac, 32, 32);
s->period_frac = (uint32_t)period_frac;
if (s->enabled) {
s->need_reload = true;
}
}
6.定时器接口
6.1.获取计数值 - ptimer_get_count()
// hw/core/ptimer.c
uint64_t ptimer_get_count(ptimer_state *s)
{
uint64_t counter;
if (s->enabled && s->delta != 0) {
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
int64_t next = s->next_event;
int64_t last = s->last_event;
bool expired = (now - next >= 0);
bool oneshot = (s->enabled == 2);
/* Figure out the current counter value. */
if (expired) {
/* Prevent timer underflowing if it should already have
triggered. */
counter = 0;
} else {
uint64_t rem;
uint64_t div;
int clz1, clz2;
int shift;
uint32_t period_frac = s->period_frac;
uint64_t period = s->period;
if (!oneshot && (s->delta * period < 10000) &&
!icount_enabled() && !qtest_enabled()) {
period = 10000 / s->delta;
period_frac = 0;
}
/* We need to divide time by period, where time is stored in
rem (64-bit integer) and period is stored in period/period_frac
(64.32 fixed point).
Doing full precision division is hard, so scale values and
do a 64-bit division. The result should be rounded down,
so that the rounding error never causes the timer to go
backwards.
*/
rem = next - now; // 0x31fffb2188405c
div = period;
clz1 = clz64(rem);
clz2 = clz64(div);
shift = clz1 < clz2 ? clz1 : clz2;
rem <<= shift; // 0xc7ffec8621017000
div <<= shift; // 0xc800
if (shift >= 32) {
div |= ((uint64_t)period_frac << (shift - 32));
} else {
if (shift != 0)
div |= (period_frac >> (32 - shift));
/* Look at remaining bits of period_frac and round div up if
necessary. */
if ((uint32_t)(period_frac << shift))
div += 1;
}
counter = rem / div;
if (s->policy_mask & PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD) {
/* Before wrapping around, timer should stay with counter = 0
for a one period. */
if (!oneshot && s->delta == s->limit) {
if (now == last) {
/* Counter == delta here, check whether it was
adjusted and if it was, then right now it is
that "one period". */
if (counter == s->limit + DELTA_ADJUST) {
return 0;
}
} else if (counter == s->limit) {
/* Since the counter is rounded down and now != last,
the counter == limit means that delta was adjusted
by +1 and right now it is that adjusted period. */
return 0;
}
}
}
}
if (s->policy_mask & PTIMER_POLICY_NO_COUNTER_ROUND_DOWN) {
/* If now == last then delta == limit, i.e. the counter already
represents the correct value. It would be rounded down a 1ns
later. */
if (now != last) {
counter += 1;
}
}
} else {
counter = s->delta;
}
return counter;
}
6.2.设置计数值 - ptimer_set_count()
// hw/core/ptimer.c
void ptimer_set_count(ptimer_state *s, uint64_t count)
{
...
s->delta = count;
if (s->enabled) {
s->need_reload = true;
}
}