From: Ackerley Tng <ackerley...@google.com>

Adds a selftest to verify interrupts sent to a TDX VM before migration
are successfully handled by the migrated VM.

Co-developed-by: Ryan Afranji <afra...@google.com>
Signed-off-by: Ryan Afranji <afra...@google.com>
Signed-off-by: Ackerley Tng <ackerley...@google.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../testing/selftests/kvm/include/kvm_util.h  |   4 +
 .../selftests/kvm/include/x86/tdx/tdx_util.h  |   2 +
 .../selftests/kvm/include/x86/tdx/test_util.h |   5 +
 tools/testing/selftests/kvm/lib/kvm_util.c    |  35 ++-
 .../selftests/kvm/lib/x86/tdx/tdx_util.c      |  20 ++
 .../selftests/kvm/lib/x86/tdx/test_util.c     |  17 ++
 .../kvm/x86/tdx_irqfd_migrate_test.c          | 264 ++++++++++++++++++
 .../selftests/kvm/x86/tdx_migrate_tests.c     |  21 --
 9 files changed, 343 insertions(+), 26 deletions(-)
 create mode 100644 tools/testing/selftests/kvm/x86/tdx_irqfd_migrate_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm 
b/tools/testing/selftests/kvm/Makefile.kvm
index d4c8cfb5910f..4ae0d105c2a7 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -156,6 +156,7 @@ TEST_GEN_PROGS_x86 += x86/tdx_vm_test
 TEST_GEN_PROGS_x86 += x86/tdx_shared_mem_test
 TEST_GEN_PROGS_x86 += x86/tdx_upm_test
 TEST_GEN_PROGS_x86 += x86/tdx_migrate_tests
+TEST_GEN_PROGS_x86 += x86/tdx_irqfd_migrate_test
 
 # Compiled outputs used by test targets
 TEST_GEN_PROGS_EXTENDED_x86 += x86/nx_huge_pages_test
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h 
b/tools/testing/selftests/kvm/include/kvm_util.h
index 8b252a668c78..f93ac2b9b0ff 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -80,6 +80,7 @@ enum kvm_mem_region_type {
        MEM_REGION_PT,
        MEM_REGION_TEST_DATA,
        MEM_REGION_TDX_BOOT_PARAMS,
+       MEM_REGION_TDX_SHARED_DATA,
        MEM_REGION_UCALL,
        NR_MEM_REGIONS,
 };
@@ -958,6 +959,9 @@ int _kvm_irq_line(struct kvm_vm *vm, uint32_t irq, int 
level);
 struct kvm_irq_routing *kvm_gsi_routing_create(void);
 void kvm_gsi_routing_irqchip_add(struct kvm_irq_routing *routing,
                uint32_t gsi, uint32_t pin);
+void kvm_gsi_routing_msi_add(struct kvm_irq_routing *routing, uint32_t gsi,
+                            uint32_t address_lo, uint32_t address_hi,
+                            uint32_t data);
 int _kvm_gsi_routing_write(struct kvm_vm *vm, struct kvm_irq_routing *routing);
 void kvm_gsi_routing_write(struct kvm_vm *vm, struct kvm_irq_routing *routing);
 
diff --git a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h 
b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h
index 9b495e621225..4393c8649718 100644
--- a/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h
+++ b/tools/testing/selftests/kvm/include/x86/tdx/tdx_util.h
@@ -10,6 +10,8 @@ extern uint64_t tdx_s_bit;
 void tdx_filter_cpuid(struct kvm_vm *vm, struct kvm_cpuid2 *cpuid_data);
 void __tdx_mask_cpuid_features(struct kvm_cpuid_entry2 *entry);
 void tdx_enable_capabilities(struct kvm_vm *vm);
+int __tdx_migrate_from(int dst_fd, int src_fd);
+void tdx_migrate_from(struct kvm_vm *dst_vm, struct kvm_vm *src_vm);
 
 struct kvm_vcpu *td_vcpu_add(struct kvm_vm *vm, uint32_t vcpu_id, void 
*guest_code);
 
diff --git a/tools/testing/selftests/kvm/include/x86/tdx/test_util.h 
b/tools/testing/selftests/kvm/include/x86/tdx/test_util.h
index 3330d5a54698..0dd859974cb3 100644
--- a/tools/testing/selftests/kvm/include/x86/tdx/test_util.h
+++ b/tools/testing/selftests/kvm/include/x86/tdx/test_util.h
@@ -130,4 +130,9 @@ uint64_t tdx_test_read_64bit(struct kvm_vcpu *vcpu, 
uint64_t port);
  */
 uint64_t tdx_test_read_64bit_report_from_guest(struct kvm_vcpu *vcpu);
 
