2023-01-13 22:21:07

by Kechen Lu

[permalink] [raw]
Subject: [RFC PATCH v5 0/6] KVM: x86: add per-vCPU exits disable capability

Summary
===========
Introduce support of vCPU-scoped ioctl with KVM_CAP_X86_DISABLE_EXITS
cap for disabling exits to enable finer-grained VM exits disabling
on per vCPU scales instead of whole guest. This patch series enabled
the vCPU-scoped exits control and toggling.

Motivation
============
In use cases like Windows guest running heavy CPU-bound
workloads, disabling HLT VM-exits could mitigate host sched ctx switch
overhead. Simply HLT disabling on all vCPUs could bring
performance benefits, but if no pCPUs reserved for host threads, could
happened to the forced preemption as host does not know the time to do
the schedule for other host threads want to run. With this patch, we
could only disable part of vCPUs HLT exits for one guest, this still
keeps performance benefits, and also shows resiliency to host stressing
workload running at the same time.

Performance and Testing
=========================
In the host stressing workload experiment with Windows guest heavy
CPU-bound workloads, it shows good resiliency and having the ~3%
performance improvement. E.g. Passmark running in a Windows guest
with this patch disabling HLT exits on only half of vCPUs still
showing 2.4% higher main score v/s baseline.

Tested everything on AMD machines.

v4->v5 :
- Drop the usage of KVM request, keep the VM-scoped exits disable
as the existing design, and only allow per-vCPU settings to
override the per-VM settings (Sean Christopherson)
- Refactor the disable exits selftest without introducing any
new prerequisite patch, tests per-vCPU exits disable and overrides,
and per-VM exits disable

v3->v4 (Chao Gao) :
- Use kvm vCPU request KVM_REQ_DISABLE_EXIT to perform the arch
VMCS updating (patch 5)
- Fix selftests redundant arguments (patch 7)
- Merge overlapped fix bits from patch 4 to patch 3

v2->v3 (Sean Christopherson) :
- Reject KVM_CAP_X86_DISABLE_EXITS if userspace disable MWAIT exits
when MWAIT is not allowed in guest (patch 3)
- Make userspace able to re-enable previously disabled exits (patch 4)
- Add mwait/pause/cstate exits flag toggling instead of only hlt
exits (patch 5)
- Add selftests for KVM_CAP_X86_DISABLE_EXITS (patch 7)

v1->v2 (Sean Christopherson) :
- Add explicit restriction for VM-scoped exits disabling to be called
before vCPUs creation (patch 1)
- Use vCPU ioctl instead of 64bit vCPU bitmask (patch 5), and make exits
disable flags check purely for vCPU instead of VM (patch 2)

Best Regards,
Kechen

Kechen Lu (3):
KVM: x86: Move *_in_guest power management flags to vCPU scope
KVM: x86: add vCPU scoped toggling for disabled exits
KVM: selftests: Add tests for VM and vCPU cap
KVM_CAP_X86_DISABLE_EXITS

Sean Christopherson (3):
KVM: x86: only allow exits disable before vCPUs created
KVM: x86: Reject disabling of MWAIT interception when not allowed
KVM: x86: Let userspace re-enable previously disabled exits

Documentation/virt/kvm/api.rst | 8 +-
arch/x86/include/asm/kvm-x86-ops.h | 1 +
arch/x86/include/asm/kvm_host.h | 7 +
arch/x86/kvm/cpuid.c | 4 +-
arch/x86/kvm/lapic.c | 7 +-
arch/x86/kvm/svm/nested.c | 4 +-
arch/x86/kvm/svm/svm.c | 42 +-
arch/x86/kvm/vmx/vmx.c | 53 +-
arch/x86/kvm/x86.c | 69 ++-
arch/x86/kvm/x86.h | 16 +-
include/uapi/linux/kvm.h | 4 +-
tools/testing/selftests/kvm/Makefile | 1 +
.../selftests/kvm/x86_64/disable_exits_test.c | 457 ++++++++++++++++++
13 files changed, 626 insertions(+), 47 deletions(-)
create mode 100644 tools/testing/selftests/kvm/x86_64/disable_exits_test.c

--
2.34.1


2023-01-13 22:24:07

by Kechen Lu

[permalink] [raw]
Subject: [RFC PATCH v5 6/6] KVM: selftests: Add tests for VM and vCPU cap KVM_CAP_X86_DISABLE_EXITS

Add selftests for KVM cap KVM_CAP_X86_DISABLE_EXITS overriding flags
in VM and vCPU scope both works as expected.

Suggested-by: Chao Gao <[email protected]>
Suggested-by: Shaoqin Huang <[email protected]>
Signed-off-by: Kechen Lu <[email protected]>
---
tools/testing/selftests/kvm/Makefile | 1 +
.../selftests/kvm/x86_64/disable_exits_test.c | 457 ++++++++++++++++++
2 files changed, 458 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86_64/disable_exits_test.c

diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index 1750f91dd936..eeeba35e2536 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -114,6 +114,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/sev_migrate_tests
TEST_GEN_PROGS_x86_64 += x86_64/amx_test
TEST_GEN_PROGS_x86_64 += x86_64/max_vcpuid_cap_test
TEST_GEN_PROGS_x86_64 += x86_64/triple_fault_event_test
+TEST_GEN_PROGS_x86_64 += x86_64/disable_exits_test
TEST_GEN_PROGS_x86_64 += access_tracking_perf_test
TEST_GEN_PROGS_x86_64 += demand_paging_test
TEST_GEN_PROGS_x86_64 += dirty_log_test
diff --git a/tools/testing/selftests/kvm/x86_64/disable_exits_test.c b/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
new file mode 100644
index 000000000000..dceba3bcef5f
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
@@ -0,0 +1,457 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test per-VM and per-vCPU disable exits cap
+ * 1) Per-VM scope
+ * 2) Per-vCPU scope
+ *
+ */
+
+#define _GNU_SOURCE /* for program_invocation_short_name */
+#include <pthread.h>
+#include <inttypes.h>
+#include <string.h>
+#include <time.h>
+#include <sys/ioctl.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "svm_util.h"
+#include "vmx.h"
+#include "processor.h"
+#include "asm/kvm.h"
+#include "linux/kvm.h"
+
+/* Arbitary chosen IPI vector value from sender to halter vCPU */
+#define IPI_VECTOR 0xa5
+/* Number of HLTs halter vCPU thread executes */
+#define COUNT_HLT_EXITS 10
+
+struct guest_stats {
+ uint32_t halter_apic_id;
+ volatile uint64_t hlt_count;
+ volatile uint64_t wake_count;
+};
+
+static u64 read_vcpu_stats_halt_exits(struct kvm_vcpu *vcpu)
+{
+ int i;
+ struct kvm_stats_header header;
+ u64 *stats_data;
+ u64 ret = 0;
+ struct kvm_stats_desc *stats_desc;
+ struct kvm_stats_desc *pdesc;
+ int stats_fd = vcpu_get_stats_fd(vcpu);
+
+ read_stats_header(stats_fd, &header);
+ if (header.num_desc == 0) {
+ fprintf(stderr,
+ "Cannot read halt exits since no KVM stats defined\n");
+ return ret;
+ }
+
+ stats_desc = read_stats_descriptors(stats_fd, &header);
+ for (i = 0; i < header.num_desc; ++i) {
+ pdesc = get_stats_descriptor(stats_desc, i, &header);
+ if (!strncmp(pdesc->name, "halt_exits", 10)) {
+ stats_data = malloc(pdesc->size * sizeof(*stats_data));
+ read_stat_data(stats_fd, &header, pdesc, stats_data,
+ pdesc->size);
+ ret = *stats_data;
+ free(stats_data);
+ break;
+ }
+ }
+ free(stats_desc);
+ return ret;
+}
+
+/* HLT multiple times in one vCPU */
+static void halter_guest_code(struct guest_stats *data)
+{
+ xapic_enable();
+ data->halter_apic_id = GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
+
+ for (;;) {
+ data->hlt_count++;
+ asm volatile("sti; hlt; cli");
+ data->wake_count++;
+ }
+}
+
+static void halter_waiting_guest_code(struct guest_stats *data)
+{
+ uint64_t tsc_start = rdtsc();
+
+ xapic_enable();
+ data->halter_apic_id = GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
+
+ for (;;) {
+ data->hlt_count++;
+ asm volatile("sti; hlt; cli");
+ data->wake_count++;
+ /* Wait for ~0.5sec for each HLT execution */
+ tsc_start = rdtsc();
+ while (rdtsc() - tsc_start < 2000000000);
+ }
+}
+
+/* Runs on halter vCPU when IPI arrives */
+static void guest_ipi_handler(struct ex_regs *regs)
+{
+ xapic_write_reg(APIC_EOI, 11);
+}
+
+/* Sender vCPU waits for ~1sec to assume HLT executed */
+static void sender_wait_loop(struct guest_stats *data, uint64_t old_hlt_count,
+ uint64_t old_wake_count)
+{
+ uint64_t tsc_start = rdtsc();
+ while (rdtsc() - tsc_start < 4000000000);
+ GUEST_ASSERT((data->wake_count != old_wake_count) &&
+ (data->hlt_count != old_hlt_count));
+}
+
+/* Sender vCPU loops sending IPI to halter vCPU every ~1sec */
+static void sender_guest_code(struct guest_stats *data)
+{
+ uint32_t icr_val;
+ uint32_t icr2_val;
+ uint64_t old_hlt_count = 0;
+ uint64_t old_wake_count = 0;
+
+ xapic_enable();
+ /* Init interrupt command register for sending IPIs */
+ icr_val = (APIC_DEST_PHYSICAL | APIC_DM_FIXED | IPI_VECTOR);
+ icr2_val = SET_APIC_DEST_FIELD(data->halter_apic_id);
+
+ for (;;) {
+ /*
+ * Send IPI to halted vCPU
+ * First IPI sends here as already waited before sender vCPU
+ * thread creation
+ */
+ xapic_write_reg(APIC_ICR2, icr2_val);
+ xapic_write_reg(APIC_ICR, icr_val);
+ sender_wait_loop(data, old_hlt_count, old_wake_count);
+ GUEST_ASSERT((data->wake_count != old_wake_count) &&
+ (data->hlt_count != old_hlt_count));
+ old_wake_count = data->wake_count;
+ old_hlt_count = data->hlt_count;
+ }
+}
+
+static void *vcpu_thread(void *arg)
+{
+ struct kvm_vcpu *vcpu = (struct kvm_vcpu *)arg;
+ int old;
+ int r;
+
+ r = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old);
+ TEST_ASSERT(r == 0,
+ "pthread_setcanceltype failed on vcpu_id=%u with errno=%d",
+ vcpu->id, r);
+ fprintf(stderr, "vCPU thread running vCPU %u\n", vcpu->id);
+ vcpu_run(vcpu);
+ return NULL;
+}
+
+static void cancel_join_vcpu_thread(pthread_t thread, struct kvm_vcpu *vcpu)
+{
+ void *retval;
+ int r;
+
+ r = pthread_cancel(thread);
+ TEST_ASSERT(r == 0,
+ "pthread_cancel on vcpu_id=%d failed with errno=%d",
+ vcpu->id, r);
+
+ r = pthread_join(thread, &retval);
+ TEST_ASSERT(r == 0,
+ "pthread_join on vcpu_id=%d failed with errno=%d",
+ vcpu->id, r);
+}
+
+/*
+ * Test case 1:
+ * Normal VM running with one vCPU keeps executing HLTs,
+ * another vCPU sending IPIs to wake it up, should expect
+ * all HLTs exiting to host
+ */
+static void test_vm_without_disable_exits_cap(void)
+{
+ int r;
+ int wait_secs;
+ const int first_halter_wait = 10;
+ uint64_t kvm_halt_exits;
+ struct kvm_vm *vm;
+ struct kvm_vcpu *halter_vcpu;
+ struct kvm_vcpu *sender_vcpu;
+ struct guest_stats *data;
+ vm_vaddr_t guest_stats_page_vaddr;
+ pthread_t threads[2];
+
+ /* Create VM */
+ vm = vm_create(2);
+
+ /* Add vCPU with loops halting */
+ halter_vcpu = vm_vcpu_add(vm, 0, halter_guest_code);
+
+ vm_init_descriptor_tables(vm);
+ vcpu_init_descriptor_tables(halter_vcpu);
+ vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
+ virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
+
+ /* Add vCPU with IPIs waking up halter vCPU */
+ sender_vcpu = vm_vcpu_add(vm, 1, sender_guest_code);
+
+ guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
+ data = addr_gva2hva(vm, guest_stats_page_vaddr);
+ memset(data, 0, sizeof(*data));
+
+ vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
+ vcpu_args_set(sender_vcpu, 1, guest_stats_page_vaddr);
+
+ /* Start halter vCPU thread and wait for it to execute first HLT. */
+ r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
+ TEST_ASSERT(r == 0,
+ "pthread_create halter failed errno=%d", errno);
+ fprintf(stderr, "Halter vCPU thread started\n");
+
+ wait_secs = 0;
+ while ((wait_secs < first_halter_wait) && !data->hlt_count) {
+ sleep(1);
+ wait_secs++;
+ }
+ TEST_ASSERT(data->hlt_count,
+ "Halter vCPU did not execute first HLT within %d seconds",
+ first_halter_wait);
+ fprintf(stderr,
+ "Halter vCPU thread reported its first HLT executed "
+ "after %d seconds.\n",
+ wait_secs);
+
+ /*
+ * After guest halter vCPU executed first HLT, start the sender
+ * vCPU thread to wakeup halter vCPU
+ */
+ r = pthread_create(&threads[1], NULL, vcpu_thread, sender_vcpu);
+ TEST_ASSERT(r == 0, "pthread_create sender failed errno=%d", errno);
+
+ while (data->hlt_count < COUNT_HLT_EXITS);
+
+ cancel_join_vcpu_thread(threads[0], halter_vcpu);
+ cancel_join_vcpu_thread(threads[1], sender_vcpu);
+
+ kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
+ TEST_ASSERT(kvm_halt_exits == data->hlt_count,
+ "Halter vCPU had unmatched %lu halt exits - %lu HLTs "
+ "executed, when not disabling VM halt exits\n",
+ kvm_halt_exits, data->hlt_count);
+ fprintf(stderr, "Halter vCPU had %lu halt exits\n",
+ kvm_halt_exits);
+ fprintf(stderr, "Guest records %lu HLTs executed, "
+ "waked %lu times\n",
+ data->hlt_count, data->wake_count);
+
+ kvm_vm_free(vm);
+}
+
+/*
+ * Test case 2:
+ * VM scoped exits disabling, HLT instructions
+ * stay inside guest without exits
+ */
+static void test_vm_disable_exits_cap(void)
+{
+ int r;
+ uint64_t kvm_halt_exits;
+ struct kvm_vm *vm;
+ struct kvm_vcpu *halter_vcpu;
+ struct guest_stats *data;
+ vm_vaddr_t guest_stats_page_vaddr;
+ pthread_t halter_thread;
+
+ /* Create VM */
+ vm = vm_create(1);
+
+ /*
+ * Before adding any vCPUs, enable the KVM_X86_DISABLE_EXITS cap
+ * with flag KVM_X86_DISABLE_EXITS_HLT
+ */
+ vm_enable_cap(vm, KVM_CAP_X86_DISABLE_EXITS,
+ KVM_X86_DISABLE_EXITS_HLT);
+
+ /* Add vCPU with loops halting */
+ halter_vcpu = vm_vcpu_add(vm, 0, halter_waiting_guest_code);
+
+ vm_init_descriptor_tables(vm);
+ vcpu_init_descriptor_tables(halter_vcpu);
+ vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
+ virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
+
+ guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
+ data = addr_gva2hva(vm, guest_stats_page_vaddr);
+ memset(data, 0, sizeof(*data));
+ vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
+
+ /* Start halter vCPU thread and execute HLTs immediately */
+ r = pthread_create(&halter_thread, NULL, vcpu_thread, halter_vcpu);
+ TEST_ASSERT(r == 0,
+ "pthread_create halter failed errno=%d", errno);
+ fprintf(stderr, "Halter vCPU thread started\n");
+
+ while (data->hlt_count < COUNT_HLT_EXITS);
+
+ cancel_join_vcpu_thread(halter_thread, halter_vcpu);
+
+ kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
+ TEST_ASSERT(kvm_halt_exits == 0,
+ "Halter vCPU had unexpected halt exits occuring after "
+ "disabling VM-scoped halt exits cap\n");
+ fprintf(stderr, "Halter vCPU had %lu HLT exits\n",
+ kvm_halt_exits);
+ fprintf(stderr, "Guest records %lu HLTs executed\n",
+ data->hlt_count);
+
+ kvm_vm_free(vm);
+}
+
+/*
+ * Test case 3:
+ * VM overrides exits disable flags after vCPU created,
+ * which is not allowed
+ */
+static void test_vm_disable_exits_cap_with_vcpu_created(void)
+{
+ int r;
+ struct kvm_vm *vm;
+ struct kvm_enable_cap cap = {
+ .cap = KVM_CAP_X86_DISABLE_EXITS,
+ .args[0] = KVM_X86_DISABLE_EXITS_HLT | KVM_X86_DISABLE_EXITS_OVERRIDE,
+ };
+
+ /* Create VM */
+ vm = vm_create(1);
+ /* Add vCPU with loops halting */
+ vm_vcpu_add(vm, 0, halter_waiting_guest_code);
+
+ /*
+ * After creating vCPU, the current VM-scoped ABI should
+ * discard the cap enable of KVM_CAP_X86_DISABLE_EXITS
+ * and return non-zero. Since vm_enabled_cap() not able
+ * to assert the return value, so use the __vm_ioctl()
+ */
+ r = __vm_ioctl(vm, KVM_ENABLE_CAP, &cap);
+
+ TEST_ASSERT(r != 0,
+ "Setting VM-scoped KVM_CAP_X86_DISABLE_EXITS after "
+ "vCPUs created is not allowed, but it succeeds here\n");
+}
+
+/*
+ * Test case 4:
+ * vCPU scoped halt exits disabling and enabling tests,
+ * verify overides are working after vCPU created
+ */
+static void test_vcpu_toggling_disable_exits_cap(void)
+{
+ int r;
+ uint64_t kvm_halt_exits;
+ struct kvm_vm *vm;
+ struct kvm_vcpu *halter_vcpu;
+ struct kvm_vcpu *sender_vcpu;
+ struct guest_stats *data;
+ vm_vaddr_t guest_stats_page_vaddr;
+ pthread_t threads[2];
+
+ /* Create VM */
+ vm = vm_create(2);
+
+ /* Add vCPU with loops halting */
+ halter_vcpu = vm_vcpu_add(vm, 0, halter_waiting_guest_code);
+ /* Set KVM_CAP_X86_DISABLE_EXITS_HLT for halter vCPU */
+ vcpu_enable_cap(halter_vcpu, KVM_CAP_X86_DISABLE_EXITS,
+ KVM_X86_DISABLE_EXITS_HLT | KVM_X86_DISABLE_EXITS_OVERRIDE);
+
+ vm_init_descriptor_tables(vm);
+ vcpu_init_descriptor_tables(halter_vcpu);
+ vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
+
+ virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
+
+ /* Add vCPU with IPIs waking up halter vCPU */
+ sender_vcpu = vm_vcpu_add(vm, 1, sender_guest_code);
+
+ guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
+ data = addr_gva2hva(vm, guest_stats_page_vaddr);
+ memset(data, 0, sizeof(*data));
+
+ vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
+ vcpu_args_set(sender_vcpu, 1, guest_stats_page_vaddr);
+
+ r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
+ TEST_ASSERT(r == 0,
+ "pthread_create halter failed errno=%d", errno);
+ fprintf(stderr, "Halter vCPU thread started with halt exits"
+ "disabled\n");
+
+ /*
+ * For the first phase of the running, halt exits
+ * are disabled, halter vCPU executes HLT instruction
+ * but never exits to host
+ */
+ while (data->hlt_count < (COUNT_HLT_EXITS / 2));
+
+ cancel_join_vcpu_thread(threads[0], halter_vcpu);
+ /*
+ * Override and clean KVM_CAP_X86_DISABLE_EXITS flags
+ * for halter vCPU. Expect to see halt exits occurs then.
+ */
+ vcpu_enable_cap(halter_vcpu, KVM_CAP_X86_DISABLE_EXITS,
+ KVM_X86_DISABLE_EXITS_OVERRIDE);
+
+ r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
+ TEST_ASSERT(r == 0,
+ "pthread_create halter failed errno=%d", errno);
+ fprintf(stderr, "Halter vCPU thread restarted and cleared "
+ "halt exits flag\n");
+
+ sleep(1);
+ /*
+ * Second phase of the test, after guest halter vCPU
+ * reenabled halt exits, start the sender
+ * vCPU thread to wakeup halter vCPU
+ */
+ r = pthread_create(&threads[1], NULL, vcpu_thread, sender_vcpu);
+ TEST_ASSERT(r == 0, "pthread_create sender failed errno=%d", errno);
+
+ while (data->hlt_count < COUNT_HLT_EXITS);
+
+ cancel_join_vcpu_thread(threads[0], halter_vcpu);
+ cancel_join_vcpu_thread(threads[1], sender_vcpu);
+
+ kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
+ TEST_ASSERT(kvm_halt_exits == (COUNT_HLT_EXITS / 2),
+ "Halter vCPU had unexpected %lu halt exits, "
+ "there should be %d halt exits while "
+ "not disabling VM halt exits\n",
+ kvm_halt_exits, COUNT_HLT_EXITS / 2);
+ fprintf(stderr, "Halter vCPU had %lu halt exits\n",
+ kvm_halt_exits);
+ fprintf(stderr, "Guest records %lu HLTs executed, "
+ "waked %lu times\n",
+ data->hlt_count, data->wake_count);
+
+ kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+ fprintf(stderr, "VM-scoped tests start\n");
+ test_vm_without_disable_exits_cap();
+ test_vm_disable_exits_cap();
+ test_vm_disable_exits_cap_with_vcpu_created();
+ fprintf(stderr, "vCPU-scoped test starts\n");
+ test_vcpu_toggling_disable_exits_cap();
+ return 0;
+}
--
2.34.1

2023-01-18 09:25:39

by Zhi Wang

[permalink] [raw]
Subject: Re: [RFC PATCH v5 0/6] KVM: x86: add per-vCPU exits disable capability

On Fri, 13 Jan 2023 22:01:08 +0000
Kechen Lu <[email protected]> wrote:

Hi:

checkpatch.pl throws a lot of warning and errors when I was trying
this series. Can you fix them?

total: 470 errors, 22 warnings, 464 lines checked

