From nobody Wed Aug 26 15:53:29 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.xenproject.org designates 192.237.175.120 as permitted sender) client-ip=192.237.175.120; envelope-from=xen-devel-bounces@lists.xenproject.org; helo=lists.xenproject.org; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of lists.xenproject.org designates 192.237.175.120 as permitted sender) smtp.mailfrom=xen-devel-bounces@lists.xenproject.org; dmarc=pass(p=none dis=none) header.from=infradead.org ARC-Seal: i=1; a=rsa-sha256; t=1783113831; cv=none; d=zohomail.com; s=zohoarc; b=EDcYyhEEuant4fT0wIH2Uekj4RvO8sgaHKjlFxwVUb1yqpmjIMwexLCJ8nVUgFQOIlE401YBoYBbF0aQ2RM3F4Fv0TP2be3VWpvO7EYJeuc7yTJc/VL2o7tkqnmE2130mWTV9nDLIUD3acDpEFph1k6LmugSNAUWxjnlGi14h1c= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783113831; h=Content-Type:Content-Transfer-Encoding:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To:Cc; bh=MSS/L0DlzIiHi7Bw2B047mUTxdF1yD6n9Esd4lrjANw=; b=ls7mveoHc7+ICOEhpT+an6KJSDCfWF+tHNpBCTd4gp7zUTYsd/+NeFbAWDGfWfvY6X9klYDcdUVr4pQaxPQhgYgSNPQyHgydN52U5QbldYImJn4w9y7U6xsWk39aEm7uC39FoO08dotr6/nf9YrLSmfueTdyY1wVyqRC0n0Dl64= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of lists.xenproject.org designates 192.237.175.120 as permitted sender) smtp.mailfrom=xen-devel-bounces@lists.xenproject.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.xenproject.org (lists.xenproject.org [192.237.175.120]) by mx.zohomail.com with SMTPS id 178311383177689.28777516888238; Fri, 3 Jul 2026 14:23:51 -0700 (PDT) Received: from list by lists.xenproject.org with outflank-mailman.1353876.1609596 (Exim 4.92) (envelope-from ) id 1wflM6-0004Ul-Py; Fri, 03 Jul 2026 21:23:26 +0000 Received: by outflank-mailman (output) from mailman id 1353876.1609596; Fri, 03 Jul 2026 21:23:26 +0000 Received: from localhost ([127.0.0.1] helo=lists.xenproject.org) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1wflM6-0004UL-K2; Fri, 03 Jul 2026 21:23:26 +0000 Received: by outflank-mailman (input) for mailman id 1353876; Fri, 03 Jul 2026 21:23:25 +0000 Received: from mx.expurgate.net ([194.145.224.20]) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1wflM4-0004QI-El for xen-devel@lists.xenproject.org; Fri, 03 Jul 2026 21:23:25 +0000 Received: from mx.expurgate.net (helo=localhost) by mx.expurgate.net with esmtp id 1wflM3-00BXKf-Rt; Fri, 03 Jul 2026 23:23:23 +0200 Received: from [10.42.69.9] (helo=localhost) by localhost with ESMTP (eXpurgate MTA 0.9.1) (envelope-from ) id 6a4827dc-2eae-0a2a0a5409dd-0a2a4509cd18-38 for ; Fri, 03 Jul 2026 23:23:23 +0200 Received: from [90.155.92.199] (helo=desiato.infradead.org) by tlsNG-bad1c0.mxtls.expurgate.net with ESMTPS (eXpurgate 4.57.1) (envelope-from ) id 6a48284a-97e6-0a2a45090019-5a9b5cc7a69c-3 for ; Fri, 03 Jul 2026 23:23:23 +0200 Received: from [2001:8b0:10b:1::425] (helo=i7.infradead.org) by desiato.infradead.org with esmtpsa (Exim 4.99.2 #2 (Red Hat Linux)) id 1wflKe-000000059OD-0yjS; Fri, 03 Jul 2026 21:22:53 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.99.2 #2 (Red Hat Linux)) id 1wflKX-00000001RPs-2B0E; Fri, 03 Jul 2026 22:21:49 +0100 X-Outflank-Mailman: Message body and most headers restored to incoming version X-BeenThere: xen-devel@lists.xenproject.org List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Errors-To: xen-devel-bounces@lists.xenproject.org Precedence: list Authentication-Results: eu.smtp.expurgate.cloud; dkim=pass header.s=desiato.20200630 header.d=infradead.org header.i="@infradead.org" header.h="Sender:Content-Transfer-Encoding:Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:To:From" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:To: From:Reply-To:Cc:Content-ID:Content-Description; bh=MSS/L0DlzIiHi7Bw2B047mUTxdF1yD6n9Esd4lrjANw=; b=iU6SK+6HlatYeQ2gl9tGwyLkqi sAlSNaCj1PI5JPhBJmRbg/ZFhceYepOCKk0FhLL4jYcu0Ca1VR2EtHfZyR4QbM6Nkewx7oXq5ElzM SEIB8TwnLNxYKX69h8tecrdlNIfS4rDY4Bwhm5882oYca8mbjNYb3jUkg7/HSWCpmyYBUBFyQOOi3 tcR/CDUHlwPBWqbHVVgaWWgQRlA6+jwPo9gNJ9sC8Gj+x8HkO25EeY7RimOfYveAQMCdQmD5iWBYI UP7CHZJJi5EKQaHg39iDBGsOfy3hGBLeHuhNl9ZJkgXvFeo9mpsYJEgU9DxfO7FEEbwn5yRsKKDgf yzRGECYw==; From: David Woodhouse To: Paolo Bonzini , Jonathan Corbet , Shuah Khan , Sean Christopherson , Thomas Gleixner , Ingo Molnar , Borislav Petkov , Dave Hansen , x86@kernel.org, "H. Peter Anvin" , Vitaly Kuznetsov , Juergen Gross , Boris Ostrovsky , David Woodhouse , Paul Durrant , Jonathan Cameron , Sascha Bischoff , Marc Zyngier , Joey Gouly , Jack Allister , Dongli Zhang , joe.jin@oracle.com, kvm@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, xen-devel@lists.xenproject.org, linux-kselftest@vger.kernel.org Subject: [PATCH v6 31/36] KVM: selftests: Add Xen runstate migration test Date: Fri, 3 Jul 2026 22:18:10 +0100 Message-ID: <20260703212145.343527-32-dwmw2@infradead.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703212145.343527-1-dwmw2@infradead.org> References: <20260703212145.343527-1-dwmw2@infradead.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Sender: David Woodhouse X-SRS-Rewrite: SMTP reverse-path rewritten from by desiato.infradead.org. See http://www.infradead.org/rpr.html X-purgate-ID: tlsNG-bad1c0/1783113803-47731986-BAA3D1A4/0/0 X-purgate-type: clean X-purgate-size: 8510 X-ZohoMail-DKIM: pass (identity @infradead.org) X-ZM-MESSAGEID: 1783113832968158500 From: David Woodhouse Test that Xen runstate (steal time) is correctly accounted across a simulated live migration using KVM_XEN_VCPU_ATTR and KVM_[GS]ET_CLOCK_GUEST. The test simulates what a real VMM does during migration: 1. Creates a VM with Xen HVM config and runstate tracking 2. Runs the guest to accumulate some kvmclock time 3. Saves clock (KVM_GET_CLOCK_GUEST), TSC offset, and runstate 4. Marks the saved state as RUNSTATE_runnable (vCPU not running) 5. Destroys the source VM 6. Sleeps 10ms (simulating migration network transfer time) 7. Creates a new VM and restores all state precisely as saved 8. Runs the guest and verifies the migration gap appears as steal The kernel accounts the gap because: on vcpu_load, it transitions from RUNSTATE_runnable to RUNSTATE_running, computing delta =3D kvmclock_now - state_entry_time. Since kvmclock has advanced past the saved entry time (real time elapsed during migration), the delta is added to time_runnable. Signed-off-by: David Woodhouse --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/x86/xen_migration_test.c | 194 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86/xen_migration_test.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selft= ests/kvm/Makefile.kvm index 58aac2980cdf..f155683bd7f8 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -142,6 +142,7 @@ TEST_GEN_PROGS_x86 +=3D x86/debug_regs TEST_GEN_PROGS_x86 +=3D x86/tsc_msrs_test TEST_GEN_PROGS_x86 +=3D x86/vmx_pmu_caps_test TEST_GEN_PROGS_x86 +=3D x86/xen_cpuid_timing_test +TEST_GEN_PROGS_x86 +=3D x86/xen_migration_test TEST_GEN_PROGS_x86 +=3D x86/xen_shinfo_test TEST_GEN_PROGS_x86 +=3D x86/xen_vmcall_test TEST_GEN_PROGS_x86 +=3D x86/sev_init2_tests diff --git a/tools/testing/selftests/kvm/x86/xen_migration_test.c b/tools/t= esting/selftests/kvm/x86/xen_migration_test.c new file mode 100644 index 000000000000..4a041d36f1b9 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/xen_migration_test.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Test Xen runstate (steal time) preservation across simulated migration. + * + * Verifies that the kernel correctly accounts the migration gap as + * steal time (runnable) when runstate data is saved and restored + * precisely, but real time elapses during the migration. + * + * The key insight: userspace saves the runstate with state=3DRUNSTATE_run= nable + * (the vCPU is not running during migration). On restore, the kernel sees + * that kvmclock has advanced past state_entry_time, and accounts the + * difference as time spent in the runnable state. + */ +#include +#include +#include +#include +#include + +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" + +#include + +#define SHINFO_GPA 0xc0000000ULL +#define RUNSTATE_GPA (SHINFO_GPA + 0x1000) + +#define RUNSTATE_running 0 +#define RUNSTATE_runnable 1 +#define RUNSTATE_blocked 2 +#define RUNSTATE_offline 3 + +struct vcpu_runstate_info { + uint32_t state; + uint64_t state_entry_time; + uint64_t time[4]; +} __attribute__((packed)); + +static void guest_code(void) +{ + volatile struct vcpu_runstate_info *rs =3D + (void *)(unsigned long)RUNSTATE_GPA; + + /* Report runstate times =E2=80=94 no need to enable kvmclock MSR, + * the kernel writes runstate using its internal kvmclock. */ + GUEST_SYNC_ARGS(0, rs->time[RUNSTATE_runnable], + rs->time[RUNSTATE_running], 0, 0); +} + +static struct kvm_vm *create_xen_vm(struct kvm_vcpu **vcpu) +{ + struct kvm_vm *vm; + int xen_caps; + + vm =3D vm_create_with_one_vcpu(vcpu, guest_code); + + xen_caps =3D kvm_check_cap(KVM_CAP_XEN_HVM); + TEST_REQUIRE(xen_caps & KVM_XEN_HVM_CONFIG_SHARED_INFO); + TEST_REQUIRE(xen_caps & KVM_XEN_HVM_CONFIG_RUNSTATE); + + /* Map pages */ + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, + SHINFO_GPA, 1, 2, 0); + virt_map(vm, SHINFO_GPA, SHINFO_GPA, 2); + + /* Enable Xen HVM with MSR interception (enables runstate tracking) */ + struct kvm_xen_hvm_config cfg =3D { + .flags =3D KVM_XEN_HVM_CONFIG_INTERCEPT_HCALL, + .msr =3D 0x40000000, + }; + vm_ioctl(vm, KVM_XEN_HVM_CONFIG, &cfg); + + /* Set shared_info */ + struct kvm_xen_hvm_attr ha =3D { + .type =3D KVM_XEN_ATTR_TYPE_SHARED_INFO, + .u.shared_info.gfn =3D SHINFO_GPA >> 12, + }; + vm_ioctl(vm, KVM_XEN_HVM_SET_ATTR, &ha); + + /* Set runstate address */ + struct kvm_xen_vcpu_attr rs_addr =3D { + .type =3D KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_ADDR, + .u.gpa =3D RUNSTATE_GPA, + }; + vcpu_ioctl(*vcpu, KVM_XEN_VCPU_SET_ATTR, &rs_addr); + + return vm; +} + +int main(void) +{ + struct pvclock_vcpu_time_info pvti; + struct kvm_xen_vcpu_attr runstate_save; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + uint64_t tsc_offset; + int ret; + + /* =3D=3D=3D SOURCE SIDE =3D=3D=3D */ + pr_info("=3D=3D=3D Source: create VM and run guest =3D=3D=3D\n"); + vm =3D create_xen_vm(&vcpu); + + /* Run guest once to accumulate some runstate time */ + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC); + + pr_info(" Guest sees: runnable=3D%" PRIu64 " running=3D%" PRIu64 "\n", + uc.args[2], uc.args[3]); + + /* Save clock state */ + ret =3D __vcpu_ioctl(vcpu, KVM_GET_CLOCK_GUEST, &pvti); + TEST_ASSERT(!ret, "KVM_GET_CLOCK_GUEST failed"); + + /* Save TSC offset */ + tsc_offset =3D vcpu_get_msr(vcpu, MSR_IA32_TSC_ADJUST); + + /* Save runstate =E2=80=94 the vCPU is now "runnable" (not running) */ + runstate_save.type =3D KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_DATA; + vcpu_ioctl(vcpu, KVM_XEN_VCPU_GET_ATTR, &runstate_save); + + /* + * Transition to runnable state before saving =E2=80=94 the vCPU is + * not running during migration. + */ + runstate_save.u.runstate.state =3D RUNSTATE_runnable; + + pr_info(" Saved runstate: running=3D%" PRIu64 " runnable=3D%" PRIu64 + " entry=3D%" PRIu64 "\n", + (uint64_t)runstate_save.u.runstate.time_running, + (uint64_t)runstate_save.u.runstate.time_runnable, + (uint64_t)runstate_save.u.runstate.state_entry_time); + + uint64_t saved_runnable =3D runstate_save.u.runstate.time_runnable; + + kvm_vm_free(vm); + + /* =3D=3D=3D MIGRATION GAP =3D=3D=3D */ + pr_info("=3D=3D=3D Simulating migration (sleeping 10ms) =3D=3D=3D\n"); + usleep(10000); + + /* =3D=3D=3D DESTINATION SIDE =3D=3D=3D */ + pr_info("=3D=3D=3D Destination: create new VM and restore =3D=3D=3D\n"); + vm =3D create_xen_vm(&vcpu); + + /* Restore TSC offset */ + vcpu_set_msr(vcpu, MSR_IA32_TSC_ADJUST, tsc_offset); + + /* Restore clock =E2=80=94 kvmclock will now be ~10ms ahead of the snapsh= ot */ + vcpu_ioctl(vcpu, KVM_SET_CLOCK_GUEST, &pvti); + + /* Restore runstate exactly as saved (state=3Drunnable) */ + runstate_save.type =3D KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_DATA; + ret =3D __vcpu_ioctl(vcpu, KVM_XEN_VCPU_SET_ATTR, &runstate_save); + TEST_ASSERT(!ret, "Restore runstate failed: errno %d", errno); + + /* + * Run the guest. When the vCPU enters vcpu_run, the kernel + * transitions from RUNSTATE_runnable to RUNSTATE_running. + * It computes: delta =3D kvmclock_now - state_entry_time + * This delta (which includes the migration gap) is added to + * time_runnable (steal time). + */ + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC); + + uint64_t guest_runnable =3D uc.args[2]; + uint64_t guest_running =3D uc.args[3]; + + pr_info(" Guest sees: runnable=3D%" PRIu64 " running=3D%" PRIu64 "\n", + guest_runnable, guest_running); + + uint64_t steal_increase =3D guest_runnable - saved_runnable; + pr_info(" Steal time increase: %" PRIu64 " ns (migration gap)\n", + steal_increase); + + /* + * The steal time increase should be at least 10ms (the sleep) + * but not more than 5s (allowing for VM creation overhead). + * The actual gap is from the source's state_entry_time to the + * destination's kvmclock "now" at vcpu_load time. + */ + TEST_ASSERT(steal_increase >=3D 10000000ULL && + steal_increase < 5000000000ULL, + "Steal time increase %" PRIu64 " ns not in expected range " + "[10ms, 5s]", steal_increase); + + kvm_vm_free(vm); + pr_info("PASS: Migration gap correctly accounted as steal time\n"); + return 0; +} --=20 2.54.0