X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=qemu-timer.c;h=e15ce477ccd55c44f518d348f5a2e4c0e68bf8a6;hb=455df3f32341a3dff00f1726ff0749b3dd783bdf;hp=95ff47fef3091a876b5d08c0b54e38c8b550e6cc;hpb=3e998a778846de4ea24188278f18e4191a56412e;p=qemu.git diff --git a/qemu-timer.c b/qemu-timer.c index 95ff47fef..e15ce477c 100644 --- a/qemu-timer.c +++ b/qemu-timer.c @@ -45,6 +45,7 @@ /* timers */ typedef struct QEMUClock { + /* We rely on BQL to protect the timerlists */ QLIST_HEAD(, QEMUTimerList) timerlists; NotifierList reset_notifiers; @@ -66,10 +67,14 @@ QEMUClock qemu_clocks[QEMU_CLOCK_MAX]; struct QEMUTimerList { QEMUClock *clock; + QemuMutex active_timers_lock; QEMUTimer *active_timers; QLIST_ENTRY(QEMUTimerList) list; QEMUTimerListNotifyCB *notify_cb; void *notify_opaque; + + /* lightweight method to mark the end of timerlist's running */ + QemuEvent timers_done_ev; }; /** @@ -98,9 +103,11 @@ QEMUTimerList *timerlist_new(QEMUClockType type, QEMUClock *clock = qemu_clock_ptr(type); timer_list = g_malloc0(sizeof(QEMUTimerList)); + qemu_event_init(&timer_list->timers_done_ev, false); timer_list->clock = clock; timer_list->notify_cb = cb; timer_list->notify_opaque = opaque; + qemu_mutex_init(&timer_list->active_timers_lock); QLIST_INSERT_HEAD(&clock->timerlists, timer_list, list); return timer_list; } @@ -111,6 +118,7 @@ void timerlist_free(QEMUTimerList *timer_list) if (timer_list->clock) { QLIST_REMOVE(timer_list, list); } + qemu_mutex_destroy(&timer_list->active_timers_lock); g_free(timer_list); } @@ -140,13 +148,25 @@ void qemu_clock_notify(QEMUClockType type) } } +/* Disabling the clock will wait for related timerlists to stop + * executing qemu_run_timers. Thus, this functions should not + * be used from the callback of a timer that is based on @clock. + * Doing so would cause a deadlock. + * + * Caller should hold BQL. + */ void qemu_clock_enable(QEMUClockType type, bool enabled) { QEMUClock *clock = qemu_clock_ptr(type); + QEMUTimerList *tl; bool old = clock->enabled; clock->enabled = enabled; if (enabled && !old) { qemu_clock_notify(type); + } else if (!enabled && old) { + QLIST_FOREACH(tl, &clock->timerlists, list) { + qemu_event_wait(&tl->timers_done_ev); + } } } @@ -163,9 +183,17 @@ bool qemu_clock_has_timers(QEMUClockType type) bool timerlist_expired(QEMUTimerList *timer_list) { - return (timer_list->active_timers && - timer_list->active_timers->expire_time < - qemu_clock_get_ns(timer_list->clock->type)); + int64_t expire_time; + + qemu_mutex_lock(&timer_list->active_timers_lock); + if (!timer_list->active_timers) { + qemu_mutex_unlock(&timer_list->active_timers_lock); + return false; + } + expire_time = timer_list->active_timers->expire_time; + qemu_mutex_unlock(&timer_list->active_timers_lock); + + return expire_time < qemu_clock_get_ns(timer_list->clock->type); } bool qemu_clock_expired(QEMUClockType type) @@ -182,13 +210,25 @@ bool qemu_clock_expired(QEMUClockType type) int64_t timerlist_deadline_ns(QEMUTimerList *timer_list) { int64_t delta; + int64_t expire_time; - if (!timer_list->clock->enabled || !timer_list->active_timers) { + if (!timer_list->clock->enabled) { return -1; } - delta = timer_list->active_timers->expire_time - - qemu_clock_get_ns(timer_list->clock->type); + /* The active timers list may be modified before the caller uses our return + * value but ->notify_cb() is called when the deadline changes. Therefore + * the caller should notice the change and there is no race condition. + */ + qemu_mutex_lock(&timer_list->active_timers_lock); + if (!timer_list->active_timers) { + qemu_mutex_unlock(&timer_list->active_timers_lock); + return -1; + } + expire_time = timer_list->active_timers->expire_time; + qemu_mutex_unlock(&timer_list->active_timers_lock); + + delta = expire_time - qemu_clock_get_ns(timer_list->clock->type); if (delta <= 0) { return 0; @@ -289,6 +329,7 @@ void timer_init(QEMUTimer *ts, ts->cb = cb; ts->opaque = opaque; ts->scale = scale; + ts->expire_time = -1; } void timer_free(QEMUTimer *ts) @@ -296,14 +337,12 @@ void timer_free(QEMUTimer *ts) g_free(ts); } -/* stop a timer, but do not dealloc it */ -void timer_del(QEMUTimer *ts) +static void timer_del_locked(QEMUTimerList *timer_list, QEMUTimer *ts) { QEMUTimer **pt, *t; - /* NOTE: this code must be signal safe because - timer_expired() can be called from a signal. */ - pt = &ts->timer_list->active_timers; + ts->expire_time = -1; + pt = &timer_list->active_timers; for(;;) { t = *pt; if (!t) @@ -316,34 +355,82 @@ void timer_del(QEMUTimer *ts) } } -/* modify the current timer so that it will be fired when current_time - >= expire_time. The corresponding callback will be called. */ -void timer_mod_ns(QEMUTimer *ts, int64_t expire_time) +static bool timer_mod_ns_locked(QEMUTimerList *timer_list, + QEMUTimer *ts, int64_t expire_time) { QEMUTimer **pt, *t; - timer_del(ts); - /* add the timer in the sorted list */ - /* NOTE: this code must be signal safe because - timer_expired() can be called from a signal. */ - pt = &ts->timer_list->active_timers; - for(;;) { + pt = &timer_list->active_timers; + for (;;) { t = *pt; if (!timer_expired_ns(t, expire_time)) { break; } pt = &t->next; } - ts->expire_time = expire_time; + ts->expire_time = MAX(expire_time, 0); ts->next = *pt; *pt = ts; - /* Rearm if necessary */ - if (pt == &ts->timer_list->active_timers) { - /* Interrupt execution to force deadline recalculation. */ - qemu_clock_warp(ts->timer_list->clock->type); - timerlist_notify(ts->timer_list); + return pt == &timer_list->active_timers; +} + +static void timerlist_rearm(QEMUTimerList *timer_list) +{ + /* Interrupt execution to force deadline recalculation. */ + qemu_clock_warp(timer_list->clock->type); + timerlist_notify(timer_list); +} + +/* stop a timer, but do not dealloc it */ +void timer_del(QEMUTimer *ts) +{ + QEMUTimerList *timer_list = ts->timer_list; + + qemu_mutex_lock(&timer_list->active_timers_lock); + timer_del_locked(timer_list, ts); + qemu_mutex_unlock(&timer_list->active_timers_lock); +} + +/* modify the current timer so that it will be fired when current_time + >= expire_time. The corresponding callback will be called. */ +void timer_mod_ns(QEMUTimer *ts, int64_t expire_time) +{ + QEMUTimerList *timer_list = ts->timer_list; + bool rearm; + + qemu_mutex_lock(&timer_list->active_timers_lock); + timer_del_locked(timer_list, ts); + rearm = timer_mod_ns_locked(timer_list, ts, expire_time); + qemu_mutex_unlock(&timer_list->active_timers_lock); + + if (rearm) { + timerlist_rearm(timer_list); + } +} + +/* modify the current timer so that it will be fired when current_time + >= expire_time or the current deadline, whichever comes earlier. + The corresponding callback will be called. */ +void timer_mod_anticipate_ns(QEMUTimer *ts, int64_t expire_time) +{ + QEMUTimerList *timer_list = ts->timer_list; + bool rearm; + + qemu_mutex_lock(&timer_list->active_timers_lock); + if (ts->expire_time == -1 || ts->expire_time > expire_time) { + if (ts->expire_time != -1) { + timer_del_locked(timer_list, ts); + } + rearm = timer_mod_ns_locked(timer_list, ts, expire_time); + } else { + rearm = false; + } + qemu_mutex_unlock(&timer_list->active_timers_lock); + + if (rearm) { + timerlist_rearm(timer_list); } } @@ -352,15 +439,14 @@ void timer_mod(QEMUTimer *ts, int64_t expire_time) timer_mod_ns(ts, expire_time * ts->scale); } +void timer_mod_anticipate(QEMUTimer *ts, int64_t expire_time) +{ + timer_mod_anticipate_ns(ts, expire_time * ts->scale); +} + bool timer_pending(QEMUTimer *ts) { - QEMUTimer *t; - for (t = ts->timer_list->active_timers; t != NULL; t = t->next) { - if (t == ts) { - return true; - } - } - return false; + return ts->expire_time >= 0; } bool timer_expired(QEMUTimer *timer_head, int64_t current_time) @@ -373,25 +459,38 @@ bool timerlist_run_timers(QEMUTimerList *timer_list) QEMUTimer *ts; int64_t current_time; bool progress = false; - + QEMUTimerCB *cb; + void *opaque; + + qemu_event_reset(&timer_list->timers_done_ev); if (!timer_list->clock->enabled) { - return progress; + goto out; } current_time = qemu_clock_get_ns(timer_list->clock->type); for(;;) { + qemu_mutex_lock(&timer_list->active_timers_lock); ts = timer_list->active_timers; if (!timer_expired_ns(ts, current_time)) { + qemu_mutex_unlock(&timer_list->active_timers_lock); break; } + /* remove timer from the list before calling the callback */ timer_list->active_timers = ts->next; ts->next = NULL; + ts->expire_time = -1; + cb = ts->cb; + opaque = ts->opaque; + qemu_mutex_unlock(&timer_list->active_timers_lock); /* run the callback (the timer list can be modified) */ - ts->cb(ts->opaque); + cb(opaque); progress = true; } + +out: + qemu_event_set(&timer_list->timers_done_ev); return progress; }