> Summary
> ===========
> Introduce support of vCPU-scoped ioctl with KVM_CAP_X86_DISABLE_EXITS
> cap for disabling exits to enable finer-grained VM exits disabling
> on per vCPU scales instead of whole guest. This patch series enabled
> the vCPU-scoped exits control and toggling.
>
> Motivation
> ============
> In use cases like Windows guest running heavy CPU-bound
> workloads, disabling HLT VM-exits could mitigate host sched ctx switch
> overhead. Simply HLT disabling on all vCPUs could bring
> performance benefits, but if no pCPUs reserved for host threads, could
> happened to the forced preemption as host does not know the time to do
> the schedule for other host threads want to run. With this patch, we
> could only disable part of vCPUs HLT exits for one guest, this still
> keeps performance benefits, and also shows resiliency to host stressing
> workload running at the same time.
>
> Performance and Testing
> =========================
> In the host stressing workload experiment with Windows guest heavy
> CPU-bound workloads, it shows good resiliency and having the ~3%
> performance improvement. E.g. Passmark running in a Windows guest
> with this patch disabling HLT exits on only half of vCPUs still
> showing 2.4% higher main score v/s baseline.
>
> Tested everything on AMD machines.
>
> v4->v5 :
> - Drop the usage of KVM request, keep the VM-scoped exits disable
> as the existing design, and only allow per-vCPU settings to
> override the per-VM settings (Sean Christopherson)
> - Refactor the disable exits selftest without introducing any
> new prerequisite patch, tests per-vCPU exits disable and overrides,
> and per-VM exits disable
>
> v3->v4 (Chao Gao) :
> - Use kvm vCPU request KVM_REQ_DISABLE_EXIT to perform the arch
> VMCS updating (patch 5)
> - Fix selftests redundant arguments (patch 7)
> - Merge overlapped fix bits from patch 4 to patch 3
>
> v2->v3 (Sean Christopherson) :
> - Reject KVM_CAP_X86_DISABLE_EXITS if userspace disable MWAIT exits
> when MWAIT is not allowed in guest (patch 3)
> - Make userspace able to re-enable previously disabled exits (patch 4)
> - Add mwait/pause/cstate exits flag toggling instead of only hlt
> exits (patch 5)
> - Add selftests for KVM_CAP_X86_DISABLE_EXITS (patch 7)
>
> v1->v2 (Sean Christopherson) :
> - Add explicit restriction for VM-scoped exits disabling to be called
> before vCPUs creation (patch 1)
> - Use vCPU ioctl instead of 64bit vCPU bitmask (patch 5), and make exits
> disable flags check purely for vCPU instead of VM (patch 2)
>
> Best Regards,
> Kechen
>
> Kechen Lu (3):
> KVM: x86: Move *_in_guest power management flags to vCPU scope
> KVM: x86: add vCPU scoped toggling for disabled exits
> KVM: selftests: Add tests for VM and vCPU cap
> KVM_CAP_X86_DISABLE_EXITS
>
> Sean Christopherson (3):
> KVM: x86: only allow exits disable before vCPUs created
> KVM: x86: Reject disabling of MWAIT interception when not allowed
> KVM: x86: Let userspace re-enable previously disabled exits
>
> Documentation/virt/kvm/api.rst | 8 +-
> arch/x86/include/asm/kvm-x86-ops.h | 1 +
> arch/x86/include/asm/kvm_host.h | 7 +
> arch/x86/kvm/cpuid.c | 4 +-
> arch/x86/kvm/lapic.c | 7 +-
> arch/x86/kvm/svm/nested.c | 4 +-
> arch/x86/kvm/svm/svm.c | 42 +-
> arch/x86/kvm/vmx/vmx.c | 53 +-
> arch/x86/kvm/x86.c | 69 ++-
> arch/x86/kvm/x86.h | 16 +-
> include/uapi/linux/kvm.h | 4 +-
> tools/testing/selftests/kvm/Makefile | 1 +
> .../selftests/kvm/x86_64/disable_exits_test.c | 457 ++++++++++++++++++
> 13 files changed, 626 insertions(+), 47 deletions(-)
> create mode 100644 tools/testing/selftests/kvm/x86_64/disable_exits_test.c
>

2023-01-18 14:25:31

by Zhi Wang

[permalink] [raw]
Subject: Re: [RFC PATCH v5 0/6] KVM: x86: add per-vCPU exits disable capability

On Wed, 18 Jan 2023 10:30:03 +0200
Zhi Wang <[email protected]> wrote:

Hi:

No sure why the test never finishes on my testing machine. Will take a look
later today.

My CPU:
model name : Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz

branch kvm.git/master top commit:
310bc39546a435c83cc27a0eba878afac0d74714

-----

[inno@inno-lk-server x86_64]$ time ./disable_exits_test
VM-scoped tests start
Halter vCPU thread started
vCPU thread running vCPU 0
Halter vCPU thread reported its first HLT executed after 1 seconds.
vCPU thread running vCPU 1
Halter vCPU had 10 halt exits
Guest records 10 HLTs executed, waked 9 times
Halter vCPU thread started
vCPU thread running vCPU 0



^C

real 19m0.923s
user 37m56.512s
sys 0m1.086s

-----

> On Fri, 13 Jan 2023 22:01:08 +0000
> Kechen Lu <[email protected]> wrote:
>
> Hi:
>
> checkpatch.pl throws a lot of warning and errors when I was trying
> this series. Can you fix them?
>
> total: 470 errors, 22 warnings, 464 lines checked
>
> > Summary
> > ===========
> > Introduce support of vCPU-scoped ioctl with KVM_CAP_X86_DISABLE_EXITS
> > cap for disabling exits to enable finer-grained VM exits disabling
> > on per vCPU scales instead of whole guest. This patch series enabled
> > the vCPU-scoped exits control and toggling.
> >
> > Motivation
> > ============
> > In use cases like Windows guest running heavy CPU-bound
> > workloads, disabling HLT VM-exits could mitigate host sched ctx switch
> > overhead. Simply HLT disabling on all vCPUs could bring
> > performance benefits, but if no pCPUs reserved for host threads, could
> > happened to the forced preemption as host does not know the time to do
> > the schedule for other host threads want to run. With this patch, we
> > could only disable part of vCPUs HLT exits for one guest, this still
> > keeps performance benefits, and also shows resiliency to host stressing
> > workload running at the same time.
> >
> > Performance and Testing
> > =========================
> > In the host stressing workload experiment with Windows guest heavy
> > CPU-bound workloads, it shows good resiliency and having the ~3%
> > performance improvement. E.g. Passmark running in a Windows guest
> > with this patch disabling HLT exits on only half of vCPUs still
> > showing 2.4% higher main score v/s baseline.
> >
> > Tested everything on AMD machines.
> >
> > v4->v5 :
> > - Drop the usage of KVM request, keep the VM-scoped exits disable
> > as the existing design, and only allow per-vCPU settings to
> > override the per-VM settings (Sean Christopherson)
> > - Refactor the disable exits selftest without introducing any
> > new prerequisite patch, tests per-vCPU exits disable and overrides,
> > and per-VM exits disable
> >
> > v3->v4 (Chao Gao) :
> > - Use kvm vCPU request KVM_REQ_DISABLE_EXIT to perform the arch
> > VMCS updating (patch 5)
> > - Fix selftests redundant arguments (patch 7)
> > - Merge overlapped fix bits from patch 4 to patch 3
> >
> > v2->v3 (Sean Christopherson) :
> > - Reject KVM_CAP_X86_DISABLE_EXITS if userspace disable MWAIT exits
> > when MWAIT is not allowed in guest (patch 3)
> > - Make userspace able to re-enable previously disabled exits (patch 4)
> > - Add mwait/pause/cstate exits flag toggling instead of only hlt
> > exits (patch 5)
> > - Add selftests for KVM_CAP_X86_DISABLE_EXITS (patch 7)
> >
> > v1->v2 (Sean Christopherson) :
> > - Add explicit restriction for VM-scoped exits disabling to be called
> > before vCPUs creation (patch 1)
> > - Use vCPU ioctl instead of 64bit vCPU bitmask (patch 5), and make exits
> > disable flags check purely for vCPU instead of VM (patch 2)
> >
> > Best Regards,
> > Kechen
> >
> > Kechen Lu (3):
> > KVM: x86: Move *_in_guest power management flags to vCPU scope
> > KVM: x86: add vCPU scoped toggling for disabled exits
> > KVM: selftests: Add tests for VM and vCPU cap
> > KVM_CAP_X86_DISABLE_EXITS
> >
> > Sean Christopherson (3):
> > KVM: x86: only allow exits disable before vCPUs created
> > KVM: x86: Reject disabling of MWAIT interception when not allowed
> > KVM: x86: Let userspace re-enable previously disabled exits
> >
> > Documentation/virt/kvm/api.rst | 8 +-
> > arch/x86/include/asm/kvm-x86-ops.h | 1 +
> > arch/x86/include/asm/kvm_host.h | 7 +
> > arch/x86/kvm/cpuid.c | 4 +-
> > arch/x86/kvm/lapic.c | 7 +-
> > arch/x86/kvm/svm/nested.c | 4 +-
> > arch/x86/kvm/svm/svm.c | 42 +-
> > arch/x86/kvm/vmx/vmx.c | 53 +-
> > arch/x86/kvm/x86.c | 69 ++-
> > arch/x86/kvm/x86.h | 16 +-
> > include/uapi/linux/kvm.h | 4 +-
> > tools/testing/selftests/kvm/Makefile | 1 +
> > .../selftests/kvm/x86_64/disable_exits_test.c | 457 ++++++++++++++++++
> > 13 files changed, 626 insertions(+), 47 deletions(-)
> > create mode 100644 tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> >
>

2023-01-18 20:38:28

by Kechen Lu

[permalink] [raw]
Subject: RE: [RFC PATCH v5 6/6] KVM: selftests: Add tests for VM and vCPU cap KVM_CAP_X86_DISABLE_EXITS

Hi Zhi,

Thanks for testing the patch series. Comments below.

> -----Original Message-----
> From: Zhi Wang <[email protected]>
> Sent: Wednesday, January 18, 2023 12:03 PM
> To: Kechen Lu <[email protected]>
> Cc: [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; [email protected];
> [email protected]
> Subject: Re: [RFC PATCH v5 6/6] KVM: selftests: Add tests for VM and vCPU
> cap KVM_CAP_X86_DISABLE_EXITS
>
> External email: Use caution opening links or attachments
>
>
> On Fri, 13 Jan 2023 22:01:14 +0000
> Kechen Lu <[email protected]> wrote:
>
> I think I figure out why this test case doesn't work:
>
> The 2nd case always hangs because:
>
> 1) Unlike the 1st case in which a halter and an IPI sender will be created,
> there is only halter thread created in the 2nd case.
> 2) The halter enables KVM_X86_DISABLE_EXITS_HLT. Thus, HLT will not cause
> VMEXIT
> 3) The halter stuck in the halter_waiting_guest_code(). data->hlt_count is
> always 1 and data->wake_count is always 0.
> 4) In the main thread, you have test_vm_disable_exits_cap() ->
> while (data->hlt_count < COUNT_HLT_EXITS);
>
> As data->hlt_count will never increase in the vcpu_thread, the main thread
> always stuck in the while loop.
>
> Can you explain more about your thoughts of designing this test case?

For this test case, we want to test for the VM-scoped KVM_CAP_X86_DISABLE_EXITS cap flags setting.
So if we set KVM_X86_DISABLE_EXITS_HLT, there would be no halt vmexits, and what expect
is the HLT instructions looping executed within guest halter vCPU thread, and not stuck here, no IPIs
required to wake it up.

Here is what I got for this test case running in an AMD machine.
-------------------------------------
Halter vCPU thread started
vCPU thread running vCPU 0
Halter vCPU had 0 HLT exits
Guest records 10 HLTs executed
-------------------------------------

BR,
Kechen

