A CPU reset from cpu_exec() clears the remaining icount budget,
causing it to be counted as executed and advancing virtual time.

x86 INIT exposes this as a large TSC jump during AP startup.

Account executed instructions before reset and discard the unused
budget. Add an x86 qtest covering INIT/SIPI through x2APIC.

AI-used-for: qtest reproducer
Fixes: e4cd96571f00 ("cpus: don't credit executed instructions before they have 
run")
Signed-off-by: Philippe Mathieu-Daudé <[email protected]>
---
 accel/tcg/icount-common.c     |  10 +++
 tests/qtest/icount-x86-test.c | 119 ++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build       |   2 +
 3 files changed, 131 insertions(+)
 create mode 100644 tests/qtest/icount-x86-test.c

diff --git a/accel/tcg/icount-common.c b/accel/tcg/icount-common.c
index 4c8ceafc112b..45aadf1416d9 100644
--- a/accel/tcg/icount-common.c
+++ b/accel/tcg/icount-common.c
@@ -106,6 +106,16 @@ void icount_update(CPUState *cpu)
 
 void icount_cpu_reset(CPUState *cpu)
 {
+    if (qemu_cpu_is_self(cpu)
+            && qatomic_read(&cpu->running)
+            && cpu->icount_budget) {
+        /*
+         * Account the instructions executed so far and discard the
+         * unused part of the current budget before the counters are cleared.
+         */
+        icount_update(cpu);
+        cpu->icount_budget = 0;
+    }
     cpu->icount_extra = 0;
     qatomic_set(&cpu->neg.icount_decr.u32, 0);
 }
diff --git a/tests/qtest/icount-x86-test.c b/tests/qtest/icount-x86-test.c
new file mode 100644
index 000000000000..1138a7535e7c
--- /dev/null
+++ b/tests/qtest/icount-x86-test.c
@@ -0,0 +1,119 @@
+/*
+ * QTest testcase for TCG instruction counting.
+ *
+ * Copyright Qualcomm Innovation Center, Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define AP_VECTOR       0x01
+#define DONE_ADDR       0x50c
+#define TSC0_ADDR       0x504
+#define TSC1_ADDR       0x508
+#define ITER_ADDR       0x510
+#define AP_FLAG_ADDR    0x600
+
+/*
+ * The BSP reads the TSC, sends INIT and SIPI to the AP and spins until
+ * the AP sets a flag, counting iterations.  The AP then halts.
+ */
+static const uint8_t x86_init_sector[] = {
+    /* mov $0, %ax; mov %ax, %ds */
+    0xb8, 0x00, 0x00, 0x8e, 0xd8,
+    /* AP code at AP_VECTOR << 12: movb $1, flag; cli; 1: hlt; jmp 1b */
+    0xc7, 0x06, 0x00, 0x10, 0xc6, 0x06,
+    0xc7, 0x06, 0x02, 0x10, AP_FLAG_ADDR & 0xff, AP_FLAG_ADDR >> 8,
+    0xc7, 0x06, 0x04, 0x10, 0x01, 0xfa,
+    0xc7, 0x06, 0x06, 0x10, 0xf4, 0xeb,
+    0xc6, 0x06, 0x08, 0x10, 0xfd,
+    /* mov $MSR_IA32_APICBASE, %ecx; rdmsr; or $EXTD, %eax; wrmsr */
+    0x66, 0xb9, 0x1b, 0x00, 0x00, 0x00, 0x0f, 0x32,
+    0x66, 0x0d, 0x00, 0x04, 0x00, 0x00, 0x0f, 0x30,
+    /* xor %esi, %esi; rdtsc; mov %eax, tsc0 */
+    0x66, 0x31, 0xf6,
+    0x0f, 0x31, 0x66, 0xa3, TSC0_ADDR & 0xff, TSC0_ADDR >> 8,
+    /* mov $ICR, %ecx; mov $1, %edx; INIT, then SIPI */
+    0x66, 0xb9, 0x30, 0x08, 0x00, 0x00,
+    0x66, 0xba, 0x01, 0x00, 0x00, 0x00,
+    0x66, 0xb8, 0x00, 0x45, 0x00, 0x00, 0x0f, 0x30,
+    0x66, 0xb8, AP_VECTOR, 0x46, 0x00, 0x00, 0x0f, 0x30,
+    /* 1: inc %esi; cmpb $0, flag; je 1b: three instructions */
+    0x66, 0x46,
+    0x80, 0x3e, AP_FLAG_ADDR & 0xff, AP_FLAG_ADDR >> 8, 0x00,
+    0x74, 0xf7,
+    /* rdtsc; mov %eax, tsc1; mov %esi, iterations; done = 1 */
+    0x0f, 0x31, 0x66, 0xa3, TSC1_ADDR & 0xff, TSC1_ADDR >> 8,
+    0x66, 0x89, 0x36, ITER_ADDR & 0xff, ITER_ADDR >> 8,
+    0xc6, 0x06, DONE_ADDR & 0xff, DONE_ADDR >> 8, 0x01,
+    /* cli; 1: hlt; jmp 1b */
+    0xfa, 0xf4, 0xeb, 0xfd,
+};
+
+static char *write_boot_disk(void)
+{
+    size_t image_size = 0x7e000;
+    g_autofree uint8_t *image = g_malloc0(image_size);
+    char *path = g_strdup("/tmp/qemu-icount-XXXXXX");
+    ssize_t written;
+    int fd;
+
+    memcpy(image, x86_init_sector, sizeof(x86_init_sector));
+    image[0x1fe] = 0x55;
+    image[0x1ff] = 0xaa;
+
+    fd = mkstemp(path);
+    g_assert_cmpint(fd, >=, 0);
+    written = write(fd, image, image_size);
+    close(fd);
+    g_assert_cmpint(written, ==, image_size);
+    return path;
+}
+
+static void wait_for_done(QTestState *qts)
+{
+    for (unsigned i = 0; i < 5000; i++) {
+        if (qtest_readb(qts, DONE_ADDR)) {
+            return;
+        }
+        g_usleep(1000);
+    }
+    g_assert_not_reached();
+}
+
+static void test_x86_pc_x2apic_fdc(void)
+{
+    g_autofree char *disk = write_boot_disk();
+    QTestState *qts;
+    uint32_t iterations;
+    uint32_t delta;
+
+    qts = qtest_initf("-M pc -m 16M -smp 2 -accel tcg -icount shift=0 "
+                      "-cpu max,x2apic=on,tsc-frequency=1000000000 "
+                      "-drive if=floppy,format=raw,file=%s", disk);
+    wait_for_done(qts);
+
+    delta = qtest_readl(qts, TSC1_ADDR) - qtest_readl(qts, TSC0_ADDR);
+    iterations = qtest_readl(qts, ITER_ADDR);
+    g_test_message("TSC delta %u, %u iterations", delta, iterations);
+    g_assert_cmpuint(iterations, >, 0);
+    g_assert_cmpuint(delta, >=, 3 * iterations);
+    g_assert_cmpuint(delta, <=, 3 * iterations + 32);
+
+    qtest_quit(qts);
+    unlink(disk);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    if (qtest_has_accel("tcg") && qtest_has_machine("pc")) {
+        qtest_add_func("/icount/x86-pc-x2apic-fdc",
+                       test_x86_pc_x2apic_fdc);
+    }
+
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 46685d6d46fd..d7ae09313e32 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -57,6 +57,8 @@ qtests_filter = \
   (get_option('default_devices') and host_os != 'windows' ? 
['test-filter-redirector'] : [])
 
 qtests_i386 = \
+  (config_all_devices.has_key('CONFIG_I440FX') and
+   config_all_devices.has_key('CONFIG_FDC_ISA') ? ['icount-x86-test'] : []) +  
                \
   (slirp.found() ? ['pxe-test'] : []) + \
   qtests_filter + \
   (config_all_devices.has_key('CONFIG_ACPI_VMGENID') ? ['vmgenid-test'] : []) 
+             \
-- 
2.55.0


Reply via email to