diff mbox series

[kvm-unit-tests,PATCHv2] x86: nSVM: Add support for VNMI test

Message ID 20220810061924.1418-1-santosh.shukla@amd.com (mailing list archive)
State New, archived
Headers show
Series [kvm-unit-tests,PATCHv2] x86: nSVM: Add support for VNMI test | expand

Commit Message

Shukla, Santosh Aug. 10, 2022, 6:19 a.m. UTC
Add a VNMI test case to test Virtual NMI in a nested environment,
The test covers the Virtual NMI (VNMI) delivery.

Signed-off-by: Santosh Shukla <santosh.shukla@amd.com>
---
v2:
- Added test to test NMI_INTERCEPT unset case.
- Rebased on tip.

v1:
- https://www.spinics.net/lists/kvm/msg279165.html

 lib/x86/processor.h |  2 +-
 x86/svm.c           |  5 +++
 x86/svm.h           |  8 ++++
 x86/svm_tests.c     | 90 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 104 insertions(+), 1 deletion(-)

Comments

Sean Christopherson April 5, 2023, 8:45 p.m. UTC | #1
On Wed, Aug 10, 2022, Santosh Shukla wrote:
> Add a VNMI test case to test Virtual NMI in a nested environment,
> The test covers the Virtual NMI (VNMI) delivery.
> 
> Signed-off-by: Santosh Shukla <santosh.shukla@amd.com>
> ---

Quite a few comments, but I'll post a v3 with everything cleaned up.  More below.

> diff --git a/x86/svm.h b/x86/svm.h
> index 766ff7e36449..91a0dee2c864 100644
> --- a/x86/svm.h
> +++ b/x86/svm.h
> @@ -131,6 +131,13 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
>  #define V_INTR_MASKING_SHIFT 24
>  #define V_INTR_MASKING_MASK (1 << V_INTR_MASKING_SHIFT)
>  
> +#define V_NMI_PENDING_SHIFT 11
> +#define V_NMI_PENDING (1 << V_NMI_PENDING_SHIFT)
> +#define V_NMI_MASK_SHIFT 12
> +#define V_NMI_MASK (1 << V_NMI_MASK_SHIFT)
> +#define V_NMI_ENABLE_SHIFT 26
> +#define V_NMI_ENABLE (1 << V_NMI_ENABLE_SHIFT)

Same complaints as I had for the kernel side, e.g. VM_NMI_MASK vs. V_NMI_BLOCKING_MASK.

> +
>  #define SVM_INTERRUPT_SHADOW_MASK 1
>  
>  #define SVM_IOIO_STR_SHIFT 2
> @@ -419,6 +426,7 @@ void default_prepare(struct svm_test *test);
>  void default_prepare_gif_clear(struct svm_test *test);
>  bool default_finished(struct svm_test *test);
>  bool npt_supported(void);
> +bool vnmi_supported(void);
>  int get_test_stage(struct svm_test *test);
>  void set_test_stage(struct svm_test *test, int s);
>  void inc_test_stage(struct svm_test *test);
> diff --git a/x86/svm_tests.c b/x86/svm_tests.c
> index e2ec9541fd29..f83a2b56ce52 100644
> --- a/x86/svm_tests.c
> +++ b/x86/svm_tests.c
> @@ -1445,6 +1445,93 @@ static bool nmi_hlt_check(struct svm_test *test)
>  	return get_test_stage(test) == 3;
>  }
>  
> +static volatile bool vnmi_fired;

There's no need to use a separate flag, just reuse nmi_fired.  And then this
test can also reuse nmi_prepare().

