@@ -22,6 +22,7 @@
#include "tcg.h"
#include "qemu/atomic.h"
#include "sysemu/qtest.h"
+#include "qemu/timer.h"
void cpu_loop_exit(CPUState *cpu)
{
@@ -209,6 +210,26 @@ static void cpu_handle_debug_exception(CPUArchState *env)
}
}
+/* Allow the guest to have a max 300us advance.
+ * The difference between the 2 clocks could therefore
+ * oscillate around 0.
+ */
+#define VM_CLOCK_ADVANCE 300000
+
+static int64_t diff_clk;
+static void delay_host(void)
+{
+ static struct timespec sleep_delay, rem_delay;
+ sleep_delay.tv_sec = diff_clk / 1000000000LL;
+ sleep_delay.tv_nsec = diff_clk % 1000000000LL;
+ if (nanosleep(&sleep_delay, &rem_delay) < 0) {
+ diff_clk -= (sleep_delay.tv_sec - rem_delay.tv_sec) * 1000000000LL;
+ diff_clk -= sleep_delay.tv_nsec - rem_delay.tv_nsec;
+ } else {
+ diff_clk = 0;
+ }
+}
+
/* main execution loop */
volatile sig_atomic_t exit_request;
@@ -227,6 +248,8 @@ int cpu_exec(CPUArchState *env)
TranslationBlock *tb;
uint8_t *tc_ptr;
uintptr_t next_tb;
+ /* Delay algorithm */
+ int64_t instr_exec_time, original_extra, original_low;
/* This must be volatile so it is not trashed by longjmp() */
volatile bool have_tb_lock = false;
@@ -283,6 +306,16 @@ int cpu_exec(CPUArchState *env)
#endif
cpu->exception_index = -1;
+ if (icount_align_option) {
+ /* Calculate difference between guest clock and host clock.
+ This delay includes the delay of the last cycle, so
+ what we have to do is sleep until it is 0. As for the
+ advance/delay we gain here, we try to fix it next time. */
+ diff_clk = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
+ qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
+ original_extra = cpu->icount_extra;
+ original_low = cpu->icount_decr.u16.low;
+ }
/* prepare setjmp context for exception handling */
for(;;) {
if (sigsetjmp(cpu->jmp_env, 0) == 0) {
@@ -600,6 +633,11 @@ int cpu_exec(CPUArchState *env)
}
}
if (unlikely(cpu->exit_request)) {
+ if (icount_align_option) {
+ if (diff_clk > VM_CLOCK_ADVANCE) {
+ delay_host();
+ }
+ }
cpu->exit_request = 0;
cpu->exception_index = EXCP_INTERRUPT;
cpu_loop_exit(cpu);
@@ -668,10 +706,26 @@ int cpu_exec(CPUArchState *env)
}
cpu->icount_extra -= insns_left;
cpu->icount_decr.u16.low = insns_left;
+ if (icount_align_option) {
+ /* Instruction counters have been
+ updated so we update ours */
+ original_extra = cpu->icount_extra;
+ original_low = cpu->icount_decr.u16.low;
+ }
} else {
if (insns_left > 0) {
/* Execute remaining instructions. */
cpu_exec_nocache(env, insns_left, tb);
+ if (icount_align_option) {
+ instr_exec_time = original_low -
+ cpu->icount_decr.u16.low;
+ instr_exec_time = instr_exec_time <<
+ icount_time_shift;
+ diff_clk += instr_exec_time;
+ if (diff_clk > VM_CLOCK_ADVANCE) {
+ delay_host();
+ }
+ }
}
cpu->exception_index = EXCP_INTERRUPT;
next_tb = 0;
@@ -679,11 +733,30 @@ int cpu_exec(CPUArchState *env)
}
break;
}
+ case 1:
+ case 0:
+ if (icount_align_option) {
+ instr_exec_time = original_extra -
+ cpu->icount_extra +
+ original_low -
+ cpu->icount_decr.u16.low;
+ instr_exec_time = instr_exec_time <<
+ icount_time_shift;
+ diff_clk += instr_exec_time;
+ original_extra = cpu->icount_extra;
+ original_low = cpu->icount_decr.u16.low;
+ }
+ break;
default:
break;
}
}
cpu->current_tb = NULL;
+ if (icount_align_option) {
+ if (diff_clk > VM_CLOCK_ADVANCE) {
+ delay_host();
+ }
+ }
/* reset soft MMU for next block (it can currently
only be set by a memory fault) */
} /* for(;;) */