+/*
+ * Enables X2APIC for TDX guests.
+ */
+void tdx_guest_x2apic_enable(void);
+
 #endif // SELFTEST_TDX_TEST_UTIL_H
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c 
b/tools/testing/selftests/kvm/lib/kvm_util.c
index 9dc3c7bf0443..bbb489635064 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -1293,10 +1293,12 @@ static void vm_migrate_mem_region(struct kvm_vm 
*dst_vm, struct kvm_vm *src_vm,
                                  struct userspace_mem_region *src_region)
 {
        struct userspace_mem_region *dst_region;
-       int dst_guest_memfd;
+       int dst_guest_memfd = -1;
 
-       dst_guest_memfd =
-               vm_link_guest_memfd(dst_vm, src_region->region.guest_memfd, 0);
+       if (src_region->region.guest_memfd != -1)
+               dst_guest_memfd = vm_link_guest_memfd(dst_vm,
+                                                     
src_region->region.guest_memfd,
+                                                     0);
 
        dst_region = vm_mem_region_alloc(
                        dst_vm, src_region->region.guest_phys_addr,
@@ -1312,8 +1314,12 @@ static void vm_migrate_mem_region(struct kvm_vm *dst_vm, 
struct kvm_vm *src_vm,
        src_region->host_mem = 0;
 
        dst_region->region.guest_memfd = dst_guest_memfd;
-       dst_region->region.guest_memfd_offset =
-               src_region->region.guest_memfd_offset;
+       if (src_region->region.guest_memfd == -1) {
+               dst_region->fd = src_region->fd;
+       } else {
+               dst_region->region.guest_memfd_offset =
+                       src_region->region.guest_memfd_offset;
+       }
 
        userspace_mem_region_commit(dst_vm, dst_region);
 }
@@ -2057,6 +2063,25 @@ void kvm_gsi_routing_irqchip_add(struct kvm_irq_routing 
*routing,
        routing->nr++;
 }
 
+void kvm_gsi_routing_msi_add(struct kvm_irq_routing *routing, uint32_t gsi,
+                            uint32_t address_lo, uint32_t address_hi,
+                            uint32_t data)
+{
+       int i;
+
+       assert(routing);
+       assert(routing->nr < KVM_MAX_IRQ_ROUTES);
+
+       i = routing->nr;
+       routing->entries[i].gsi = gsi;
+       routing->entries[i].type = KVM_IRQ_ROUTING_MSI;
+       routing->entries[i].flags = 0;
+       routing->entries[i].u.msi.address_lo = address_lo;
+       routing->entries[i].u.msi.address_hi = address_hi;
+       routing->entries[i].u.msi.data = data;
+       routing->nr++;
+}
+
 int _kvm_gsi_routing_write(struct kvm_vm *vm, struct kvm_irq_routing *routing)
 {
        int ret;
diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c 
b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c
index a3612bf187a0..8216a778474a 100644
--- a/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c
+++ b/tools/testing/selftests/kvm/lib/x86/tdx/tdx_util.c
@@ -372,6 +372,26 @@ static void tdx_apply_cr4_restrictions(struct kvm_sregs 
*sregs)
        sregs->cr4 &= ~(X86_CR4_VMXE | X86_CR4_SMXE);
 }
 
+int __tdx_migrate_from(int dst_fd, int src_fd)
+{
+       struct kvm_enable_cap cap = {
+               .cap = KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM,
+               .args = { src_fd }
+       };
+
+       return ioctl(dst_fd, KVM_ENABLE_CAP, &cap);
+}
+
+void tdx_migrate_from(struct kvm_vm *dst_vm, struct kvm_vm *src_vm)
+{
+       int ret;
+
+       vm_migrate_mem_regions(dst_vm, src_vm);
+       ret = __tdx_migrate_from(dst_vm->fd, src_vm->fd);
+       TEST_ASSERT(!ret, "Migration failed, ret: %d, errno: %d\n", ret, errno);
+       src_vm->enc_migrated = true;
+}
+
 static void load_td_boot_code(struct kvm_vm *vm)
 {
        void *boot_code_hva = addr_gpa2hva(vm, FOUR_GIGABYTES_GPA - 
TD_BOOT_CODE_SIZE);
diff --git a/tools/testing/selftests/kvm/lib/x86/tdx/test_util.c 
b/tools/testing/selftests/kvm/lib/x86/tdx/test_util.c
index f92ddda2d1ac..7b622ccb2433 100644
--- a/tools/testing/selftests/kvm/lib/x86/tdx/test_util.c
+++ b/tools/testing/selftests/kvm/lib/x86/tdx/test_util.c
@@ -6,6 +6,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include "apic.h"
 #include "kvm_util.h"
 #include "tdx/tdcall.h"
 #include "tdx/tdx.h"
@@ -185,3 +186,19 @@ uint64_t tdx_test_read_64bit_report_from_guest(struct 
kvm_vcpu *vcpu)
 {
        return tdx_test_read_64bit(vcpu, TDX_TEST_REPORT_PORT);
 }
+
+void tdx_guest_x2apic_enable(void)
+{
+       uint64_t x2apic_spiv = APIC_BASE_MSR + (APIC_SPIV >> 4);
+       uint64_t value, ret;
+
+       /*
+        * x2apic does not have to be enabled for TDs, TDs already have x2apic
+        * enabled, and must use x2apic. Hence, we just soft-enable APIC.
+        */
+       ret = tdg_vp_vmcall_instruction_rdmsr(x2apic_spiv, &value);
+       GUEST_ASSERT_EQ(ret, 0);
+       ret = tdg_vp_vmcall_instruction_wrmsr(x2apic_spiv,
+                                             value | APIC_SPIV_APIC_ENABLED);
+       GUEST_ASSERT_EQ(ret, 0);
+}
diff --git a/tools/testing/selftests/kvm/x86/tdx_irqfd_migrate_test.c 
b/tools/testing/selftests/kvm/x86/tdx_irqfd_migrate_test.c
new file mode 100644
index 000000000000..d80cc204bd67
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/tdx_irqfd_migrate_test.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <stdint.h>
+#include <stdio.h>
+#include <linux/kvm.h>
+#include <string.h>
+#include <sys/eventfd.h>
+
+#include "apic.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "tdx/tdcall.h"
+#include "tdx/tdx.h"
+#include "tdx/tdx_util.h"
+#include "tdx/test_util.h"
+#include "test_util.h"
+#include "ucall_common.h"
+
+#define TEST_IRQ_PIN 24
+
+#define NUM_INTERRUPTS 256
+#define INTERRUPT_COUNT_GPA 0x100000000ULL
+#define INTERRUPT_COUNT_MEMSLOT 5
+
+#define MIGRATION_LOOPS 10
+
+static uint32_t (*interrupt_count_per_vector)[NUM_INTERRUPTS];
+
+static void interrupt_handler_increment_count(struct ex_regs *regs)
+{
+       (*interrupt_count_per_vector)[regs->vector]++;
+       x2apic_write_reg(APIC_EOI, 0);
+}
+
+static void guest_code(void)
+{
+       uint32_t sync_count = 0;
+
+       tdx_guest_x2apic_enable();
+
+       /* Enable interrupts which are disabled by default. */
+       asm volatile("sti");
+
+       /* Keep guest runnable by continuously looping. */
+       while (true)
+               GUEST_SYNC(++sync_count);
+}
+
+/**
+ * gsi_route_add - Used to add a GSI route.
+ *
+ * @msi_redir_hint: Look up "Message Address Register Format" in Intel SDM
+ * @dest_mode: Look up "Message Address Register Format" in Intel SDM
+ *             Use false for DM=0 and true for DM=1
+ * @trig_mode: Look up "Message Data Register Format" in Intel SDM
+ *             Use false for edge sensitive and true for level sensitive
+ * @delivery_mode: A 3 bit code: look up "Message Data Register Format"
+ *
+ * Add a route by building up the routing information in address_hi, address_lo
+ * and data according to how it is used in struct kvm_lapic_irq. For full
+ * details, look up how fields in struct kvm_lapic_irq are used.
+ *
+ * Return: None
+ */
+static void gsi_route_add(struct kvm_irq_routing *table, uint32_t gsi,
+                         bool use_x2apic_format, uint32_t dest_id,
+                         uint8_t vector, bool msi_redir_hint, bool dest_mode,
+                         bool trig_mode, uint8_t delivery_mode)
+{
+       union {
+               struct {
+                       u32 vector : 8, delivery_mode : 3,
+                       dest_mode_logical : 1, reserved : 2,
+                       active_low : 1, is_level : 1;
+               };
+               uint32_t as_uint32;
+       } data = { 0 };
+       union {
+               struct {
+                       u32 reserved_0 : 2, dest_mode_logical : 1,
+                           redirect_hint : 1, reserved_1 : 1,
+                           virt_destid_8_14 : 7, destid_0_7 : 8,
+                           base_address : 12;
+               };
+               uint32_t as_uint32;
+       } address_lo = { 0 };
+       union {
+               struct {
+                       u32 reserved : 8, destid_8_31 : 24;
+               };
+               uint32_t as_uint32;
+       } address_hi = { 0 };
+
+       /* Fixed 0xfee (see Intel SDM "Message Address Register Format") */
+       address_lo.base_address = 0xfee;
+
+       address_lo.destid_0_7 = dest_id & 0xff;
+       if (use_x2apic_format)
+               address_hi.destid_8_31 = (dest_id & 0xffffff00) >> 8;
+
+       data.vector = vector;
+       address_lo.dest_mode_logical = dest_mode;
+       data.is_level = trig_mode;
+       data.delivery_mode = delivery_mode & 0b111;
+       address_lo.redirect_hint = msi_redir_hint;
+
+       kvm_gsi_routing_msi_add(table, gsi, address_lo.as_uint32,
+                               address_hi.as_uint32, data.as_uint32);
+}
+
+/**
+ * Sets up KVM irqfd in @vm
+ *
+ * @gsi: irqchip pin toggled by this event
+ */
+static void set_irqfd(struct kvm_vm *vm, int fd, uint32_t gsi, bool assign)
+{
+       struct kvm_irqfd ifd = {
+               .fd = fd,
+               .gsi = gsi,
+               .flags = assign ? 0 : KVM_IRQFD_FLAG_DEASSIGN,
+               .resamplefd = 0,
+       };
+
+       vm_ioctl(vm, KVM_IRQFD, &ifd);
+}
+
+static void setup_interrupt_count_per_vector(struct kvm_vm *vm)
+{
+       vm_vaddr_t gva;
+       int npages;
+
+       npages = round_up(sizeof(*interrupt_count_per_vector), PAGE_SIZE);
+       vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+                                   INTERRUPT_COUNT_GPA,
+                                   INTERRUPT_COUNT_MEMSLOT, npages, 0);
+       vm->memslots[MEM_REGION_TDX_SHARED_DATA] = INTERRUPT_COUNT_MEMSLOT;
+
+       gva = vm_vaddr_alloc_shared(vm, sizeof(*interrupt_count_per_vector),
+                                   KVM_UTIL_MIN_VADDR,
+                                   MEM_REGION_TDX_SHARED_DATA);
+
+       interrupt_count_per_vector = addr_gva2hva(vm, gva);
+       memset(interrupt_count_per_vector, 0,
+              sizeof(*interrupt_count_per_vector));
+
+       write_guest_global(vm, interrupt_count_per_vector,
+                          (uint32_t(*)[NUM_INTERRUPTS])gva);
+}
+
+static void handle_vcpu_exit(struct kvm_vcpu *vcpu)
+{
+       struct ucall uc;
+
+       switch (get_ucall(vcpu, &uc)) {
+       case UCALL_SYNC:
+               break;
+       case UCALL_ABORT:
+               REPORT_GUEST_ASSERT(uc);
+       default:
+               TEST_FAIL("Unexpected exit: %s",
+                         exit_reason_str(vcpu->run->exit_reason));
+       }
+}
+
+void map_gsis_to_vectors(struct kvm_vm *vm, struct kvm_vcpu *vcpu, int 
*eventfds)
+{
+       struct kvm_irq_routing *table;
+       uint32_t vector_and_gsi;
+       int efd;
+
+       /* Flush table first. */
+       table = kvm_gsi_routing_create();
+       kvm_gsi_routing_write(vm, table);
+
+       /* Writing frees table, so we have to create another one. */
+       table = kvm_gsi_routing_create();
+
+       /* Map vectors to gsis 1 to 1 */
+       for (vector_and_gsi = 32; vector_and_gsi < NUM_INTERRUPTS;
+            ++vector_and_gsi) {
+               gsi_route_add(table, vector_and_gsi,
+                             /*use_x2apic_format=*/true,
+                             /*dest_id=*/vcpu->id,
+                             /*vector=*/vector_and_gsi,
+                             /*msi_redir_hint=*/false,
+                             /*dest_mode=*/false,
+                             /*trig_mode=*/false,
+                             /*delivery_mode=*/0b000);
+
+               efd = eventfd(0, EFD_NONBLOCK);
+               set_irqfd(vm, efd, vector_and_gsi, true);
+
+               eventfds[vector_and_gsi] = efd;
+       }
+
+       /* Configure KVM. Writing frees table. */
+       kvm_gsi_routing_write(vm, table);
+
+}
+
+int main(int argc, char *argv[])
+{
+       int eventfds[NUM_INTERRUPTS] = { 0 };
+       struct kvm_vcpu *vcpu;
+       struct kvm_vm *vm;
+       int vector, migration;
+
+       TEST_REQUIRE(kvm_check_cap(KVM_CAP_SPLIT_IRQCHIP));
+
+       setbuf(stdout, NULL);
+
+       vm = td_create();
+       td_initialize(vm, VM_MEM_SRC_ANONYMOUS, 0);
+
+       vcpu = td_vcpu_add(vm, 0, guest_code);
+
+       for (vector = 0; vector < NUM_INTERRUPTS; ++vector) {
+               vm_install_exception_handler(vm, vector,
+                                            interrupt_handler_increment_count);
+       }
+
+       setup_interrupt_count_per_vector(vm);
+
+       td_finalize(vm);
+
+       map_gsis_to_vectors(vm, vcpu, eventfds);
+
+       tdx_run(vcpu);
+       handle_vcpu_exit(vcpu);
+
+       for (migration = 0; migration < MIGRATION_LOOPS; ++migration) {
+               struct kvm_vcpu *next_vcpu;
+               struct kvm_vm *next_vm;
+
+               next_vm = td_create();
+               tdx_enable_capabilities(next_vm);
+               next_vcpu = vm_vcpu_recreate(next_vm, 0);
+
+               /* Inject on source VM. */
+               for (vector = 32; vector < NUM_INTERRUPTS; ++vector)
+                       TEST_ASSERT_EQ(eventfd_write(eventfds[vector], 1), 0);
+
+               map_gsis_to_vectors(next_vm, next_vcpu, eventfds);
+
+               vcpu = next_vcpu;
+
+               tdx_migrate_from(next_vm, vm);
+               kvm_vm_free(vm);
+               vm = next_vm;
+
+               tdx_run(vcpu);
+               handle_vcpu_exit(vcpu);
+
+               for (vector = 32; vector < NUM_INTERRUPTS; ++vector)
+                       TEST_ASSERT_EQ((*interrupt_count_per_vector)[vector],
+                                      migration + 1);
+       }
+
+       kvm_vm_free(vm);
+       for (vector = 32; vector < NUM_INTERRUPTS; ++vector)
+               close(eventfds[vector]);
+       return 0;
+}
diff --git a/tools/testing/selftests/kvm/x86/tdx_migrate_tests.c 
b/tools/testing/selftests/kvm/x86/tdx_migrate_tests.c
index e15da2aa0437..498e42f37697 100644
--- a/tools/testing/selftests/kvm/x86/tdx_migrate_tests.c
+++ b/tools/testing/selftests/kvm/x86/tdx_migrate_tests.c
@@ -10,27 +10,6 @@
 #define NR_MIGRATE_TEST_VMS 10
 #define TDX_IOEXIT_TEST_PORT 0x50
 
-static int __tdx_migrate_from(int dst_fd, int src_fd)
-{
-       struct kvm_enable_cap cap = {
-               .cap = KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM,
-               .args = { src_fd }
-       };
-
-       return ioctl(dst_fd, KVM_ENABLE_CAP, &cap);
-}
-
-
-static void tdx_migrate_from(struct kvm_vm *dst_vm, struct kvm_vm *src_vm)
-{
-       int ret;
-
-       vm_migrate_mem_regions(dst_vm, src_vm);
-       ret = __tdx_migrate_from(dst_vm->fd, src_vm->fd);
-       TEST_ASSERT(!ret, "Migration failed, ret: %d, errno: %d\n", ret, errno);
-       src_vm->enc_migrated = true;
-}
-
 void guest_code(void)
 {
        int ret;
-- 
2.50.0.rc1.591.g9c95f17f64-goog


Reply via email to