* [PATCH v5 1/8] KVM: x86: Add helper to provide intercept linear addresses
2026-08-24 12:39 [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
@ 2026-08-24 12:39 ` Tina Zhang
2026-09-04 22:34 ` Jim Mattson
2026-08-24 12:39 ` [PATCH v5 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts Tina Zhang
` (7 subsequent siblings)
8 siblings, 1 reply; 26+ messages in thread
From: Tina Zhang @ 2026-08-24 12:39 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest,
linux-kernel, Tina Zhang
Add get_intercept_linear_addr() to compute the linear address for
intercepts that report one, and pass the result through
x86_instruction_info.
Handle INVLPG as the initial user. INVLPG's memory operand is decoded
with NoAccess, and thus src_val does not contain the operand address.
Compute the address from the decoded segment base and effective address
in the emulator, where the operand state is available.
This allows intercept handlers to use the linear address directly
without duplicating the emulator's address calculation.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/emulate.c | 29 +++++++++++++++++++++--------
arch/x86/kvm/kvm_emulate.h | 1 +
2 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index 8071b372d233..a0e57b64cadd 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -410,6 +410,26 @@ static int em_salc(struct x86_emulate_ctxt *ctxt)
_fault ? X86EMUL_UNHANDLEABLE : X86EMUL_CONTINUE; \
})
+static unsigned long seg_base(struct x86_emulate_ctxt *ctxt, int seg)
+{
+ if (ctxt->mode == X86EMUL_MODE_PROT64 && seg < VCPU_SREG_FS)
+ return 0;
+
+ return ctxt->ops->get_cached_segment_base(ctxt, seg);
+}
+
+static u64 get_intercept_linear_addr(struct x86_emulate_ctxt *ctxt,
+ enum x86_intercept intercept)
+{
+ u64 la;
+
+ if (intercept != x86_intercept_invlpg)
+ return 0;
+
+ la = seg_base(ctxt, ctxt->src.addr.mem.seg) + ctxt->src.addr.mem.ea;
+ return ctxt->mode == X86EMUL_MODE_PROT64 ? la : (u32)la;
+}
+
static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
enum x86_intercept intercept,
enum x86_intercept_stage stage)
@@ -427,6 +447,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,
+ .intercept_linear_addr = get_intercept_linear_addr(ctxt, intercept),
.rip = ctxt->eip,
.next_rip = ctxt->_eip,
};
@@ -520,14 +541,6 @@ static u32 desc_limit_scaled(struct desc_struct *desc)
return desc->g ? (limit << 12) | 0xfff : limit;
}
-static unsigned long seg_base(struct x86_emulate_ctxt *ctxt, int seg)
-{
- if (ctxt->mode == X86EMUL_MODE_PROT64 && seg < VCPU_SREG_FS)
- return 0;
-
- return ctxt->ops->get_cached_segment_base(ctxt, seg);
-}
-
static int emulate_exception(struct x86_emulate_ctxt *ctxt, int vec,
u32 error, bool valid)
{
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index 3e375af15c03..9eda648a1314 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 intercept_linear_addr; /* arch-reported linear address, if any */
u64 rip; /* rip of the instruction */
u64 next_rip; /* rip following the instruction */
};
base-commit: 1b731e5ded480bd1e5546aed35584238661ce72e
--
2.43.7
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 1/8] KVM: x86: Add helper to provide intercept linear addresses
2026-08-24 12:39 ` [PATCH v5 1/8] KVM: x86: Add helper to provide intercept linear addresses Tina Zhang
@ 2026-09-04 22:34 ` Jim Mattson
2026-09-06 0:57 ` Tina Zhang
0 siblings, 1 reply; 26+ messages in thread
From: Jim Mattson @ 2026-09-04 22:34 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> Add get_intercept_linear_addr() to compute the linear address for
> intercepts that report one, and pass the result through
> x86_instruction_info.
>
> Handle INVLPG as the initial user. INVLPG's memory operand is decoded
> with NoAccess, and thus src_val does not contain the operand address.
> Compute the address from the decoded segment base and effective address
> in the emulator, where the operand state is available.
>
> This allows intercept handlers to use the linear address directly
> without duplicating the emulator's address calculation.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> ---
> arch/x86/kvm/emulate.c | 29 +++++++++++++++++++++--------
> arch/x86/kvm/kvm_emulate.h | 1 +
> 2 files changed, 22 insertions(+), 8 deletions(-)
>
> diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
> index 8071b372d233..a0e57b64cadd 100644
> --- a/arch/x86/kvm/emulate.c
> +++ b/arch/x86/kvm/emulate.c
> @@ -410,6 +410,26 @@ static int em_salc(struct x86_emulate_ctxt *ctxt)
> _fault ? X86EMUL_UNHANDLEABLE : X86EMUL_CONTINUE; \
> })
>
> +static unsigned long seg_base(struct x86_emulate_ctxt *ctxt, int seg)
> +{
> + if (ctxt->mode == X86EMUL_MODE_PROT64 && seg < VCPU_SREG_FS)
> + return 0;
> +
> + return ctxt->ops->get_cached_segment_base(ctxt, seg);
> +}
> +
> +static u64 get_intercept_linear_addr(struct x86_emulate_ctxt *ctxt,
> + enum x86_intercept intercept)
> +{
> + u64 la;
> +
> + if (intercept != x86_intercept_invlpg)
> + return 0;
> +
> + la = seg_base(ctxt, ctxt->src.addr.mem.seg) + ctxt->src.addr.mem.ea;
> + return ctxt->mode == X86EMUL_MODE_PROT64 ? la : (u32)la;
> +}
The linear address calculation here is replicated from __linearize().
Perhaps something like:
static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt)
{
unsigned int max_size;
ulong linear;
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 emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
> enum x86_intercept intercept,
> enum x86_intercept_stage stage)
> @@ -427,6 +447,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,
> + .intercept_linear_addr = get_intercept_linear_addr(ctxt, intercept),
Maybe:
+ .invlpg_linear_addr = get_invlpg_linear_addr(ctxt, intercept),
> .rip = ctxt->eip,
> .next_rip = ctxt->_eip,
> };
> @@ -520,14 +541,6 @@ static u32 desc_limit_scaled(struct desc_struct *desc)
> return desc->g ? (limit << 12) | 0xfff : limit;
> }
>
> -static unsigned long seg_base(struct x86_emulate_ctxt *ctxt, int seg)
> -{
> - if (ctxt->mode == X86EMUL_MODE_PROT64 && seg < VCPU_SREG_FS)
> - return 0;
> -
> - return ctxt->ops->get_cached_segment_base(ctxt, seg);
> -}
> -
> static int emulate_exception(struct x86_emulate_ctxt *ctxt, int vec,
> u32 error, bool valid)
> {
> diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
> index 3e375af15c03..9eda648a1314 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 intercept_linear_addr; /* arch-reported linear address, if any */
+ u64 invlpg_linear_addr; /* linear address, if invlpg */
> u64 rip; /* rip of the instruction */
> u64 next_rip; /* rip following the instruction */
> };
>
> base-commit: 1b731e5ded480bd1e5546aed35584238661ce72e
> --
> 2.43.7
>
I don't really like special-casing INVLPG, but I don't see an easy alternative.
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 1/8] KVM: x86: Add helper to provide intercept linear addresses
2026-09-04 22:34 ` Jim Mattson
@ 2026-09-06 0:57 ` Tina Zhang
0 siblings, 0 replies; 26+ messages in thread
From: Tina Zhang @ 2026-09-06 0:57 UTC (permalink / raw)
To: Jim Mattson
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On 9/5/2026 6:34 AM, Jim Mattson wrote:
>> +static u64 get_intercept_linear_addr(struct x86_emulate_ctxt *ctxt,
>> + enum x86_intercept intercept)
>> +{
>> + u64 la;
>> +
>> + if (intercept != x86_intercept_invlpg)
>> + return 0;
>> +
>> + la = seg_base(ctxt, ctxt->src.addr.mem.seg) + ctxt->src.addr.mem.ea;
>> + return ctxt->mode == X86EMUL_MODE_PROT64 ? la : (u32)la;
>> +}
>
> The linear address calculation here is replicated from __linearize().
> Perhaps something like:
> static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt)
> {
> unsigned int max_size;
> ulong linear;
>
> if (intercept != x86_intercept_invlpg)
> return 0;
>
> __linearize(ctxt, ctxt->src.addr.mem, &max_size, 1,
> ctxt->mode, &linear, X86EMUL_F_INVLPG);
> return linear;
> }
I initially used the minimal calculation needed for EXITINFO1 because I
thought some of the additional processing in __linearize() was
unnecessary when reporting the linear address to L1. However, reusing
__linearize() keeps the intercepted path consistent with em_invlpg(). I
will switch to __linearize() with X86EMUL_F_INVLPG.
>
>> static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
>> enum x86_intercept intercept,
>> enum x86_intercept_stage stage)
>> @@ -427,6 +447,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,
>> + .intercept_linear_addr = get_intercept_linear_addr(ctxt, intercept),
>
> Maybe:
> + .invlpg_linear_addr = get_invlpg_linear_addr(ctxt, intercept),
Yes, this is clearer.
Thanks,
Tina
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v5 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts
2026-08-24 12:39 [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
2026-08-24 12:39 ` [PATCH v5 1/8] KVM: x86: Add helper to provide intercept linear addresses Tina Zhang
@ 2026-08-24 12:39 ` Tina Zhang
2026-09-04 23:07 ` Jim Mattson
2026-08-24 12:39 ` [PATCH v5 3/8] KVM: nSVM: Track fresh hardware DecodeAssist bytes Tina Zhang
` (6 subsequent siblings)
8 siblings, 1 reply; 26+ messages in thread
From: Tina Zhang @ 2026-08-24 12:39 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest,
linux-kernel, Tina Zhang
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 EXITINFO fields when
DecodeAssists is exposed to L1.
Generate EXITINFO1 with the GPR number for MOV CR/DR, the interrupt vector
for INTn, and the linear address for INVLPG. Set EXITINFO1 to zero for
CLTS, LMSW, SMSW, and INVLPGA; the INVLPGA address remains in guest rAX.
Clear EXITINFO2 for all covered intercepts and leave unrelated intercepts
unchanged.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/svm/svm.c | 48 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 9d607b98bd06..c7c1f1527c3c 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4810,6 +4810,52 @@ static const struct __x86_intercept {
#undef POST_EX
#undef POST_MEM
+static void svm_prepare_decode_assist_exit_info(struct kvm_vcpu *vcpu,
+ const struct x86_instruction_info *info)
+{
+ struct vmcb *vmcb = to_svm(vcpu)->vmcb;
+ u64 exit_info_1;
+
+ if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
+ return;
+
+ switch (info->intercept) {
+ case x86_intercept_cr_read:
+ case x86_intercept_cr_write:
+ /* MOV CRx: bit 63 set, GPR number in bits 3:0. */
+ exit_info_1 = BIT_ULL(63) | (info->modrm_rm & 0xf);
+ break;
+ case x86_intercept_clts:
+ case x86_intercept_lmsw:
+ case x86_intercept_smsw:
+ /* CLTS/LMSW/SMSW: no decode information, bit 63 clear. */
+ exit_info_1 = 0;
+ break;
+ case x86_intercept_dr_read:
+ case x86_intercept_dr_write:
+ /* MOV DRx: GPR number in bits 3:0. */
+ exit_info_1 = info->modrm_rm & 0xf;
+ break;
+ case x86_intercept_intn:
+ /* INTn: software interrupt number in bits 7:0. */
+ exit_info_1 = info->src_val & 0xff;
+ break;
+ case x86_intercept_invlpg:
+ /* INVLPG: linear address of the target page. */
+ exit_info_1 = info->intercept_linear_addr;
+ break;
+ case x86_intercept_invlpga:
+ /* INVLPGA: the address remains available in guest rAX. */
+ exit_info_1 = 0;
+ break;
+ default:
+ return;
+ }
+
+ vmcb->control.exit_info_1 = exit_info_1;
+ vmcb->control.exit_info_2 = 0;
+}
+
static int svm_check_intercept(struct kvm_vcpu *vcpu,
struct x86_instruction_info *info,
enum x86_intercept_stage stage,
@@ -4931,6 +4977,8 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
break;
}
+ svm_prepare_decode_assist_exit_info(vcpu, info);
+
/* TODO: Advertise NRIPS to guest hypervisor unconditionally */
if (static_cpu_has(X86_FEATURE_NRIPS))
vmcb->control.next_rip = info->next_rip;
--
2.43.7
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts
2026-08-24 12:39 ` [PATCH v5 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts Tina Zhang
@ 2026-09-04 23:07 ` Jim Mattson
2026-09-06 2:24 ` Tina Zhang
0 siblings, 1 reply; 26+ messages in thread
From: Jim Mattson @ 2026-09-04 23:07 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Mon, Aug 24, 2026 at 5:40 AM 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 EXITINFO fields when
> DecodeAssists is exposed to L1.
>
> Generate EXITINFO1 with the GPR number for MOV CR/DR, the interrupt vector
> for INTn, and the linear address for INVLPG. Set EXITINFO1 to zero for
> CLTS, LMSW, SMSW, and INVLPGA; the INVLPGA address remains in guest rAX.
> Clear EXITINFO2 for all covered intercepts and leave unrelated intercepts
> unchanged.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> ---
> arch/x86/kvm/svm/svm.c | 48 ++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 48 insertions(+)
>
> diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
> index 9d607b98bd06..c7c1f1527c3c 100644
> --- a/arch/x86/kvm/svm/svm.c
> +++ b/arch/x86/kvm/svm/svm.c
> @@ -4810,6 +4810,52 @@ static const struct __x86_intercept {
> #undef POST_EX
> #undef POST_MEM
>
> +static void svm_prepare_decode_assist_exit_info(struct kvm_vcpu *vcpu,
> + const struct x86_instruction_info *info)
> +{
> + struct vmcb *vmcb = to_svm(vcpu)->vmcb;
> + u64 exit_info_1;
> +
> + if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
> + return;
> +
> + switch (info->intercept) {
> + case x86_intercept_cr_read:
> + case x86_intercept_cr_write:
> + /* MOV CRx: bit 63 set, GPR number in bits 3:0. */
> + exit_info_1 = BIT_ULL(63) | (info->modrm_rm & 0xf);
> + break;
> + case x86_intercept_clts:
> + case x86_intercept_lmsw:
> + case x86_intercept_smsw:
> + /* CLTS/LMSW/SMSW: no decode information, bit 63 clear. */
> + exit_info_1 = 0;
> + break;
> + case x86_intercept_dr_read:
> + case x86_intercept_dr_write:
> + /* MOV DRx: GPR number in bits 3:0. */
> + exit_info_1 = info->modrm_rm & 0xf;
> + break;
> + case x86_intercept_intn:
> + /* INTn: software interrupt number in bits 7:0. */
> + exit_info_1 = info->src_val & 0xff;
> + break;
> + case x86_intercept_invlpg:
> + /* INVLPG: linear address of the target page. */
> + exit_info_1 = info->intercept_linear_addr;
> + break;
> + case x86_intercept_invlpga:
> + /* INVLPGA: the address remains available in guest rAX. */
> + exit_info_1 = 0;
> + break;
> + default:
> + return;
> + }
> +
> + vmcb->control.exit_info_1 = exit_info_1;
> + vmcb->control.exit_info_2 = 0;
> +}
Can this entire function be integrated into the existing switch
statement in svm_check_intercept()?
And, if !guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS), should
exit_info_1 be cleared for these intercepts?
> static int svm_check_intercept(struct kvm_vcpu *vcpu,
> struct x86_instruction_info *info,
> enum x86_intercept_stage stage,
> @@ -4931,6 +4977,8 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
> break;
> }
>
> + svm_prepare_decode_assist_exit_info(vcpu, info);
> +
> /* TODO: Advertise NRIPS to guest hypervisor unconditionally */
> if (static_cpu_has(X86_FEATURE_NRIPS))
> vmcb->control.next_rip = info->next_rip;
> --
> 2.43.7
>
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts
2026-09-04 23:07 ` Jim Mattson
@ 2026-09-06 2:24 ` Tina Zhang
2026-09-06 16:29 ` Jim Mattson
0 siblings, 1 reply; 26+ messages in thread
From: Tina Zhang @ 2026-09-06 2:24 UTC (permalink / raw)
To: Jim Mattson
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On 9/5/2026 7:07 AM, Jim Mattson wrote:
> On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>>
>> +
>> + vmcb->control.exit_info_1 = exit_info_1;
>> + vmcb->control.exit_info_2 = 0;
>> +}
>
> Can this entire function be integrated into the existing switch
> statement in svm_check_intercept()?
Yes, that makes sense.
>
> And, if !guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS), should
> exit_info_1 be cleared for these intercepts?
My reading is that exit_info_1 is undefined for these intercepts when
DecodeAssists is not exposed, and KVM previously left it untouched. I
would therefore prefer to preserve the existing behavior and update
exit_info_1 only when DecodeAssists is exposed. That said, do you see
any reason why KVM should explicitly clear exit_info_1 when
guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS) returns false?
Thanks,
Tina
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v5 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts
2026-09-06 2:24 ` Tina Zhang
@ 2026-09-06 16:29 ` Jim Mattson
0 siblings, 0 replies; 26+ messages in thread
From: Jim Mattson @ 2026-09-06 16:29 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sat, Sep 5, 2026 at 7:24 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
>
>
> On 9/5/2026 7:07 AM, Jim Mattson wrote:
> > On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
> >>
> >> +
> >> + vmcb->control.exit_info_1 = exit_info_1;
> >> + vmcb->control.exit_info_2 = 0;
> >> +}
> >
> > Can this entire function be integrated into the existing switch
> > statement in svm_check_intercept()?
>
> Yes, that makes sense.
>
> >
> > And, if !guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS), should
> > exit_info_1 be cleared for these intercepts?
>
> My reading is that exit_info_1 is undefined for these intercepts when
> DecodeAssists is not exposed, and KVM previously left it untouched. I
> would therefore prefer to preserve the existing behavior and update
> exit_info_1 only when DecodeAssists is exposed. That said, do you see
> any reason why KVM should explicitly clear exit_info_1 when
> guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS) returns false?
You're right. The APM says, "Note that the contents of the EXITINFO1
and EXITINFO2 fields are undefined for intercepts where their use is
not indicated," so pre-Bulldozer CPUs would probably have left them
alone.
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v5 3/8] KVM: nSVM: Track fresh hardware DecodeAssist bytes
2026-08-24 12:39 [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
2026-08-24 12:39 ` [PATCH v5 1/8] KVM: x86: Add helper to provide intercept linear addresses Tina Zhang
2026-08-24 12:39 ` [PATCH v5 2/8] KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts Tina Zhang
@ 2026-08-24 12:39 ` Tina Zhang
2026-09-04 23:42 ` Jim Mattson
2026-08-24 12:39 ` [PATCH v5 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12 Tina Zhang
` (5 subsequent siblings)
8 siblings, 1 reply; 26+ messages in thread
From: Tina Zhang @ 2026-08-24 12:39 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest,
linux-kernel, Tina Zhang
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.
Clear the VMCB02 instruction-byte fields and freshness state before each
nested run. Mark the bytes as fresh only when a data #PF or #NPF came
from hardware; KVM-synthesized exits and instruction-fetch faults leave
the state clear.
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 | 41 +++++++++++++++++++++++++++++++++++++--
arch/x86/kvm/svm/svm.c | 6 +++---
arch/x86/kvm/svm/svm.h | 5 ++++-
3 files changed, 46 insertions(+), 6 deletions(-)
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 73f37b050d0a..6770721d4e4c 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -35,6 +35,38 @@
#define CC KVM_NESTED_VMENTER_CONSISTENCY_CHECK
+static void nested_svm_clear_insn_bytes(struct vmcb *vmcb)
+{
+ vmcb->control.insn_len = 0;
+ memset(vmcb->control.insn_bytes, 0,
+ sizeof(vmcb->control.insn_bytes));
+}
+
+static bool nested_svm_vmexit_has_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_fresh(struct vcpu_svm *svm,
+ bool from_hardware)
+{
+ svm->nested.vmcb02_insn_bytes_fresh =
+ from_hardware && static_cpu_has(X86_FEATURE_DECODEASSISTS) &&
+ nested_svm_vmexit_has_insn_bytes(svm->vmcb);
+}
+
+static void nested_svm_clear_vmcb02_insn_bytes(struct vcpu_svm *svm)
+{
+ nested_svm_clear_insn_bytes(svm->nested.vmcb02.ptr);
+ svm->nested.vmcb02_insn_bytes_fresh = false;
+}
+
static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
struct x86_exception *fault,
bool from_hardware)
@@ -68,6 +100,7 @@ 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;
+ nested_svm_set_vmcb02_insn_bytes_fresh(svm, from_hardware);
nested_svm_vmexit(svm);
}
@@ -868,7 +901,9 @@ 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.
+ * Clear stale DecodeAssist data before L2 runs.
*/
+ nested_svm_clear_vmcb02_insn_bytes(svm);
if (guest_cpu_cap_has(vcpu, X86_FEATURE_VGIF) &&
(vmcb12_ctrl->int_ctl & V_GIF_ENABLE_MASK))
@@ -1643,14 +1678,16 @@ static int nested_svm_intercept(struct vcpu_svm *svm)
return vmexit;
}
-int nested_svm_exit_handled(struct vcpu_svm *svm)
+int nested_svm_exit_handled(struct vcpu_svm *svm, bool from_hardware)
{
int vmexit;
vmexit = nested_svm_intercept(svm);
- if (vmexit == NESTED_EXIT_DONE)
+ if (vmexit == NESTED_EXIT_DONE) {
+ nested_svm_set_vmcb02_insn_bytes_fresh(svm, from_hardware);
nested_svm_vmexit(svm);
+ }
return vmexit;
}
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index c7c1f1527c3c..5426a9669053 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -2571,7 +2571,7 @@ static bool check_selective_cr0_intercepted(struct kvm_vcpu *vcpu,
if (cr0 ^ val) {
svm->vmcb->control.exit_code = SVM_EXIT_CR0_SEL_WRITE;
- ret = (nested_svm_exit_handled(svm) == NESTED_EXIT_DONE);
+ ret = (nested_svm_exit_handled(svm, false) == NESTED_EXIT_DONE);
}
return ret;
@@ -3723,7 +3723,7 @@ static int svm_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)
vmexit = nested_svm_exit_special(svm);
if (vmexit == NESTED_EXIT_CONTINUE)
- vmexit = nested_svm_exit_handled(svm);
+ vmexit = nested_svm_exit_handled(svm, true);
if (vmexit == NESTED_EXIT_DONE)
return 1;
@@ -4983,7 +4983,7 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
if (static_cpu_has(X86_FEATURE_NRIPS))
vmcb->control.next_rip = info->next_rip;
vmcb->control.exit_code = icpt_info.exit_code;
- vmexit = nested_svm_exit_handled(svm);
+ vmexit = nested_svm_exit_handled(svm, false);
ret = (vmexit == NESTED_EXIT_DONE) ? X86EMUL_INTERCEPTED
: X86EMUL_CONTINUE;
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 66b44b54608e..610def16f700 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -242,6 +242,9 @@ struct svm_nested_state {
* on its side.
*/
bool force_msr_bitmap_recalc;
+
+ /* True if VMCB02 has instruction bytes from the current hardware exit. */
+ bool vmcb02_insn_bytes_fresh;
};
struct vcpu_sev_es_state {
@@ -887,7 +890,7 @@ static inline void nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code)
nested_svm_vmexit(svm);
}
-int nested_svm_exit_handled(struct vcpu_svm *svm);
+int nested_svm_exit_handled(struct vcpu_svm *svm, bool from_hardware);
int nested_svm_check_permissions(struct kvm_vcpu *vcpu);
int nested_svm_check_cached_vmcb12(struct kvm_vcpu *vcpu);
int nested_svm_check_exception(struct vcpu_svm *svm, unsigned nr,
--
2.43.7
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 3/8] KVM: nSVM: Track fresh hardware DecodeAssist bytes
2026-08-24 12:39 ` [PATCH v5 3/8] KVM: nSVM: Track fresh hardware DecodeAssist bytes Tina Zhang
@ 2026-09-04 23:42 ` Jim Mattson
2026-09-05 14:02 ` Tina Zhang
0 siblings, 1 reply; 26+ messages in thread
From: Jim Mattson @ 2026-09-04 23:42 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Mon, Aug 24, 2026 at 5:40 AM 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.
>
> Clear the VMCB02 instruction-byte fields and freshness state before each
> nested run. Mark the bytes as fresh only when a data #PF or #NPF came
By "nested run," do you mean "emulated VMRUN"? IIUC, that's when the
bytes and freshness are cleared.
> from hardware; KVM-synthesized exits and instruction-fetch faults leave
> the state clear.
>
> 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 | 41 +++++++++++++++++++++++++++++++++++++--
> arch/x86/kvm/svm/svm.c | 6 +++---
> arch/x86/kvm/svm/svm.h | 5 ++++-
> 3 files changed, 46 insertions(+), 6 deletions(-)
>
> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> index 73f37b050d0a..6770721d4e4c 100644
> --- a/arch/x86/kvm/svm/nested.c
> +++ b/arch/x86/kvm/svm/nested.c
> @@ -35,6 +35,38 @@
>
> #define CC KVM_NESTED_VMENTER_CONSISTENCY_CHECK
>
> +static void nested_svm_clear_insn_bytes(struct vmcb *vmcb)
> +{
> + vmcb->control.insn_len = 0;
> + memset(vmcb->control.insn_bytes, 0,
> + sizeof(vmcb->control.insn_bytes));
> +}
Is it necessary to clear the bytes? The APM says, "All other
intercepts clear bits 7:0 in this field to zero (to indicate an
invalid condition); implementations may leave the other bytes
untouched."
Are you concerned about leaking bytes from unreflected VM-exits handled by L0?
> +static bool nested_svm_vmexit_has_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_fresh(struct vcpu_svm *svm,
> + bool from_hardware)
> +{
> + svm->nested.vmcb02_insn_bytes_fresh =
> + from_hardware && static_cpu_has(X86_FEATURE_DECODEASSISTS) &&
> + nested_svm_vmexit_has_insn_bytes(svm->vmcb);
For consistency, should svm->vmcb be svm->nested.vmcb02.ptr?
> +}
> +
> +static void nested_svm_clear_vmcb02_insn_bytes(struct vcpu_svm *svm)
> +{
> + nested_svm_clear_insn_bytes(svm->nested.vmcb02.ptr);
> + svm->nested.vmcb02_insn_bytes_fresh = false;
> +}
> +
> static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
> struct x86_exception *fault,
> bool from_hardware)
> @@ -68,6 +100,7 @@ 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;
>
> + nested_svm_set_vmcb02_insn_bytes_fresh(svm, from_hardware);
> nested_svm_vmexit(svm);
> }
>
> @@ -868,7 +901,9 @@ 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.
> + * Clear stale DecodeAssist data before L2 runs.
> */
> + nested_svm_clear_vmcb02_insn_bytes(svm);
>
> if (guest_cpu_cap_has(vcpu, X86_FEATURE_VGIF) &&
> (vmcb12_ctrl->int_ctl & V_GIF_ENABLE_MASK))
> @@ -1643,14 +1678,16 @@ static int nested_svm_intercept(struct vcpu_svm *svm)
> return vmexit;
> }
>
> -int nested_svm_exit_handled(struct vcpu_svm *svm)
> +int nested_svm_exit_handled(struct vcpu_svm *svm, bool from_hardware)
I don't think 'from_hardware' is necessary. The two callsites where
from_hardware is false are for opcode exits, and will be ruled out by
the check for nested_svm_vmexit_has_insn_bytes(svm->vmcb). If you drop
this extra parameter, there will be less churn.
> {
> int vmexit;
>
> vmexit = nested_svm_intercept(svm);
>
> - if (vmexit == NESTED_EXIT_DONE)
> + if (vmexit == NESTED_EXIT_DONE) {
> + nested_svm_set_vmcb02_insn_bytes_fresh(svm, from_hardware);
> nested_svm_vmexit(svm);
> + }
>
> return vmexit;
> }
> diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
> index c7c1f1527c3c..5426a9669053 100644
> --- a/arch/x86/kvm/svm/svm.c
> +++ b/arch/x86/kvm/svm/svm.c
> @@ -2571,7 +2571,7 @@ static bool check_selective_cr0_intercepted(struct kvm_vcpu *vcpu,
>
> if (cr0 ^ val) {
> svm->vmcb->control.exit_code = SVM_EXIT_CR0_SEL_WRITE;
> - ret = (nested_svm_exit_handled(svm) == NESTED_EXIT_DONE);
> + ret = (nested_svm_exit_handled(svm, false) == NESTED_EXIT_DONE);
> }
>
> return ret;
> @@ -3723,7 +3723,7 @@ static int svm_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)
> vmexit = nested_svm_exit_special(svm);
>
> if (vmexit == NESTED_EXIT_CONTINUE)
> - vmexit = nested_svm_exit_handled(svm);
> + vmexit = nested_svm_exit_handled(svm, true);
>
> if (vmexit == NESTED_EXIT_DONE)
> return 1;
> @@ -4983,7 +4983,7 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
> if (static_cpu_has(X86_FEATURE_NRIPS))
> vmcb->control.next_rip = info->next_rip;
> vmcb->control.exit_code = icpt_info.exit_code;
> - vmexit = nested_svm_exit_handled(svm);
> + vmexit = nested_svm_exit_handled(svm, false);
>
> ret = (vmexit == NESTED_EXIT_DONE) ? X86EMUL_INTERCEPTED
> : X86EMUL_CONTINUE;
> diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
> index 66b44b54608e..610def16f700 100644
> --- a/arch/x86/kvm/svm/svm.h
> +++ b/arch/x86/kvm/svm/svm.h
> @@ -242,6 +242,9 @@ struct svm_nested_state {
> * on its side.
> */
> bool force_msr_bitmap_recalc;
> +
> + /* True if VMCB02 has instruction bytes from the current hardware exit. */
> + bool vmcb02_insn_bytes_fresh;
> };
>
> struct vcpu_sev_es_state {
> @@ -887,7 +890,7 @@ static inline void nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code)
> nested_svm_vmexit(svm);
> }
>
> -int nested_svm_exit_handled(struct vcpu_svm *svm);
> +int nested_svm_exit_handled(struct vcpu_svm *svm, bool from_hardware);
> int nested_svm_check_permissions(struct kvm_vcpu *vcpu);
> int nested_svm_check_cached_vmcb12(struct kvm_vcpu *vcpu);
> int nested_svm_check_exception(struct vcpu_svm *svm, unsigned nr,
> --
> 2.43.7
>
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 3/8] KVM: nSVM: Track fresh hardware DecodeAssist bytes
2026-09-04 23:42 ` Jim Mattson
@ 2026-09-05 14:02 ` Tina Zhang
0 siblings, 0 replies; 26+ messages in thread
From: Tina Zhang @ 2026-09-05 14:02 UTC (permalink / raw)
To: Jim Mattson
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On 9/5/2026 7:42 AM, Jim Mattson wrote:
> On Mon, Aug 24, 2026 at 5:40 AM 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.
>>
>> Clear the VMCB02 instruction-byte fields and freshness state before each
>> nested run. Mark the bytes as fresh only when a data #PF or #NPF came
>
> By "nested run," do you mean "emulated VMRUN"? IIUC, that's when the
> bytes and freshness are cleared.
Yes, I meant when KVM prepares VMCB02 after emulating L1's VMRUN. The
same helper is also used when restoring nested state. I'll reword the
commit message to make that clear.
>> +static void nested_svm_clear_insn_bytes(struct vmcb *vmcb)
>> +{
>> + vmcb->control.insn_len = 0;
>> + memset(vmcb->control.insn_bytes, 0,
>> + sizeof(vmcb->control.insn_bytes));
>> +}
>
> Is it necessary to clear the bytes? The APM says, "All other
> intercepts clear bits 7:0 in this field to zero (to indicate an
> invalid condition); implementations may leave the other bytes
> untouched."
>
> Are you concerned about leaking bytes from unreflected VM-exits handled by L0?
No, clearing the entire byte array isn't necessary. VMCB02 is private
to L0, and insn_len determines whether the bytes are valid and how many
bytes may be copied to VMCB12. Setting insn_len to zero is sufficient
and matches the behavior allowed by the APM. I'll drop the memset().
>> -int nested_svm_exit_handled(struct vcpu_svm *svm)
>> +int nested_svm_exit_handled(struct vcpu_svm *svm, bool from_hardware)
>
> I don't think 'from_hardware' is necessary. The two callsites where
> from_hardware is false are for opcode exits, and will be ruled out by
> the check for nested_svm_vmexit_has_insn_bytes(svm->vmcb). If you drop
> this extra parameter, there will be less churn.
Agreed. Both non-hardware callsites set an opcode exit code before
calling nested_svm_exit_handled(), so nested_svm_vmexit_has_insn_bytes()
will reject them. I'll drop the from_hardware parameter from
nested_svm_exit_handled() and restore its callers.
Thanks,
Tina
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v5 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12
2026-08-24 12:39 [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (2 preceding siblings ...)
2026-08-24 12:39 ` [PATCH v5 3/8] KVM: nSVM: Track fresh hardware DecodeAssist bytes Tina Zhang
@ 2026-08-24 12:39 ` Tina Zhang
2026-09-04 23:58 ` Jim Mattson
2026-08-24 12:39 ` [PATCH v5 5/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF Tina Zhang
` (4 subsequent siblings)
8 siblings, 1 reply; 26+ messages in thread
From: Tina Zhang @ 2026-08-24 12:39 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest,
linux-kernel, Tina Zhang
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.
Clear the VMCB12 instruction-byte state before rebuilding it, and leave the
length zero 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 | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 6770721d4e4c..2db0ec66e8dc 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -42,6 +42,20 @@ static void nested_svm_clear_insn_bytes(struct vmcb *vmcb)
sizeof(vmcb->control.insn_bytes));
}
+static void nested_svm_copy_insn_bytes(struct vmcb *to,
+ const struct vmcb *from)
+{
+ u8 insn_len = from->control.insn_len;
+
+ nested_svm_clear_insn_bytes(to);
+
+ if (WARN_ON_ONCE(insn_len > sizeof(from->control.insn_bytes)))
+ return;
+
+ to->control.insn_len = insn_len;
+ memcpy(to->control.insn_bytes, from->control.insn_bytes, insn_len);
+}
+
static bool nested_svm_vmexit_has_insn_bytes(const struct vmcb *vmcb)
{
u64 exit_code = vmcb->control.exit_code;
@@ -67,6 +81,25 @@ static void nested_svm_clear_vmcb02_insn_bytes(struct vcpu_svm *svm)
svm->nested.vmcb02_insn_bytes_fresh = 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);
+
+ nested_svm_clear_insn_bytes(vmcb12);
+
+ if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS) ||
+ !nested_svm_vmexit_has_insn_bytes(vmcb02))
+ goto out;
+
+ if (svm->nested.vmcb02_insn_bytes_fresh)
+ nested_svm_copy_insn_bytes(vmcb12, vmcb02);
+
+out:
+ svm->nested.vmcb02_insn_bytes_fresh = false;
+}
+
static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
struct x86_exception *fault,
bool from_hardware)
@@ -1332,6 +1365,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] 26+ messages in thread* Re: [PATCH v5 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12
2026-08-24 12:39 ` [PATCH v5 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12 Tina Zhang
@ 2026-09-04 23:58 ` Jim Mattson
2026-09-06 2:45 ` Tina Zhang
0 siblings, 1 reply; 26+ messages in thread
From: Jim Mattson @ 2026-09-04 23:58 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Mon, Aug 24, 2026 at 5:40 AM 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.
>
> Clear the VMCB12 instruction-byte state before rebuilding it, and leave the
> length zero 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 | 35 +++++++++++++++++++++++++++++++++++
> 1 file changed, 35 insertions(+)
>
> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> index 6770721d4e4c..2db0ec66e8dc 100644
> --- a/arch/x86/kvm/svm/nested.c
> +++ b/arch/x86/kvm/svm/nested.c
> @@ -42,6 +42,20 @@ static void nested_svm_clear_insn_bytes(struct vmcb *vmcb)
> sizeof(vmcb->control.insn_bytes));
> }
>
> +static void nested_svm_copy_insn_bytes(struct vmcb *to,
> + const struct vmcb *from)
> +{
> + u8 insn_len = from->control.insn_len;
> +
> + nested_svm_clear_insn_bytes(to);
It's not obvious to me that this clearing is necessary (except in the
case of the early return below). The APM does not say what happens to
the remaining bytes if there is a short read.
> + if (WARN_ON_ONCE(insn_len > sizeof(from->control.insn_bytes)))
> + return;
> +
> + to->control.insn_len = insn_len;
> + memcpy(to->control.insn_bytes, from->control.insn_bytes, insn_len);
> +}
> static bool nested_svm_vmexit_has_insn_bytes(const struct vmcb *vmcb)
> {
> u64 exit_code = vmcb->control.exit_code;
> @@ -67,6 +81,25 @@ static void nested_svm_clear_vmcb02_insn_bytes(struct vcpu_svm *svm)
> svm->nested.vmcb02_insn_bytes_fresh = 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);
> +
> + nested_svm_clear_insn_bytes(vmcb12);
Clearing here is premature. If L1 does not have
X86_FEATURE_DECODEASSISTS, the "Guest Instruction Bytes" fields of
vmcs12 should not be touched.
Moreover, as I pointed out earlier, if L1 has
X86_FEATURE_DECODEASSISTS, and the VM-exit doesn't have instruction
bytes, you onlyhave to clear bits 7:0.
> + if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS) ||
> + !nested_svm_vmexit_has_insn_bytes(vmcb02))
> + goto out;
> +
> + if (svm->nested.vmcb02_insn_bytes_fresh)
> + nested_svm_copy_insn_bytes(vmcb12, vmcb02);
> +
> +out:
> + svm->nested.vmcb02_insn_bytes_fresh = false;
> +}
> +
> static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
> struct x86_exception *fault,
> bool from_hardware)
> @@ -1332,6 +1365,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] 26+ messages in thread* Re: [PATCH v5 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12
2026-09-04 23:58 ` Jim Mattson
@ 2026-09-06 2:45 ` Tina Zhang
0 siblings, 0 replies; 26+ messages in thread
From: Tina Zhang @ 2026-09-06 2:45 UTC (permalink / raw)
To: Jim Mattson
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On 9/5/2026 7:58 AM, Jim Mattson wrote:
> On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>>
>> +static void nested_svm_copy_insn_bytes(struct vmcb *to,
>> + const struct vmcb *from)
>> +{
>> + u8 insn_len = from->control.insn_len;
>> +
>> + nested_svm_clear_insn_bytes(to);
>
> It's not obvious to me that this clearing is necessary (except in the
> case of the early return below). The APM does not say what happens to
> the remaining bytes if there is a short read.
You're right. I had interpreted the APM as requiring the entire Guest
Instruction Bytes field to be cleared when rebuilding the state, but
that was too broad. For a short read, the remaining bytes are not
defined and do not need to be cleared. Thanks for correcting my
understanding.
>> +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);
>> +
>> + nested_svm_clear_insn_bytes(vmcb12);
>
> Clearing here is premature. If L1 does not have
> X86_FEATURE_DECODEASSISTS, the "Guest Instruction Bytes" fields of
> vmcs12 should not be touched.
Agreed.
>
> Moreover, as I pointed out earlier, if L1 has
> X86_FEATURE_DECODEASSISTS, and the VM-exit doesn't have instruction
> bytes, you onlyhave to clear bits 7:0.
Yes. I'll update it in the next version.
Thanks,
Tina
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v5 5/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF
2026-08-24 12:39 [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (3 preceding siblings ...)
2026-08-24 12:39 ` [PATCH v5 4/8] KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12 Tina Zhang
@ 2026-08-24 12:39 ` Tina Zhang
2026-09-05 0:17 ` Jim Mattson
2026-08-24 12:39 ` [PATCH v5 6/8] KVM: nSVM: Fetch missing DecodeAssist bytes for synthesized #NPF/#PF Tina Zhang
` (3 subsequent siblings)
8 siblings, 1 reply; 26+ messages in thread
From: Tina Zhang @ 2026-08-24 12:39 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest,
linux-kernel, Tina Zhang
SVM DecodeAssists provides instruction bytes for data-access #NPF exits.
Hardware-reflected VM-Exits can use fresh VMCB02 bytes, but a
KVM-synthesized #NPF has no hardware byte state to propagate.
Use the emulator fetch cache when the nested #NPF is the current emulator
exception. Store the bytes in a one-shot buffer that is consumed while
constructing VMCB12 and cleared before the next nested run, preventing an
unrelated nested VM-Exit from reusing stale emulator bytes.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/svm/nested.c | 50 ++++++++++++++++++++++++++++++++++++++-
arch/x86/kvm/svm/svm.h | 12 ++++++++++
2 files changed, 61 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 2db0ec66e8dc..635ff20cc431 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -81,11 +81,45 @@ static void nested_svm_clear_vmcb02_insn_bytes(struct vcpu_svm *svm)
svm->nested.vmcb02_insn_bytes_fresh = false;
}
+static void nested_svm_clear_synthesized_insn_bytes(struct vcpu_svm *svm)
+{
+ svm->nested.synthesized_insn_bytes.prepared = false;
+ svm->nested.synthesized_insn_bytes.insn_len = 0;
+}
+
+static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ struct nested_svm_insn_bytes *synthesized =
+ &svm->nested.synthesized_insn_bytes;
+ struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+
+ static_assert(sizeof(synthesized->insn_bytes) >=
+ sizeof(ctxt->fetch.data));
+
+ nested_svm_clear_synthesized_insn_bytes(svm);
+
+ if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
+ return;
+
+ if (!ctxt || ctxt->eip != kvm_rip_read(vcpu) ||
+ ctxt->fetch.end < ctxt->fetch.data ||
+ ctxt->fetch.end > ctxt->fetch.data + sizeof(ctxt->fetch.data))
+ return;
+
+ synthesized->insn_len = ctxt->fetch.end - ctxt->fetch.data;
+ memcpy(synthesized->insn_bytes, ctxt->fetch.data,
+ synthesized->insn_len);
+ synthesized->prepared = true;
+}
+
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);
+ struct nested_svm_insn_bytes *synthesized =
+ &svm->nested.synthesized_insn_bytes;
nested_svm_clear_insn_bytes(vmcb12);
@@ -93,11 +127,20 @@ static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
!nested_svm_vmexit_has_insn_bytes(vmcb02))
goto out;
- if (svm->nested.vmcb02_insn_bytes_fresh)
+ if (svm->nested.vmcb02_insn_bytes_fresh) {
nested_svm_copy_insn_bytes(vmcb12, vmcb02);
+ goto out;
+ }
+
+ if (synthesized->prepared) {
+ vmcb12->control.insn_len = synthesized->insn_len;
+ memcpy(vmcb12->control.insn_bytes, synthesized->insn_bytes,
+ vmcb12->control.insn_len);
+ }
out:
svm->nested.vmcb02_insn_bytes_fresh = false;
+ nested_svm_clear_synthesized_insn_bytes(svm);
}
static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
@@ -106,6 +149,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;
/*
@@ -134,6 +179,8 @@ static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
vmcb->control.exit_info_2 = fault->address;
nested_svm_set_vmcb02_insn_bytes_fresh(svm, from_hardware);
+ if (from_emulation && !(fault->error_code & PFERR_FETCH_MASK))
+ nested_svm_prepare_synthesized_insn_bytes(vcpu);
nested_svm_vmexit(svm);
}
@@ -937,6 +984,7 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
* Clear stale DecodeAssist data before L2 runs.
*/
nested_svm_clear_vmcb02_insn_bytes(svm);
+ nested_svm_clear_synthesized_insn_bytes(svm);
if (guest_cpu_cap_has(vcpu, X86_FEATURE_VGIF) &&
(vmcb12_ctrl->int_ctl & V_GIF_ENABLE_MASK))
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 610def16f700..9344707e9ffd 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -209,6 +209,12 @@ struct vmcb_ctrl_area_cached {
};
};
+struct nested_svm_insn_bytes {
+ bool prepared;
+ u8 insn_len;
+ u8 insn_bytes[X86_MAX_INSTRUCTION_LENGTH];
+};
+
struct svm_nested_state {
struct kvm_vmcb_info vmcb02;
u64 hsave_msr;
@@ -245,6 +251,12 @@ struct svm_nested_state {
/* True if VMCB02 has instruction bytes from the current hardware exit. */
bool vmcb02_insn_bytes_fresh;
+
+ /*
+ * Cached instruction bytes for the current synthesized nested #NPF.
+ * Valid until the corresponding nested VM-Exit is constructed.
+ */
+ struct nested_svm_insn_bytes synthesized_insn_bytes;
};
struct vcpu_sev_es_state {
--
2.43.7
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 5/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF
2026-08-24 12:39 ` [PATCH v5 5/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF Tina Zhang
@ 2026-09-05 0:17 ` Jim Mattson
2026-09-06 5:33 ` Tina Zhang
0 siblings, 1 reply; 26+ messages in thread
From: Jim Mattson @ 2026-09-05 0:17 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> SVM DecodeAssists provides instruction bytes for data-access #NPF exits.
> Hardware-reflected VM-Exits can use fresh VMCB02 bytes, but a
> KVM-synthesized #NPF has no hardware byte state to propagate.
>
> Use the emulator fetch cache when the nested #NPF is the current emulator
> exception. Store the bytes in a one-shot buffer that is consumed while
> constructing VMCB12 and cleared before the next nested run, preventing an
> unrelated nested VM-Exit from reusing stale emulator bytes.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> ---
> arch/x86/kvm/svm/nested.c | 50 ++++++++++++++++++++++++++++++++++++++-
> arch/x86/kvm/svm/svm.h | 12 ++++++++++
> 2 files changed, 61 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> index 2db0ec66e8dc..635ff20cc431 100644
> --- a/arch/x86/kvm/svm/nested.c
> +++ b/arch/x86/kvm/svm/nested.c
> @@ -81,11 +81,45 @@ static void nested_svm_clear_vmcb02_insn_bytes(struct vcpu_svm *svm)
> svm->nested.vmcb02_insn_bytes_fresh = false;
> }
>
> +static void nested_svm_clear_synthesized_insn_bytes(struct vcpu_svm *svm)
> +{
> + svm->nested.synthesized_insn_bytes.prepared = false;
> + svm->nested.synthesized_insn_bytes.insn_len = 0;
> +}
> +
> +static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)
> +{
> + struct vcpu_svm *svm = to_svm(vcpu);
> + struct nested_svm_insn_bytes *synthesized =
> + &svm->nested.synthesized_insn_bytes;
> + struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
> +
> + static_assert(sizeof(synthesized->insn_bytes) >=
> + sizeof(ctxt->fetch.data));
> +
> + nested_svm_clear_synthesized_insn_bytes(svm);
> +
> + if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
> + return;
> +
> + if (!ctxt || ctxt->eip != kvm_rip_read(vcpu) ||
> + ctxt->fetch.end < ctxt->fetch.data ||
> + ctxt->fetch.end > ctxt->fetch.data + sizeof(ctxt->fetch.data))
> + return;
Peeking into emulator internals violates encapsulation. Rather than
performing arithmetic on fetch.data and fetch.end, the emulator should
provide a function for accessing the fetch cache.
> + synthesized->insn_len = ctxt->fetch.end - ctxt->fetch.data;
> + memcpy(synthesized->insn_bytes, ctxt->fetch.data,
> + synthesized->insn_len);
> + synthesized->prepared = true;
> +}
> +
> 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);
> + struct nested_svm_insn_bytes *synthesized =
> + &svm->nested.synthesized_insn_bytes;
>
> nested_svm_clear_insn_bytes(vmcb12);
>
> @@ -93,11 +127,20 @@ static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
> !nested_svm_vmexit_has_insn_bytes(vmcb02))
> goto out;
>
> - if (svm->nested.vmcb02_insn_bytes_fresh)
> + if (svm->nested.vmcb02_insn_bytes_fresh) {
> nested_svm_copy_insn_bytes(vmcb12, vmcb02);
> + goto out;
> + }
> +
> + if (synthesized->prepared) {
> + vmcb12->control.insn_len = synthesized->insn_len;
> + memcpy(vmcb12->control.insn_bytes, synthesized->insn_bytes,
> + vmcb12->control.insn_len);
These instruction bytes may be short. Although this is fixed in the
next commit, introducing a bug in one patch and fixing it in the next
is bad practice.
> + }
>
> out:
> svm->nested.vmcb02_insn_bytes_fresh = false;
> + nested_svm_clear_synthesized_insn_bytes(svm);
> }
>
> static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
> @@ -106,6 +149,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;
>
> /*
> @@ -134,6 +179,8 @@ static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
> vmcb->control.exit_info_2 = fault->address;
>
> nested_svm_set_vmcb02_insn_bytes_fresh(svm, from_hardware);
> + if (from_emulation && !(fault->error_code & PFERR_FETCH_MASK))
> + nested_svm_prepare_synthesized_insn_bytes(vcpu);
> nested_svm_vmexit(svm);
> }
>
> @@ -937,6 +984,7 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
> * Clear stale DecodeAssist data before L2 runs.
> */
> nested_svm_clear_vmcb02_insn_bytes(svm);
> + nested_svm_clear_synthesized_insn_bytes(svm);
>
> if (guest_cpu_cap_has(vcpu, X86_FEATURE_VGIF) &&
> (vmcb12_ctrl->int_ctl & V_GIF_ENABLE_MASK))
> diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
> index 610def16f700..9344707e9ffd 100644
> --- a/arch/x86/kvm/svm/svm.h
> +++ b/arch/x86/kvm/svm/svm.h
> @@ -209,6 +209,12 @@ struct vmcb_ctrl_area_cached {
> };
> };
>
> +struct nested_svm_insn_bytes {
> + bool prepared;
> + u8 insn_len;
> + u8 insn_bytes[X86_MAX_INSTRUCTION_LENGTH];
> +};
> +
> struct svm_nested_state {
> struct kvm_vmcb_info vmcb02;
> u64 hsave_msr;
> @@ -245,6 +251,12 @@ struct svm_nested_state {
>
> /* True if VMCB02 has instruction bytes from the current hardware exit. */
> bool vmcb02_insn_bytes_fresh;
> +
> + /*
> + * Cached instruction bytes for the current synthesized nested #NPF.
> + * Valid until the corresponding nested VM-Exit is constructed.
> + */
> + struct nested_svm_insn_bytes synthesized_insn_bytes;
> };
I think the implementatiom would be much cleaner if you wrote the
synthesized instruction bytes directly to the vmcb02, rather than
going through this staging buffer.
> struct vcpu_sev_es_state {
> --
> 2.43.7
>
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 5/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF
2026-09-05 0:17 ` Jim Mattson
@ 2026-09-06 5:33 ` Tina Zhang
0 siblings, 0 replies; 26+ messages in thread
From: Tina Zhang @ 2026-09-06 5:33 UTC (permalink / raw)
To: Jim Mattson
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On 9/5/2026 8:17 AM, Jim Mattson wrote:
> On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>> +
>> +static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)
>> +{
>> + struct vcpu_svm *svm = to_svm(vcpu);
>> + struct nested_svm_insn_bytes *synthesized =
>> + &svm->nested.synthesized_insn_bytes;
>> + struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
>> +
>> + static_assert(sizeof(synthesized->insn_bytes) >=
>> + sizeof(ctxt->fetch.data));
>> +
>> + nested_svm_clear_synthesized_insn_bytes(svm);
>> +
>> + if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
>> + return;
>> +
>> + if (!ctxt || ctxt->eip != kvm_rip_read(vcpu) ||
>> + ctxt->fetch.end < ctxt->fetch.data ||
>> + ctxt->fetch.end > ctxt->fetch.data + sizeof(ctxt->fetch.data))
>> + return;
>
> Peeking into emulator internals violates encapsulation. Rather than
> performing arithmetic on fetch.data and fetch.end, the emulator should
> provide a function for accessing the fetch cache.
Agreed. I will add an emulator helper for this.
>
>> + synthesized->insn_len = ctxt->fetch.end - ctxt->fetch.data;
>> + memcpy(synthesized->insn_bytes, ctxt->fetch.data,
>> + synthesized->insn_len);
>> + synthesized->prepared = true;
>> +}
>> +
>> 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);
>> + struct nested_svm_insn_bytes *synthesized =
>> + &svm->nested.synthesized_insn_bytes;
>>
>> nested_svm_clear_insn_bytes(vmcb12);
>>
>> @@ -93,11 +127,20 @@ static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
>> !nested_svm_vmexit_has_insn_bytes(vmcb02))
>> goto out;
>>
>> - if (svm->nested.vmcb02_insn_bytes_fresh)
>> + if (svm->nested.vmcb02_insn_bytes_fresh) {
>> nested_svm_copy_insn_bytes(vmcb12, vmcb02);
>> + goto out;
>> + }
>> +
>> + if (synthesized->prepared) {
>> + vmcb12->control.insn_len = synthesized->insn_len;
>> + memcpy(vmcb12->control.insn_bytes, synthesized->insn_bytes,
>> + vmcb12->control.insn_len);
>
> These instruction bytes may be short. Although this is fixed in the
> next commit, introducing a bug in one patch and fixing it in the next
> is bad practice.
DecodeAssists is advertised to L1 only later in the series, so this path
is not normally active at this point. That said, I agree that each
patch should be self-contained and provide complete behavior.
I will reorder and split the changes.
>> struct svm_nested_state {
>> struct kvm_vmcb_info vmcb02;
>> u64 hsave_msr;
>> @@ -245,6 +251,12 @@ struct svm_nested_state {
>>
>> /* True if VMCB02 has instruction bytes from the current hardware exit. */
>> bool vmcb02_insn_bytes_fresh;
>> +
>> + /*
>> + * Cached instruction bytes for the current synthesized nested #NPF.
>> + * Valid until the corresponding nested VM-Exit is constructed.
>> + */
>> + struct nested_svm_insn_bytes synthesized_insn_bytes;
>> };
>
> I think the implementatiom would be much cleaner if you wrote the
> synthesized instruction bytes directly to the vmcb02, rather than
> going through this staging buffer.
Sounds good. I'll remove the staging buffer and write the synthesized
instruction bytes directly to VMCB02 in the next version.
Thanks,
Tina
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v5 6/8] KVM: nSVM: Fetch missing DecodeAssist bytes for synthesized #NPF/#PF
2026-08-24 12:39 [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (4 preceding siblings ...)
2026-08-24 12:39 ` [PATCH v5 5/8] KVM: nSVM: Use emulator bytes for synthesized nested #NPF Tina Zhang
@ 2026-08-24 12:39 ` Tina Zhang
2026-09-05 0:39 ` Jim Mattson
2026-08-24 12:39 ` [PATCH v5 7/8] KVM: nSVM: Advertise DecodeAssists to L1 Tina Zhang
` (2 subsequent siblings)
8 siblings, 1 reply; 26+ messages in thread
From: Tina Zhang @ 2026-08-24 12:39 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest,
linux-kernel, Tina Zhang
For a synthesized #NPF, the emulator fetch cache is not guaranteed to
contain the full architected 15-byte DecodeAssist window, e.g. it may
contain only the bytes needed to decode the instruction.
Keep preparation of synthesized state limited to capturing a matching
emulator fetch cache for #NPF. When constructing VMCB12, copy those bytes
and fetch any missing tail through L2 guest page tables. If no emulator
bytes are available, fetch the full window from L2 RIP, including for a
queued or synthesized #PF VM-Exit. Stop at a translation fault, read
failure, non-canonical address, or CS limit overrun.
For a non-64-bit L2, truncate each incremented linear address to 32 bits
so that a fetch whose CS.base makes it cross the 4GB boundary wraps as
required.
Do not perform tail or fallback reads for SEV guests. KVM cannot read
plaintext instruction bytes from encrypted guest memory, and the existing
SEV emulation path treats missing hardware DecodeAssist bytes as
unavailable instead of decoding guest memory. For nested SEV, report only
matching emulator bytes already captured for a synthesized #NPF,
potentially a zero instruction-byte count.
Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
---
arch/x86/kvm/svm/nested.c | 58 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 57 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 635ff20cc431..c677ad5df8d6 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -87,6 +87,54 @@ static void nested_svm_clear_synthesized_insn_bytes(struct vcpu_svm *svm)
svm->nested.synthesized_insn_bytes.insn_len = 0;
}
+static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
+ u8 count, u8 max_bytes)
+{
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
+ u64 access = PFERR_FETCH_MASK;
+ gva_t rip = kvm_get_linear_rip(vcpu);
+ struct x86_exception e;
+
+ if (kvm_x86_call(get_cpl)(vcpu) == 3)
+ access |= PFERR_USER_MASK;
+
+ 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);
+ }
+
+ count = min(count, max_bytes);
+
+ while (count < max_bytes) {
+ gva_t addr = rip + count;
+ unsigned int chunk;
+ gpa_t gpa;
+
+ if (!is_64_bit_mode(vcpu))
+ addr = (u32)addr;
+ else if (is_noncanonical_address(addr, vcpu, 0))
+ break;
+
+ chunk = min_t(unsigned int, max_bytes - count,
+ PAGE_SIZE - offset_in_page(addr));
+ gpa = gva_walk->gva_to_gpa(vcpu, gva_walk, addr, access, &e);
+
+ if (gpa == INVALID_GPA ||
+ kvm_vcpu_read_guest_page(vcpu, gpa_to_gfn(gpa),
+ bytes + count,
+ offset_in_page(gpa), chunk))
+ break;
+
+ count += chunk;
+ }
+
+ return count;
+}
+
static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)
{
struct vcpu_svm *svm = to_svm(vcpu);
@@ -120,6 +168,7 @@ static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
struct vcpu_svm *svm = to_svm(vcpu);
struct nested_svm_insn_bytes *synthesized =
&svm->nested.synthesized_insn_bytes;
+ const u8 max_bytes = sizeof(vmcb12->control.insn_bytes);
nested_svm_clear_insn_bytes(vmcb12);
@@ -135,9 +184,16 @@ static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
if (synthesized->prepared) {
vmcb12->control.insn_len = synthesized->insn_len;
memcpy(vmcb12->control.insn_bytes, synthesized->insn_bytes,
- vmcb12->control.insn_len);
+ synthesized->insn_len);
}
+ if (!is_sev_guest(vcpu))
+ vmcb12->control.insn_len =
+ nested_svm_fetch_insn_bytes(vcpu,
+ vmcb12->control.insn_bytes,
+ vmcb12->control.insn_len,
+ max_bytes);
+
out:
svm->nested.vmcb02_insn_bytes_fresh = false;
nested_svm_clear_synthesized_insn_bytes(svm);
--
2.43.7
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 6/8] KVM: nSVM: Fetch missing DecodeAssist bytes for synthesized #NPF/#PF
2026-08-24 12:39 ` [PATCH v5 6/8] KVM: nSVM: Fetch missing DecodeAssist bytes for synthesized #NPF/#PF Tina Zhang
@ 2026-09-05 0:39 ` Jim Mattson
2026-09-06 6:40 ` Tina Zhang
0 siblings, 1 reply; 26+ messages in thread
From: Jim Mattson @ 2026-09-05 0:39 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> For a synthesized #NPF, the emulator fetch cache is not guaranteed to
> contain the full architected 15-byte DecodeAssist window, e.g. it may
> contain only the bytes needed to decode the instruction.
>
> Keep preparation of synthesized state limited to capturing a matching
> emulator fetch cache for #NPF. When constructing VMCB12, copy those bytes
> and fetch any missing tail through L2 guest page tables. If no emulator
> bytes are available, fetch the full window from L2 RIP, including for a
> queued or synthesized #PF VM-Exit. Stop at a translation fault, read
> failure, non-canonical address, or CS limit overrun.
>
> For a non-64-bit L2, truncate each incremented linear address to 32 bits
> so that a fetch whose CS.base makes it cross the 4GB boundary wraps as
> required.
>
> Do not perform tail or fallback reads for SEV guests. KVM cannot read
> plaintext instruction bytes from encrypted guest memory, and the existing
> SEV emulation path treats missing hardware DecodeAssist bytes as
> unavailable instead of decoding guest memory. For nested SEV, report only
> matching emulator bytes already captured for a synthesized #NPF,
> potentially a zero instruction-byte count.
>
> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> ---
> arch/x86/kvm/svm/nested.c | 58 ++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 57 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> index 635ff20cc431..c677ad5df8d6 100644
> --- a/arch/x86/kvm/svm/nested.c
> +++ b/arch/x86/kvm/svm/nested.c
> @@ -87,6 +87,54 @@ static void nested_svm_clear_synthesized_insn_bytes(struct vcpu_svm *svm)
> svm->nested.synthesized_insn_bytes.insn_len = 0;
> }
>
> +static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
> + u8 count, u8 max_bytes)
> +{
> + struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
> + u64 access = PFERR_FETCH_MASK;
> + gva_t rip = kvm_get_linear_rip(vcpu);
> + struct x86_exception e;
> +
> + if (kvm_x86_call(get_cpl)(vcpu) == 3)
> + access |= PFERR_USER_MASK;
> +
> + 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);
> + }
> +
> + count = min(count, max_bytes);
Ugh. Pasting together two partial reads performed at different times
is egregious. This function should read all 15 bytes in one go. That
pretty much renders the emulator's fetch cache useless, except when it
contains the necessary 15 bytes.
> + while (count < max_bytes) {
> + gva_t addr = rip + count;
> + unsigned int chunk;
> + gpa_t gpa;
> +
> + if (!is_64_bit_mode(vcpu))
> + addr = (u32)addr;
> + else if (is_noncanonical_address(addr, vcpu, 0))
> + break;
> +
> + chunk = min_t(unsigned int, max_bytes - count,
> + PAGE_SIZE - offset_in_page(addr));
> + gpa = gva_walk->gva_to_gpa(vcpu, gva_walk, addr, access, &e);
> +
> + if (gpa == INVALID_GPA ||
> + kvm_vcpu_read_guest_page(vcpu, gpa_to_gfn(gpa),
> + bytes + count,
> + offset_in_page(gpa), chunk))
> + break;
> +
> + count += chunk;
> + }
This loop doesn't belong here. Perhaps you just need a simple wrapper
calling kvm_read_guest_virt_helper()?
> + return count;
> +}
> +
> static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)
> {
> struct vcpu_svm *svm = to_svm(vcpu);
> @@ -120,6 +168,7 @@ static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
> struct vcpu_svm *svm = to_svm(vcpu);
> struct nested_svm_insn_bytes *synthesized =
> &svm->nested.synthesized_insn_bytes;
> + const u8 max_bytes = sizeof(vmcb12->control.insn_bytes);
>
> nested_svm_clear_insn_bytes(vmcb12);
>
> @@ -135,9 +184,16 @@ static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
> if (synthesized->prepared) {
> vmcb12->control.insn_len = synthesized->insn_len;
> memcpy(vmcb12->control.insn_bytes, synthesized->insn_bytes,
> - vmcb12->control.insn_len);
> + synthesized->insn_len);
> }
>
> + if (!is_sev_guest(vcpu))
> + vmcb12->control.insn_len =
> + nested_svm_fetch_insn_bytes(vcpu,
> + vmcb12->control.insn_bytes,
> + vmcb12->control.insn_len,
> + max_bytes);
> +
> out:
> svm->nested.vmcb02_insn_bytes_fresh = false;
> nested_svm_clear_synthesized_insn_bytes(svm);
> --
> 2.43.7
>
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 6/8] KVM: nSVM: Fetch missing DecodeAssist bytes for synthesized #NPF/#PF
2026-09-05 0:39 ` Jim Mattson
@ 2026-09-06 6:40 ` Tina Zhang
2026-09-06 16:33 ` Jim Mattson
0 siblings, 1 reply; 26+ messages in thread
From: Tina Zhang @ 2026-09-06 6:40 UTC (permalink / raw)
To: Jim Mattson, Sean Christopherson
Cc: kvm, Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest,
linux-kernel
On 9/5/2026 8:39 AM, Jim Mattson wrote:
> On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>>
>> For a synthesized #NPF, the emulator fetch cache is not guaranteed to
>> contain the full architected 15-byte DecodeAssist window, e.g. it may
>> contain only the bytes needed to decode the instruction.
>>
>> Keep preparation of synthesized state limited to capturing a matching
>> emulator fetch cache for #NPF. When constructing VMCB12, copy those bytes
>> and fetch any missing tail through L2 guest page tables. If no emulator
>> bytes are available, fetch the full window from L2 RIP, including for a
>> queued or synthesized #PF VM-Exit. Stop at a translation fault, read
>> failure, non-canonical address, or CS limit overrun.
>>
>> For a non-64-bit L2, truncate each incremented linear address to 32 bits
>> so that a fetch whose CS.base makes it cross the 4GB boundary wraps as
>> required.
>>
>> Do not perform tail or fallback reads for SEV guests. KVM cannot read
>> plaintext instruction bytes from encrypted guest memory, and the existing
>> SEV emulation path treats missing hardware DecodeAssist bytes as
>> unavailable instead of decoding guest memory. For nested SEV, report only
>> matching emulator bytes already captured for a synthesized #NPF,
>> potentially a zero instruction-byte count.
>>
>> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
>> ---
>> arch/x86/kvm/svm/nested.c | 58 ++++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 57 insertions(+), 1 deletion(-)
>>
>> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
>> index 635ff20cc431..c677ad5df8d6 100644
>> --- a/arch/x86/kvm/svm/nested.c
>> +++ b/arch/x86/kvm/svm/nested.c
>> @@ -87,6 +87,54 @@ static void nested_svm_clear_synthesized_insn_bytes(struct vcpu_svm *svm)
>> svm->nested.synthesized_insn_bytes.insn_len = 0;
>> }
>>
>> +static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
>> + u8 count, u8 max_bytes)
>> +{
>> + struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
>> + u64 access = PFERR_FETCH_MASK;
>> + gva_t rip = kvm_get_linear_rip(vcpu);
>> + struct x86_exception e;
>> +
>> + if (kvm_x86_call(get_cpl)(vcpu) == 3)
>> + access |= PFERR_USER_MASK;
>> +
>> + 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);
>> + }
>> +
>> + count = min(count, max_bytes);
>
> Ugh. Pasting together two partial reads performed at different times
> is egregious. This function should read all 15 bytes in one go. That
> pretty much renders the emulator's fetch cache useless, except when it
> contains the necessary 15 bytes.
This patch was based on the discussion from the first version of this
series[1]. My understanding from that exchange was that preserving the
bytes used by the emulator and fetching the missing tail later was the
intended approach, as it retains the bytes actually used to decode the
instruction.
Did I misunderstand the conclusion of that discussion? If the
preference is now to avoid combining reads performed at different times,
I can change the next version to use the emulator fetch cache only when
it contains the full 15-byte window, and otherwise fetch all 15 bytes in
one operation.
[1]
https://lore.kernel.org/kvm/20260629125205.52394-1-zhang_wei@open-hieco.net/T/#m3fa3f64ddd3284b312d3ddb44fd30a2e26708037
Thanks,
Tina
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 6/8] KVM: nSVM: Fetch missing DecodeAssist bytes for synthesized #NPF/#PF
2026-09-06 6:40 ` Tina Zhang
@ 2026-09-06 16:33 ` Jim Mattson
2026-09-10 0:51 ` Sean Christopherson
0 siblings, 1 reply; 26+ messages in thread
From: Jim Mattson @ 2026-09-06 16:33 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sat, Sep 5, 2026 at 11:40 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
>
>
> On 9/5/2026 8:39 AM, Jim Mattson wrote:
> > On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
> >>
> >> For a synthesized #NPF, the emulator fetch cache is not guaranteed to
> >> contain the full architected 15-byte DecodeAssist window, e.g. it may
> >> contain only the bytes needed to decode the instruction.
> >>
> >> Keep preparation of synthesized state limited to capturing a matching
> >> emulator fetch cache for #NPF. When constructing VMCB12, copy those bytes
> >> and fetch any missing tail through L2 guest page tables. If no emulator
> >> bytes are available, fetch the full window from L2 RIP, including for a
> >> queued or synthesized #PF VM-Exit. Stop at a translation fault, read
> >> failure, non-canonical address, or CS limit overrun.
> >>
> >> For a non-64-bit L2, truncate each incremented linear address to 32 bits
> >> so that a fetch whose CS.base makes it cross the 4GB boundary wraps as
> >> required.
> >>
> >> Do not perform tail or fallback reads for SEV guests. KVM cannot read
> >> plaintext instruction bytes from encrypted guest memory, and the existing
> >> SEV emulation path treats missing hardware DecodeAssist bytes as
> >> unavailable instead of decoding guest memory. For nested SEV, report only
> >> matching emulator bytes already captured for a synthesized #NPF,
> >> potentially a zero instruction-byte count.
> >>
> >> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> >> ---
> >> arch/x86/kvm/svm/nested.c | 58 ++++++++++++++++++++++++++++++++++++++-
> >> 1 file changed, 57 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> >> index 635ff20cc431..c677ad5df8d6 100644
> >> --- a/arch/x86/kvm/svm/nested.c
> >> +++ b/arch/x86/kvm/svm/nested.c
> >> @@ -87,6 +87,54 @@ static void nested_svm_clear_synthesized_insn_bytes(struct vcpu_svm *svm)
> >> svm->nested.synthesized_insn_bytes.insn_len = 0;
> >> }
> >>
> >> +static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
> >> + u8 count, u8 max_bytes)
> >> +{
> >> + struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
> >> + u64 access = PFERR_FETCH_MASK;
> >> + gva_t rip = kvm_get_linear_rip(vcpu);
> >> + struct x86_exception e;
> >> +
> >> + if (kvm_x86_call(get_cpl)(vcpu) == 3)
> >> + access |= PFERR_USER_MASK;
> >> +
> >> + 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);
> >> + }
> >> +
> >> + count = min(count, max_bytes);
> >
> > Ugh. Pasting together two partial reads performed at different times
> > is egregious. This function should read all 15 bytes in one go. That
> > pretty much renders the emulator's fetch cache useless, except when it
> > contains the necessary 15 bytes.
>
> This patch was based on the discussion from the first version of this
> series[1]. My understanding from that exchange was that preserving the
> bytes used by the emulator and fetching the missing tail later was the
> intended approach, as it retains the bytes actually used to decode the
> instruction.
>
> Did I misunderstand the conclusion of that discussion? If the
> preference is now to avoid combining reads performed at different times,
> I can change the next version to use the emulator fetch cache only when
> it contains the full 15-byte window, and otherwise fetch all 15 bytes in
> one operation.
>
> [1]
> https://lore.kernel.org/kvm/20260629125205.52394-1-zhang_wei@open-hieco.net/T/#m3fa3f64ddd3284b312d3ddb44fd30a2e26708037
I still don't like it, but Sean overruled me, so I will be quiet now. :)
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 6/8] KVM: nSVM: Fetch missing DecodeAssist bytes for synthesized #NPF/#PF
2026-09-06 16:33 ` Jim Mattson
@ 2026-09-10 0:51 ` Sean Christopherson
0 siblings, 0 replies; 26+ messages in thread
From: Sean Christopherson @ 2026-09-10 0:51 UTC (permalink / raw)
To: Jim Mattson
Cc: Tina Zhang, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Sun, Sep 06, 2026, Jim Mattson wrote:
> On Sat, Sep 5, 2026 at 11:40 PM Tina Zhang <zhang_wei@open-hieco.net> wrote:
> > On 9/5/2026 8:39 AM, Jim Mattson wrote:
> > > On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
> > >>
> > >> For a synthesized #NPF, the emulator fetch cache is not guaranteed to
> > >> contain the full architected 15-byte DecodeAssist window, e.g. it may
> > >> contain only the bytes needed to decode the instruction.
> > >>
> > >> Keep preparation of synthesized state limited to capturing a matching
> > >> emulator fetch cache for #NPF. When constructing VMCB12, copy those bytes
> > >> and fetch any missing tail through L2 guest page tables. If no emulator
> > >> bytes are available, fetch the full window from L2 RIP, including for a
> > >> queued or synthesized #PF VM-Exit. Stop at a translation fault, read
> > >> failure, non-canonical address, or CS limit overrun.
> > >>
> > >> For a non-64-bit L2, truncate each incremented linear address to 32 bits
> > >> so that a fetch whose CS.base makes it cross the 4GB boundary wraps as
> > >> required.
> > >>
> > >> Do not perform tail or fallback reads for SEV guests. KVM cannot read
> > >> plaintext instruction bytes from encrypted guest memory, and the existing
> > >> SEV emulation path treats missing hardware DecodeAssist bytes as
> > >> unavailable instead of decoding guest memory. For nested SEV, report only
> > >> matching emulator bytes already captured for a synthesized #NPF,
> > >> potentially a zero instruction-byte count.
> > >>
> > >> Signed-off-by: Tina Zhang <zhang_wei@open-hieco.net>
> > >> ---
> > >> arch/x86/kvm/svm/nested.c | 58 ++++++++++++++++++++++++++++++++++++++-
> > >> 1 file changed, 57 insertions(+), 1 deletion(-)
> > >>
> > >> diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
> > >> index 635ff20cc431..c677ad5df8d6 100644
> > >> --- a/arch/x86/kvm/svm/nested.c
> > >> +++ b/arch/x86/kvm/svm/nested.c
> > >> @@ -87,6 +87,54 @@ static void nested_svm_clear_synthesized_insn_bytes(struct vcpu_svm *svm)
> > >> svm->nested.synthesized_insn_bytes.insn_len = 0;
> > >> }
> > >>
> > >> +static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
> > >> + u8 count, u8 max_bytes)
> > >> +{
> > >> + struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
> > >> + u64 access = PFERR_FETCH_MASK;
> > >> + gva_t rip = kvm_get_linear_rip(vcpu);
> > >> + struct x86_exception e;
> > >> +
> > >> + if (kvm_x86_call(get_cpl)(vcpu) == 3)
> > >> + access |= PFERR_USER_MASK;
> > >> +
> > >> + 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);
> > >> + }
> > >> +
> > >> + count = min(count, max_bytes);
> > >
> > > Ugh. Pasting together two partial reads performed at different times
> > > is egregious.
I don't love it either, but IMO (obviously) it's better than potentially reporting
completely different bytes than what KVM emulated, especially when KVM emulated
using the buffer provided by the CPU.
And practically speaking, KVM will always be splicing together two partial reads
when the instruction splits a page boundary, which is the most common case where
KVM will even need to read more bytes at this phase.
> > > This function should read all 15 bytes in one go. That
> > > pretty much renders the emulator's fetch cache useless, except when it
> > > contains the necessary 15 bytes.
> >
> > This patch was based on the discussion from the first version of this
> > series[1]. My understanding from that exchange was that preserving the
> > bytes used by the emulator and fetching the missing tail later was the
> > intended approach, as it retains the bytes actually used to decode the
> > instruction.
> >
> > Did I misunderstand the conclusion of that discussion? If the
> > preference is now to avoid combining reads performed at different times,
> > I can change the next version to use the emulator fetch cache only when
> > it contains the full 15-byte window, and otherwise fetch all 15 bytes in
> > one operation.
> >
> > [1]
> > https://lore.kernel.org/kvm/20260629125205.52394-1-zhang_wei@open-hieco.net/T/#m3fa3f64ddd3284b312d3ddb44fd30a2e26708037
>
> I still don't like it, but Sean overruled me, so I will be quiet now. :)
You can always appeal to Paolo. I'm one of the District Courts, Paolo is the
Court of Appeals, and Linus is the Supreme Court. :-D
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v5 7/8] KVM: nSVM: Advertise DecodeAssists to L1
2026-08-24 12:39 [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (5 preceding siblings ...)
2026-08-24 12:39 ` [PATCH v5 6/8] KVM: nSVM: Fetch missing DecodeAssist bytes for synthesized #NPF/#PF Tina Zhang
@ 2026-08-24 12:39 ` Tina Zhang
2026-09-05 0:44 ` Jim Mattson
2026-08-24 12:39 ` [PATCH v5 8/8] KVM: selftests: Add nested SVM DecodeAssists test Tina Zhang
2026-09-03 2:41 ` [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
8 siblings, 1 reply; 26+ messages in thread
From: Tina Zhang @ 2026-08-24 12:39 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest,
linux-kernel, Tina Zhang
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>
---
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 5426a9669053..a99a1dc3426f 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] 26+ messages in thread* Re: [PATCH v5 7/8] KVM: nSVM: Advertise DecodeAssists to L1
2026-08-24 12:39 ` [PATCH v5 7/8] KVM: nSVM: Advertise DecodeAssists to L1 Tina Zhang
@ 2026-09-05 0:44 ` Jim Mattson
0 siblings, 0 replies; 26+ messages in thread
From: Jim Mattson @ 2026-09-05 0:44 UTC (permalink / raw)
To: Tina Zhang
Cc: Sean Christopherson, kvm, Paolo Bonzini, Shuah Khan, zhouyanjing,
linux-kselftest, linux-kernel
On Mon, Aug 24, 2026 at 5:40 AM Tina Zhang <zhang_wei@open-hieco.net> wrote:
>
> 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>
Once the implementation issues are addressed...
Reviewed-by: Jim Mattson <jmattson@google.com>
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v5 8/8] KVM: selftests: Add nested SVM DecodeAssists test
2026-08-24 12:39 [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (6 preceding siblings ...)
2026-08-24 12:39 ` [PATCH v5 7/8] KVM: nSVM: Advertise DecodeAssists to L1 Tina Zhang
@ 2026-08-24 12:39 ` Tina Zhang
2026-09-03 2:41 ` [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
8 siblings, 0 replies; 26+ messages in thread
From: Tina Zhang @ 2026-08-24 12:39 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest,
linux-kernel, Tina Zhang
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, truncated on-demand fetching at an unreadable page, and the
absence of instruction bytes for an instruction-fetch #PF.
The synthesized OUTSB #NPF runs by default. The synthesized #PF and
instruction-intercept cases run when kvm.force_emulation_prefix=1 is
enabled.
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 | 482 ++++++++++++++++++
3 files changed, 484 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 00123169a190..41a3db9a386b 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..7db8042a27c0
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
@@ -0,0 +1,482 @@
+// 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 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 __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");
+}
+
+static void l2_movsb_code(void)
+{
+ asm volatile("mov %0, %%rsi\n\t"
+ "mov %1, %%rdi\n\t"
+ "movsb"
+ : : "r"(&mmio_source), "r"(&npf_target)
+ : "rsi", "rdi", "memory");
+ GUEST_FAIL("L2 MOVSB did not cause a nested page fault");
+}
+
+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;
+ control->exit_info_2 = ~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);
+
+ __GUEST_ASSERT(!control->exit_info_2,
+ "%s: expected EXITINFO2 to be clear, got %#lx",
+ test->name, (unsigned long)control->exit_info_2);
+
+ 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.
+ */
+static void test_synthesized_npf_after_hardware_npf(struct svm_test_data *svm,
+ gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ 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);
+ assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * 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);
+}
+
+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_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 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;
+ 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");
+
+ vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+ 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);
+ vcpu_run(vcpu);
+ assert_ucall_done(vcpu);
+
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.43.7
^ permalink raw reply [flat|nested] 26+ messages in thread* Re: [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests
2026-08-24 12:39 [PATCH v5 0/8] KVM: nSVM: Enable DecodeAssists for nested guests Tina Zhang
` (7 preceding siblings ...)
2026-08-24 12:39 ` [PATCH v5 8/8] KVM: selftests: Add nested SVM DecodeAssists test Tina Zhang
@ 2026-09-03 2:41 ` Tina Zhang
8 siblings, 0 replies; 26+ messages in thread
From: Tina Zhang @ 2026-09-03 2:41 UTC (permalink / raw)
To: Sean Christopherson, Jim Mattson, kvm
Cc: Paolo Bonzini, Shuah Khan, zhouyanjing, linux-kselftest, linux-kernel
Hi Sean and Jim,
A gentle ping on this series. I believe v5 addresses all the comments
from the previous review rounds. Could you please take another look when
you have a chance? Any further comments would be appreciated.
Thanks,
Tina
On 8/24/2026 8:39 PM, Tina Zhang wrote:
> The SVM DecodeAssists feature provides decode state for selected
> VM-Exits. KVM currently does not expose this feature to L1. Some L1
> hypervisors may therefore treat the platform's SVM support as
> incomplete.
>
> In practice, this was observed with Hyper-V running on top of KVM.
> Hyper-V appears to require DecodeAssists before enabling nested SVM for
> its guests. Virtualizing the feature lets users enable Hyper-V
> virtualization features inside a Windows VM when needed, e.g. to run
> QEMU/KVM in WSL. Without DecodeAssists, Hyper-V does not enable nested
> SVM because DecodeAssists is missing from KVM's virtual SVM model.
>
> Virtualize both parts of DecodeAssists for nested SVM. For emulated
> MOV CR/DR, INTn, INVLPG, and INVLPGA intercepts, synthesize the
> architectural EXITINFO state that hardware would provide to L1.
>
> For data #PF and #NPF VM-Exits, provide GuestInstrBytes from fresh
> VMCB02 state, matching emulator fetch bytes, or an on-demand fetch from
> the current L2 CS:RIP, depending on how the nested VM-Exit was produced.
> Instruction-fetch faults report no instruction bytes, and encrypted
> guests do not use the on-demand fallback.
>
> Add a focused selftest covering synthesized EXITINFO, hardware and
> synthesized GuestInstrBytes, truncated instruction fetching, and
> instruction-fetch faults. The test has been run with
> kvm.force_emulation_prefix both disabled and enabled.
>
> Changes since v4:
> - Drop the generic queued-exception provenance tracking for delayed and
> synthesized #PF VM-Exits. Fetch their instruction bytes from the
> current L2 CS:RIP when constructing VMCB12 instead.
> - Keep matching emulator fetch bytes only for synthesized #NPF exits,
> and use the on-demand fetch to fill any missing tail.
> - Reduce the series from nine to eight patches by removing the generic
> x86 exception-tracking patch.
> - Trim the selftest to focused cases that cover distinct implementation
> paths and regressions, removing redundant instruction variants,
> overlapping boundary cases, and userspace event-state coverage.
>
> v4:
> https://lore.kernel.org/r/cover.1787116250.git.zhang_wei@open-hieco.net
>
> Changes since v3:
> - Rebase onto kvm-x86/next.
> - Register DecodeAssists in the CPUID 0x8000000A SVM capability
> initializer so that common code validates its CPUID word before the
> SVM code enables it for nested guests.
> - Make the VMCB02 instruction-byte source const and simplify the
> synthesized-byte copy and fallback-fetch flow.
>
> v3:
> https://lore.kernel.org/r/cover.1785411877.git.zhang_wei@open-hieco.net
>
> Changes since v2:
> - Rebase onto kvm-x86/next.
> - Track hardware-provided instruction bytes independently of the VMCB02
> exit code, and preserve the bytes when L0 handles an intercepted #PF
> before reflecting it to L1.
> - Select the instruction-byte source using host-owned VMCB02 state
> instead of control fields in guest-owned VMCB12.
> - Record whether a queued #PF VM-Exit has a matching emulator context,
> so userspace-injected #PF exits do not consume stale emulator bytes.
> - Stop fallback instruction fetches at noncanonical addresses and at the
> 32-bit linear-address boundary.
> - Extend the selftest with regression coverage for replacing a hardware
> #NPF with a synthesized #NPF and for userspace-injected #PF during
> emulation, and harden its page layout and ucall handling.
>
> v2:
> https://lore.kernel.org/r/cover.1783999988.git.zhang_wei@open-hieco.net
>
> Changes since v1:
> - Split the implementation into seven patches to make the individual
> pieces easier to review.
> - Add EXITINFO virtualization for emulator-generated MOV CR/DR, INTn,
> INVLPG, and INVLPGA intercepts.
> - Limit GuestInstrBytes propagation to data #NPF and intercepted #PF
> exits, and clear the fields for instruction-fetch and unrelated exits.
> - Provide GuestInstrBytes for KVM-synthesized data #PF/#NPF exits. Use
> matching emulator bytes first and fetch missing bytes from L2 RIP as a
> fallback, while avoiding fallback reads for SEV guests.
> - Expand the selftest beyond hardware #NPF and stale-state coverage to
> exercise hardware, synthesized, userspace-injected, instruction-fetch,
> page-boundary, and CS-limit cases.
>
> v1:
> https://lore.kernel.org/r/20260629125205.52394-1-zhang_wei@open-hieco.net
>
> Tina Zhang (8):
> KVM: x86: Add helper to provide intercept linear addresses
> KVM: nSVM: Synthesize DecodeAssists EXITINFO for emulated intercepts
> KVM: nSVM: Track fresh hardware DecodeAssist bytes
> KVM: nSVM: Propagate hardware DecodeAssist bytes to VMCB12
> KVM: nSVM: Use emulator bytes for synthesized nested #NPF
> KVM: nSVM: Fetch missing DecodeAssist bytes for synthesized #NPF/#PF
> KVM: nSVM: Advertise DecodeAssists to L1
> KVM: selftests: Add nested SVM DecodeAssists test
>
> arch/x86/kvm/cpuid.c | 1 +
> arch/x86/kvm/emulate.c | 29 +-
> arch/x86/kvm/kvm_emulate.h | 1 +
> arch/x86/kvm/svm/nested.c | 180 ++++++-
> arch/x86/kvm/svm/svm.c | 56 +-
> arch/x86/kvm/svm/svm.h | 17 +-
> tools/testing/selftests/kvm/Makefile.kvm | 1 +
> .../selftests/kvm/include/x86/processor.h | 1 +
> .../kvm/x86/svm_nested_decode_assists_test.c | 482 ++++++++++++++++++
> 9 files changed, 754 insertions(+), 14 deletions(-)
> create mode 100644 tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
>
>
> base-commit: 1b731e5ded480bd1e5546aed35584238661ce72e
^ permalink raw reply [flat|nested] 26+ messages in thread