* [PATCH v6 1/8] KVM: x86: Provide INVLPG linear address to intercept handlers
2026-09-13 6:43 [PATCH v6 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
@ 2026-09-13 6:43 ` Tina Zhang
2026-09-14 18:23 ` Jim Mattson
2026-09-13 6:43 ` [PATCH v6 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts Tina Zhang
` (6 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Tina Zhang @ 2026-09-13 6:43 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest, linux-kernel
INVLPG's memory operand is decoded with NoAccess, and thus src_val does
not contain the operand address. Intercept handlers therefore cannot
construct exit state that reports the linear address from the existing
x86_instruction_info fields.
Add get_invlpg_linear_addr() to compute the address through __linearize(),
using the same flags as em_invlpg(), and pass the result through
x86_instruction_info.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/emulate.c | 18 ++++++++++++++++++
arch/x86/kvm/kvm_emulate.h | 1 +
2 files changed, 19 insertions(+)
diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index 8071b372d233..1dfece6af81e 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -410,6 +410,9 @@ static int em_salc(struct x86_emulate_ctxt *ctxt)
_fault ? X86EMUL_UNHANDLEABLE : X86EMUL_CONTINUE; \
})
+static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
+ enum x86_intercept intercept);
+
static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
enum x86_intercept intercept,
enum x86_intercept_stage stage)
@@ -427,6 +430,7 @@ static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
.src_type = ctxt->src.type,
.dst_type = ctxt->dst.type,
.ad_bytes = ctxt->ad_bytes,
+ .invlpg_linear_addr = get_invlpg_linear_addr(ctxt, intercept),
.rip = ctxt->eip,
.next_rip = ctxt->_eip,
};
@@ -702,6 +706,20 @@ static __always_inline int __linearize(struct x86_emulate_ctxt *ctxt,
return emulate_gp(ctxt, 0);
}
+static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
+ enum x86_intercept intercept)
+{
+ unsigned int max_size;
+ unsigned long linear = 0;
+
+ if (intercept != x86_intercept_invlpg)
+ return 0;
+
+ __linearize(ctxt, ctxt->src.addr.mem, &max_size, 1, ctxt->mode,
+ &linear, X86EMUL_F_INVLPG);
+ return linear;
+}
+
static int linearize(struct x86_emulate_ctxt *ctxt,
struct segmented_address addr,
unsigned size, bool write,
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index 3e375af15c03..d05558ef28ae 100644
--- a/arch/x86/kvm/kvm_emulate.h
+++ b/arch/x86/kvm/kvm_emulate.h
@@ -51,6 +51,7 @@ struct x86_instruction_info {
u8 src_type; /* type of source operand */
u8 dst_type; /* type of destination operand */
u8 ad_bytes; /* size of src/dst address */
+ u64 invlpg_linear_addr; /* linear address, if INVLPG */
u64 rip; /* rip of the instruction */
u64 next_rip; /* rip following the instruction */
};
--
2.43.7
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v6 1/8] KVM: x86: Provide INVLPG linear address to intercept handlers
2026-09-13 6:43 ` [PATCH v6 1/8] KVM: x86: Provide INVLPG linear address to intercept handlers Tina Zhang
@ 2026-09-14 18:23 ` Jim Mattson
2026-09-16 0:25 ` Tina Zhang
0 siblings, 1 reply; 18+ messages in thread
From: Jim Mattson @ 2026-09-14 18:23 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sat, Sep 12, 2026 at 11:44 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> INVLPG's memory operand is decoded with NoAccess, and thus src_val does
> not contain the operand address. Intercept handlers therefore cannot
> construct exit state that reports the linear address from the existing
> x86_instruction_info fields.
>
> Add get_invlpg_linear_addr() to compute the address through __linearize(),
> using the same flags as em_invlpg(), and pass the result through
> x86_instruction_info.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> ---
> arch/x86/kvm/emulate.c | 18 ++++++++++++++++++
> arch/x86/kvm/kvm_emulate.h | 1 +
> 2 files changed, 19 insertions(+)
>
> diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
> index 8071b372d233..1dfece6af81e 100644
> --- a/arch/x86/kvm/emulate.c
> +++ b/arch/x86/kvm/emulate.c
> @@ -410,6 +410,9 @@ static int em_salc(struct x86_emulate_ctxt *ctxt)
> _fault ? X86EMUL_UNHANDLEABLE : X86EMUL_CONTINUE; \
> })
>
> +static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
> + enum x86_intercept intercept);
> +
Nit: I would move __linearize() rather than add a forward declaration,
but maybe it all unravels if you pull on that thread.
> static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
> enum x86_intercept intercept,
> enum x86_intercept_stage stage)
> @@ -427,6 +430,7 @@ static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
> .src_type = ctxt->src.type,
> .dst_type = ctxt->dst.type,
> .ad_bytes = ctxt->ad_bytes,
> + .invlpg_linear_addr = get_invlpg_linear_addr(ctxt, intercept),
> .rip = ctxt->eip,
> .next_rip = ctxt->_eip,
> };
> @@ -702,6 +706,20 @@ static __always_inline int __linearize(struct x86_emulate_ctxt *ctxt,
> return emulate_gp(ctxt, 0);
> }
>
> +static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
> + enum x86_intercept intercept)
> +{
> + unsigned int max_size;
> + unsigned long linear = 0;
> +
> + if (intercept != x86_intercept_invlpg)
> + return 0;
> +
> + __linearize(ctxt, ctxt->src.addr.mem, &max_size, 1, ctxt->mode,
> + &linear, X86EMUL_F_INVLPG);
What if __linearize() fails?
> + return linear;
> +}
> +
> static int linearize(struct x86_emulate_ctxt *ctxt,
> struct segmented_address addr,
> unsigned size, bool write,
> diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
> index 3e375af15c03..d05558ef28ae 100644
> --- a/arch/x86/kvm/kvm_emulate.h
> +++ b/arch/x86/kvm/kvm_emulate.h
> @@ -51,6 +51,7 @@ struct x86_instruction_info {
> u8 src_type; /* type of source operand */
> u8 dst_type; /* type of destination operand */
> u8 ad_bytes; /* size of src/dst address */
> + u64 invlpg_linear_addr; /* linear address, if INVLPG */
> u64 rip; /* rip of the instruction */
> u64 next_rip; /* rip following the instruction */
> };
> --
> 2.43.7
>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v6 1/8] KVM: x86: Provide INVLPG linear address to intercept handlers
2026-09-14 18:23 ` Jim Mattson
@ 2026-09-16 0:25 ` Tina Zhang
2026-09-16 19:06 ` Jim Mattson
0 siblings, 1 reply; 18+ messages in thread
From: Tina Zhang @ 2026-09-16 0:25 UTC (permalink / raw)
To: Jim Mattson
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On 9/15/2026 2:23 AM, Jim Mattson wrote:
> On Sat, Sep 12, 2026 at 11:44 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>>
>> INVLPG's memory operand is decoded with NoAccess, and thus src_val does
>> not contain the operand address. Intercept handlers therefore cannot
>> construct exit state that reports the linear address from the existing
>> x86_instruction_info fields.
>>
>> Add get_invlpg_linear_addr() to compute the address through __linearize(),
>> using the same flags as em_invlpg(), and pass the result through
>> x86_instruction_info.
>>
>> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
>> ---
>> arch/x86/kvm/emulate.c | 18 ++++++++++++++++++
>> arch/x86/kvm/kvm_emulate.h | 1 +
>> 2 files changed, 19 insertions(+)
>>
>> diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
>> index 8071b372d233..1dfece6af81e 100644
>> --- a/arch/x86/kvm/emulate.c
>> +++ b/arch/x86/kvm/emulate.c
>> @@ -410,6 +410,9 @@ static int em_salc(struct x86_emulate_ctxt *ctxt)
>> _fault ? X86EMUL_UNHANDLEABLE : X86EMUL_CONTINUE; \
>> })
>>
>> +static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
>> + enum x86_intercept intercept);
>> +
> Nit: I would move __linearize() rather than add a forward declaration,
> but maybe it all unravels if you pull on that thread.
>
>> static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
>> enum x86_intercept intercept,
>> enum x86_intercept_stage stage)
>> @@ -427,6 +430,7 @@ static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
>> .src_type = ctxt->src.type,
>> .dst_type = ctxt->dst.type,
>> .ad_bytes = ctxt->ad_bytes,
>> + .invlpg_linear_addr = get_invlpg_linear_addr(ctxt, intercept),
>> .rip = ctxt->eip,
>> .next_rip = ctxt->_eip,
>> };
>> @@ -702,6 +706,20 @@ static __always_inline int __linearize(struct x86_emulate_ctxt *ctxt,
>> return emulate_gp(ctxt, 0);
>> }
>>
>> +static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
>> + enum x86_intercept intercept)
>> +{
>> + unsigned int max_size;
>> + unsigned long linear = 0;
>> +
>> + if (intercept != x86_intercept_invlpg)
>> + return 0;
>> +
>> + __linearize(ctxt, ctxt->src.addr.mem, &max_size, 1, ctxt->mode,
>> + &linear, X86EMUL_F_INVLPG);
>
> What if __linearize() fails?
This was also my concern with using __linearize() in v5, which is why I
initially calculated the address directly. I wasn't sure how to handle
its failures when we only need the address for EXITINFO1.
Simply propagating the error seems questionable, since em_invlpg()
itself skips the invalidation on failure and returns X86EMUL_CONTINUE.
Would either of these approaches make sense?
Option 1: Factor the address calculation out into a helper shared by
__linearize() and get_invlpg_linear_addr(), leaving the checks in
__linearize().
Option 2: Extend the existing flags argument to __linearize() with an
option to calculate the address without performing access checks.
Both would avoid duplicating the address calculation or modifying
ctxt->exception just to obtain the address for EXITINFO1.
Do you have a preference, or another approach in mind?
Thanks,
Tina
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v6 1/8] KVM: x86: Provide INVLPG linear address to intercept handlers
2026-09-16 0:25 ` Tina Zhang
@ 2026-09-16 19:06 ` Jim Mattson
0 siblings, 0 replies; 18+ messages in thread
From: Jim Mattson @ 2026-09-16 19:06 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Tue, Sep 15, 2026 at 5:26 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
>
>
> On 9/15/2026 2:23 AM, Jim Mattson wrote:
> > What if __linearize() fails?
>
> This was also my concern with using __linearize() in v5, which is why I
> initially calculated the address directly. I wasn't sure how to handle
> its failures when we only need the address for EXITINFO1.
>
> Simply propagating the error seems questionable, since em_invlpg()
> itself skips the invalidation on failure and returns X86EMUL_CONTINUE.
>
> Would either of these approaches make sense?
> Option 1: Factor the address calculation out into a helper shared by
> __linearize() and get_invlpg_linear_addr(), leaving the checks in
> __linearize().
>
> Option 2: Extend the existing flags argument to __linearize() with an
> option to calculate the address without performing access checks.
>
> Both would avoid duplicating the address calculation or modifying
> ctxt->exception just to obtain the address for EXITINFO1.
>
> Do you have a preference, or another approach in mind?
I think a comment should suffice.
EXITINFO1 should be the address that INVLPG would have used if it hadn't
been intercepted, and the address that INVLPG would have used is exactly
what __linearize() computes--even on the 'bad:' path.
I know it looks like I asked the exception-return question as a "test," but
I actually just realized the additional benefit of my suggestion to use
__linearize() here rather than open-coding an alternative: em_invlpg()
makes an identical call, so the two must agree.
The APM, as usual, doesn't really cover the details of the corner cases,
but on the Intel side, the SDM says this about the exit qualification field
for INVLPG intercepts:
If the INVLPG source operand specifies an unusable segment, the linear
address specified in the exit qualification will match the linear address
that the INVLPG would have used if no VM exit occurred. This address is
not architecturally defined and may be implementation-specific.
That's just one of the scenarios that leads to the __linearize() 'bad:'
path, but I think the same logic must apply to the other cases.
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts
2026-09-13 6:43 [PATCH v6 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
2026-09-13 6:43 ` [PATCH v6 1/8] KVM: x86: Provide INVLPG linear address to intercept handlers Tina Zhang
@ 2026-09-13 6:43 ` Tina Zhang
2026-09-14 18:59 ` Jim Mattson
2026-09-13 6:43 ` [PATCH v6 3/8] KVM: nSVM: Track valid hardware DecodeAssist bytes Tina Zhang
` (5 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Tina Zhang @ 2026-09-13 6:43 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest, linux-kernel
When the x86 emulator encounters an instruction intercepted by L1,
svm_check_intercept() synthesizes a nested VM-Exit without fresh hardware
DecodeAssist state. Populate the architectural EXITINFO1 field when
DecodeAssists is exposed to L1.
Provide the GPR number for MOV CR/DR, the interrupt vector for INTn, and
the linear address for INVLPG. Leave EXITINFO1 clear for CLTS, LMSW,
SMSW, selective CR0 writes, and INVLPGA; the INVLPGA address remains in
guest rAX. Leave EXITINFO1 unchanged when DecodeAssists is not exposed,
and leave unrelated intercepts unchanged.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/svm/svm.c | 32 +++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index ea647938a2a6..6834e1101bb6 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4837,6 +4837,7 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
int vmexit, ret = X86EMUL_CONTINUE;
struct __x86_intercept icpt_info;
struct vmcb *vmcb = svm->vmcb;
+ bool decode_assists;
if (info->intercept >= ARRAY_SIZE(x86_intercept_map))
goto out;
@@ -4846,14 +4847,26 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
if (stage != icpt_info.stage)
goto out;
+ decode_assists = guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS);
+
switch (icpt_info.exit_code) {
case SVM_EXIT_READ_CR0:
if (info->intercept == x86_intercept_cr_read)
icpt_info.exit_code += info->modrm_reg;
+
+ if (decode_assists)
+ vmcb->control.exit_info_1 =
+ info->intercept == x86_intercept_cr_read ?
+ BIT_ULL(63) | (info->modrm_rm & 0xf) : 0;
break;
case SVM_EXIT_WRITE_CR0: {
unsigned long cr0, val;
+ if (decode_assists)
+ vmcb->control.exit_info_1 =
+ info->intercept == x86_intercept_cr_write ?
+ BIT_ULL(63) | (info->modrm_rm & 0xf) : 0;
+
/*
* Adjust the exit code accordingly if a CR other than CR0 is
* being written, and skip straight to the common handling as
@@ -4891,13 +4904,30 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
*/
cr0 = vcpu->arch.cr0 & ~SVM_CR0_SELECTIVE_MASK;
val = info->src_val & ~SVM_CR0_SELECTIVE_MASK;
- if (cr0 ^ val)
+ if (cr0 ^ val) {
icpt_info.exit_code = SVM_EXIT_CR0_SEL_WRITE;
+ if (decode_assists)
+ vmcb->control.exit_info_1 = 0;
+ }
break;
}
case SVM_EXIT_READ_DR0:
case SVM_EXIT_WRITE_DR0:
icpt_info.exit_code += info->modrm_reg;
+ if (decode_assists)
+ vmcb->control.exit_info_1 = info->modrm_rm & 0xf;
+ break;
+ case SVM_EXIT_SWINT:
+ if (decode_assists)
+ vmcb->control.exit_info_1 = info->src_val & 0xff;
+ break;
+ case SVM_EXIT_INVLPG:
+ if (decode_assists)
+ vmcb->control.exit_info_1 = info->invlpg_linear_addr;
+ break;
+ case SVM_EXIT_INVLPGA:
+ if (decode_assists)
+ vmcb->control.exit_info_1 = 0;
break;
case SVM_EXIT_MSR:
if (info->intercept == x86_intercept_wrmsr)
--
2.43.7
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v6 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts
2026-09-13 6:43 ` [PATCH v6 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts Tina Zhang
@ 2026-09-14 18:59 ` Jim Mattson
0 siblings, 0 replies; 18+ messages in thread
From: Jim Mattson @ 2026-09-14 18:59 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sat, Sep 12, 2026 at 11:44 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> When the x86 emulator encounters an instruction intercepted by L1,
> svm_check_intercept() synthesizes a nested VM-Exit without fresh hardware
> DecodeAssist state. Populate the architectural EXITINFO1 field when
> DecodeAssists is exposed to L1.
>
> Provide the GPR number for MOV CR/DR, the interrupt vector for INTn, and
> the linear address for INVLPG. Leave EXITINFO1 clear for CLTS, LMSW,
> SMSW, selective CR0 writes, and INVLPGA; the INVLPGA address remains in
> guest rAX. Leave EXITINFO1 unchanged when DecodeAssists is not exposed,
> and leave unrelated intercepts unchanged.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> ---
> arch/x86/kvm/svm/svm.c | 32 +++++++++++++++++++++++++++++++-
> 1 file changed, 31 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
> index ea647938a2a6..6834e1101bb6 100644
> --- a/arch/x86/kvm/svm/svm.c
> +++ b/arch/x86/kvm/svm/svm.c
> @@ -4837,6 +4837,7 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
> int vmexit, ret = X86EMUL_CONTINUE;
> struct __x86_intercept icpt_info;
> struct vmcb *vmcb = svm->vmcb;
> + bool decode_assists;
>
> if (info->intercept >= ARRAY_SIZE(x86_intercept_map))
> goto out;
> @@ -4846,14 +4847,26 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
> if (stage != icpt_info.stage)
> goto out;
>
> + decode_assists = guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS);
> +
> switch (icpt_info.exit_code) {
> case SVM_EXIT_READ_CR0:
> if (info->intercept == x86_intercept_cr_read)
> icpt_info.exit_code += info->modrm_reg;
> +
> + if (decode_assists)
> + vmcb->control.exit_info_1 =
> + info->intercept == x86_intercept_cr_read ?
> + BIT_ULL(63) | (info->modrm_rm & 0xf) : 0;
Nit: CR_VALID | (info->modrm_rm & SVM_EXITINFO_REG_MASK)
> break;
> case SVM_EXIT_WRITE_CR0: {
> unsigned long cr0, val;
>
> + if (decode_assists)
> + vmcb->control.exit_info_1 =
> + info->intercept == x86_intercept_cr_write ?
> + BIT_ULL(63) | (info->modrm_rm & 0xf) : 0;
Same nit.
> +
> /*
> * Adjust the exit code accordingly if a CR other than CR0 is
> * being written, and skip straight to the common handling as
> @@ -4891,13 +4904,30 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
> */
> cr0 = vcpu->arch.cr0 & ~SVM_CR0_SELECTIVE_MASK;
> val = info->src_val & ~SVM_CR0_SELECTIVE_MASK;
> - if (cr0 ^ val)
> + if (cr0 ^ val) {
> icpt_info.exit_code = SVM_EXIT_CR0_SEL_WRITE;
> + if (decode_assists)
> + vmcb->control.exit_info_1 = 0;
> + }
I don't think clearing EXITINFO1 here is correct. The APM says:
> MOV-to-CR0 Special Case. If the instruction is MOV-to-CR, the GPR number is provided. If the instruction is LMSW or CLTS, no additional information is provided and bit 63 is not set.
The Decode Assists depend on the instruction opcode, not on the intercept.
> break;
> }
> case SVM_EXIT_READ_DR0:
> case SVM_EXIT_WRITE_DR0:
> icpt_info.exit_code += info->modrm_reg;
> + if (decode_assists)
> + vmcb->control.exit_info_1 = info->modrm_rm & 0xf;
Nit: SVM_EXITINFO_REG_MASK
> + break;
> + case SVM_EXIT_SWINT:
> + if (decode_assists)
> + vmcb->control.exit_info_1 = info->src_val & 0xff;
> + break;
> + case SVM_EXIT_INVLPG:
> + if (decode_assists)
> + vmcb->control.exit_info_1 = info->invlpg_linear_addr;
> + break;
> + case SVM_EXIT_INVLPGA:
> + if (decode_assists)
> + vmcb->control.exit_info_1 = 0;
> break;
> case SVM_EXIT_MSR:
> if (info->intercept == x86_intercept_wrmsr)
> --
> 2.43.7
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 3/8] KVM: nSVM: Track valid hardware DecodeAssist bytes
2026-09-13 6:43 [PATCH v6 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
2026-09-13 6:43 ` [PATCH v6 1/8] KVM: x86: Provide INVLPG linear address to intercept handlers Tina Zhang
2026-09-13 6:43 ` [PATCH v6 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts Tina Zhang
@ 2026-09-13 6:43 ` Tina Zhang
2026-09-14 19:17 ` Jim Mattson
2026-09-13 6:43 ` [PATCH v6 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12 Tina Zhang
` (4 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Tina Zhang @ 2026-09-13 6:43 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest, linux-kernel
VMCB02 instruction bytes are valid only for the hardware VM-Exit that
populated them. Track whether VMCB02 contains instruction bytes for the
data #PF or #NPF currently being reflected to L1 so that stale bytes are
not copied to VMCB12.
Invalidate VMCB02's instruction-byte state when preparing VMCB02,
normally in response to an emulated VMRUN. Mark the bytes as valid only
when the current VMCB02 exit is a data #PF or #NPF. Opcode exits and
instruction-fetch faults are excluded by inspecting the exit state.
A subsequent change will use this state when propagating hardware
DecodeAssist instruction bytes to VMCB12.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/svm/nested.c | 38 +++++++++++++++++++++++++++++++++++++-
arch/x86/kvm/svm/svm.h | 3 +++
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 73f37b050d0a..1cc3af8247f2 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -35,6 +35,35 @@
#define CC KVM_NESTED_VMENTER_CONSISTENCY_CHECK
+static void nested_svm_invalidate_insn_bytes(struct vmcb *vmcb)
+{
+ vmcb->control.insn_len = 0;
+}
+
+static bool nested_svm_vmexit_supports_insn_bytes(const struct vmcb *vmcb)
+{
+ u64 exit_code = vmcb->control.exit_code;
+
+ if (exit_code != SVM_EXIT_NPF &&
+ exit_code != SVM_EXIT_EXCP_BASE + PF_VECTOR)
+ return false;
+
+ return !(vmcb->control.exit_info_1 & PFERR_FETCH_MASK);
+}
+
+static void nested_svm_set_vmcb02_insn_bytes_valid(struct vcpu_svm *svm)
+{
+ svm->nested.vmcb02_insn_bytes_valid =
+ static_cpu_has(X86_FEATURE_DECODEASSISTS) &&
+ nested_svm_vmexit_supports_insn_bytes(svm->nested.vmcb02.ptr);
+}
+
+static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)
+{
+ nested_svm_invalidate_insn_bytes(svm->nested.vmcb02.ptr);
+ svm->nested.vmcb02_insn_bytes_valid = false;
+}
+
static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
struct x86_exception *fault,
bool from_hardware)
@@ -68,6 +97,10 @@ static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
(fault->error_code & ~PFERR_GUEST_FAULT_STAGE_MASK);
vmcb->control.exit_info_2 = fault->address;
+ if (from_hardware)
+ nested_svm_set_vmcb02_insn_bytes_valid(svm);
+ else
+ svm->nested.vmcb02_insn_bytes_valid = false;
nested_svm_vmexit(svm);
}
@@ -869,6 +902,7 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
* Filled at exit: exit_code, exit_info_1, exit_info_2, exit_int_info,
* exit_int_info_err, next_rip, insn_len, insn_bytes.
*/
+ nested_svm_invalidate_vmcb02_insn_bytes(svm);
if (guest_cpu_cap_has(vcpu, X86_FEATURE_VGIF) &&
(vmcb12_ctrl->int_ctl & V_GIF_ENABLE_MASK))
@@ -1649,8 +1683,10 @@ int nested_svm_exit_handled(struct vcpu_svm *svm)
vmexit = nested_svm_intercept(svm);
- if (vmexit == NESTED_EXIT_DONE)
+ if (vmexit == NESTED_EXIT_DONE) {
+ nested_svm_set_vmcb02_insn_bytes_valid(svm);
nested_svm_vmexit(svm);
+ }
return vmexit;
}
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index e958943b8162..1e12ef1105c0 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -243,6 +243,9 @@ struct svm_nested_state {
* on its side.
*/
bool force_msr_bitmap_recalc;
+
+ /* True if VMCB02 has instruction bytes for the current nested exit. */
+ bool vmcb02_insn_bytes_valid;
};
struct vcpu_sev_es_state {
--
2.43.7
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v6 3/8] KVM: nSVM: Track valid hardware DecodeAssist bytes
2026-09-13 6:43 ` [PATCH v6 3/8] KVM: nSVM: Track valid hardware DecodeAssist bytes Tina Zhang
@ 2026-09-14 19:17 ` Jim Mattson
0 siblings, 0 replies; 18+ messages in thread
From: Jim Mattson @ 2026-09-14 19:17 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sat, Sep 12, 2026 at 11:44 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> VMCB02 instruction bytes are valid only for the hardware VM-Exit that
> populated them. Track whether VMCB02 contains instruction bytes for the
> data #PF or #NPF currently being reflected to L1 so that stale bytes are
> not copied to VMCB12.
>
> Invalidate VMCB02's instruction-byte state when preparing VMCB02,
> normally in response to an emulated VMRUN. Mark the bytes as valid only
> when the current VMCB02 exit is a data #PF or #NPF. Opcode exits and
> instruction-fetch faults are excluded by inspecting the exit state.
>
> A subsequent change will use this state when propagating hardware
> DecodeAssist instruction bytes to VMCB12.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> ---
> arch/x86/kvm/svm/nested.c | 38 +++++++++++++++++++++++++++++++++++++-
> arch/x86/kvm/svm/svm.h | 3 +++
> 2 files changed, 40 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> index 73f37b050d0a..1cc3af8247f2 100644
> --- a/arch/x86/kvm/svm/nested.c
> +++ b/arch/x86/kvm/svm/nested.c
> @@ -35,6 +35,35 @@
>
> #define CC KVM_NESTED_VMENTER_CONSISTENCY_CHECK
>
> +static void nested_svm_invalidate_insn_bytes(struct vmcb *vmcb)
> +{
> + vmcb->control.insn_len = 0;
> +}
> +
> +static bool nested_svm_vmexit_supports_insn_bytes(const struct vmcb *vmcb)
> +{
> + u64 exit_code = vmcb->control.exit_code;
> +
> + if (exit_code != SVM_EXIT_NPF &&
> + exit_code != SVM_EXIT_EXCP_BASE + PF_VECTOR)
> + return false;
> +
> + return !(vmcb->control.exit_info_1 & PFERR_FETCH_MASK);
> +}
> +
> +static void nested_svm_set_vmcb02_insn_bytes_valid(struct vcpu_svm *svm)
> +{
> + svm->nested.vmcb02_insn_bytes_valid =
> + static_cpu_has(X86_FEATURE_DECODEASSISTS) &&
> + nested_svm_vmexit_supports_insn_bytes(svm->nested.vmcb02.ptr);
> +}
> +
> +static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)
> +{
> + nested_svm_invalidate_insn_bytes(svm->nested.vmcb02.ptr);
> + svm->nested.vmcb02_insn_bytes_valid = false;
> +}
> +
> static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
> struct x86_exception *fault,
> bool from_hardware)
> @@ -68,6 +97,10 @@ static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
> (fault->error_code & ~PFERR_GUEST_FAULT_STAGE_MASK);
> vmcb->control.exit_info_2 = fault->address;
>
> + if (from_hardware)
> + nested_svm_set_vmcb02_insn_bytes_valid(svm);
> + else
> + svm->nested.vmcb02_insn_bytes_valid = false;
For consistency, should the line above be:
nested_svm_invalidate_vmcb02_insn_bytes(svm);
> nested_svm_vmexit(svm);
> }
>
> @@ -869,6 +902,7 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
> * Filled at exit: exit_code, exit_info_1, exit_info_2, exit_int_info,
> * exit_int_info_err, next_rip, insn_len, insn_bytes.
> */
> + nested_svm_invalidate_vmcb02_insn_bytes(svm);
>
> if (guest_cpu_cap_has(vcpu, X86_FEATURE_VGIF) &&
> (vmcb12_ctrl->int_ctl & V_GIF_ENABLE_MASK))
> @@ -1649,8 +1683,10 @@ int nested_svm_exit_handled(struct vcpu_svm *svm)
>
> vmexit = nested_svm_intercept(svm);
>
> - if (vmexit == NESTED_EXIT_DONE)
> + if (vmexit == NESTED_EXIT_DONE) {
> + nested_svm_set_vmcb02_insn_bytes_valid(svm);
> nested_svm_vmexit(svm);
> + }
>
> return vmexit;
> }
> diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
> index e958943b8162..1e12ef1105c0 100644
> --- a/arch/x86/kvm/svm/svm.h
> +++ b/arch/x86/kvm/svm/svm.h
> @@ -243,6 +243,9 @@ struct svm_nested_state {
> * on its side.
> */
> bool force_msr_bitmap_recalc;
> +
> + /* True if VMCB02 has instruction bytes for the current nested exit. */
> + bool vmcb02_insn_bytes_valid;
> };
>
> struct vcpu_sev_es_state {
> --
> 2.43.7
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12
2026-09-13 6:43 [PATCH v6 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (2 preceding siblings ...)
2026-09-13 6:43 ` [PATCH v6 3/8] KVM: nSVM: Track valid hardware DecodeAssist bytes Tina Zhang
@ 2026-09-13 6:43 ` Tina Zhang
2026-09-14 19:40 ` Jim Mattson
2026-09-13 6:43 ` [PATCH v6 5/8] KVM: nSVM: Fetch DecodeAssist bytes for synthesized faults Tina Zhang
` (3 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Tina Zhang @ 2026-09-13 6:43 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest, linux-kernel
DecodeAssists provides instruction bytes for nested page faults and
intercepted page faults caused by data accesses. When the feature is
exposed to L1, copy fresh hardware-provided instruction bytes from VMCB02
to VMCB12 for these exits.
Leave the VMCB12 instruction-byte state untouched when DecodeAssists is
not exposed. Otherwise, invalidate the state for instruction-fetch page
faults, unrelated exits, and exits without fresh hardware bytes.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/svm/nested.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 1cc3af8247f2..fba230058870 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -40,6 +40,20 @@ static void nested_svm_invalidate_insn_bytes(struct vmcb *vmcb)
vmcb->control.insn_len = 0;
}
+static void nested_svm_copy_insn_bytes(struct vmcb *to,
+ const struct vmcb *from)
+{
+ u8 insn_len = from->control.insn_len;
+
+ if (WARN_ON_ONCE(insn_len > sizeof(from->control.insn_bytes))) {
+ nested_svm_invalidate_insn_bytes(to);
+ return;
+ }
+
+ memcpy(to->control.insn_bytes, from->control.insn_bytes, insn_len);
+ to->control.insn_len = insn_len;
+}
+
static bool nested_svm_vmexit_supports_insn_bytes(const struct vmcb *vmcb)
{
u64 exit_code = vmcb->control.exit_code;
@@ -64,6 +78,27 @@ static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)
svm->nested.vmcb02_insn_bytes_valid = false;
}
+static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
+ struct vmcb *vmcb12,
+ const struct vmcb *vmcb02)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+
+ if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
+ goto out;
+
+ if (!nested_svm_vmexit_supports_insn_bytes(vmcb02) ||
+ !svm->nested.vmcb02_insn_bytes_valid) {
+ nested_svm_invalidate_insn_bytes(vmcb12);
+ goto out;
+ }
+
+ nested_svm_copy_insn_bytes(vmcb12, vmcb02);
+
+out:
+ svm->nested.vmcb02_insn_bytes_valid = false;
+}
+
static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
struct x86_exception *fault,
bool from_hardware)
@@ -1331,6 +1366,8 @@ static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu)
if (guest_cpu_cap_has(vcpu, X86_FEATURE_NRIPS))
vmcb12->control.next_rip = vmcb02->control.next_rip;
+ nested_svm_update_vmcb12_insn_bytes(vcpu, vmcb12, vmcb02);
+
if (nested_vmcb12_has_lbrv(vcpu))
svm_copy_lbrs(&vmcb12->save, &vmcb02->save);
--
2.43.7
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v6 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12
2026-09-13 6:43 ` [PATCH v6 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12 Tina Zhang
@ 2026-09-14 19:40 ` Jim Mattson
0 siblings, 0 replies; 18+ messages in thread
From: Jim Mattson @ 2026-09-14 19:40 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sat, Sep 12, 2026 at 11:44 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> DecodeAssists provides instruction bytes for nested page faults and
> intercepted page faults caused by data accesses. When the feature is
> exposed to L1, copy fresh hardware-provided instruction bytes from VMCB02
> to VMCB12 for these exits.
>
> Leave the VMCB12 instruction-byte state untouched when DecodeAssists is
> not exposed. Otherwise, invalidate the state for instruction-fetch page
> faults, unrelated exits, and exits without fresh hardware bytes.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
Reviewed-by: Jim Mattson <jmattson@google.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 5/8] KVM: nSVM: Fetch DecodeAssist bytes for synthesized faults
2026-09-13 6:43 [PATCH v6 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (3 preceding siblings ...)
2026-09-13 6:43 ` [PATCH v6 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12 Tina Zhang
@ 2026-09-13 6:43 ` Tina Zhang
2026-09-14 21:16 ` Jim Mattson
2026-09-13 6:43 ` [PATCH v6 6/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF Tina Zhang
` (2 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Tina Zhang @ 2026-09-13 6:43 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest, linux-kernel
Hardware-reflected #NPF and #PF VM-Exits can propagate instruction bytes
from VMCB02, but KVM-synthesized exits have no hardware-provided state for
the current exit.
Add kvm_fetch_guest_virt() as a wrapper around the generic guest virtual
memory helper that applies instruction-fetch permissions. When a
synthesized data #NPF or #PF has no valid VMCB02 instruction bytes, fetch
up to 15 bytes from L2's RIP while constructing VMCB12. Preserve the bytes
already read if a subsequent read cannot be completed.
Limit each read to the current page so that linear address boundaries can
be checked before continuing. Outside 64-bit mode, truncate each address
to 32 bits and limit the window at the code-segment limit. In 64-bit mode,
stop at a non-canonical address.
Do not attempt the fallback for SEV guests, as KVM cannot read encrypted
guest memory directly.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/svm/nested.c | 64 +++++++++++++++++++++++++++++++++++++--
arch/x86/kvm/x86.c | 23 +++++++++++---
arch/x86/kvm/x86.h | 3 ++
3 files changed, 83 insertions(+), 7 deletions(-)
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index fba230058870..5cb6a9d6907b 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -78,22 +78,80 @@ static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)
svm->nested.vmcb02_insn_bytes_valid = false;
}
+static u8 nested_svm_get_insn_bytes_len(struct kvm_vcpu *vcpu, u8 max_bytes)
+{
+ if (!is_64_bit_mode(vcpu)) {
+ u32 eip = kvm_rip_read(vcpu);
+ u32 limit = to_svm(vcpu)->vmcb->save.cs.limit;
+
+ if (eip > limit)
+ return 0;
+ max_bytes = min_t(u64, max_bytes, (u64)limit - eip + 1);
+ }
+
+ return max_bytes;
+}
+
+static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
+ u8 max_bytes)
+{
+ struct x86_exception e;
+ gva_t rip = kvm_get_linear_rip(vcpu);
+ u8 count = 0;
+
+ max_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);
+
+ while (count < max_bytes) {
+ gva_t addr = rip + count;
+ u8 chunk;
+
+ if (!is_64_bit_mode(vcpu))
+ addr = (u32)addr;
+ else if (is_noncanonical_address(addr, vcpu, 0))
+ break;
+
+ /*
+ * Read one page at a time to recheck the linear address at each
+ * boundary.
+ */
+ chunk = min_t(unsigned int, max_bytes - count,
+ PAGE_SIZE - offset_in_page(addr));
+ if (kvm_fetch_guest_virt(vcpu, addr, bytes + count,
+ chunk, &e) != X86EMUL_CONTINUE)
+ break;
+ count += chunk;
+ }
+
+ return count;
+}
+
static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
struct vmcb *vmcb12,
const struct vmcb *vmcb02)
{
struct vcpu_svm *svm = to_svm(vcpu);
+ const u8 max_bytes = sizeof(vmcb12->control.insn_bytes);
if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
goto out;
- if (!nested_svm_vmexit_supports_insn_bytes(vmcb02) ||
- !svm->nested.vmcb02_insn_bytes_valid) {
+ if (!nested_svm_vmexit_supports_insn_bytes(vmcb02)) {
nested_svm_invalidate_insn_bytes(vmcb12);
goto out;
}
- nested_svm_copy_insn_bytes(vmcb12, vmcb02);
+ if (svm->nested.vmcb02_insn_bytes_valid) {
+ nested_svm_copy_insn_bytes(vmcb12, vmcb02);
+ goto out;
+ }
+
+ if (!is_sev_guest(vcpu))
+ vmcb12->control.insn_len =
+ nested_svm_fetch_insn_bytes(vcpu,
+ vmcb12->control.insn_bytes,
+ max_bytes);
+ else
+ nested_svm_invalidate_insn_bytes(vmcb12);
out:
svm->nested.vmcb02_insn_bytes_valid = false;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 4b3681796c75..735b67781d55 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -4848,9 +4848,24 @@ static int kvm_read_guest_virt_helper(gva_t addr, void *val, unsigned int bytes,
}
/* used for instruction fetching */
-static int kvm_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
- gva_t addr, void *val, unsigned int bytes,
- struct x86_exception *exception)
+int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,
+ unsigned int bytes, struct x86_exception *exception)
+{
+ u64 access = PFERR_FETCH_MASK;
+
+ if (kvm_x86_call(get_cpl)(vcpu) == 3)
+ access |= PFERR_USER_MASK;
+
+ memset(exception, 0, sizeof(*exception));
+ return kvm_read_guest_virt_helper(addr, val, bytes, vcpu, access,
+ exception);
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_fetch_guest_virt);
+
+static int emulator_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
+ gva_t addr, void *val,
+ unsigned int bytes,
+ struct x86_exception *exception)
{
struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
@@ -5817,7 +5832,7 @@ static const struct x86_emulate_ops emulate_ops = {
.write_gpr = emulator_write_gpr,
.read_std = emulator_read_std,
.write_std = emulator_write_std,
- .fetch = kvm_fetch_guest_virt,
+ .fetch = emulator_fetch_guest_virt,
.read_emulated = emulator_read_emulated,
.write_emulated = emulator_write_emulated,
.cmpxchg_emulated = emulator_cmpxchg_emulated,
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index 0f5919b092e4..f03419a79a11 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -356,6 +356,9 @@ static inline void adjust_tsc_offset_host(struct kvm_vcpu *vcpu, s64 adjustment)
int kvm_read_guest_virt(struct kvm_vcpu *vcpu,
gva_t addr, void *val, unsigned int bytes,
struct x86_exception *exception);
+int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,
+ unsigned int bytes,
+ struct x86_exception *exception);
int kvm_write_guest_virt_system(struct kvm_vcpu *vcpu,
gva_t addr, void *val, unsigned int bytes,
--
2.43.7
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v6 5/8] KVM: nSVM: Fetch DecodeAssist bytes for synthesized faults
2026-09-13 6:43 ` [PATCH v6 5/8] KVM: nSVM: Fetch DecodeAssist bytes for synthesized faults Tina Zhang
@ 2026-09-14 21:16 ` Jim Mattson
0 siblings, 0 replies; 18+ messages in thread
From: Jim Mattson @ 2026-09-14 21:16 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sat, Sep 12, 2026 at 11:44 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> Hardware-reflected #NPF and #PF VM-Exits can propagate instruction bytes
> from VMCB02, but KVM-synthesized exits have no hardware-provided state for
> the current exit.
>
> Add kvm_fetch_guest_virt() as a wrapper around the generic guest virtual
> memory helper that applies instruction-fetch permissions. When a
> synthesized data #NPF or #PF has no valid VMCB02 instruction bytes, fetch
> up to 15 bytes from L2's RIP while constructing VMCB12. Preserve the bytes
> already read if a subsequent read cannot be completed.
>
> Limit each read to the current page so that linear address boundaries can
> be checked before continuing. Outside 64-bit mode, truncate each address
> to 32 bits and limit the window at the code-segment limit. In 64-bit mode,
> stop at a non-canonical address.
>
> Do not attempt the fallback for SEV guests, as KVM cannot read encrypted
> guest memory directly.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> ---
> arch/x86/kvm/svm/nested.c | 64 +++++++++++++++++++++++++++++++++++++--
> arch/x86/kvm/x86.c | 23 +++++++++++---
> arch/x86/kvm/x86.h | 3 ++
> 3 files changed, 83 insertions(+), 7 deletions(-)
>
> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> index fba230058870..5cb6a9d6907b 100644
> --- a/arch/x86/kvm/svm/nested.c
> +++ b/arch/x86/kvm/svm/nested.c
> @@ -78,22 +78,80 @@ static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)
> svm->nested.vmcb02_insn_bytes_valid = false;
> }
>
> +static u8 nested_svm_get_insn_bytes_len(struct kvm_vcpu *vcpu, u8 max_bytes)
> +{
> + if (!is_64_bit_mode(vcpu)) {
> + u32 eip = kvm_rip_read(vcpu);
> + u32 limit = to_svm(vcpu)->vmcb->save.cs.limit;
> +
> + if (eip > limit)
> + return 0;
> + max_bytes = min_t(u64, max_bytes, (u64)limit - eip + 1);
> + }
> +
> + return max_bytes;
> +}
> +
> +static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
> + u8 max_bytes)
> +{
> + struct x86_exception e;
> + gva_t rip = kvm_get_linear_rip(vcpu);
> + u8 count = 0;
> +
> + max_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);
> +
> + while (count < max_bytes) {
> + gva_t addr = rip + count;
> + u8 chunk;
> +
> + if (!is_64_bit_mode(vcpu))
> + addr = (u32)addr;
> + else if (is_noncanonical_address(addr, vcpu, 0))
> + break;
> +
> + /*
> + * Read one page at a time to recheck the linear address at each
> + * boundary.
> + */
> + chunk = min_t(unsigned int, max_bytes - count,
> + PAGE_SIZE - offset_in_page(addr));
> + if (kvm_fetch_guest_virt(vcpu, addr, bytes + count,
> + chunk, &e) != X86EMUL_CONTINUE)
> + break;
> + count += chunk;
> + }
> +
> + return count;
> +}
Hmmm...You added the wrapper I suggested, but you still have the loop
I don't like. See my comments on the wrapper, below.
> static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
> struct vmcb *vmcb12,
> const struct vmcb *vmcb02)
> {
> struct vcpu_svm *svm = to_svm(vcpu);
> + const u8 max_bytes = sizeof(vmcb12->control.insn_bytes);
>
> if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
> goto out;
>
> - if (!nested_svm_vmexit_supports_insn_bytes(vmcb02) ||
> - !svm->nested.vmcb02_insn_bytes_valid) {
> + if (!nested_svm_vmexit_supports_insn_bytes(vmcb02)) {
> nested_svm_invalidate_insn_bytes(vmcb12);
> goto out;
> }
>
> - nested_svm_copy_insn_bytes(vmcb12, vmcb02);
> + if (svm->nested.vmcb02_insn_bytes_valid) {
> + nested_svm_copy_insn_bytes(vmcb12, vmcb02);
> + goto out;
> + }
> +
> + if (!is_sev_guest(vcpu))
> + vmcb12->control.insn_len =
> + nested_svm_fetch_insn_bytes(vcpu,
> + vmcb12->control.insn_bytes,
> + max_bytes);
> + else
> + nested_svm_invalidate_insn_bytes(vmcb12);
>
> out:
> svm->nested.vmcb02_insn_bytes_valid = false;
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 4b3681796c75..735b67781d55 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -4848,9 +4848,24 @@ static int kvm_read_guest_virt_helper(gva_t addr, void *val, unsigned int bytes,
> }
>
> /* used for instruction fetching */
> -static int kvm_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
> - gva_t addr, void *val, unsigned int bytes,
> - struct x86_exception *exception)
> +int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,
> + unsigned int bytes, struct x86_exception *exception)
> +{
> + u64 access = PFERR_FETCH_MASK;
> +
> + if (kvm_x86_call(get_cpl)(vcpu) == 3)
> + access |= PFERR_USER_MASK;
> +
> + memset(exception, 0, sizeof(*exception));
> + return kvm_read_guest_virt_helper(addr, val, bytes, vcpu, access,
> + exception);
> +}
> +EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_fetch_guest_virt);
To make this work the way I envisioned it, you need to add an out
variable, unsigned int *bytes_read, to kvm_read_guest_virt_helper():
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -4818,7 +4818,8 @@ gpa_t kvm_mmu_gva_to_gpa_system(struct kvm_vcpu
*vcpu, gva_t gva,
static int kvm_read_guest_virt_helper(gva_t addr, void *val, unsigned
int bytes,
struct kvm_vcpu *vcpu, u64 access,
- struct x86_exception *exception)
+ struct x86_exception *exception,
+ unsigned int *bytes_read)
{
struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
void *data = val;
@@ -4830,8 +4831,10 @@ static int kvm_read_guest_virt_helper(gva_t
addr, void *val, unsigned int bytes,
unsigned toread = min(bytes, (unsigned)PAGE_SIZE - offset);
int ret;
- if (gpa == INVALID_GPA)
- return X86EMUL_PROPAGATE_FAULT;
+ if (gpa == INVALID_GPA) {
+ r = X86EMUL_PROPAGATE_FAULT;
+ goto out;
+ }
ret = kvm_vcpu_read_guest_page(vcpu, gpa >> PAGE_SHIFT, data,
offset, toread);
if (ret < 0) {
@@ -4844,6 +4847,8 @@ static int kvm_read_guest_virt_helper(gva_t
addr, void *val, unsigned int bytes,
addr += toread;
}
out:
+ if (bytes_read)
+ *bytes_read = data - val;
return r;
}
Then, kvm_fetch_guest_virt() should return the number of bytes read
rather than an X86_EMUL_* code.
That should allow you to eliminate the loop in nested_svm_fetch_insn_bytes().
> +static int emulator_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
> + gva_t addr, void *val,
> + unsigned int bytes,
> + struct x86_exception *exception)
> {
> struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
> struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
> @@ -5817,7 +5832,7 @@ static const struct x86_emulate_ops emulate_ops = {
> .write_gpr = emulator_write_gpr,
> .read_std = emulator_read_std,
> .write_std = emulator_write_std,
> - .fetch = kvm_fetch_guest_virt,
> + .fetch = emulator_fetch_guest_virt,
> .read_emulated = emulator_read_emulated,
> .write_emulated = emulator_write_emulated,
> .cmpxchg_emulated = emulator_cmpxchg_emulated,
> diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
> index 0f5919b092e4..f03419a79a11 100644
> --- a/arch/x86/kvm/x86.h
> +++ b/arch/x86/kvm/x86.h
> @@ -356,6 +356,9 @@ static inline void adjust_tsc_offset_host(struct kvm_vcpu *vcpu, s64 adjustment)
> int kvm_read_guest_virt(struct kvm_vcpu *vcpu,
> gva_t addr, void *val, unsigned int bytes,
> struct x86_exception *exception);
> +int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,
> + unsigned int bytes,
> + struct x86_exception *exception);
>
> int kvm_write_guest_virt_system(struct kvm_vcpu *vcpu,
> gva_t addr, void *val, unsigned int bytes,
> --
> 2.43.7
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 6/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF
2026-09-13 6:43 [PATCH v6 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (4 preceding siblings ...)
2026-09-13 6:43 ` [PATCH v6 5/8] KVM: nSVM: Fetch DecodeAssist bytes for synthesized faults Tina Zhang
@ 2026-09-13 6:43 ` Tina Zhang
2026-09-14 21:34 ` Jim Mattson
2026-09-13 6:43 ` [PATCH v6 7/8] KVM: nSVM: Advertise DecodeAssists to L1 Tina Zhang
2026-09-13 6:43 ` [PATCH v6 8/8] KVM: selftests: Add nested SVM DecodeAssists test Tina Zhang
7 siblings, 1 reply; 18+ messages in thread
From: Tina Zhang @ 2026-09-13 6:43 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest, linux-kernel
Preserve the bytes used by the emulator when synthesizing a data #NPF.
Reuse the fetch cache only when the fault is the current emulator
exception, identified by fault == &ctxt->exception.
Add an emulator accessor to copy cached instruction bytes, checking
the RIP and cache bounds. These checks alone do not establish that
the cache belongs to the current emulation.
Write the cached bytes directly to VMCB02 and fetch any missing tail
through the instruction-fetch helper. If the tail cannot be read,
report only the bytes already available. For SEV guests, retain only
the cached bytes because KVM cannot fetch plaintext guest instructions.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/emulate.c | 22 ++++++++++++++++++++++
arch/x86/kvm/kvm_emulate.h | 3 +++
arch/x86/kvm/svm/nested.c | 34 ++++++++++++++++++++++++++++++++--
3 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index 1dfece6af81e..5017a9aa1d4b 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -913,6 +913,28 @@ static __always_inline int do_insn_fetch_bytes(struct x86_emulate_ctxt *ctxt,
return X86EMUL_CONTINUE;
}
+/*
+ * The caller is responsible for ensuring that the fetch cache belongs to
+ * the current instruction. Matching EIP alone does not guarantee this.
+ */
+unsigned int x86_emulator_copy_insn_bytes(struct x86_emulate_ctxt *ctxt,
+ unsigned long eip, u8 *bytes,
+ unsigned int max_bytes)
+{
+ unsigned int nr_bytes;
+
+ if (!ctxt || ctxt->eip != eip ||
+ ctxt->fetch.end < ctxt->fetch.data ||
+ ctxt->fetch.end > ctxt->fetch.data + sizeof(ctxt->fetch.data))
+ return 0;
+
+ nr_bytes = min_t(unsigned int, ctxt->fetch.end - ctxt->fetch.data,
+ max_bytes);
+ memcpy(bytes, ctxt->fetch.data, nr_bytes);
+ return nr_bytes;
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(x86_emulator_copy_insn_bytes);
+
/* Fetch next part of the instruction being emulated. */
#define insn_fetch(_type, _ctxt) \
({ _type _x; \
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index d05558ef28ae..5361d42e6e05 100644
--- a/arch/x86/kvm/kvm_emulate.h
+++ b/arch/x86/kvm/kvm_emulate.h
@@ -528,6 +528,9 @@ enum x86_intercept {
};
int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int emulation_type);
+unsigned int x86_emulator_copy_insn_bytes(struct x86_emulate_ctxt *ctxt,
+ unsigned long eip, u8 *bytes,
+ unsigned int max_bytes);
bool x86_page_table_writing_insn(struct x86_emulate_ctxt *ctxt);
#define EMULATION_FAILED -1
#define EMULATION_OK 0
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 5cb6a9d6907b..2dc515d73eb6 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -93,13 +93,13 @@ static u8 nested_svm_get_insn_bytes_len(struct kvm_vcpu *vcpu, u8 max_bytes)
}
static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
- u8 max_bytes)
+ u8 count, u8 max_bytes)
{
struct x86_exception e;
gva_t rip = kvm_get_linear_rip(vcpu);
- u8 count = 0;
max_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);
+ count = min(count, max_bytes);
while (count < max_bytes) {
gva_t addr = rip + count;
@@ -125,6 +125,31 @@ static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
return count;
}
+static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ struct vmcb *vmcb02 = svm->nested.vmcb02.ptr;
+ struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+ u8 max_bytes = sizeof(vmcb02->control.insn_bytes);
+ u8 count;
+
+ nested_svm_invalidate_vmcb02_insn_bytes(svm);
+
+ if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
+ return;
+
+ max_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);
+ count = x86_emulator_copy_insn_bytes(ctxt, kvm_rip_read(vcpu),
+ vmcb02->control.insn_bytes, max_bytes);
+ if (!is_sev_guest(vcpu))
+ count = nested_svm_fetch_insn_bytes(vcpu,
+ vmcb02->control.insn_bytes,
+ count, max_bytes);
+ vmcb02->control.insn_len = count;
+
+ svm->nested.vmcb02_insn_bytes_valid = true;
+}
+
static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
struct vmcb *vmcb12,
const struct vmcb *vmcb02)
@@ -149,6 +174,7 @@ static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
vmcb12->control.insn_len =
nested_svm_fetch_insn_bytes(vcpu,
vmcb12->control.insn_bytes,
+ 0,
max_bytes);
else
nested_svm_invalidate_insn_bytes(vmcb12);
@@ -163,6 +189,8 @@ static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
{
struct vcpu_svm *svm = to_svm(vcpu);
struct vmcb *vmcb = svm->vmcb;
+ struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+ bool from_emulation = ctxt && fault == &ctxt->exception;
u64 fault_stage;
/*
@@ -192,6 +220,8 @@ static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
if (from_hardware)
nested_svm_set_vmcb02_insn_bytes_valid(svm);
+ else if (from_emulation && !(fault->error_code & PFERR_FETCH_MASK))
+ nested_svm_prepare_synthesized_insn_bytes(vcpu);
else
svm->nested.vmcb02_insn_bytes_valid = false;
nested_svm_vmexit(svm);
--
2.43.7
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v6 6/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF
2026-09-13 6:43 ` [PATCH v6 6/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF Tina Zhang
@ 2026-09-14 21:34 ` Jim Mattson
0 siblings, 0 replies; 18+ messages in thread
From: Jim Mattson @ 2026-09-14 21:34 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sat, Sep 12, 2026 at 11:44 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> Preserve the bytes used by the emulator when synthesizing a data #NPF.
> Reuse the fetch cache only when the fault is the current emulator
> exception, identified by fault == &ctxt->exception.
>
> Add an emulator accessor to copy cached instruction bytes, checking
> the RIP and cache bounds. These checks alone do not establish that
> the cache belongs to the current emulation.
>
> Write the cached bytes directly to VMCB02 and fetch any missing tail
> through the instruction-fetch helper. If the tail cannot be read,
> report only the bytes already available. For SEV guests, retain only
> the cached bytes because KVM cannot fetch plaintext guest instructions.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
Reviewed-by: Jim Mattson <jmattson@gmail.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 7/8] KVM: nSVM: Advertise DecodeAssists to L1
2026-09-13 6:43 [PATCH v6 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (5 preceding siblings ...)
2026-09-13 6:43 ` [PATCH v6 6/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF Tina Zhang
@ 2026-09-13 6:43 ` Tina Zhang
2026-09-13 6:43 ` [PATCH v6 8/8] KVM: selftests: Add nested SVM DecodeAssists test Tina Zhang
7 siblings, 0 replies; 18+ messages in thread
From: Tina Zhang @ 2026-09-13 6:43 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest, linux-kernel
Advertise DecodeAssists to L1 now that KVM virtualizes the
guest-visible DecodeAssist state in VMCB12: EXITINFO1 decode data for
MOV CR/DR, INTn, and INVLPG exits, plus instruction bytes for nested
page faults and intercepted #PF exits. INVLPGA's linear address remains
available directly from the saved guest rAX, as required by the APM.
Expose the feature only when supported by hardware, as KVM still relies
on hardware DecodeAssists for VM-Exits that are reflected directly from
L2.
With DecodeAssists exposed, QEMU configurations that require the feature
(e.g. "-cpu ...,+decodeassists,...,enforce") are no longer rejected.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
Reviewed-by: Jim Mattson <jmattson@google.com>
---
arch/x86/kvm/cpuid.c | 1 +
arch/x86/kvm/svm/svm.c | 2 ++
2 files changed, 3 insertions(+)
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index ddb022cb203a..b644e45eac71 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -1208,6 +1208,7 @@ void kvm_initialize_cpu_caps(void)
VENDOR_F(NPT),
VENDOR_F(VMCBCLEAN),
VENDOR_F(FLUSHBYASID),
+ VENDOR_F(DECODEASSISTS),
VENDOR_F(NRIPS),
VENDOR_F(TSCRATEMSR),
VENDOR_F(V_VMSAVE_VMLOAD),
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 6834e1101bb6..c91561a9a49f 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5560,6 +5560,8 @@ static __init void svm_set_cpu_caps(void)
*/
kvm_cpu_cap_set(X86_FEATURE_FLUSHBYASID);
+ kvm_cpu_cap_check_and_set(X86_FEATURE_DECODEASSISTS);
+
if (nrips)
kvm_cpu_cap_set(X86_FEATURE_NRIPS);
--
2.43.7
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH v6 8/8] KVM: selftests: Add nested SVM DecodeAssists test
2026-09-13 6:43 [PATCH v6 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (6 preceding siblings ...)
2026-09-13 6:43 ` [PATCH v6 7/8] KVM: nSVM: Advertise DecodeAssists to L1 Tina Zhang
@ 2026-09-13 6:43 ` Tina Zhang
2026-09-14 22:33 ` Jim Mattson
7 siblings, 1 reply; 18+ messages in thread
From: Tina Zhang @ 2026-09-13 6:43 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest, linux-kernel
Add a focused nested SVM selftest for DecodeAssists. Verify that KVM
exposes the feature to L1 and synthesizes EXITINFO for representative
MOV CR/DR, CLTS, INTn, INVLPG, and INVLPGA intercepts.
Exercise instruction bytes for hardware and synthesized #NPF/#PF exits.
Cover a synthesized #NPF that follows a hardware #NPF in the same emulated
instruction, an unreadable instruction tail that leaves only the cached
opcode, and the absence of bytes for an instruction-fetch #PF. Modify the
MOVSB opcode while its MMIO read is pending to verify that the emulator's
cached bytes are preserved.
Inject a userspace #PF while an MMIO read is pending. Verify that after
MMIO completion, L1 receives instruction bytes from the current RIP,
not the previous instruction's emulator cache.
The synthesized OUTSB #NPF and userspace-injected #PF cases run by
default. The forced-emulation #PF and instruction-intercept cases
require kvm.force_emulation_prefix=1.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/include/x86/processor.h | 1 +
.../kvm/x86/svm_nested_decode_assists_test.c | 547 ++++++++++++++++++
3 files changed, 549 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39..bb55a8c32c42 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -118,6 +118,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_preemption_timer_test
TEST_GEN_PROGS_x86 += x86/svm_vmcall_test
TEST_GEN_PROGS_x86 += x86/svm_int_ctl_test
TEST_GEN_PROGS_x86 += x86/svm_nested_clear_efer_svme
+TEST_GEN_PROGS_x86 += x86/svm_nested_decode_assists_test
TEST_GEN_PROGS_x86 += x86/svm_nested_shutdown_test
TEST_GEN_PROGS_x86 += x86/svm_nested_soft_inject_test
TEST_GEN_PROGS_x86 += x86/svm_nested_vmcb12_gpa
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
index 6e6f70035508..e9d745a2c131 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -220,6 +220,7 @@ struct kvm_x86_cpu_feature {
#define X86_FEATURE_LBRV KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 1)
#define X86_FEATURE_NRIPS KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 3)
#define X86_FEATURE_TSCRATEMSR KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 4)
+#define X86_FEATURE_DECODEASSISTS KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 7)
#define X86_FEATURE_PAUSEFILTER KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 10)
#define X86_FEATURE_PFTHRESHOLD KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 12)
#define X86_FEATURE_V_VMSAVE_VMLOAD KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 15)
diff --git a/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
new file mode 100644
index 000000000000..9f729e8542f2
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test KVM's virtualization of SVM DecodeAssists for nested guests.
+ */
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "svm_util.h"
+
+#define TEST_INT_VECTOR 0x81
+
+/* Any canonical virtual address that is never mapped by the selftest VM. */
+#define PF_TEST_GVA BIT_ULL(40)
+#define PF_FETCH_TEST_GVA BIT_ULL(41)
+
+#define OUTSB_OPCODE 0x6e
+#define MOVSB_OPCODE 0xa4
+#define NOP_OPCODE 0x90
+#define BOUNDARY_OUTSB_CODE_SIZE 15
+#define TEST_IOPM_SIZE (3 * PAGE_SIZE)
+
+static u8 npf_target[PAGE_SIZE] __aligned(PAGE_SIZE);
+static u8 mmio_source[PAGE_SIZE] __aligned(PAGE_SIZE);
+static u8 boundary_outsb_code[2 * PAGE_SIZE] __aligned(PAGE_SIZE);
+
+static void l2_read_code(void)
+{
+ asm volatile("mov (%0), %%rax" : : "r"(&npf_target) : "rax", "memory");
+ GUEST_FAIL("L2 read did not cause a nested page fault");
+}
+
+static void l2_outsb_code(void)
+{
+ asm volatile("mov %0, %%rsi\n\t"
+ "mov $0x80, %%dx\n\t"
+ "outsb"
+ : : "r"(&npf_target) : "rsi", "rdx", "memory");
+ GUEST_FAIL("L2 OUTSB did not cause a nested page fault");
+}
+
+extern u8 l2_movsb_insn[];
+
+static void l2_movsb_code(void)
+{
+ asm volatile("mov %0, %%rsi\n\t"
+ "mov %1, %%rdi\n\t"
+ "l2_movsb_insn:\n\t"
+ "movsb"
+ : : "r"(&mmio_source), "r"(&npf_target)
+ : "rsi", "rdi", "memory");
+ GUEST_FAIL("L2 MOVSB did not cause a nested page fault");
+}
+
+extern u8 l2_userspace_pf_insn[];
+
+static void l2_userspace_pf_code(void)
+{
+ asm volatile("movzbl (%0), %%eax\n\t"
+ "l2_userspace_pf_insn:\n\t"
+ "nop"
+ : : "r"(&mmio_source) : "rax", "memory");
+ GUEST_FAIL("Userspace-injected #PF was not intercepted by L1");
+}
+
+static void l2_pf_code(void)
+{
+ asm volatile("mov (%0), %%rax"
+ : : "r"(PF_TEST_GVA) : "rax", "memory");
+ GUEST_FAIL("L2 access to an unmapped VA did not #PF");
+}
+
+static void l2_fep_pf_code(void)
+{
+ asm volatile(KVM_FEP "mov (%0), %%rax"
+ : : "r"(PF_TEST_GVA) : "rax", "memory");
+ GUEST_FAIL("L2 forced-emulated access to an unmapped VA did not #PF");
+}
+
+static void l2_fep_mov_from_cr4_code(void)
+{
+ asm volatile(KVM_FEP "mov %%cr4, %%r10" : : : "r10");
+ GUEST_FAIL("L2 forced-emulated MOV-from-CR4 was not intercepted");
+}
+
+static void l2_fep_mov_to_dr7_code(void)
+{
+ asm volatile("mov %%dr7, %%rax\n\t"
+ "mov %%rax, %%rbx\n\t"
+ KVM_FEP "mov %%rbx, %%dr7" : : : "rax", "rbx");
+ GUEST_FAIL("L2 forced-emulated MOV-to-DR7 was not intercepted");
+}
+
+static void l2_fep_clts_code(void)
+{
+ asm volatile(KVM_FEP "clts" : : : "memory");
+ GUEST_FAIL("L2 forced-emulated CLTS was not intercepted");
+}
+
+static void l2_fep_int_code(void)
+{
+ asm volatile(KVM_FEP "int %0" : : "i"(TEST_INT_VECTOR));
+ GUEST_FAIL("L2 forced-emulated INTn was not intercepted");
+}
+
+static void l2_fep_invlpg_code(void)
+{
+ asm volatile(KVM_FEP "invlpg (%0)" : : "r"(&npf_target) : "memory");
+ GUEST_FAIL("L2 forced-emulated INVLPG was not intercepted");
+}
+
+static void l2_fep_invlpga_code(void)
+{
+ asm volatile(KVM_FEP "invlpga"
+ : : "a"(&npf_target), "c"(0) : "memory");
+ GUEST_FAIL("L2 forced-emulated INVLPGA was not intercepted");
+}
+
+struct instruction_intercept_test {
+ const char *name;
+ void (*code)(void);
+ u64 intercept;
+ u32 intercept_cr;
+ u32 intercept_dr;
+ u64 exit_code;
+ u64 exit_info_1;
+ u64 exit_info_1_mask;
+ bool check_rax;
+ u64 rax;
+};
+
+static const struct instruction_intercept_test instruction_intercept_tests[] = {
+ {
+ .name = "MOV-from-CR4",
+ .code = l2_fep_mov_from_cr4_code,
+ .intercept_cr = BIT(INTERCEPT_CR4_READ),
+ .exit_code = SVM_EXIT_READ_CR4,
+ .exit_info_1 = BIT_ULL(63) | 10,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "MOV-to-DR7",
+ .code = l2_fep_mov_to_dr7_code,
+ .intercept_dr = BIT(INTERCEPT_DR7_WRITE),
+ .exit_code = SVM_EXIT_WRITE_DR7,
+ .exit_info_1 = 3,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "CLTS",
+ .code = l2_fep_clts_code,
+ .intercept_cr = BIT(INTERCEPT_CR0_WRITE),
+ .exit_code = SVM_EXIT_WRITE_CR0,
+ .exit_info_1_mask = BIT_ULL(63),
+ }, {
+ .name = "INTn",
+ .code = l2_fep_int_code,
+ .intercept = BIT_ULL(INTERCEPT_INTn),
+ .exit_code = SVM_EXIT_SWINT,
+ .exit_info_1 = TEST_INT_VECTOR,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "INVLPG",
+ .code = l2_fep_invlpg_code,
+ .intercept = BIT_ULL(INTERCEPT_INVLPG),
+ .exit_code = SVM_EXIT_INVLPG,
+ .exit_info_1 = (u64)&npf_target,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "INVLPGA",
+ .code = l2_fep_invlpga_code,
+ .intercept = BIT_ULL(INTERCEPT_INVLPGA),
+ .exit_code = SVM_EXIT_INVLPGA,
+ .exit_info_1_mask = ~0ULL,
+ .check_rax = true,
+ .rax = (u64)&npf_target,
+ },
+};
+
+static void assert_decode_assist_insn_bytes(struct vmcb *vmcb)
+{
+ GUEST_ASSERT(vmcb->control.insn_len);
+ GUEST_ASSERT(vmcb->control.insn_len <=
+ sizeof(vmcb->control.insn_bytes));
+ GUEST_ASSERT(!memcmp(vmcb->control.insn_bytes,
+ (void *)vmcb->save.rip,
+ vmcb->control.insn_len));
+}
+
+static void assert_full_decode_assist_insn_bytes(struct vmcb *vmcb)
+{
+ GUEST_ASSERT_EQ(vmcb->control.insn_len,
+ sizeof(vmcb->control.insn_bytes));
+ assert_decode_assist_insn_bytes(vmcb);
+}
+
+static void prepare_l2_for_vmrun(struct svm_test_data *svm, gva_t rip)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ vmcb->save.rip = rip;
+ vmcb->save.rsp = (u64)svm->stack;
+}
+
+static void run_intercept_test(struct svm_test_data *svm,
+ const struct instruction_intercept_test *test)
+{
+ struct vmcb *vmcb = svm->vmcb;
+ struct vmcb_control_area *control = &vmcb->control;
+ u64 expected_exit_info_1 = test->exit_info_1 & test->exit_info_1_mask;
+
+ control->intercept |= test->intercept;
+ control->intercept_cr |= test->intercept_cr;
+ control->intercept_dr |= test->intercept_dr;
+
+ control->exit_info_1 = ~0ULL;
+ prepare_l2_for_vmrun(svm, (u64)test->code);
+
+ run_guest(vmcb, svm->vmcb_gpa);
+
+ __GUEST_ASSERT(control->exit_code == test->exit_code,
+ "%s: expected exit code %#lx, got %#lx",
+ test->name, (unsigned long)test->exit_code,
+ (unsigned long)control->exit_code);
+ __GUEST_ASSERT((control->exit_info_1 & test->exit_info_1_mask) ==
+ expected_exit_info_1,
+ "%s: expected EXITINFO1 %#lx with mask %#lx, got %#lx",
+ test->name, (unsigned long)expected_exit_info_1,
+ (unsigned long)test->exit_info_1_mask,
+ (unsigned long)control->exit_info_1);
+ __GUEST_ASSERT(!control->insn_len,
+ "%s: expected no instruction bytes, got %u",
+ test->name, control->insn_len);
+
+ if (test->check_rax)
+ __GUEST_ASSERT(vmcb->save.rax == test->rax,
+ "%s: expected rAX %#lx, got %#lx",
+ test->name, (unsigned long)test->rax,
+ (unsigned long)vmcb->save.rax);
+
+ control->intercept &= ~test->intercept;
+ control->intercept_cr &= ~test->intercept_cr;
+ control->intercept_dr &= ~test->intercept_dr;
+}
+
+static void test_instruction_intercepts(struct svm_test_data *svm)
+{
+ int i;
+
+ if (!is_forced_emulation_enabled)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(instruction_intercept_tests); i++)
+ run_intercept_test(svm, &instruction_intercept_tests[i]);
+}
+
+static void test_hardware_npf(struct svm_test_data *svm, gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_read_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ assert_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * The IOIO intercept causes L0 to emulate OUTSB before accessing its source
+ * operand. The emulated read then faults on L1's NPT, resulting in a
+ * KVM-synthesized #NPF.
+ */
+static void test_synthesized_npf(struct svm_test_data *svm, gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_outsb_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * MOVSB first reads from MMIO, causing a hardware #NPF that L0 emulates.
+ * After userspace completes the read, the emulated destination write faults
+ * on L1's NPT. The new #NPF must not reuse the original hardware exit's GPA.
+ * Userspace replaces MOVSB with NOP while the read is pending. DecodeAssist
+ * must retain the cached MOVSB bytes instead of re-reading the opcode.
+ */
+static void test_synthesized_npf_after_hardware_npf(struct svm_test_data *svm,
+ gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+ u8 insn_bytes[sizeof(vmcb->control.insn_bytes)];
+
+ memcpy(insn_bytes, l2_movsb_insn, sizeof(insn_bytes));
+ GUEST_ASSERT_EQ(insn_bytes[0], MOVSB_OPCODE);
+ prepare_l2_for_vmrun(svm, (u64)l2_movsb_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ GUEST_ASSERT_EQ(vmcb->save.rip, (u64)l2_movsb_insn);
+ GUEST_ASSERT_EQ(l2_movsb_insn[0], NOP_OPCODE);
+ GUEST_ASSERT_EQ(vmcb->control.insn_len, sizeof(insn_bytes));
+ GUEST_ASSERT(!memcmp(vmcb->control.insn_bytes, insn_bytes,
+ sizeof(insn_bytes)));
+}
+
+/*
+ * OUTSB is the final byte of a mapped code page, and the following page is
+ * not present in L2's page tables. DecodeAssist byte fetching must stop at
+ * the page boundary and report only the OUTSB opcode.
+ */
+static void test_synthesized_npf_truncated(struct svm_test_data *svm,
+ gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm,
+ (u64)&boundary_outsb_code[PAGE_SIZE -
+ BOUNDARY_OUTSB_CODE_SIZE]);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ GUEST_ASSERT_EQ(vmcb->save.rip,
+ (u64)&boundary_outsb_code[PAGE_SIZE - 1]);
+ GUEST_ASSERT_EQ(vmcb->control.insn_len, 1);
+ GUEST_ASSERT_EQ(vmcb->control.insn_bytes[0], OUTSB_OPCODE);
+}
+
+static void test_hardware_intercepted_pf(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_pf_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_FETCH_MASK));
+ assert_decode_assist_insn_bytes(vmcb);
+}
+
+static void test_hardware_intercepted_fetch_pf(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, PF_FETCH_TEST_GVA);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_FETCH_TEST_GVA);
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+ GUEST_ASSERT(vmcb->control.exit_info_1 & PFERR_FETCH_MASK);
+ GUEST_ASSERT_EQ(vmcb->control.insn_len, 0);
+}
+
+static void test_synthesized_pf(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ if (!is_forced_emulation_enabled)
+ return;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_fep_pf_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_FETCH_MASK));
+ assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/* The pending MMIO instruction must complete before reflecting userspace #PF. */
+static void test_userspace_injected_pf_during_emulation(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_userspace_pf_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_1, 0);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+ GUEST_ASSERT_EQ(vmcb->save.rip, (u64)l2_userspace_pf_insn);
+ GUEST_ASSERT_EQ(vmcb->save.rax, 0x5a);
+ assert_full_decode_assist_insn_bytes(vmcb);
+ GUEST_ASSERT_EQ(vmcb->control.insn_bytes[0], NOP_OPCODE);
+}
+
+static void l1_guest_code(struct svm_test_data *svm, gpa_t npf_gpa,
+ gpa_t iopm_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ GUEST_ASSERT(this_cpu_has(X86_FEATURE_DECODEASSISTS));
+
+ generic_svm_setup(svm, l2_read_code);
+ vmcb->control.iopm_base_pa = iopm_gpa;
+
+ vmcb->control.intercept |= BIT_ULL(INTERCEPT_IOIO_PROT);
+ vmcb->control.intercept_exceptions |= 1U << PF_VECTOR;
+
+ test_hardware_npf(svm, npf_gpa);
+ test_synthesized_npf(svm, npf_gpa);
+ test_synthesized_npf_after_hardware_npf(svm, npf_gpa);
+ test_synthesized_npf_truncated(svm, npf_gpa);
+ test_hardware_intercepted_pf(svm);
+ test_hardware_intercepted_fetch_pf(svm);
+ test_synthesized_pf(svm);
+ test_userspace_injected_pf_during_emulation(svm);
+ test_instruction_intercepts(svm);
+
+ GUEST_DONE();
+}
+
+static void build_boundary_outsb_code(u8 *code)
+{
+ u64 source = (u64)&npf_target;
+
+ /* movabs $npf_target, %rsi */
+ code[0] = 0x48;
+ code[1] = 0xbe;
+ memcpy(&code[2], &source, sizeof(source));
+
+ /* mov $0x80, %dx; outsb */
+ code[10] = 0x66;
+ code[11] = 0xba;
+ code[12] = 0x80;
+ code[13] = 0x00;
+ code[14] = OUTSB_OPCODE;
+}
+
+static void prepare_boundary_outsb_code(struct kvm_vm *vm)
+{
+ gva_t code_gva = (gva_t)&boundary_outsb_code[PAGE_SIZE -
+ BOUNDARY_OUTSB_CODE_SIZE];
+
+ build_boundary_outsb_code(addr_gva2hva(vm, code_gva));
+}
+
+static void queue_userspace_pf(struct kvm_vcpu *vcpu)
+{
+ struct kvm_vcpu_events events;
+
+ vcpu_events_get(vcpu, &events);
+ TEST_ASSERT(!events.exception.pending && !events.exception.injected,
+ "Unexpected exception before userspace #PF injection");
+ TEST_ASSERT(events.flags & KVM_VCPUEVENT_VALID_PAYLOAD,
+ "KVM_CAP_EXCEPTION_PAYLOAD was not enabled");
+
+ events.exception.pending = true;
+ events.exception.nr = PF_VECTOR;
+ events.exception.has_error_code = true;
+ events.exception.error_code = 0;
+ events.exception_has_payload = true;
+ events.exception_payload = PF_TEST_GVA;
+ vcpu_events_set(vcpu, &events);
+}
+
+static void complete_mmio_read(struct kvm_vcpu *vcpu, gpa_t expected_gpa,
+ u8 value)
+{
+ if (vcpu->run->exit_reason == KVM_EXIT_IO) {
+ struct ucall uc;
+
+ if (get_ucall(vcpu, &uc) == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+ }
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
+ TEST_ASSERT(!vcpu->run->mmio.is_write,
+ "Expected an MMIO read, got a write");
+ TEST_ASSERT_EQ(vcpu->run->mmio.phys_addr, expected_gpa);
+ TEST_ASSERT_EQ(vcpu->run->mmio.len, 1);
+ vcpu->run->mmio.data[0] = value;
+}
+
+static void assert_ucall_done(struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+ u64 actual;
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+ actual = get_ucall(vcpu, &uc);
+ if (actual == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+
+ TEST_ASSERT_EQ(actual, UCALL_DONE);
+}
+
+int main(int argc, char *argv[])
+{
+ gva_t svm_gva, npf_gva, boundary_page_gva, iopm_gva;
+ gpa_t npf_gpa, mmio_source_gpa, mmio_gpa, iopm_gpa;
+ struct userspace_mem_region *region;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ u8 *movsb_insn;
+ u64 *pte;
+
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_NPT));
+ TEST_REQUIRE(this_cpu_has(X86_FEATURE_DECODEASSISTS));
+ TEST_ASSERT(kvm_cpu_has(X86_FEATURE_DECODEASSISTS),
+ "KVM failed to expose DecodeAssists");
+ TEST_REQUIRE(kvm_has_cap(KVM_CAP_EXCEPTION_PAYLOAD));
+
+ vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+ vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, 1);
+ prepare_boundary_outsb_code(vm);
+ vm_enable_npt(vm);
+ vcpu_alloc_svm(vm, &svm_gva);
+ iopm_gva = vm_alloc_pages(vm, TEST_IOPM_SIZE / PAGE_SIZE);
+ iopm_gpa = addr_gva2gpa(vm, iopm_gva);
+ memset(addr_gva2hva(vm, iopm_gva), 0, TEST_IOPM_SIZE);
+ npf_gva = (gva_t)&npf_target;
+ npf_gpa = addr_gva2gpa(vm, npf_gva);
+
+ tdp_identity_map_default_memslots(vm);
+ pte = tdp_get_pte(vm, npf_gpa);
+ *pte &= ~PTE_PRESENT_MASK(&vm->stage2_mmu);
+ region = memslot2region(vm, 0);
+ mmio_gpa = region->region.guest_phys_addr +
+ region->region.memory_size + PAGE_SIZE;
+ mmio_source_gpa = addr_gva2gpa(vm, (gva_t)&mmio_source);
+ pte = tdp_get_pte(vm, mmio_source_gpa);
+ *pte = (*pte & ~PHYSICAL_PAGE_MASK) | mmio_gpa;
+
+ boundary_page_gva = (gva_t)&boundary_outsb_code[PAGE_SIZE];
+ pte = vm_get_pte(vm, boundary_page_gva);
+ *pte &= ~PTE_PRESENT_MASK(&vm->mmu);
+
+ vcpu_args_set(vcpu, 3, svm_gva, npf_gpa, iopm_gpa);
+
+ /* Complete the MOVSB source read. */
+ vcpu_run(vcpu);
+ complete_mmio_read(vcpu, mmio_gpa, 0xa5);
+ movsb_insn = addr_gva2hva(vm, (gva_t)l2_movsb_insn);
+ TEST_ASSERT_EQ(movsb_insn[0], MOVSB_OPCODE);
+ movsb_insn[0] = NOP_OPCODE;
+ vcpu_run(vcpu);
+ complete_mmio_read(vcpu, mmio_gpa, 0x5a);
+ queue_userspace_pf(vcpu);
+ vcpu_run(vcpu);
+ assert_ucall_done(vcpu);
+
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.43.7
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v6 8/8] KVM: selftests: Add nested SVM DecodeAssists test
2026-09-13 6:43 ` [PATCH v6 8/8] KVM: selftests: Add nested SVM DecodeAssists test Tina Zhang
@ 2026-09-14 22:33 ` Jim Mattson
0 siblings, 0 replies; 18+ messages in thread
From: Jim Mattson @ 2026-09-14 22:33 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sat, Sep 12, 2026 at 11:44 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> Add a focused nested SVM selftest for DecodeAssists. Verify that KVM
> exposes the feature to L1 and synthesizes EXITINFO for representative
> MOV CR/DR, CLTS, INTn, INVLPG, and INVLPGA intercepts.
>
> Exercise instruction bytes for hardware and synthesized #NPF/#PF exits.
> Cover a synthesized #NPF that follows a hardware #NPF in the same emulated
> instruction, an unreadable instruction tail that leaves only the cached
> opcode, and the absence of bytes for an instruction-fetch #PF. Modify the
> MOVSB opcode while its MMIO read is pending to verify that the emulator's
> cached bytes are preserved.
>
> Inject a userspace #PF while an MMIO read is pending. Verify that after
> MMIO completion, L1 receives instruction bytes from the current RIP,
> not the previous instruction's emulator cache.
>
> The synthesized OUTSB #NPF and userspace-injected #PF cases run by
> default. The forced-emulation #PF and instruction-intercept cases
> require kvm.force_emulation_prefix=1.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> ---
> ...
> +static const struct instruction_intercept_test instruction_intercept_tests[] = {
> + {
> + .name = "MOV-from-CR4",
> + .code = l2_fep_mov_from_cr4_code,
> + .intercept_cr = BIT(INTERCEPT_CR4_READ),
> + .exit_code = SVM_EXIT_READ_CR4,
> + .exit_info_1 = BIT_ULL(63) | 10,
> + .exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "MOV-to-DR7",
> + .code = l2_fep_mov_to_dr7_code,
> + .intercept_dr = BIT(INTERCEPT_DR7_WRITE),
> + .exit_code = SVM_EXIT_WRITE_DR7,
> + .exit_info_1 = 3,
> + .exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "CLTS",
> + .code = l2_fep_clts_code,
> + .intercept_cr = BIT(INTERCEPT_CR0_WRITE),
> + .exit_code = SVM_EXIT_WRITE_CR0,
> + .exit_info_1_mask = BIT_ULL(63),
Should this be:
.exit_info_1 = BIT_ULL(63),
.exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "INTn",
> + .code = l2_fep_int_code,
> + .intercept = BIT_ULL(INTERCEPT_INTn),
> + .exit_code = SVM_EXIT_SWINT,
> + .exit_info_1 = TEST_INT_VECTOR,
> + .exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "INVLPG",
> + .code = l2_fep_invlpg_code,
> + .intercept = BIT_ULL(INTERCEPT_INVLPG),
> + .exit_code = SVM_EXIT_INVLPG,
> + .exit_info_1 = (u64)&npf_target,
> + .exit_info_1_mask = ~0ULL,
> + }, {
> + .name = "INVLPGA",
> + .code = l2_fep_invlpga_code,
> + .intercept = BIT_ULL(INTERCEPT_INVLPGA),
> + .exit_code = SVM_EXIT_INVLPGA,
> + .exit_info_1_mask = ~0ULL,
> + .check_rax = true,
> + .rax = (u64)&npf_target,
> + },
> +};
If you omit the FEP from the assembly in
instruction_intercept_tests[], KVM will copy VMCS02's EXITINFO1 to
VMCS12. Assuming L1 itself is not nested, this can be used to validate
KVM emulator behavior against hardware. (Maybe this can be used to see
if I'm right about CR0_SEL_WRITE, which is currently untested.)
> ...
> +static void run_intercept_test(struct svm_test_data *svm,
> + const struct instruction_intercept_test *test)
> +{
> + struct vmcb *vmcb = svm->vmcb;
> + struct vmcb_control_area *control = &vmcb->control;
> + u64 expected_exit_info_1 = test->exit_info_1 & test->exit_info_1_mask;
> +
> + control->intercept |= test->intercept;
> + control->intercept_cr |= test->intercept_cr;
> + control->intercept_dr |= test->intercept_dr;
> +
> + control->exit_info_1 = ~0ULL;
Perhaps you should poison insn_len and insn_bytes here as well?
^ permalink raw reply [flat|nested] 18+ messages in thread