Test KVM instruction emulation for cmpxchg8b/16b (0x0FC7) with and without the LOCK prefix, and with the KVM fast-emulate prefix (FEP) when available. Cover both the compare-succeed (ZF=1) and compare-fail (ZF=0) outcomes. Signed-off-by: Sairaj Kodilkar --- This patch is intended to validate the cmpxchg16b kernel patch[1], which is required to support the AMD hardware accelerated vIOMMU. The cmpxchg16b tests hangs (with emulation error) on the kernel which does not have patch[1]. I am not sure what is the right approach to resolve this issue, any suggestions on how to resolve this are welcomed. [1] https://lore.kernel.org/all/20260306102047.29760-1-sarunkod@amd.com/ Base: 1da1819e49fc4938985edca67df669099b4c87a7 --- x86/emulator64.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/x86/emulator64.c b/x86/emulator64.c index c4df5178147c..3d92053a06d8 100644 --- a/x86/emulator64.c +++ b/x86/emulator64.c @@ -245,6 +245,164 @@ static void test_xchg(void *mem) "xchg reg, r/m (4)"); } +static void test_cmpxchg8b_success(void *mem) +{ + u64 *memq = mem; + u64 rax, rbx, rcx, rdx; + u32 eax, ebx, ecx, edx; + u8 zf; + +#define LOCK_PREFIX "lock;" +#define EMPTY "" + +#define TEST_CMPXCHG8B(fep, lock, report_str) do { \ + asm volatile("mov $0x0123456789abcdef, %%rax\n\t" \ + "mov %%rax, (%[memq])\n\t" \ + "mov $0x01234567, %%edx\n\t" \ + "mov $0x89abcdef, %%eax\n\t" \ + "mov $0xfedcba98, %%ecx\n\t" \ + "mov $0x76543210, %%ebx\n\t" \ + fep lock "cmpxchg8b (%[memq])\n\t" \ + "mov %%eax, %[eax]\n\t" \ + "mov %%ebx, %[ebx]\n\t" \ + "mov %%ecx, %[ecx]\n\t" \ + "mov %%edx, %[edx]\n\t" \ + "sete %[zf]\n\t" \ + : [zf]"=qm"(zf), [eax]"=r"(eax), [ebx]"=r"(ebx), \ + [ecx]"=r"(ecx), [edx]"=r"(edx) \ + : [memq]"r"(memq) \ + : "memory", "eax", "ebx", "ecx", "edx", "cc"); \ + report(memq[0] == 0xfedcba9876543210 && zf \ + && edx == 0x01234567 && eax == 0x89abcdef \ + && ecx == 0xfedcba98 && ebx == 0x76543210, \ + "cmpxchg8b mem == edx:eax (" report_str ")"); \ +} while (0) + +#define TEST_CMPXCHG16B(fep, lock, report_str) do { \ + asm volatile("mov $0x0123456789abcdef, %%rax\n\t" \ + "mov %%rax, (%[memq])\n\t" \ + "mov $0xfedcba9876543210, %%rax\n\t" \ + "mov %%rax, 8(%[memq])\n\t" \ + "mov $0xfedcba9876543210, %%rdx\n\t" \ + "mov $0x0123456789abcdef, %%rax\n\t" \ + "mov $0x0123456789abcdef, %%rcx\n\t" \ + "mov $0xfedcba9876543210, %%rbx\n\t" \ + fep lock "cmpxchg16b (%[memq])\n\t" \ + "mov %%rax, %[rax]\n\t" \ + "mov %%rbx, %[rbx]\n\t" \ + "mov %%rcx, %[rcx]\n\t" \ + "mov %%rdx, %[rdx]\n\t" \ + "sete %[zf]\n\t" \ + : [zf]"=qm"(zf), [rax]"=r"(rax), [rbx]"=r"(rbx), \ + [rcx]"=r"(rcx), [rdx]"=r"(rdx) \ + : [memq]"r"(memq) \ + : "memory", "rax", "rbx", "rcx", "rdx", "cc"); \ + report(memq[0] == 0xfedcba9876543210 && memq[1] == 0x0123456789abcdef && zf \ + && rdx == 0xfedcba9876543210 && rax == 0x0123456789abcdef \ + && rcx == 0x0123456789abcdef && rbx == 0xfedcba9876543210, \ + "cmpxchg16b mem == rdx:rax (" report_str ")"); \ +} while (0) + + TEST_CMPXCHG8B(EMPTY, EMPTY, "nofep, nolock"); + TEST_CMPXCHG16B(EMPTY, EMPTY, "nofep, nolock"); + TEST_CMPXCHG8B(EMPTY, LOCK_PREFIX, "nofep, lock"); + TEST_CMPXCHG16B(EMPTY, LOCK_PREFIX, "nofep, lock"); + + if (!is_fep_available) + return; + + memq = vmap(virt_to_phys(alloc_page()), PAGE_SIZE); + + TEST_CMPXCHG8B(KVM_FEP, EMPTY, "fep, nolock"); + TEST_CMPXCHG16B(KVM_FEP, EMPTY, "fep, nolock"); + TEST_CMPXCHG8B(KVM_FEP, LOCK_PREFIX, "fep, lock"); + TEST_CMPXCHG16B(KVM_FEP, LOCK_PREFIX, "fep, lock"); + +#undef TEST_CMPXCHG8B +#undef TEST_CMPXCHG16B +#undef LOCK_PREFIX +#undef EMPTY +} + +static void test_cmpxchg8b_fail(void *mem) +{ + u64 *memq = mem; + u64 rax, rbx, rcx, rdx; + u32 eax, ebx, ecx, edx; + u8 zf; + +#define LOCK_PREFIX "lock;" +#define EMPTY "" + +#define TEST_CMPXCHG8B(fep, lock, report_str) do { \ + asm volatile("mov $0x0123456789abcdef, %%rax\n\t" \ + "mov %%rax, (%[memq])\n\t" \ + "mov $0xfedcba98, %%edx\n\t" \ + "mov $0x76543210, %%eax\n\t" \ + "mov $0x01234567, %%ecx\n\t" \ + "mov $0x89abcdef, %%ebx\n\t" \ + fep lock "cmpxchg8b (%[memq])\n\t" \ + "mov %%eax, %[eax]\n\t" \ + "mov %%ebx, %[ebx]\n\t" \ + "mov %%ecx, %[ecx]\n\t" \ + "mov %%edx, %[edx]\n\t" \ + "sete %[zf]\n\t" \ + : [zf]"=qm"(zf), [eax]"=r"(eax), [ebx]"=r"(ebx), \ + [ecx]"=r"(ecx), [edx]"=r"(edx) \ + : [memq]"r"(memq) \ + : "memory", "eax", "ebx", "ecx", "edx", "cc"); \ + report(memq[0] == 0x0123456789abcdef && !zf \ + && edx == 0x01234567 && eax == 0x89abcdef \ + && ecx == 0x01234567 && ebx == 0x89abcdef, \ + "cmpxchg8b mem != edx:eax (" report_str ")"); \ +} while (0) + +#define TEST_CMPXCHG16B(fep, lock, report_str) do { \ + asm volatile("mov $0x0123456789abcdef, %%rax\n\t" \ + "mov %%rax, (%[memq])\n\t" \ + "mov $0xfedcba9876543210, %%rax\n\t" \ + "mov %%rax, 8(%[memq])\n\t" \ + "mov $0x0123456789abcdef, %%rdx\n\t" \ + "mov $0xfedcba9876543210, %%rax\n\t" \ + "mov $0x0123456789abcdef, %%rcx\n\t" \ + "mov $0xfedcba9876543210, %%rbx\n\t" \ + fep lock "cmpxchg16b (%[memq])\n\t" \ + "mov %%rax, %[rax]\n\t" \ + "mov %%rbx, %[rbx]\n\t" \ + "mov %%rcx, %[rcx]\n\t" \ + "mov %%rdx, %[rdx]\n\t" \ + "sete %[zf]\n\t" \ + : [zf]"=qm"(zf), [rax]"=r"(rax), [rbx]"=r"(rbx), \ + [rcx]"=r"(rcx), [rdx]"=r"(rdx) \ + : [memq]"r"(memq) \ + : "memory", "rax", "rbx", "rcx", "rdx", "cc"); \ + report(memq[0] == 0x0123456789abcdef && memq[1] == 0xfedcba9876543210 && !zf \ + && rdx == 0xfedcba9876543210 && rax == 0x123456789abcdef \ + && rcx == 0x0123456789abcdef && rbx == 0xfedcba9876543210, \ + "cmpxchg16b mem != rdx:rax (" report_str ")"); \ +} while (0) + + TEST_CMPXCHG8B(EMPTY, EMPTY, "nofep, nolock"); + TEST_CMPXCHG16B(EMPTY, EMPTY, "nofep, nolock"); + TEST_CMPXCHG8B(EMPTY, LOCK_PREFIX, "nofep, lock"); + TEST_CMPXCHG16B(EMPTY, LOCK_PREFIX, "nofep, lock"); + + if (!is_fep_available) + return; + + memq = vmap(virt_to_phys(alloc_page()), PAGE_SIZE); + + TEST_CMPXCHG8B(KVM_FEP, EMPTY, "fep, nolock"); + TEST_CMPXCHG16B(KVM_FEP, EMPTY, "fep, nolock"); + TEST_CMPXCHG8B(KVM_FEP, LOCK_PREFIX, "fep, lock"); + TEST_CMPXCHG16B(KVM_FEP, LOCK_PREFIX, "fep, lock"); + +#undef TEST_CMPXCHG8B +#undef TEST_CMPXCHG16B +#undef LOCK_PREFIX +#undef EMPTY +} + static void test_xadd(void *mem) { unsigned long *memq = mem; @@ -542,6 +700,9 @@ static void test_emulator_64(void *mem) test_pop(mem); test_xchg(mem); + test_cmpxchg8b_success(mem); + test_cmpxchg8b_fail(mem); + test_xadd(mem); test_cr8(); -- 2.34.1