>
> > Add selftests for KVM cap KVM_CAP_X86_DISABLE_EXITS overriding flags
> > in VM and vCPU scope both works as expected.
> >
> > Suggested-by: Chao Gao <[email protected]>
> > Suggested-by: Shaoqin Huang <[email protected]>
> > Signed-off-by: Kechen Lu <[email protected]>
> > ---
> > tools/testing/selftests/kvm/Makefile | 1 +
> > .../selftests/kvm/x86_64/disable_exits_test.c | 457
> > ++++++++++++++++++
> > 2 files changed, 458 insertions(+)
> > create mode 100644
> > tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> >
> > diff --git a/tools/testing/selftests/kvm/Makefile
> > b/tools/testing/selftests/kvm/Makefile
> > index 1750f91dd936..eeeba35e2536 100644
> > --- a/tools/testing/selftests/kvm/Makefile
> > +++ b/tools/testing/selftests/kvm/Makefile
> > @@ -114,6 +114,7 @@ TEST_GEN_PROGS_x86_64 +=
> x86_64/sev_migrate_tests
> > TEST_GEN_PROGS_x86_64 += x86_64/amx_test
> > TEST_GEN_PROGS_x86_64 += x86_64/max_vcpuid_cap_test
> > TEST_GEN_PROGS_x86_64 += x86_64/triple_fault_event_test
> > +TEST_GEN_PROGS_x86_64 += x86_64/disable_exits_test
> > TEST_GEN_PROGS_x86_64 += access_tracking_perf_test
> > TEST_GEN_PROGS_x86_64 += demand_paging_test
> > TEST_GEN_PROGS_x86_64 += dirty_log_test diff --git
> > a/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> > b/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> > new file mode 100644
> > index 000000000000..dceba3bcef5f
> > --- /dev/null
> > +++ b/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> > @@ -0,0 +1,457 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Test per-VM and per-vCPU disable exits cap
> > + * 1) Per-VM scope
> > + * 2) Per-vCPU scope
> > + *
> > + */
> > +
> > +#define _GNU_SOURCE /* for program_invocation_short_name */
> #include
> > +<pthread.h> #include <inttypes.h> #include <string.h> #include
> > +<time.h> #include <sys/ioctl.h>
> > +
> > +#include "test_util.h"
> > +#include "kvm_util.h"
> > +#include "svm_util.h"
> > +#include "vmx.h"
> > +#include "processor.h"
> > +#include "asm/kvm.h"
> > +#include "linux/kvm.h"
> > +
> > +/* Arbitary chosen IPI vector value from sender to halter vCPU */
> > +#define IPI_VECTOR 0xa5
> > +/* Number of HLTs halter vCPU thread executes */
> > +#define COUNT_HLT_EXITS 10
> > +
> > +struct guest_stats {
> > + uint32_t halter_apic_id;
> > + volatile uint64_t hlt_count;
> > + volatile uint64_t wake_count;
> > +};
> > +
> > +static u64 read_vcpu_stats_halt_exits(struct kvm_vcpu *vcpu) {
> > + int i;
> > + struct kvm_stats_header header;
> > + u64 *stats_data;
> > + u64 ret = 0;
> > + struct kvm_stats_desc *stats_desc;
> > + struct kvm_stats_desc *pdesc;
> > + int stats_fd = vcpu_get_stats_fd(vcpu);
> > +
> > + read_stats_header(stats_fd, &header);
> > + if (header.num_desc == 0) {
> > + fprintf(stderr,
> > + "Cannot read halt exits since no KVM stats defined\n");
> > + return ret;
> > + }
> > +
> > + stats_desc = read_stats_descriptors(stats_fd, &header);
> > + for (i = 0; i < header.num_desc; ++i) {
> > + pdesc = get_stats_descriptor(stats_desc, i, &header);
> > + if (!strncmp(pdesc->name, "halt_exits", 10)) {
> > + stats_data = malloc(pdesc->size * sizeof(*stats_data));
> > + read_stat_data(stats_fd, &header, pdesc, stats_data,
> > + pdesc->size);
> > + ret = *stats_data;
> > + free(stats_data);
> > + break;
> > + }
> > + }
> > + free(stats_desc);
> > + return ret;
> > +}
> > +
> > +/* HLT multiple times in one vCPU */
> > +static void halter_guest_code(struct guest_stats *data) {
> > + xapic_enable();
> > + data->halter_apic_id =
> > +GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
> > +
> > + for (;;) {
> > + data->hlt_count++;
> > + asm volatile("sti; hlt; cli");
> > + data->wake_count++;
> > + }
> > +}
> > +
> > +static void halter_waiting_guest_code(struct guest_stats *data) {
> > + uint64_t tsc_start = rdtsc();
> > +
> > + xapic_enable();
> > + data->halter_apic_id =
> > + GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
> > +
> > + for (;;) {
> > + data->hlt_count++;
> > + asm volatile("sti; hlt; cli");
> > + data->wake_count++;
> > + /* Wait for ~0.5sec for each HLT execution */
> > + tsc_start = rdtsc();
> > + while (rdtsc() - tsc_start < 2000000000);
> > + }
> > +}
> > +
> > +/* Runs on halter vCPU when IPI arrives */ static void
> > +guest_ipi_handler(struct ex_regs *regs) {
> > + xapic_write_reg(APIC_EOI, 11);
> > +}
> > +
> > +/* Sender vCPU waits for ~1sec to assume HLT executed */ static void
> > +sender_wait_loop(struct guest_stats *data, uint64_t old_hlt_count,
> > + uint64_t old_wake_count) {
> > + uint64_t tsc_start = rdtsc();
> > + while (rdtsc() - tsc_start < 4000000000);
> > + GUEST_ASSERT((data->wake_count != old_wake_count) &&
> > + (data->hlt_count != old_hlt_count)); }
> > +
> > +/* Sender vCPU loops sending IPI to halter vCPU every ~1sec */ static
> > +void sender_guest_code(struct guest_stats *data) {
> > + uint32_t icr_val;
> > + uint32_t icr2_val;
> > + uint64_t old_hlt_count = 0;
> > + uint64_t old_wake_count = 0;
> > +
> > + xapic_enable();
> > + /* Init interrupt command register for sending IPIs */
> > + icr_val = (APIC_DEST_PHYSICAL | APIC_DM_FIXED | IPI_VECTOR);
> > + icr2_val = SET_APIC_DEST_FIELD(data->halter_apic_id);
> > +
> > + for (;;) {
> > + /*
> > + * Send IPI to halted vCPU
> > + * First IPI sends here as already waited before sender vCPU
> > + * thread creation
> > + */
> > + xapic_write_reg(APIC_ICR2, icr2_val);
> > + xapic_write_reg(APIC_ICR, icr_val);
> > + sender_wait_loop(data, old_hlt_count, old_wake_count);
> > + GUEST_ASSERT((data->wake_count != old_wake_count) &&
> > + (data->hlt_count != old_hlt_count));
> > + old_wake_count = data->wake_count;
> > + old_hlt_count = data->hlt_count;
> > + }
> > +}
> > +
> > +static void *vcpu_thread(void *arg)
> > +{
> > + struct kvm_vcpu *vcpu = (struct kvm_vcpu *)arg;
> > + int old;
> > + int r;
> > +
> > + r = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,
> &old);
> > + TEST_ASSERT(r == 0,
> > + "pthread_setcanceltype failed on vcpu_id=%u with errno=%d",
> > + vcpu->id, r);
> > + fprintf(stderr, "vCPU thread running vCPU %u\n", vcpu->id);
> > + vcpu_run(vcpu);
> > + return NULL;
> > +}
> > +
> > +static void cancel_join_vcpu_thread(pthread_t thread, struct kvm_vcpu
> > +*vcpu) {
> > + void *retval;
> > + int r;
> > +
> > + r = pthread_cancel(thread);
> > + TEST_ASSERT(r == 0,
> > + "pthread_cancel on vcpu_id=%d failed with errno=%d",
> > + vcpu->id, r);
> > +
> > + r = pthread_join(thread, &retval);
> > + TEST_ASSERT(r == 0,
> > + "pthread_join on vcpu_id=%d failed with errno=%d",
> > + vcpu->id, r);
> > +}
> > +
> > +/*
> > + * Test case 1:
> > + * Normal VM running with one vCPU keeps executing HLTs,
> > + * another vCPU sending IPIs to wake it up, should expect
> > + * all HLTs exiting to host
> > + */
> > +static void test_vm_without_disable_exits_cap(void)
> > +{
> > + int r;
> > + int wait_secs;
> > + const int first_halter_wait = 10;
> > + uint64_t kvm_halt_exits;
> > + struct kvm_vm *vm;
> > + struct kvm_vcpu *halter_vcpu;
> > + struct kvm_vcpu *sender_vcpu;
> > + struct guest_stats *data;
> > + vm_vaddr_t guest_stats_page_vaddr;
> > + pthread_t threads[2];
> > +
> > + /* Create VM */
> > + vm = vm_create(2);
> > +
> > + /* Add vCPU with loops halting */
> > + halter_vcpu = vm_vcpu_add(vm, 0, halter_guest_code);
> > +
> > + vm_init_descriptor_tables(vm);
> > + vcpu_init_descriptor_tables(halter_vcpu);
> > + vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
> > + virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
> > +
> > + /* Add vCPU with IPIs waking up halter vCPU */
> > + sender_vcpu = vm_vcpu_add(vm, 1, sender_guest_code);
> > +
> > + guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
> > + data = addr_gva2hva(vm, guest_stats_page_vaddr);
> > + memset(data, 0, sizeof(*data));
> > +
> > + vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
> > + vcpu_args_set(sender_vcpu, 1, guest_stats_page_vaddr);
> > +
> > + /* Start halter vCPU thread and wait for it to execute first HLT. */
> > + r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
> > + TEST_ASSERT(r == 0,
> > + "pthread_create halter failed errno=%d", errno);
> > + fprintf(stderr, "Halter vCPU thread started\n");
> > +
> > + wait_secs = 0;
> > + while ((wait_secs < first_halter_wait) && !data->hlt_count) {
> > + sleep(1);
> > + wait_secs++;
> > + }
> > + TEST_ASSERT(data->hlt_count,
> > + "Halter vCPU did not execute first HLT within %d seconds",
> > + first_halter_wait);
> > + fprintf(stderr,
> > + "Halter vCPU thread reported its first HLT executed "
> > + "after %d seconds.\n",
> > + wait_secs);
> > +
> > + /*
> > + * After guest halter vCPU executed first HLT, start the sender
> > + * vCPU thread to wakeup halter vCPU
> > + */
> > + r = pthread_create(&threads[1], NULL, vcpu_thread, sender_vcpu);
> > + TEST_ASSERT(r == 0, "pthread_create sender failed errno=%d",
> > + errno);
> > +
> > + while (data->hlt_count < COUNT_HLT_EXITS);
> > +
> > + cancel_join_vcpu_thread(threads[0], halter_vcpu);
> > + cancel_join_vcpu_thread(threads[1], sender_vcpu);
> > +
> > + kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
> > + TEST_ASSERT(kvm_halt_exits == data->hlt_count,
> > + "Halter vCPU had unmatched %lu halt exits - %lu HLTs "
> > + "executed, when not disabling VM halt exits\n",
> > + kvm_halt_exits, data->hlt_count);
> > + fprintf(stderr, "Halter vCPU had %lu halt exits\n",
> > + kvm_halt_exits);
> > + fprintf(stderr, "Guest records %lu HLTs executed, "
> > + "waked %lu times\n",
> > + data->hlt_count, data->wake_count);
> > +
> > + kvm_vm_free(vm);
> > +}
> > +
> > +/*
> > + * Test case 2:
> > + * VM scoped exits disabling, HLT instructions
> > + * stay inside guest without exits
> > + */
> > +static void test_vm_disable_exits_cap(void) {
> > + int r;
> > + uint64_t kvm_halt_exits;
> > + struct kvm_vm *vm;
> > + struct kvm_vcpu *halter_vcpu;
> > + struct guest_stats *data;
> > + vm_vaddr_t guest_stats_page_vaddr;
> > + pthread_t halter_thread;
> > +
> > + /* Create VM */
> > + vm = vm_create(1);
> > +
> > + /*
> > + * Before adding any vCPUs, enable the KVM_X86_DISABLE_EXITS cap
> > + * with flag KVM_X86_DISABLE_EXITS_HLT
> > + */
> > + vm_enable_cap(vm, KVM_CAP_X86_DISABLE_EXITS,
> > + KVM_X86_DISABLE_EXITS_HLT);
> > +
> > + /* Add vCPU with loops halting */
> > + halter_vcpu = vm_vcpu_add(vm, 0, halter_waiting_guest_code);
> > +
> > + vm_init_descriptor_tables(vm);
> > + vcpu_init_descriptor_tables(halter_vcpu);
> > + vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
> > + virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
> > +
> > + guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
> > + data = addr_gva2hva(vm, guest_stats_page_vaddr);
> > + memset(data, 0, sizeof(*data));
> > + vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
> > +
> > + /* Start halter vCPU thread and execute HLTs immediately */
> > + r = pthread_create(&halter_thread, NULL, vcpu_thread, halter_vcpu);
> > + TEST_ASSERT(r == 0,
> > + "pthread_create halter failed errno=%d", errno);
> > + fprintf(stderr, "Halter vCPU thread started\n");
> > +
> > + while (data->hlt_count < COUNT_HLT_EXITS);
> > +
> > + cancel_join_vcpu_thread(halter_thread, halter_vcpu);
> > +
> > + kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
> > + TEST_ASSERT(kvm_halt_exits == 0,
> > + "Halter vCPU had unexpected halt exits occuring after "
> > + "disabling VM-scoped halt exits cap\n");
> > + fprintf(stderr, "Halter vCPU had %lu HLT exits\n",
> > + kvm_halt_exits);
> > + fprintf(stderr, "Guest records %lu HLTs executed\n",
> > + data->hlt_count);
> > +
> > + kvm_vm_free(vm);
> > +}
> > +
> > +/*
> > + * Test case 3:
> > + * VM overrides exits disable flags after vCPU created,
> > + * which is not allowed
> > + */
> > +static void test_vm_disable_exits_cap_with_vcpu_created(void)
> > +{
> > + int r;
> > + struct kvm_vm *vm;
> > + struct kvm_enable_cap cap = {
> > + .cap = KVM_CAP_X86_DISABLE_EXITS,
> > + .args[0] = KVM_X86_DISABLE_EXITS_HLT |
> KVM_X86_DISABLE_EXITS_OVERRIDE,
> > + };
> > +
> > + /* Create VM */
> > + vm = vm_create(1);
> > + /* Add vCPU with loops halting */
> > + vm_vcpu_add(vm, 0, halter_waiting_guest_code);
> > +
> > + /*
> > + * After creating vCPU, the current VM-scoped ABI should
> > + * discard the cap enable of KVM_CAP_X86_DISABLE_EXITS
> > + * and return non-zero. Since vm_enabled_cap() not able
> > + * to assert the return value, so use the __vm_ioctl()
> > + */
> > + r = __vm_ioctl(vm, KVM_ENABLE_CAP, &cap);
> > +
> > + TEST_ASSERT(r != 0,
> > + "Setting VM-scoped KVM_CAP_X86_DISABLE_EXITS after "
> > + "vCPUs created is not allowed, but it succeeds here\n");
> > +}
> > +
> > +/*
> > + * Test case 4:
> > + * vCPU scoped halt exits disabling and enabling tests,
> > + * verify overides are working after vCPU created */ static void
> > +test_vcpu_toggling_disable_exits_cap(void)
> > +{
> > + int r;
> > + uint64_t kvm_halt_exits;
> > + struct kvm_vm *vm;
> > + struct kvm_vcpu *halter_vcpu;
> > + struct kvm_vcpu *sender_vcpu;
> > + struct guest_stats *data;
> > + vm_vaddr_t guest_stats_page_vaddr;
> > + pthread_t threads[2];
> > +
> > + /* Create VM */
> > + vm = vm_create(2);
> > +
> > + /* Add vCPU with loops halting */
> > + halter_vcpu = vm_vcpu_add(vm, 0, halter_waiting_guest_code);
> > + /* Set KVM_CAP_X86_DISABLE_EXITS_HLT for halter vCPU */
> > + vcpu_enable_cap(halter_vcpu, KVM_CAP_X86_DISABLE_EXITS,
> > + KVM_X86_DISABLE_EXITS_HLT |
> > + KVM_X86_DISABLE_EXITS_OVERRIDE);
> > +
> > + vm_init_descriptor_tables(vm);
> > + vcpu_init_descriptor_tables(halter_vcpu);
> > + vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
> > +
> > + virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
> > +
> > + /* Add vCPU with IPIs waking up halter vCPU */
> > + sender_vcpu = vm_vcpu_add(vm, 1, sender_guest_code);
> > +
> > + guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
> > + data = addr_gva2hva(vm, guest_stats_page_vaddr);
> > + memset(data, 0, sizeof(*data));
> > +
> > + vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
> > + vcpu_args_set(sender_vcpu, 1, guest_stats_page_vaddr);
> > +
> > + r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
> > + TEST_ASSERT(r == 0,
> > + "pthread_create halter failed errno=%d", errno);
> > + fprintf(stderr, "Halter vCPU thread started with halt exits"
> > + "disabled\n");
> > +
> > + /*
> > + * For the first phase of the running, halt exits
> > + * are disabled, halter vCPU executes HLT instruction
> > + * but never exits to host
> > + */
> > + while (data->hlt_count < (COUNT_HLT_EXITS / 2));
> > +
> > + cancel_join_vcpu_thread(threads[0], halter_vcpu);
> > + /*
> > + * Override and clean KVM_CAP_X86_DISABLE_EXITS flags
> > + * for halter vCPU. Expect to see halt exits occurs then.
> > + */
> > + vcpu_enable_cap(halter_vcpu, KVM_CAP_X86_DISABLE_EXITS,
> > + KVM_X86_DISABLE_EXITS_OVERRIDE);
> > +
> > + r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
> > + TEST_ASSERT(r == 0,
> > + "pthread_create halter failed errno=%d", errno);
> > + fprintf(stderr, "Halter vCPU thread restarted and cleared "
> > + "halt exits flag\n");
> > +
> > + sleep(1);
> > + /*
> > + * Second phase of the test, after guest halter vCPU
> > + * reenabled halt exits, start the sender
> > + * vCPU thread to wakeup halter vCPU
> > + */
> > + r = pthread_create(&threads[1], NULL, vcpu_thread, sender_vcpu);
> > + TEST_ASSERT(r == 0, "pthread_create sender failed errno=%d",
> > + errno);
> > +
> > + while (data->hlt_count < COUNT_HLT_EXITS);
> > +
> > + cancel_join_vcpu_thread(threads[0], halter_vcpu);
> > + cancel_join_vcpu_thread(threads[1], sender_vcpu);
> > +
> > + kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
> > + TEST_ASSERT(kvm_halt_exits == (COUNT_HLT_EXITS / 2),
> > + "Halter vCPU had unexpected %lu halt exits, "
> > + "there should be %d halt exits while "
> > + "not disabling VM halt exits\n",
> > + kvm_halt_exits, COUNT_HLT_EXITS / 2);
> > + fprintf(stderr, "Halter vCPU had %lu halt exits\n",
> > + kvm_halt_exits);
> > + fprintf(stderr, "Guest records %lu HLTs executed, "
> > + "waked %lu times\n",
> > + data->hlt_count, data->wake_count);
> > +
> > + kvm_vm_free(vm);
> > +}
> > +
> > +int main(int argc, char *argv[])
> > +{
> > + fprintf(stderr, "VM-scoped tests start\n");
> > + test_vm_without_disable_exits_cap();
> > + test_vm_disable_exits_cap();
> > + test_vm_disable_exits_cap_with_vcpu_created();
> > + fprintf(stderr, "vCPU-scoped test starts\n");
> > + test_vcpu_toggling_disable_exits_cap();
> > + return 0;
> > +}

