[Timer] 02.QEMU 定时器 Ptimer

目录

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;
    }
}

你可能感兴趣的:(QEMU,嵌入式硬件,驱动开发,linux)