High Resolution Timers in High Resolution Mode

Let us first assume that a high-resolution clock is up and running, and that the transition to highresolution mode is completely finished. The general situation is depicted in Figure 15-13.

When the clock event device responsible for high-resolution timers raises an interrupt, hrtimer_interrupt is called as event handler. The function is responsible to select all timers that have expired and either move them to the expiration list (if they may be processed in softIRQ context) or call the handler function directly. After reprogramming the clock event device so that an interrupt is raised when the next pending timer expires, the softIRQ hrtimer_softirq is raised. When the softIRQ executes, run_hrtimer_softirq takes care of executing the handler functions of all timers on the expiration list.

Let's discuss the code responsible to implement all this. First, consider the interrupt handler hrtimer_interrupt. Some initialization work is necessary in the beginning:

kernel/hrtimer.c void hrtimer_interrupt(struct clock_event_device *dev) {

struct hrtimer_cpu_base *cpu_base = &_get_cpu_var(hrtimer_bases);

struct hrtimer_clock_base *base; ktime_t expires_next, now;

retry:

expires_next.tv64 = KTIME_MAX; base = cpu_base->clock_base;

Hrtimer Interrupt
Figure 15-13: Overview of expiration of high-resolution timers with high-resolution clocks.

The expiration time of the timer that is due next is stored in expires_next. Setting this to ktime_max initially is another way of saying that no next timer is available. The main work is to iterate over all clock bases (monotonic and real-time).

kernel/hrtimer.c for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) { ktime_t basenow; struct rb_node *node;

basenow = ktime_add(now, base->offset);

Essentially, basenow denotes the current time. base->offset is only non-zero when the real-time clock has been readjusted, so this will never affect the monotonic clock base. Starting from base->first, the expired nodes of the red-black tree can be obtained:

kernel/hrtimer.c while ((node = base->first)) { struct hrtimer *timer;

timer = rb_entry(node, struct hrtimer, node); if (basenow.tv64 < timer->expires.tv64) { ktime_t expires;

expires = ktime_sub(timer->expires, base->offset);

if (expires.tv64 < expires_next.tv64) expires_next = expires;

break;

If the next timer's expiration time lies in the future, processing can be stopped by leaving the while loop. The time of expiration is, however, remembered because it is later required to reprogram the clock event device.

If the current timer has expired, it is moved to the callback list for later processing in the softIRQ if this is allowed, that is, if hrtimer_cb_softirq is set. continue ensures that the code moves to the next timer. Erasing the timer with_remove_timer also selects the next expiration candidate by updating base->first. Additionally, this sets the timer state to hrtimer_state_pending:

kernel/hrtimer.c if (timer->cb_mode == HRTIMER_CB_SOFTIRQ) {

_remove_hrtimer(timer, base,

HRTIMER_STATE_PENDING, 0); list_add_tail(&timer->cb_entry, &base->cpu_base->cb_pending); raise = 1; continue;

Otherwise, the timer callback is directly executed in hard interrupt context. Note that this time

_remove_timer sets the timer state to HRTIMER_STATE_CALLBACK because the callback handler is executed immediately afterward:

kernel/hrtimer.c

_remove_hrtimer(timer, base,

HRTIMER_STATE_CALLBACK, 0);

if (timer->function(timer) != HRTIMER_NORESTART) { enqueue_hrtimer(timer, base, 0);

timer->state &= ~HRTIMER_STATE_CALLBACK;

The callback handler is executed by timer->function(timer). If the handler requests to be restarted by returning HRTIMER_RESTART, then enqueue_hrtimer fulfills this request. The HRTIMER_STATE_CALLBACK flag can be removed once the handler has been executed.

When the pending timers of all clock bases have been selected, the kernel needs to reprogram the event device to raise an interrupt when the next timer is due. Additionally, the hrtimer_softirq must be raised if any timers are waiting on the callback list:

kernel/hrtimer.c cpu_base->expires_next = expires_next;

/* Reprogramming necessary ? */ if (expires_next.tv64 != KTIME_MAX) {

if (tick_program_event(expires_next, 0))

goto retry;

raise_softirq(HRTIMER_SOFTIRQ);

Note that reprogramming fails if the next expiration date is already in the past — this can happen if timer processing took too long. In this case, the whole processing sequence is restarted by jumping to the retry label at the beginning of the function.

One more final step is necessary to complete one round of high-resolution timer handling: Run the softIRQ to execute the pending callbacks. The softIRQ handler is run_hrtimer_softirq, and Figure 15-14 shows the code flow diagram.17

run_hrtimer_softirq

— timer->functionl enqueue_hrtimer

Was this article helpful?

+3 0

Post a comment