2023-01-18 20:51:52

by Zhi Wang

[permalink] [raw]
Subject: Re: [RFC PATCH v5 6/6] KVM: selftests: Add tests for VM and vCPU cap KVM_CAP_X86_DISABLE_EXITS

On Fri, 13 Jan 2023 22:01:14 +0000
Kechen Lu <[email protected]> wrote:

I think I figure out why this test case doesn't work:

The 2nd case always hangs because:

1) Unlike the 1st case in which a halter and an IPI sender will be created,
there is only halter thread created in the 2nd case.
2) The halter enables KVM_X86_DISABLE_EXITS_HLT. Thus, HLT will not cause VMEXIT
3) The halter stuck in the halter_waiting_guest_code(). data->hlt_count is
always 1 and data->wake_count is always 0.
4) In the main thread, you have test_vm_disable_exits_cap() ->
while (data->hlt_count < COUNT_HLT_EXITS);

As data->hlt_count will never increase in the vcpu_thread, the main thread
always stuck in the while loop.

Can you explain more about your thoughts of designing this test case?

> Add selftests for KVM cap KVM_CAP_X86_DISABLE_EXITS overriding flags
> in VM and vCPU scope both works as expected.
>
> Suggested-by: Chao Gao <[email protected]>
> Suggested-by: Shaoqin Huang <[email protected]>
> Signed-off-by: Kechen Lu <[email protected]>
> ---
> tools/testing/selftests/kvm/Makefile | 1 +
> .../selftests/kvm/x86_64/disable_exits_test.c | 457 ++++++++++++++++++
> 2 files changed, 458 insertions(+)
> create mode 100644 tools/testing/selftests/kvm/x86_64/disable_exits_test.c
>
> diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
> index 1750f91dd936..eeeba35e2536 100644
> --- a/tools/testing/selftests/kvm/Makefile
> +++ b/tools/testing/selftests/kvm/Makefile
> @@ -114,6 +114,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/sev_migrate_tests
> TEST_GEN_PROGS_x86_64 += x86_64/amx_test
> TEST_GEN_PROGS_x86_64 += x86_64/max_vcpuid_cap_test
> TEST_GEN_PROGS_x86_64 += x86_64/triple_fault_event_test
> +TEST_GEN_PROGS_x86_64 += x86_64/disable_exits_test
> TEST_GEN_PROGS_x86_64 += access_tracking_perf_test
> TEST_GEN_PROGS_x86_64 += demand_paging_test
> TEST_GEN_PROGS_x86_64 += dirty_log_test
> diff --git a/tools/testing/selftests/kvm/x86_64/disable_exits_test.c b/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> new file mode 100644
> index 000000000000..dceba3bcef5f
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> @@ -0,0 +1,457 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Test per-VM and per-vCPU disable exits cap
> + * 1) Per-VM scope
> + * 2) Per-vCPU scope
> + *
> + */
> +
> +#define _GNU_SOURCE /* for program_invocation_short_name */
> +#include <pthread.h>
> +#include <inttypes.h>
> +#include <string.h>
> +#include <time.h>
> +#include <sys/ioctl.h>
> +
> +#include "test_util.h"
> +#include "kvm_util.h"
> +#include "svm_util.h"
> +#include "vmx.h"
> +#include "processor.h"
> +#include "asm/kvm.h"
> +#include "linux/kvm.h"
> +
> +/* Arbitary chosen IPI vector value from sender to halter vCPU */
> +#define IPI_VECTOR 0xa5
> +/* Number of HLTs halter vCPU thread executes */
> +#define COUNT_HLT_EXITS 10
> +
> +struct guest_stats {
> + uint32_t halter_apic_id;
> + volatile uint64_t hlt_count;
> + volatile uint64_t wake_count;
> +};
> +
> +static u64 read_vcpu_stats_halt_exits(struct kvm_vcpu *vcpu)
> +{
> + int i;
> + struct kvm_stats_header header;
> + u64 *stats_data;
> + u64 ret = 0;
> + struct kvm_stats_desc *stats_desc;
> + struct kvm_stats_desc *pdesc;
> + int stats_fd = vcpu_get_stats_fd(vcpu);
> +
> + read_stats_header(stats_fd, &header);
> + if (header.num_desc == 0) {
> + fprintf(stderr,
> + "Cannot read halt exits since no KVM stats defined\n");
> + return ret;
> + }
> +
> + stats_desc = read_stats_descriptors(stats_fd, &header);
> + for (i = 0; i < header.num_desc; ++i) {
> + pdesc = get_stats_descriptor(stats_desc, i, &header);
> + if (!strncmp(pdesc->name, "halt_exits", 10)) {
> + stats_data = malloc(pdesc->size * sizeof(*stats_data));
> + read_stat_data(stats_fd, &header, pdesc, stats_data,
> + pdesc->size);
> + ret = *stats_data;
> + free(stats_data);
> + break;
> + }
> + }
> + free(stats_desc);
> + return ret;
> +}
> +
> +/* HLT multiple times in one vCPU */
> +static void halter_guest_code(struct guest_stats *data)
> +{
> + xapic_enable();
> + data->halter_apic_id = GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
> +
> + for (;;) {
> + data->hlt_count++;
> + asm volatile("sti; hlt; cli");
> + data->wake_count++;
> + }
> +}
> +
> +static void halter_waiting_guest_code(struct guest_stats *data)
> +{
> + uint64_t tsc_start = rdtsc();
> +
> + xapic_enable();
> + data->halter_apic_id = GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
> +
> + for (;;) {
> + data->hlt_count++;
> + asm volatile("sti; hlt; cli");
> + data->wake_count++;
> + /* Wait for ~0.5sec for each HLT execution */
> + tsc_start = rdtsc();
> + while (rdtsc() - tsc_start < 2000000000);
> + }
> +}
> +
> +/* Runs on halter vCPU when IPI arrives */
> +static void guest_ipi_handler(struct ex_regs *regs)
> +{
> + xapic_write_reg(APIC_EOI, 11);
> +}
> +
> +/* Sender vCPU waits for ~1sec to assume HLT executed */
> +static void sender_wait_loop(struct guest_stats *data, uint64_t old_hlt_count,
> + uint64_t old_wake_count)
> +{
> + uint64_t tsc_start = rdtsc();
> + while (rdtsc() - tsc_start < 4000000000);
> + GUEST_ASSERT((data->wake_count != old_wake_count) &&
> + (data->hlt_count != old_hlt_count));
> +}
> +
> +/* Sender vCPU loops sending IPI to halter vCPU every ~1sec */
> +static void sender_guest_code(struct guest_stats *data)
> +{
> + uint32_t icr_val;
> + uint32_t icr2_val;
> + uint64_t old_hlt_count = 0;
> + uint64_t old_wake_count = 0;
> +
> + xapic_enable();
> + /* Init interrupt command register for sending IPIs */
> + icr_val = (APIC_DEST_PHYSICAL | APIC_DM_FIXED | IPI_VECTOR);
> + icr2_val = SET_APIC_DEST_FIELD(data->halter_apic_id);
> +
> + for (;;) {
> + /*
> + * Send IPI to halted vCPU
> + * First IPI sends here as already waited before sender vCPU
> + * thread creation
> + */
> + xapic_write_reg(APIC_ICR2, icr2_val);
> + xapic_write_reg(APIC_ICR, icr_val);
> + sender_wait_loop(data, old_hlt_count, old_wake_count);
> + GUEST_ASSERT((data->wake_count != old_wake_count) &&
> + (data->hlt_count != old_hlt_count));
> + old_wake_count = data->wake_count;
> + old_hlt_count = data->hlt_count;
> + }
> +}
> +
> +static void *vcpu_thread(void *arg)
> +{
> + struct kvm_vcpu *vcpu = (struct kvm_vcpu *)arg;
> + int old;
> + int r;
> +
> + r = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old);
> + TEST_ASSERT(r == 0,
> + "pthread_setcanceltype failed on vcpu_id=%u with errno=%d",
> + vcpu->id, r);
> + fprintf(stderr, "vCPU thread running vCPU %u\n", vcpu->id);
> + vcpu_run(vcpu);
> + return NULL;
> +}
> +
> +static void cancel_join_vcpu_thread(pthread_t thread, struct kvm_vcpu *vcpu)
> +{
> + void *retval;
> + int r;
> +
> + r = pthread_cancel(thread);
> + TEST_ASSERT(r == 0,
> + "pthread_cancel on vcpu_id=%d failed with errno=%d",
> + vcpu->id, r);
> +
> + r = pthread_join(thread, &retval);
> + TEST_ASSERT(r == 0,
> + "pthread_join on vcpu_id=%d failed with errno=%d",
> + vcpu->id, r);
> +}
> +
> +/*
> + * Test case 1:
> + * Normal VM running with one vCPU keeps executing HLTs,
> + * another vCPU sending IPIs to wake it up, should expect
> + * all HLTs exiting to host
> + */
> +static void test_vm_without_disable_exits_cap(void)
> +{
> + int r;
> + int wait_secs;
> + const int first_halter_wait = 10;
> + uint64_t kvm_halt_exits;
> + struct kvm_vm *vm;
> + struct kvm_vcpu *halter_vcpu;
> + struct kvm_vcpu *sender_vcpu;
> + struct guest_stats *data;
> + vm_vaddr_t guest_stats_page_vaddr;
> + pthread_t threads[2];
> +
> + /* Create VM */
> + vm = vm_create(2);
> +
> + /* Add vCPU with loops halting */
> + halter_vcpu = vm_vcpu_add(vm, 0, halter_guest_code);
> +
> + vm_init_descriptor_tables(vm);
> + vcpu_init_descriptor_tables(halter_vcpu);
> + vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
> + virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
> +
> + /* Add vCPU with IPIs waking up halter vCPU */
> + sender_vcpu = vm_vcpu_add(vm, 1, sender_guest_code);
> +
> + guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
> + data = addr_gva2hva(vm, guest_stats_page_vaddr);
> + memset(data, 0, sizeof(*data));
> +
> + vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
> + vcpu_args_set(sender_vcpu, 1, guest_stats_page_vaddr);
> +
> + /* Start halter vCPU thread and wait for it to execute first HLT. */
> + r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
> + TEST_ASSERT(r == 0,
> + "pthread_create halter failed errno=%d", errno);
> + fprintf(stderr, "Halter vCPU thread started\n");
> +
> + wait_secs = 0;
> + while ((wait_secs < first_halter_wait) && !data->hlt_count) {
> + sleep(1);
> + wait_secs++;
> + }
> + TEST_ASSERT(data->hlt_count,
> + "Halter vCPU did not execute first HLT within %d seconds",
> + first_halter_wait);
> + fprintf(stderr,
> + "Halter vCPU thread reported its first HLT executed "
> + "after %d seconds.\n",
> + wait_secs);
> +
> + /*
> + * After guest halter vCPU executed first HLT, start the sender
> + * vCPU thread to wakeup halter vCPU
> + */
> + r = pthread_create(&threads[1], NULL, vcpu_thread, sender_vcpu);
> + TEST_ASSERT(r == 0, "pthread_create sender failed errno=%d", errno);
> +
> + while (data->hlt_count < COUNT_HLT_EXITS);
> +
> + cancel_join_vcpu_thread(threads[0], halter_vcpu);
> + cancel_join_vcpu_thread(threads[1], sender_vcpu);
> +
> + kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
> + TEST_ASSERT(kvm_halt_exits == data->hlt_count,
> + "Halter vCPU had unmatched %lu halt exits - %lu HLTs "
> + "executed, when not disabling VM halt exits\n",
> + kvm_halt_exits, data->hlt_count);
> + fprintf(stderr, "Halter vCPU had %lu halt exits\n",
> + kvm_halt_exits);
> + fprintf(stderr, "Guest records %lu HLTs executed, "
> + "waked %lu times\n",
> + data->hlt_count, data->wake_count);
> +
> + kvm_vm_free(vm);
> +}
> +
> +/*
> + * Test case 2:
> + * VM scoped exits disabling, HLT instructions
> + * stay inside guest without exits
> + */
> +static void test_vm_disable_exits_cap(void)
> +{
> + int r;
> + uint64_t kvm_halt_exits;
> + struct kvm_vm *vm;
> + struct kvm_vcpu *halter_vcpu;
> + struct guest_stats *data;
> + vm_vaddr_t guest_stats_page_vaddr;
> + pthread_t halter_thread;
> +
> + /* Create VM */
> + vm = vm_create(1);
> +
> + /*
> + * Before adding any vCPUs, enable the KVM_X86_DISABLE_EXITS cap
> + * with flag KVM_X86_DISABLE_EXITS_HLT
> + */
> + vm_enable_cap(vm, KVM_CAP_X86_DISABLE_EXITS,
> + KVM_X86_DISABLE_EXITS_HLT);
> +
> + /* Add vCPU with loops halting */
> + halter_vcpu = vm_vcpu_add(vm, 0, halter_waiting_guest_code);
> +
> + vm_init_descriptor_tables(vm);
> + vcpu_init_descriptor_tables(halter_vcpu);
> + vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
> + virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
> +
> + guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
> + data = addr_gva2hva(vm, guest_stats_page_vaddr);
> + memset(data, 0, sizeof(*data));
> + vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
> +
> + /* Start halter vCPU thread and execute HLTs immediately */
> + r = pthread_create(&halter_thread, NULL, vcpu_thread, halter_vcpu);
> + TEST_ASSERT(r == 0,
> + "pthread_create halter failed errno=%d", errno);
> + fprintf(stderr, "Halter vCPU thread started\n");
> +
> + while (data->hlt_count < COUNT_HLT_EXITS);
> +
> + cancel_join_vcpu_thread(halter_thread, halter_vcpu);
> +
> + kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
> + TEST_ASSERT(kvm_halt_exits == 0,
> + "Halter vCPU had unexpected halt exits occuring after "
> + "disabling VM-scoped halt exits cap\n");
> + fprintf(stderr, "Halter vCPU had %lu HLT exits\n",
> + kvm_halt_exits);
> + fprintf(stderr, "Guest records %lu HLTs executed\n",
> + data->hlt_count);
> +
> + kvm_vm_free(vm);
> +}
> +
> +/*
> + * Test case 3:
> + * VM overrides exits disable flags after vCPU created,
> + * which is not allowed
> + */
> +static void test_vm_disable_exits_cap_with_vcpu_created(void)
> +{
> + int r;
> + struct kvm_vm *vm;
> + struct kvm_enable_cap cap = {
> + .cap = KVM_CAP_X86_DISABLE_EXITS,
> + .args[0] = KVM_X86_DISABLE_EXITS_HLT | KVM_X86_DISABLE_EXITS_OVERRIDE,
> + };
> +
> + /* Create VM */
> + vm = vm_create(1);
> + /* Add vCPU with loops halting */
> + vm_vcpu_add(vm, 0, halter_waiting_guest_code);
> +
> + /*
> + * After creating vCPU, the current VM-scoped ABI should
> + * discard the cap enable of KVM_CAP_X86_DISABLE_EXITS
> + * and return non-zero. Since vm_enabled_cap() not able
> + * to assert the return value, so use the __vm_ioctl()
> + */
> + r = __vm_ioctl(vm, KVM_ENABLE_CAP, &cap);
> +
> + TEST_ASSERT(r != 0,
> + "Setting VM-scoped KVM_CAP_X86_DISABLE_EXITS after "
> + "vCPUs created is not allowed, but it succeeds here\n");
> +}
> +
> +/*
> + * Test case 4:
> + * vCPU scoped halt exits disabling and enabling tests,
> + * verify overides are working after vCPU created
> + */
> +static void test_vcpu_toggling_disable_exits_cap(void)
> +{
> + int r;
> + uint64_t kvm_halt_exits;
> + struct kvm_vm *vm;
> + struct kvm_vcpu *halter_vcpu;
> + struct kvm_vcpu *sender_vcpu;
> + struct guest_stats *data;
> + vm_vaddr_t guest_stats_page_vaddr;
> + pthread_t threads[2];
> +
> + /* Create VM */
> + vm = vm_create(2);
> +
> + /* Add vCPU with loops halting */
> + halter_vcpu = vm_vcpu_add(vm, 0, halter_waiting_guest_code);
> + /* Set KVM_CAP_X86_DISABLE_EXITS_HLT for halter vCPU */
> + vcpu_enable_cap(halter_vcpu, KVM_CAP_X86_DISABLE_EXITS,
> + KVM_X86_DISABLE_EXITS_HLT | KVM_X86_DISABLE_EXITS_OVERRIDE);
> +
> + vm_init_descriptor_tables(vm);
> + vcpu_init_descriptor_tables(halter_vcpu);
> + vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
> +
> + virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
> +
> + /* Add vCPU with IPIs waking up halter vCPU */
> + sender_vcpu = vm_vcpu_add(vm, 1, sender_guest_code);
> +
> + guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
> + data = addr_gva2hva(vm, guest_stats_page_vaddr);
> + memset(data, 0, sizeof(*data));
> +
> + vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
> + vcpu_args_set(sender_vcpu, 1, guest_stats_page_vaddr);
> +
> + r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
> + TEST_ASSERT(r == 0,
> + "pthread_create halter failed errno=%d", errno);
> + fprintf(stderr, "Halter vCPU thread started with halt exits"
> + "disabled\n");
> +
> + /*
> + * For the first phase of the running, halt exits
> + * are disabled, halter vCPU executes HLT instruction
> + * but never exits to host
> + */
> + while (data->hlt_count < (COUNT_HLT_EXITS / 2));
> +
> + cancel_join_vcpu_thread(threads[0], halter_vcpu);
> + /*
> + * Override and clean KVM_CAP_X86_DISABLE_EXITS flags
> + * for halter vCPU. Expect to see halt exits occurs then.
> + */
> + vcpu_enable_cap(halter_vcpu, KVM_CAP_X86_DISABLE_EXITS,
> + KVM_X86_DISABLE_EXITS_OVERRIDE);
> +
> + r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
> + TEST_ASSERT(r == 0,
> + "pthread_create halter failed errno=%d", errno);
> + fprintf(stderr, "Halter vCPU thread restarted and cleared "
> + "halt exits flag\n");
> +
> + sleep(1);
> + /*
> + * Second phase of the test, after guest halter vCPU
> + * reenabled halt exits, start the sender
> + * vCPU thread to wakeup halter vCPU
> + */
> + r = pthread_create(&threads[1], NULL, vcpu_thread, sender_vcpu);
> + TEST_ASSERT(r == 0, "pthread_create sender failed errno=%d", errno);
> +
> + while (data->hlt_count < COUNT_HLT_EXITS);
> +
> + cancel_join_vcpu_thread(threads[0], halter_vcpu);
> + cancel_join_vcpu_thread(threads[1], sender_vcpu);
> +
> + kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
> + TEST_ASSERT(kvm_halt_exits == (COUNT_HLT_EXITS / 2),
> + "Halter vCPU had unexpected %lu halt exits, "
> + "there should be %d halt exits while "
> + "not disabling VM halt exits\n",
> + kvm_halt_exits, COUNT_HLT_EXITS / 2);
> + fprintf(stderr, "Halter vCPU had %lu halt exits\n",
> + kvm_halt_exits);
> + fprintf(stderr, "Guest records %lu HLTs executed, "
> + "waked %lu times\n",
> + data->hlt_count, data->wake_count);
> +
> + kvm_vm_free(vm);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + fprintf(stderr, "VM-scoped tests start\n");
> + test_vm_without_disable_exits_cap();
> + test_vm_disable_exits_cap();
> + test_vm_disable_exits_cap_with_vcpu_created();
> + fprintf(stderr, "vCPU-scoped test starts\n");
> + test_vcpu_toggling_disable_exits_cap();
> + return 0;
> +}

