Files
zinnia/lib/timer.c
T

116 lines
3.0 KiB
C

// SPDX-License-Identifier: ISC
#include <zinnia/assert.h>
#include <zinnia/atomic.h>
#include <zinnia/clock.h>
#include <zinnia/irq.h>
#include <zinnia/percpu.h>
#include <zinnia/spin.h>
#include <zinnia/timer.h>
#include <arch/cpu.h>
#include <arch/timer.h>
struct timer_queue {
struct spinlock lock;
tailq_head(struct timer) timers;
};
[[__percpu]]
static struct timer_queue timer_queue;
void timer_init(struct timer* timer, void (*func)(struct timer* timer)) {
timer->func = func;
timer->deadline = 0;
atomic_init(&timer->queue, nullptr);
atomic_init(&timer->running, false);
}
void timer_cpu_init() {
struct timer_queue* q = percpu_ref(timer_queue);
spin_init(&q->lock);
tailq_init(&q->timers);
}
static void program_next(struct timer_queue* q) {
struct timer* first = tailq_first(&q->timers);
if (first)
arch_timer_program(first->deadline);
}
static void remove_locked(struct timer_queue* q, struct timer* timer) {
tailq_remove(&q->timers, timer, link);
atomic_store(&timer->queue, nullptr, ATOMIC_RELEASE);
}
static struct timer_queue* lock_queue_of(struct timer* timer) {
while (true) {
struct timer_queue* q = atomic_load(&timer->queue, ATOMIC_ACQUIRE);
if (!q)
return nullptr;
spin_lock(&q->lock);
if (atomic_load(&timer->queue, ATOMIC_RELAXED) == q)
return q;
spin_unlock(&q->lock);
}
}
static bool dequeue(struct timer* timer) {
struct timer_queue* q = lock_queue_of(timer);
if (!q)
return false;
remove_locked(q, timer);
spin_unlock(&q->lock);
return true;
}
void timer_arm(struct timer* timer, uint64_t deadline_ns) {
dequeue(timer);
irq_lock();
struct timer_queue* q = percpu_ref(timer_queue);
spin_lock(&q->lock);
timer->deadline = deadline_ns;
struct timer* pos = tailq_first(&q->timers);
while (pos && pos->deadline <= deadline_ns)
pos = tailq_next(pos, link);
if (pos)
tailq_insert_before(pos, timer, link);
else
tailq_insert_tail(&q->timers, timer, link);
atomic_store(&timer->queue, q, ATOMIC_RELEASE);
if (tailq_first(&q->timers) == timer)
arch_timer_program(deadline_ns);
spin_unlock(&q->lock);
irq_unlock();
}
bool timer_cancel(struct timer* timer) {
bool removed = dequeue(timer);
while (atomic_load(&timer->running, ATOMIC_ACQUIRE))
arch_cpu_pause();
return removed;
}
void timer_interrupt() {
struct timer_queue* q = percpu_ref(timer_queue);
const uint64_t now = clock_get_elapsed_ns();
spin_lock(&q->lock);
struct timer* timer;
while ((timer = tailq_first(&q->timers)) && timer->deadline <= now) {
atomic_store(&timer->running, true, ATOMIC_RELAXED);
remove_locked(q, timer);
spin_unlock(&q->lock);
timer->func(timer);
atomic_store(&timer->running, false, ATOMIC_RELEASE);
spin_lock(&q->lock);
}
program_next(q);
spin_unlock(&q->lock);
}