> +static void vnmi_handler(isr_regs_t *regs)
> +{
> +    vnmi_fired = true;

Use tabs, not spaces.

> +}
> +
> +static void vnmi_prepare(struct svm_test *test)
> +{
> +    default_prepare(test);
> +    vnmi_fired = false;
> +    vmcb->control.int_ctl = V_NMI_ENABLE;
> +    vmcb->control.int_vector = NMI_VECTOR;
> +    handle_irq(NMI_VECTOR, vnmi_handler);
> +    set_test_stage(test, 0);
> +}
> +
> +static void vnmi_test(struct svm_test *test)
> +{
> +    if (vnmi_fired) {
> +        report(!vnmi_fired, "vNMI dispatched even before injection");
> +        set_test_stage(test, -1);

Ugh, so much copy+paste in the test.  I'll add a patch to provide a macro to dedup
the report_fail() + set_test_stage() + vmmcall(), the last of which is unnecessarily
dependent on non-failing code in many cases.

> +static bool vnmi_finished(struct svm_test *test)
> +{
> +    switch (get_test_stage(test)) {
> +    case 0:
> +        if (vmcb->control.exit_code != SVM_EXIT_ERR) {

Took me a bit of staring to understand why an error is expected.  The setup path
really needs a comment explaining that it deliberately creates a bad configuration,
and it should explicitly clear INTERCEPT_NMI.

> +            report_fail("VMEXIT not due to error. Exit reason 0x%x",
> +                        vmcb->control.exit_code);
> +            return true;
> +        }
> +        report(!vnmi_fired, "vNMI enabled but NMI_INTERCEPT unset!");
> +        vmcb->control.intercept |= (1ULL << INTERCEPT_NMI);
> +        vmcb->save.rip += 3;
> +        break;
> +
> +    case 1:
> +        if (vmcb->control.exit_code != SVM_EXIT_VMMCALL) {
> +            report_fail("VMEXIT not due to error. Exit reason 0x%x",

VMMCALL expected, not ERR.
diff mbox series

Patch

diff --git a/lib/x86/processor.h b/lib/x86/processor.h
index 0324220630c6..e514abd2e6a3 100644
--- a/lib/x86/processor.h
+++ b/lib/x86/processor.h
@@ -265,7 +265,7 @@  static inline bool is_intel(void)
 #define X86_FEATURE_PAUSEFILTER		(CPUID(0x8000000A, 0, EDX, 10))
 #define X86_FEATURE_PFTHRESHOLD		(CPUID(0x8000000A, 0, EDX, 12))
 #define	X86_FEATURE_VGIF		(CPUID(0x8000000A, 0, EDX, 16))
-
+#define X86_FEATURE_V_NMI               (CPUID(0x8000000A, 0, EDX, 25))
 
 static inline bool this_cpu_has(u64 feature)
 {
diff --git a/x86/svm.c b/x86/svm.c
index ba435b4ac3af..022a0fde4336 100644
--- a/x86/svm.c
+++ b/x86/svm.c
@@ -99,6 +99,11 @@  bool npt_supported(void)
 	return this_cpu_has(X86_FEATURE_NPT);
 }
 
+bool vnmi_supported(void)
+{
+       return this_cpu_has(X86_FEATURE_V_NMI);
+}
+
 int get_test_stage(struct svm_test *test)
 {
 	barrier();
diff --git a/x86/svm.h b/x86/svm.h
index 766ff7e36449..91a0dee2c864 100644
--- a/x86/svm.h
+++ b/x86/svm.h
@@ -131,6 +131,13 @@  struct __attribute__ ((__packed__)) vmcb_control_area {
 #define V_INTR_MASKING_SHIFT 24
 #define V_INTR_MASKING_MASK (1 << V_INTR_MASKING_SHIFT)
 
+#define V_NMI_PENDING_SHIFT 11
+#define V_NMI_PENDING (1 << V_NMI_PENDING_SHIFT)
+#define V_NMI_MASK_SHIFT 12
+#define V_NMI_MASK (1 << V_NMI_MASK_SHIFT)
+#define V_NMI_ENABLE_SHIFT 26
+#define V_NMI_ENABLE (1 << V_NMI_ENABLE_SHIFT)
+
 #define SVM_INTERRUPT_SHADOW_MASK 1
 
 #define SVM_IOIO_STR_SHIFT 2
@@ -419,6 +426,7 @@  void default_prepare(struct svm_test *test);
 void default_prepare_gif_clear(struct svm_test *test);
 bool default_finished(struct svm_test *test);
 bool npt_supported(void);
+bool vnmi_supported(void);
 int get_test_stage(struct svm_test *test);
 void set_test_stage(struct svm_test *test, int s);
 void inc_test_stage(struct svm_test *test);
diff --git a/x86/svm_tests.c b/x86/svm_tests.c
index e2ec9541fd29..f83a2b56ce52 100644
--- a/x86/svm_tests.c
+++ b/x86/svm_tests.c
@@ -1445,6 +1445,93 @@  static bool nmi_hlt_check(struct svm_test *test)
 	return get_test_stage(test) == 3;
 }
 
+static volatile bool vnmi_fired;
+static void vnmi_handler(isr_regs_t *regs)
+{
+    vnmi_fired = true;
+}
+
+static void vnmi_prepare(struct svm_test *test)
+{
+    default_prepare(test);
+    vnmi_fired = false;
+    vmcb->control.int_ctl = V_NMI_ENABLE;
+    vmcb->control.int_vector = NMI_VECTOR;
+    handle_irq(NMI_VECTOR, vnmi_handler);
+    set_test_stage(test, 0);
+}
+
+static void vnmi_test(struct svm_test *test)
+{
+    if (vnmi_fired) {
+        report(!vnmi_fired, "vNMI dispatched even before injection");
+        set_test_stage(test, -1);
+    }
+
+    vmmcall();
+
+    if (!vnmi_fired) {
+        report(vnmi_fired, "pending vNMI not dispatched");
+        set_test_stage(test, -1);
+    }
+    report(vnmi_fired, "vNMI delivered to guest");
+
+    vmmcall();
+}
+
+static bool vnmi_finished(struct svm_test *test)
+{
+    switch (get_test_stage(test)) {
+    case 0:
+        if (vmcb->control.exit_code != SVM_EXIT_ERR) {
+            report_fail("VMEXIT not due to error. Exit reason 0x%x",
+                        vmcb->control.exit_code);
+            return true;
+        }
+        report(!vnmi_fired, "vNMI enabled but NMI_INTERCEPT unset!");
+        vmcb->control.intercept |= (1ULL << INTERCEPT_NMI);
+        vmcb->save.rip += 3;
+        break;
+
+    case 1:
+        if (vmcb->control.exit_code != SVM_EXIT_VMMCALL) {
+            report_fail("VMEXIT not due to error. Exit reason 0x%x",
+                        vmcb->control.exit_code);
+            return true;
+        }
+        report(!vnmi_fired, "vNMI with vector 2 not injected");
+        vmcb->control.int_ctl |= V_NMI_PENDING;
+        vmcb->save.rip += 3;
+        break;
+
+    case 2:
+        if (vmcb->control.exit_code != SVM_EXIT_VMMCALL) {
+            report_fail("VMEXIT not due to vmmcall. Exit reason 0x%x",
+                        vmcb->control.exit_code);
+            return true;
+        }
+        if (vmcb->control.int_ctl & V_NMI_MASK) {
+            report_fail("V_NMI_MASK not cleared on VMEXIT");
+            return true;
+        }
+        report_pass("VNMI serviced");
+        vmcb->save.rip += 3;
+        break;
+
+    default:
+        return true;
+    }
+
+    inc_test_stage(test);
+
+    return get_test_stage(test) == 3;
+}
+
+static bool vnmi_check(struct svm_test *test)
+{
+    return get_test_stage(test) == 3;
+}
+
 static volatile int count_exc = 0;
 
 static void my_isr(struct ex_regs *r)
@@ -3363,6 +3450,9 @@  struct svm_test svm_tests[] = {
 	{ "nmi_hlt", smp_supported, nmi_prepare,
 	  default_prepare_gif_clear, nmi_hlt_test,
 	  nmi_hlt_finished, nmi_hlt_check },
+        { "vnmi", vnmi_supported, vnmi_prepare,
+          default_prepare_gif_clear, vnmi_test,
+          vnmi_finished, vnmi_check },
 	{ "virq_inject", default_supported, virq_inject_prepare,
 	  default_prepare_gif_clear, virq_inject_test,
 	  virq_inject_finished, virq_inject_check },