2023-01-18 21:33:56

by Kechen Lu

[permalink] [raw]
Subject: RE: [RFC PATCH v5 6/6] KVM: selftests: Add tests for VM and vCPU cap KVM_CAP_X86_DISABLE_EXITS

Hi Zhi,

My apologies, I think messed up on the test case 2 and 4 design here.
The test passed on my setup is probably because of my BIOS setting.

I will refactor this selftest.
Thanks!

BR,
Kechen

> -----Original Message-----
> From: Kechen Lu
> Sent: Wednesday, January 18, 2023 12:26 PM
> To: Zhi Wang <[email protected]>
> Cc: [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; [email protected];
> [email protected]
> Subject: RE: [RFC PATCH v5 6/6] KVM: selftests: Add tests for VM and vCPU
> cap KVM_CAP_X86_DISABLE_EXITS
>
> Hi Zhi,
>
> Thanks for testing the patch series. Comments below.
>
> > -----Original Message-----
> > From: Zhi Wang <[email protected]>
> > Sent: Wednesday, January 18, 2023 12:03 PM
> > To: Kechen Lu <[email protected]>
> > Cc: [email protected]; [email protected]; [email protected];
> > [email protected]; [email protected]; [email protected];
> > [email protected]
> > Subject: Re: [RFC PATCH v5 6/6] KVM: selftests: Add tests for VM and
> > vCPU cap KVM_CAP_X86_DISABLE_EXITS
> >
> > External email: Use caution opening links or attachments
> >
> >
> > On Fri, 13 Jan 2023 22:01:14 +0000
> > Kechen Lu <[email protected]> wrote:
> >
> > I think I figure out why this test case doesn't work:
> >
> > The 2nd case always hangs because:
> >
> > 1) Unlike the 1st case in which a halter and an IPI sender will be
> > created, there is only halter thread created in the 2nd case.
> > 2) The halter enables KVM_X86_DISABLE_EXITS_HLT. Thus, HLT will not
> > cause VMEXIT
> > 3) The halter stuck in the halter_waiting_guest_code().
> > data->hlt_count is always 1 and data->wake_count is always 0.
> > 4) In the main thread, you have test_vm_disable_exits_cap() ->
> > while (data->hlt_count < COUNT_HLT_EXITS);
> >
> > As data->hlt_count will never increase in the vcpu_thread, the main
> > thread always stuck in the while loop.
> >
> > Can you explain more about your thoughts of designing this test case?
>
> For this test case, we want to test for the VM-scoped
> KVM_CAP_X86_DISABLE_EXITS cap flags setting.
> So if we set KVM_X86_DISABLE_EXITS_HLT, there would be no halt vmexits,
> and what expect is the HLT instructions looping executed within guest halter
> vCPU thread, and not stuck here, no IPIs required to wake it up.
>
> Here is what I got for this test case running in an AMD machine.
> -------------------------------------
> Halter vCPU thread started
> vCPU thread running vCPU 0
> Halter vCPU had 0 HLT exits
> Guest records 10 HLTs executed
> -------------------------------------
>
> BR,
> Kechen
>
> >
> > > Add selftests for KVM cap KVM_CAP_X86_DISABLE_EXITS overriding
> flags
> > > in VM and vCPU scope both works as expected.
> > >
> > > Suggested-by: Chao Gao <[email protected]>
> > > Suggested-by: Shaoqin Huang <[email protected]>
> > > Signed-off-by: Kechen Lu <[email protected]>
> > > ---
> > > tools/testing/selftests/kvm/Makefile | 1 +
> > > .../selftests/kvm/x86_64/disable_exits_test.c | 457
> > > ++++++++++++++++++
> > > 2 files changed, 458 insertions(+)
> > > create mode 100644
> > > tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> > >
> > > diff --git a/tools/testing/selftests/kvm/Makefile
> > > b/tools/testing/selftests/kvm/Makefile
> > > index 1750f91dd936..eeeba35e2536 100644
> > > --- a/tools/testing/selftests/kvm/Makefile
> > > +++ b/tools/testing/selftests/kvm/Makefile
> > > @@ -114,6 +114,7 @@ TEST_GEN_PROGS_x86_64 +=
> > x86_64/sev_migrate_tests
> > > TEST_GEN_PROGS_x86_64 += x86_64/amx_test
> > > TEST_GEN_PROGS_x86_64 += x86_64/max_vcpuid_cap_test
> > > TEST_GEN_PROGS_x86_64 += x86_64/triple_fault_event_test
> > > +TEST_GEN_PROGS_x86_64 += x86_64/disable_exits_test
> > > TEST_GEN_PROGS_x86_64 += access_tracking_perf_test
> > > TEST_GEN_PROGS_x86_64 += demand_paging_test
> > > TEST_GEN_PROGS_x86_64 += dirty_log_test diff --git
> > > a/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> > > b/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> > > new file mode 100644
> > > index 000000000000..dceba3bcef5f
> > > --- /dev/null
> > > +++ b/tools/testing/selftests/kvm/x86_64/disable_exits_test.c
> > > @@ -0,0 +1,457 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +/*
> > > + * Test per-VM and per-vCPU disable exits cap
> > > + * 1) Per-VM scope
> > > + * 2) Per-vCPU scope
> > > + *
> > > + */
> > > +
> > > +#define _GNU_SOURCE /* for program_invocation_short_name */
> > #include
> > > +<pthread.h> #include <inttypes.h> #include <string.h> #include
> > > +<time.h> #include <sys/ioctl.h>
> > > +
> > > +#include "test_util.h"
> > > +#include "kvm_util.h"
> > > +#include "svm_util.h"
> > > +#include "vmx.h"
> > > +#include "processor.h"
> > > +#include "asm/kvm.h"
> > > +#include "linux/kvm.h"
> > > +
> > > +/* Arbitary chosen IPI vector value from sender to halter vCPU */
> > > +#define IPI_VECTOR 0xa5
> > > +/* Number of HLTs halter vCPU thread executes */
> > > +#define COUNT_HLT_EXITS 10
> > > +
> > > +struct guest_stats {
> > > + uint32_t halter_apic_id;
> > > + volatile uint64_t hlt_count;
> > > + volatile uint64_t wake_count;
> > > +};
> > > +
> > > +static u64 read_vcpu_stats_halt_exits(struct kvm_vcpu *vcpu) {
> > > + int i;
> > > + struct kvm_stats_header header;
> > > + u64 *stats_data;
> > > + u64 ret = 0;
> > > + struct kvm_stats_desc *stats_desc;
> > > + struct kvm_stats_desc *pdesc;
> > > + int stats_fd = vcpu_get_stats_fd(vcpu);
> > > +
> > > + read_stats_header(stats_fd, &header);
> > > + if (header.num_desc == 0) {
> > > + fprintf(stderr,
> > > + "Cannot read halt exits since no KVM stats defined\n");
> > > + return ret;
> > > + }
> > > +
> > > + stats_desc = read_stats_descriptors(stats_fd, &header);
> > > + for (i = 0; i < header.num_desc; ++i) {
> > > + pdesc = get_stats_descriptor(stats_desc, i, &header);
> > > + if (!strncmp(pdesc->name, "halt_exits", 10)) {
> > > + stats_data = malloc(pdesc->size * sizeof(*stats_data));
> > > + read_stat_data(stats_fd, &header, pdesc, stats_data,
> > > + pdesc->size);
> > > + ret = *stats_data;
> > > + free(stats_data);
> > > + break;
> > > + }
> > > + }
> > > + free(stats_desc);
> > > + return ret;
> > > +}
> > > +
> > > +/* HLT multiple times in one vCPU */ static void
> > > +halter_guest_code(struct guest_stats *data) {
> > > + xapic_enable();
> > > + data->halter_apic_id =
> > > +GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
> > > +
> > > + for (;;) {
> > > + data->hlt_count++;
> > > + asm volatile("sti; hlt; cli");
> > > + data->wake_count++;
> > > + }
> > > +}
> > > +
> > > +static void halter_waiting_guest_code(struct guest_stats *data) {
> > > + uint64_t tsc_start = rdtsc();
> > > +
> > > + xapic_enable();
> > > + data->halter_apic_id =
> > > + GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
> > > +
> > > + for (;;) {
> > > + data->hlt_count++;
> > > + asm volatile("sti; hlt; cli");
> > > + data->wake_count++;
> > > + /* Wait for ~0.5sec for each HLT execution */
> > > + tsc_start = rdtsc();
> > > + while (rdtsc() - tsc_start < 2000000000);
> > > + }
> > > +}
> > > +
> > > +/* Runs on halter vCPU when IPI arrives */ static void
> > > +guest_ipi_handler(struct ex_regs *regs) {
> > > + xapic_write_reg(APIC_EOI, 11); }
> > > +
> > > +/* Sender vCPU waits for ~1sec to assume HLT executed */ static
> > > +void sender_wait_loop(struct guest_stats *data, uint64_t
> old_hlt_count,
> > > + uint64_t old_wake_count) {
> > > + uint64_t tsc_start = rdtsc();
> > > + while (rdtsc() - tsc_start < 4000000000);
> > > + GUEST_ASSERT((data->wake_count != old_wake_count) &&
> > > + (data->hlt_count != old_hlt_count)); }
> > > +
> > > +/* Sender vCPU loops sending IPI to halter vCPU every ~1sec */
> > > +static void sender_guest_code(struct guest_stats *data) {
> > > + uint32_t icr_val;
> > > + uint32_t icr2_val;
> > > + uint64_t old_hlt_count = 0;
> > > + uint64_t old_wake_count = 0;
> > > +
> > > + xapic_enable();
> > > + /* Init interrupt command register for sending IPIs */
> > > + icr_val = (APIC_DEST_PHYSICAL | APIC_DM_FIXED | IPI_VECTOR);
> > > + icr2_val = SET_APIC_DEST_FIELD(data->halter_apic_id);
> > > +
> > > + for (;;) {
> > > + /*
> > > + * Send IPI to halted vCPU
> > > + * First IPI sends here as already waited before sender vCPU
> > > + * thread creation
> > > + */
> > > + xapic_write_reg(APIC_ICR2, icr2_val);
> > > + xapic_write_reg(APIC_ICR, icr_val);
> > > + sender_wait_loop(data, old_hlt_count, old_wake_count);
> > > + GUEST_ASSERT((data->wake_count != old_wake_count) &&
> > > + (data->hlt_count != old_hlt_count));
> > > + old_wake_count = data->wake_count;
> > > + old_hlt_count = data->hlt_count;
> > > + }
> > > +}
> > > +
> > > +static void *vcpu_thread(void *arg) {
> > > + struct kvm_vcpu *vcpu = (struct kvm_vcpu *)arg;
> > > + int old;
> > > + int r;
> > > +
> > > + r = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,
> > &old);
> > > + TEST_ASSERT(r == 0,
> > > + "pthread_setcanceltype failed on vcpu_id=%u with errno=%d",
> > > + vcpu->id, r);
> > > + fprintf(stderr, "vCPU thread running vCPU %u\n", vcpu->id);
> > > + vcpu_run(vcpu);
> > > + return NULL;
> > > +}
> > > +
> > > +static void cancel_join_vcpu_thread(pthread_t thread, struct
> > > +kvm_vcpu
> > > +*vcpu) {
> > > + void *retval;
> > > + int r;
> > > +
> > > + r = pthread_cancel(thread);
> > > + TEST_ASSERT(r == 0,
> > > + "pthread_cancel on vcpu_id=%d failed with errno=%d",
> > > + vcpu->id, r);
> > > +
> > > + r = pthread_join(thread, &retval);
> > > + TEST_ASSERT(r == 0,
> > > + "pthread_join on vcpu_id=%d failed with errno=%d",
> > > + vcpu->id, r);
> > > +}
> > > +
> > > +/*
> > > + * Test case 1:
> > > + * Normal VM running with one vCPU keeps executing HLTs,
> > > + * another vCPU sending IPIs to wake it up, should expect
> > > + * all HLTs exiting to host
> > > + */
> > > +static void test_vm_without_disable_exits_cap(void)
> > > +{
> > > + int r;
> > > + int wait_secs;
> > > + const int first_halter_wait = 10;
> > > + uint64_t kvm_halt_exits;
> > > + struct kvm_vm *vm;
> > > + struct kvm_vcpu *halter_vcpu;
> > > + struct kvm_vcpu *sender_vcpu;
> > > + struct guest_stats *data;
> > > + vm_vaddr_t guest_stats_page_vaddr;
> > > + pthread_t threads[2];
> > > +
> > > + /* Create VM */
> > > + vm = vm_create(2);
> > > +
> > > + /* Add vCPU with loops halting */
> > > + halter_vcpu = vm_vcpu_add(vm, 0, halter_guest_code);
> > > +
> > > + vm_init_descriptor_tables(vm);
> > > + vcpu_init_descriptor_tables(halter_vcpu);
> > > + vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
> > > + virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
> > > +
> > > + /* Add vCPU with IPIs waking up halter vCPU */
> > > + sender_vcpu = vm_vcpu_add(vm, 1, sender_guest_code);
> > > +
> > > + guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
> > > + data = addr_gva2hva(vm, guest_stats_page_vaddr);
> > > + memset(data, 0, sizeof(*data));
> > > +
> > > + vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
> > > + vcpu_args_set(sender_vcpu, 1, guest_stats_page_vaddr);
> > > +
> > > + /* Start halter vCPU thread and wait for it to execute first HLT. */
> > > + r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
> > > + TEST_ASSERT(r == 0,
> > > + "pthread_create halter failed errno=%d", errno);
> > > + fprintf(stderr, "Halter vCPU thread started\n");
> > > +
> > > + wait_secs = 0;
> > > + while ((wait_secs < first_halter_wait) && !data->hlt_count) {
> > > + sleep(1);
> > > + wait_secs++;
> > > + }
> > > + TEST_ASSERT(data->hlt_count,
> > > + "Halter vCPU did not execute first HLT within %d seconds",
> > > + first_halter_wait);
> > > + fprintf(stderr,
> > > + "Halter vCPU thread reported its first HLT executed "
> > > + "after %d seconds.\n",
> > > + wait_secs);
> > > +
> > > + /*
> > > + * After guest halter vCPU executed first HLT, start the sender
> > > + * vCPU thread to wakeup halter vCPU
> > > + */
> > > + r = pthread_create(&threads[1], NULL, vcpu_thread, sender_vcpu);
> > > + TEST_ASSERT(r == 0, "pthread_create sender failed errno=%d",
> > > + errno);
> > > +
> > > + while (data->hlt_count < COUNT_HLT_EXITS);
> > > +
> > > + cancel_join_vcpu_thread(threads[0], halter_vcpu);
> > > + cancel_join_vcpu_thread(threads[1], sender_vcpu);
> > > +
> > > + kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
> > > + TEST_ASSERT(kvm_halt_exits == data->hlt_count,
> > > + "Halter vCPU had unmatched %lu halt exits - %lu HLTs "
> > > + "executed, when not disabling VM halt exits\n",
> > > + kvm_halt_exits, data->hlt_count);
> > > + fprintf(stderr, "Halter vCPU had %lu halt exits\n",
> > > + kvm_halt_exits);
> > > + fprintf(stderr, "Guest records %lu HLTs executed, "
> > > + "waked %lu times\n",
> > > + data->hlt_count, data->wake_count);
> > > +
> > > + kvm_vm_free(vm);
> > > +}
> > > +
> > > +/*
> > > + * Test case 2:
> > > + * VM scoped exits disabling, HLT instructions
> > > + * stay inside guest without exits
> > > + */
> > > +static void test_vm_disable_exits_cap(void) {
> > > + int r;
> > > + uint64_t kvm_halt_exits;
> > > + struct kvm_vm *vm;
> > > + struct kvm_vcpu *halter_vcpu;
> > > + struct guest_stats *data;
> > > + vm_vaddr_t guest_stats_page_vaddr;
> > > + pthread_t halter_thread;
> > > +
> > > + /* Create VM */
> > > + vm = vm_create(1);
> > > +
> > > + /*
> > > + * Before adding any vCPUs, enable the KVM_X86_DISABLE_EXITS
> cap
> > > + * with flag KVM_X86_DISABLE_EXITS_HLT
> > > + */
> > > + vm_enable_cap(vm, KVM_CAP_X86_DISABLE_EXITS,
> > > + KVM_X86_DISABLE_EXITS_HLT);
> > > +
> > > + /* Add vCPU with loops halting */
> > > + halter_vcpu = vm_vcpu_add(vm, 0, halter_waiting_guest_code);
> > > +
> > > + vm_init_descriptor_tables(vm);
> > > + vcpu_init_descriptor_tables(halter_vcpu);
> > > + vm_install_exception_handler(vm, IPI_VECTOR, guest_ipi_handler);
> > > + virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
> > > +
> > > + guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
> > > + data = addr_gva2hva(vm, guest_stats_page_vaddr);
> > > + memset(data, 0, sizeof(*data));
> > > + vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
> > > +
> > > + /* Start halter vCPU thread and execute HLTs immediately */
> > > + r = pthread_create(&halter_thread, NULL, vcpu_thread,
> halter_vcpu);
> > > + TEST_ASSERT(r == 0,
> > > + "pthread_create halter failed errno=%d", errno);
> > > + fprintf(stderr, "Halter vCPU thread started\n");
> > > +
> > > + while (data->hlt_count < COUNT_HLT_EXITS);
> > > +
> > > + cancel_join_vcpu_thread(halter_thread, halter_vcpu);
> > > +
> > > + kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
> > > + TEST_ASSERT(kvm_halt_exits == 0,
> > > + "Halter vCPU had unexpected halt exits occuring after "
> > > + "disabling VM-scoped halt exits cap\n");
> > > + fprintf(stderr, "Halter vCPU had %lu HLT exits\n",
> > > + kvm_halt_exits);
> > > + fprintf(stderr, "Guest records %lu HLTs executed\n",
> > > + data->hlt_count);
> > > +
> > > + kvm_vm_free(vm);
> > > +}
> > > +
> > > +/*
> > > + * Test case 3:
> > > + * VM overrides exits disable flags after vCPU created,
> > > + * which is not allowed
> > > + */
> > > +static void test_vm_disable_exits_cap_with_vcpu_created(void)
> > > +{
> > > + int r;
> > > + struct kvm_vm *vm;
> > > + struct kvm_enable_cap cap = {
> > > + .cap = KVM_CAP_X86_DISABLE_EXITS,
> > > + .args[0] = KVM_X86_DISABLE_EXITS_HLT |
> > KVM_X86_DISABLE_EXITS_OVERRIDE,
> > > + };
> > > +
> > > + /* Create VM */
> > > + vm = vm_create(1);
> > > + /* Add vCPU with loops halting */
> > > + vm_vcpu_add(vm, 0, halter_waiting_guest_code);
> > > +
> > > + /*
> > > + * After creating vCPU, the current VM-scoped ABI should
> > > + * discard the cap enable of KVM_CAP_X86_DISABLE_EXITS
> > > + * and return non-zero. Since vm_enabled_cap() not able
> > > + * to assert the return value, so use the __vm_ioctl()
> > > + */
> > > + r = __vm_ioctl(vm, KVM_ENABLE_CAP, &cap);
> > > +
> > > + TEST_ASSERT(r != 0,
> > > + "Setting VM-scoped KVM_CAP_X86_DISABLE_EXITS after "
> > > + "vCPUs created is not allowed, but it succeeds
> > > +here\n"); }
> > > +
> > > +/*
> > > + * Test case 4:
> > > + * vCPU scoped halt exits disabling and enabling tests,
> > > + * verify overides are working after vCPU created */ static void
> > > +test_vcpu_toggling_disable_exits_cap(void)
> > > +{
> > > + int r;
> > > + uint64_t kvm_halt_exits;
> > > + struct kvm_vm *vm;
> > > + struct kvm_vcpu *halter_vcpu;
> > > + struct kvm_vcpu *sender_vcpu;
> > > + struct guest_stats *data;
> > > + vm_vaddr_t guest_stats_page_vaddr;
> > > + pthread_t threads[2];
> > > +
> > > + /* Create VM */
> > > + vm = vm_create(2);
> > > +
> > > + /* Add vCPU with loops halting */
> > > + halter_vcpu = vm_vcpu_add(vm, 0, halter_waiting_guest_code);
> > > + /* Set KVM_CAP_X86_DISABLE_EXITS_HLT for halter vCPU */
> > > + vcpu_enable_cap(halter_vcpu, KVM_CAP_X86_DISABLE_EXITS,
> > > + KVM_X86_DISABLE_EXITS_HLT |
> > > + KVM_X86_DISABLE_EXITS_OVERRIDE);
> > > +
> > > + vm_init_descriptor_tables(vm);
> > > + vcpu_init_descriptor_tables(halter_vcpu);
> > > + vm_install_exception_handler(vm, IPI_VECTOR,
> > > + guest_ipi_handler);
> > > +
> > > + virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
> > > +
> > > + /* Add vCPU with IPIs waking up halter vCPU */
> > > + sender_vcpu = vm_vcpu_add(vm, 1, sender_guest_code);
> > > +
> > > + guest_stats_page_vaddr = vm_vaddr_alloc_page(vm);
> > > + data = addr_gva2hva(vm, guest_stats_page_vaddr);
> > > + memset(data, 0, sizeof(*data));
> > > +
> > > + vcpu_args_set(halter_vcpu, 1, guest_stats_page_vaddr);
> > > + vcpu_args_set(sender_vcpu, 1, guest_stats_page_vaddr);
> > > +
> > > + r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
> > > + TEST_ASSERT(r == 0,
> > > + "pthread_create halter failed errno=%d", errno);
> > > + fprintf(stderr, "Halter vCPU thread started with halt exits"
> > > + "disabled\n");
> > > +
> > > + /*
> > > + * For the first phase of the running, halt exits
> > > + * are disabled, halter vCPU executes HLT instruction
> > > + * but never exits to host
> > > + */
> > > + while (data->hlt_count < (COUNT_HLT_EXITS / 2));
> > > +
> > > + cancel_join_vcpu_thread(threads[0], halter_vcpu);
> > > + /*
> > > + * Override and clean KVM_CAP_X86_DISABLE_EXITS flags
> > > + * for halter vCPU. Expect to see halt exits occurs then.
> > > + */
> > > + vcpu_enable_cap(halter_vcpu, KVM_CAP_X86_DISABLE_EXITS,
> > > + KVM_X86_DISABLE_EXITS_OVERRIDE);
> > > +
> > > + r = pthread_create(&threads[0], NULL, vcpu_thread, halter_vcpu);
> > > + TEST_ASSERT(r == 0,
> > > + "pthread_create halter failed errno=%d", errno);
> > > + fprintf(stderr, "Halter vCPU thread restarted and cleared "
> > > + "halt exits flag\n");
> > > +
> > > + sleep(1);
> > > + /*
> > > + * Second phase of the test, after guest halter vCPU
> > > + * reenabled halt exits, start the sender
> > > + * vCPU thread to wakeup halter vCPU
> > > + */
> > > + r = pthread_create(&threads[1], NULL, vcpu_thread, sender_vcpu);
> > > + TEST_ASSERT(r == 0, "pthread_create sender failed errno=%d",
> > > + errno);
> > > +
> > > + while (data->hlt_count < COUNT_HLT_EXITS);
> > > +
> > > + cancel_join_vcpu_thread(threads[0], halter_vcpu);
> > > + cancel_join_vcpu_thread(threads[1], sender_vcpu);
> > > +
> > > + kvm_halt_exits = read_vcpu_stats_halt_exits(halter_vcpu);
> > > + TEST_ASSERT(kvm_halt_exits == (COUNT_HLT_EXITS / 2),
> > > + "Halter vCPU had unexpected %lu halt exits, "
> > > + "there should be %d halt exits while "
> > > + "not disabling VM halt exits\n",
> > > + kvm_halt_exits, COUNT_HLT_EXITS / 2);
> > > + fprintf(stderr, "Halter vCPU had %lu halt exits\n",
> > > + kvm_halt_exits);
> > > + fprintf(stderr, "Guest records %lu HLTs executed, "
> > > + "waked %lu times\n",
> > > + data->hlt_count, data->wake_count);
> > > +
> > > + kvm_vm_free(vm);
> > > +}
> > > +
> > > +int main(int argc, char *argv[])
> > > +{
> > > + fprintf(stderr, "VM-scoped tests start\n");
> > > + test_vm_without_disable_exits_cap();
> > > + test_vm_disable_exits_cap();
> > > + test_vm_disable_exits_cap_with_vcpu_created();
> > > + fprintf(stderr, "vCPU-scoped test starts\n");
> > > + test_vcpu_toggling_disable_exits_cap();
> > > + return 0;
> > > +}