diff --git a/vl.c b/vl.c index a5c1ce64eb..cfeea7269a 100644 --- a/vl.c +++ b/vl.c @@ -784,12 +784,32 @@ struct QEMUTimer { struct qemu_alarm_timer { char const *name; + unsigned int flags; int (*start)(struct qemu_alarm_timer *t); void (*stop)(struct qemu_alarm_timer *t); + void (*rearm)(struct qemu_alarm_timer *t); void *priv; }; +#define ALARM_FLAG_DYNTICKS 0x1 + +static inline int alarm_has_dynticks(struct qemu_alarm_timer *t) +{ + return t->flags & ALARM_FLAG_DYNTICKS; +} + +static void qemu_rearm_alarm_timer(struct qemu_alarm_timer *t) +{ + if (!alarm_has_dynticks(t)) + return; + + t->rearm(t); +} + +/* TODO: MIN_TIMER_REARM_US should be optimized */ +#define MIN_TIMER_REARM_US 250 + static struct qemu_alarm_timer *alarm_timer; #ifdef _WIN32 @@ -802,12 +822,17 @@ struct qemu_alarm_win32 { static int win32_start_timer(struct qemu_alarm_timer *t); static void win32_stop_timer(struct qemu_alarm_timer *t); +static void win32_rearm_timer(struct qemu_alarm_timer *t); #else static int unix_start_timer(struct qemu_alarm_timer *t); static void unix_stop_timer(struct qemu_alarm_timer *t); +static int dynticks_start_timer(struct qemu_alarm_timer *t); +static void dynticks_stop_timer(struct qemu_alarm_timer *t); +static void dynticks_rearm_timer(struct qemu_alarm_timer *t); + #ifdef __linux__ static int hpet_start_timer(struct qemu_alarm_timer *t); @@ -816,21 +841,26 @@ static void hpet_stop_timer(struct qemu_alarm_timer *t); static int rtc_start_timer(struct qemu_alarm_timer *t); static void rtc_stop_timer(struct qemu_alarm_timer *t); -#endif +#endif /* __linux__ */ #endif /* _WIN32 */ static struct qemu_alarm_timer alarm_timers[] = { +#ifndef _WIN32 + {"dynticks", ALARM_FLAG_DYNTICKS, dynticks_start_timer, + dynticks_stop_timer, dynticks_rearm_timer, NULL}, #ifdef __linux__ /* HPET - if available - is preferred */ - {"hpet", hpet_start_timer, hpet_stop_timer, NULL}, + {"hpet", 0, hpet_start_timer, hpet_stop_timer, NULL, NULL}, /* ...otherwise try RTC */ - {"rtc", rtc_start_timer, rtc_stop_timer, NULL}, + {"rtc", 0, rtc_start_timer, rtc_stop_timer, NULL, NULL}, #endif -#ifndef _WIN32 - {"unix", unix_start_timer, unix_stop_timer, NULL}, + {"unix", 0, unix_start_timer, unix_stop_timer, NULL, NULL}, #else - {"win32", win32_start_timer, win32_stop_timer, &alarm_win32_data}, + {"dynticks", ALARM_FLAG_DYNTICKS, win32_start_timer, + win32_stop_timer, win32_rearm_timer, &alarm_win32_data}, + {"win32", 0, win32_start_timer, + win32_stop_timer, NULL, &alarm_win32_data}, #endif {NULL, } }; @@ -949,6 +979,8 @@ void qemu_del_timer(QEMUTimer *ts) } pt = &t->next; } + + qemu_rearm_alarm_timer(alarm_timer); } /* modify the current timer so that it will be fired when current_time @@ -1008,6 +1040,7 @@ static void qemu_run_timers(QEMUTimer **ptimer_head, int64_t current_time) /* run the callback (the timer list can be modified) */ ts->cb(ts->opaque); } + qemu_rearm_alarm_timer(alarm_timer); } int64_t qemu_get_clock(QEMUClock *clock) @@ -1115,7 +1148,8 @@ static void host_alarm_handler(int host_signum) last_clock = ti; } #endif - if (qemu_timer_expired(active_timers[QEMU_TIMER_VIRTUAL], + if (alarm_has_dynticks(alarm_timer) || + qemu_timer_expired(active_timers[QEMU_TIMER_VIRTUAL], qemu_get_clock(vm_clock)) || qemu_timer_expired(active_timers[QEMU_TIMER_REALTIME], qemu_get_clock(rt_clock))) { @@ -1136,6 +1170,30 @@ static void host_alarm_handler(int host_signum) } } +static uint64_t qemu_next_deadline(void) +{ + int64_t nearest_delta_us = ULLONG_MAX; + int64_t vmdelta_us; + + if (active_timers[QEMU_TIMER_REALTIME]) + nearest_delta_us = (active_timers[QEMU_TIMER_REALTIME]->expire_time - + qemu_get_clock(rt_clock))*1000; + + if (active_timers[QEMU_TIMER_VIRTUAL]) { + /* round up */ + vmdelta_us = (active_timers[QEMU_TIMER_VIRTUAL]->expire_time - + qemu_get_clock(vm_clock)+999)/1000; + if (vmdelta_us < nearest_delta_us) + nearest_delta_us = vmdelta_us; + } + + /* Avoid arming the timer to negative, zero, or too low values */ + if (nearest_delta_us <= MIN_TIMER_REARM_US) + nearest_delta_us = MIN_TIMER_REARM_US; + + return nearest_delta_us; +} + #ifndef _WIN32 #if defined(__linux__) @@ -1243,6 +1301,80 @@ static void rtc_stop_timer(struct qemu_alarm_timer *t) #endif /* !defined(__linux__) */ +static int dynticks_start_timer(struct qemu_alarm_timer *t) +{ + struct sigevent ev; + timer_t host_timer; + struct sigaction act; + + sigfillset(&act.sa_mask); + act.sa_flags = 0; +#if defined(TARGET_I386) && defined(USE_CODE_COPY) + act.sa_flags |= SA_ONSTACK; +#endif + act.sa_handler = host_alarm_handler; + + sigaction(SIGALRM, &act, NULL); + + ev.sigev_value.sival_int = 0; + ev.sigev_notify = SIGEV_SIGNAL; + ev.sigev_signo = SIGALRM; + + if (timer_create(CLOCK_REALTIME, &ev, &host_timer)) { + perror("timer_create"); + + /* disable dynticks */ + fprintf(stderr, "Dynamic Ticks disabled\n"); + + return -1; + } + + t->priv = (void *)host_timer; + + return 0; +} + +static void dynticks_stop_timer(struct qemu_alarm_timer *t) +{ + timer_t host_timer = (timer_t)t->priv; + + timer_delete(host_timer); +} + +static void dynticks_rearm_timer(struct qemu_alarm_timer *t) +{ + timer_t host_timer = (timer_t)t->priv; + struct itimerspec timeout; + int64_t nearest_delta_us = INT64_MAX; + int64_t current_us; + + if (!active_timers[QEMU_TIMER_REALTIME] && + !active_timers[QEMU_TIMER_VIRTUAL]) + return; + + nearest_delta_us = qemu_next_deadline(); + + /* check whether a timer is already running */ + if (timer_gettime(host_timer, &timeout)) { + perror("gettime"); + fprintf(stderr, "Internal timer error: aborting\n"); + exit(1); + } + current_us = timeout.it_value.tv_sec * 1000000 + timeout.it_value.tv_nsec/1000; + if (current_us && current_us <= nearest_delta_us) + return; + + timeout.it_interval.tv_sec = 0; + timeout.it_interval.tv_nsec = 0; /* 0 for one-shot timer */ + timeout.it_value.tv_sec = nearest_delta_us / 1000000; + timeout.it_value.tv_nsec = (nearest_delta_us % 1000000) * 1000; + if (timer_settime(host_timer, 0 /* RELATIVE */, &timeout, NULL)) { + perror("settime"); + fprintf(stderr, "Internal timer error: aborting\n"); + exit(1); + } +} + static int unix_start_timer(struct qemu_alarm_timer *t) { struct sigaction act; @@ -1288,6 +1420,7 @@ static int win32_start_timer(struct qemu_alarm_timer *t) { TIMECAPS tc; struct qemu_alarm_win32 *data = t->priv; + UINT flags; data->host_alarm = CreateEvent(NULL, FALSE, FALSE, NULL); if (!data->host_alarm) { @@ -1303,11 +1436,17 @@ static int win32_start_timer(struct qemu_alarm_timer *t) timeBeginPeriod(data->period); + flags = TIME_CALLBACK_FUNCTION; + if (alarm_has_dynticks(t)) + flags |= TIME_ONESHOT; + else + flags |= TIME_PERIODIC; + data->timerId = timeSetEvent(1, // interval (ms) data->period, // resolution host_alarm_handler, // function (DWORD)t, // parameter - TIME_PERIODIC | TIME_CALLBACK_FUNCTION); + flags); if (!data->timerId) { perror("Failed to initialize win32 alarm timer"); @@ -1332,6 +1471,35 @@ static void win32_stop_timer(struct qemu_alarm_timer *t) CloseHandle(data->host_alarm); } +static void win32_rearm_timer(struct qemu_alarm_timer *t) +{ + struct qemu_alarm_win32 *data = t->priv; + uint64_t nearest_delta_us; + + if (!active_timers[QEMU_TIMER_REALTIME] && + !active_timers[QEMU_TIMER_VIRTUAL]) + return; + + nearest_delta_us = qemu_next_deadline(); + nearest_delta_us /= 1000; + + timeKillEvent(data->timerId); + + data->timerId = timeSetEvent(1, + data->period, + host_alarm_handler, + (DWORD)t, + TIME_ONESHOT | TIME_PERIODIC); + + if (!data->timerId) { + perror("Failed to re-arm win32 alarm timer"); + + timeEndPeriod(data->period); + CloseHandle(data->host_alarm); + exit(1); + } +} + #endif /* _WIN32 */ static void init_timer_alarm(void) @@ -6490,6 +6658,7 @@ void vm_start(void) cpu_enable_ticks(); vm_running = 1; vm_state_notify(1); + qemu_rearm_alarm_timer(alarm_timer); } }