* Re: [PATCH v3 1/2] KVM: TDX: Allow userspace to return errors to guest for MAPGPA
From: Sean Christopherson @ 2026-02-17 19:20 UTC (permalink / raw)
To: Michael Roth
Cc: Tom Lendacky, Sagi Shahar, Paolo Bonzini, Dave Hansen,
Kiryl Shutsemau, Rick Edgecombe, Thomas Gleixner, Borislav Petkov,
H. Peter Anvin, x86, kvm, linux-kernel, linux-coco,
Vishal Annapurve
In-Reply-To: <20260217191635.swit2awsmwrj57th@amd.com>
On Tue, Feb 17, 2026, Michael Roth wrote:
> On Tue, Feb 17, 2026 at 12:45:52PM -0600, Tom Lendacky wrote:
> > On 2/17/26 12:05, Michael Roth wrote:
> > >> diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
> > >> index 2d7a4d52ccfb..056a44b9d78b 100644
> > >> --- a/arch/x86/kvm/vmx/tdx.c
> > >> +++ b/arch/x86/kvm/vmx/tdx.c
> > >> @@ -1186,10 +1186,21 @@ static void __tdx_map_gpa(struct vcpu_tdx *tdx);
> > >>
> > >> static int tdx_complete_vmcall_map_gpa(struct kvm_vcpu *vcpu)
> > >> {
> > >> + u64 hypercall_ret = READ_ONCE(vcpu->run->hypercall.ret);
> > >> struct vcpu_tdx *tdx = to_tdx(vcpu);
> > >>
> > >> - if (vcpu->run->hypercall.ret) {
> > >> - tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> > >> + if (hypercall_ret) {
> > >> + if (hypercall_ret == EAGAIN) {
> > >> + tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
> > >> + } else if (vcpu->run->hypercall.ret == EINVAL) {
> > >> + tdvmcall_set_return_code(
> > >> + vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> > >> + } else {
> > >> + WARN_ON_ONCE(
> > >> + kvm_is_valid_map_gpa_range_ret(hypercall_ret));
> > >> + return -EINVAL;
> > >> + }
> > >> +
> > >> tdx->vp_enter_args.r11 = tdx->map_gpa_next;
> > >> return 1;
> > >> }
> > >
> > > Maybe slightly more readable?
> > >
> > > switch (hypercall_ret) {
> > > case EAGAIN:
> > > tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
> > > /* fallthrough */
> >
> > I think you want a break here, not a fallthrough, so that you don't set
> > the return code twice with the last one not being correct for EAGAIN.
>
> Doh, thanks for the catch. I guess a break for the EINVAL case as well would
> be more consistent then.
>
> switch (hypercall_ret) {
> case EAGAIN:
> tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
> break;
> case EINVAL:
> tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> break;
> case 0:
> break;
> case default:
> WARN_ON_ONCE(kvm_is_valid_map_gpa_range_ret(hypercall_ret));
> return -EINVAL;
> }
>
> tdx->vp_enter_args.r11 = tdx->map_gpa_next;
> return 1;
Heh, except then KVM will fail to handle the next chunk on success. I like the
idea of a switch statement, so what if we add that and dedup the error handling?
static int tdx_complete_vmcall_map_gpa(struct kvm_vcpu *vcpu)
{
u64 hypercall_ret = READ_ONCE(vcpu->run->hypercall.ret);
struct vcpu_tdx *tdx = to_tdx(vcpu);
long rc;
switch (hypercall_ret) {
case 0:
break;
case EAGAIN:
rc = TDVMCALL_STATUS_RETRY;
goto propagate_error;
case EINVAL:
rc = TDVMCALL_STATUS_INVALID_OPERAND;
goto propagate_error;
default:
WARN_ON_ONCE(kvm_is_valid_map_gpa_range_ret(hypercall_ret));
return -EINVAL;
}
tdx->map_gpa_next += TDX_MAP_GPA_MAX_LEN;
if (tdx->map_gpa_next >= tdx->map_gpa_end)
return 1;
/*
* Stop processing the remaining part if there is a pending interrupt,
* which could be qualified to deliver. Skip checking pending RVI for
* TDVMCALL_MAP_GPA, see comments in tdx_protected_apic_has_interrupt().
*/
if (kvm_vcpu_has_events(vcpu)) {
rc = TDVMCALL_STATUS_RETRY;
goto propagate_error;
}
__tdx_map_gpa(tdx);
return 0;
propagate_error:
tdvmcall_set_return_code(vcpu, rc);
tdx->vp_enter_args.r11 = tdx->map_gpa_next;
return 1;
}
^ permalink raw reply
* Re: [PATCH v3 1/2] KVM: TDX: Allow userspace to return errors to guest for MAPGPA
From: Michael Roth @ 2026-02-17 19:16 UTC (permalink / raw)
To: Tom Lendacky
Cc: Sagi Shahar, Sean Christopherson, Paolo Bonzini, Dave Hansen,
Kiryl Shutsemau, Rick Edgecombe, Thomas Gleixner, Borislav Petkov,
H. Peter Anvin, x86, kvm, linux-kernel, linux-coco,
Vishal Annapurve
In-Reply-To: <037084a1-2019-4bd2-b1ed-7f34f9128e37@amd.com>
On Tue, Feb 17, 2026 at 12:45:52PM -0600, Tom Lendacky wrote:
> On 2/17/26 12:05, Michael Roth wrote:
> > On Fri, Feb 06, 2026 at 10:28:28PM +0000, Sagi Shahar wrote:
> >> From: Vishal Annapurve <vannapurve@google.com>
> >>
> >> MAPGPA request from TDX VMs gets split into chunks by KVM using a loop
> >> of userspace exits until the complete range is handled.
> >>
> >> In some cases userspace VMM might decide to break the MAPGPA operation
> >> and continue it later. For example: in the case of intrahost migration
> >> userspace might decide to continue the MAPGPA operation after the
> >> migration is completed.
> >>
> >> Allow userspace to signal to TDX guests that the MAPGPA operation should
> >> be retried the next time the guest is scheduled.
> >>
> >> This is potentially a breaking change since if userspace sets
> >> hypercall.ret to a value other than EBUSY or EINVAL an EINVAL error code
> >> will be returned to userspace. As of now QEMU never sets hypercall.ret
> >> to a non-zero value after handling KVM_EXIT_HYPERCALL so this change
> >> should be safe.
> >>
> >> Signed-off-by: Vishal Annapurve <vannapurve@google.com>
> >> Co-developed-by: Sagi Shahar <sagis@google.com>
> >> Signed-off-by: Sagi Shahar <sagis@google.com>
> >> ---
> >> Documentation/virt/kvm/api.rst | 3 +++
> >> arch/x86/kvm/vmx/tdx.c | 15 +++++++++++++--
> >> arch/x86/kvm/x86.h | 6 ++++++
> >> 3 files changed, 22 insertions(+), 2 deletions(-)
> >>
> >> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> >> index 01a3abef8abb..9978cd9d897e 100644
> >> --- a/Documentation/virt/kvm/api.rst
> >> +++ b/Documentation/virt/kvm/api.rst
> >> @@ -8679,6 +8679,9 @@ block sizes is exposed in KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES as a
> >>
> >> This capability, if enabled, will cause KVM to exit to userspace
> >> with KVM_EXIT_HYPERCALL exit reason to process some hypercalls.
> >> +Userspace may fail the hypercall by setting hypercall.ret to EINVAL
> >> +or may request the hypercall to be retried the next time the guest run
> >> +by setting hypercall.ret to EAGAIN.
> >>
> >> Calling KVM_CHECK_EXTENSION for this capability will return a bitmask
> >> of hypercalls that can be configured to exit to userspace.
> >> diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
> >> index 2d7a4d52ccfb..056a44b9d78b 100644
> >> --- a/arch/x86/kvm/vmx/tdx.c
> >> +++ b/arch/x86/kvm/vmx/tdx.c
> >> @@ -1186,10 +1186,21 @@ static void __tdx_map_gpa(struct vcpu_tdx *tdx);
> >>
> >> static int tdx_complete_vmcall_map_gpa(struct kvm_vcpu *vcpu)
> >> {
> >> + u64 hypercall_ret = READ_ONCE(vcpu->run->hypercall.ret);
> >> struct vcpu_tdx *tdx = to_tdx(vcpu);
> >>
> >> - if (vcpu->run->hypercall.ret) {
> >> - tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> >> + if (hypercall_ret) {
> >> + if (hypercall_ret == EAGAIN) {
> >> + tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
> >> + } else if (vcpu->run->hypercall.ret == EINVAL) {
> >> + tdvmcall_set_return_code(
> >> + vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> >> + } else {
> >> + WARN_ON_ONCE(
> >> + kvm_is_valid_map_gpa_range_ret(hypercall_ret));
> >> + return -EINVAL;
> >> + }
> >> +
> >> tdx->vp_enter_args.r11 = tdx->map_gpa_next;
> >> return 1;
> >> }
> >
> > Maybe slightly more readable?
> >
> > switch (hypercall_ret) {
> > case EAGAIN:
> > tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
> > /* fallthrough */
>
> I think you want a break here, not a fallthrough, so that you don't set
> the return code twice with the last one not being correct for EAGAIN.
Doh, thanks for the catch. I guess a break for the EINVAL case as well would
be more consistent then.
switch (hypercall_ret) {
case EAGAIN:
tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
break;
case EINVAL:
tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
break;
case 0:
break;
case default:
WARN_ON_ONCE(kvm_is_valid_map_gpa_range_ret(hypercall_ret));
return -EINVAL;
}
tdx->vp_enter_args.r11 = tdx->map_gpa_next;
return 1;
Thanks,
Mike
> > switch (hypercall_ret) {
> > case EAGAIN:
> > tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
> > /* fallthrough */
>
> I think you want a break here, not a fallthrough, so that you don't set
> the return code twice with the last one not being correct for EAGAIN.
>
> Thanks,
> Tom
>
> > case EINVAL:
> > tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> > /* fallthrough */
> > case 0:
> > break;
> > case default:
> > WARN_ON_ONCE(kvm_is_valid_map_gpa_range_ret(hypercall_ret));
> > return -EINVAL;
> > }
> >
> > tdx->vp_enter_args.r11 = tdx->map_gpa_next;
> > return 1;
>
> Thanks,
> Tom
>
> > case EINVAL:
> > tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> > /* fallthrough */
> > case 0:
> > break;
> > case default:
> > WARN_ON_ONCE(kvm_is_valid_map_gpa_range_ret(hypercall_ret));
> > return -EINVAL;
> > }
> >
> > tdx->vp_enter_args.r11 = tdx->map_gpa_next;
> > return 1;
> >
> > Either way:
> >
> > Reviewed-by: Michael Roth <michael.roth@amd.com>
> >
> >> diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
> >> index fdab0ad49098..3d464d12423a 100644
> >> --- a/arch/x86/kvm/x86.h
> >> +++ b/arch/x86/kvm/x86.h
> >> @@ -706,6 +706,12 @@ int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size,
> >> unsigned int port, void *data, unsigned int count,
> >> int in);
> >>
> >> +static inline bool kvm_is_valid_map_gpa_range_ret(u64 hypercall_ret)
> >> +{
> >> + return !hypercall_ret || hypercall_ret == EINVAL ||
> >> + hypercall_ret == EAGAIN;
> >> +}
> >> +
> >> static inline bool user_exit_on_hypercall(struct kvm *kvm, unsigned long hc_nr)
> >> {
> >> return kvm->arch.hypercall_exit_enabled & BIT(hc_nr);
> >> --
> >> 2.53.0.rc2.204.g2597b5adb4-goog
> >>
>
^ permalink raw reply
* Re: [PATCH v3 1/2] KVM: TDX: Allow userspace to return errors to guest for MAPGPA
From: Tom Lendacky @ 2026-02-17 18:45 UTC (permalink / raw)
To: Michael Roth, Sagi Shahar
Cc: Sean Christopherson, Paolo Bonzini, Dave Hansen, Kiryl Shutsemau,
Rick Edgecombe, Thomas Gleixner, Borislav Petkov, H. Peter Anvin,
x86, kvm, linux-kernel, linux-coco, Vishal Annapurve
In-Reply-To: <20260217180511.rvgsx7y45xfmrxvz@amd.com>
On 2/17/26 12:05, Michael Roth wrote:
> On Fri, Feb 06, 2026 at 10:28:28PM +0000, Sagi Shahar wrote:
>> From: Vishal Annapurve <vannapurve@google.com>
>>
>> MAPGPA request from TDX VMs gets split into chunks by KVM using a loop
>> of userspace exits until the complete range is handled.
>>
>> In some cases userspace VMM might decide to break the MAPGPA operation
>> and continue it later. For example: in the case of intrahost migration
>> userspace might decide to continue the MAPGPA operation after the
>> migration is completed.
>>
>> Allow userspace to signal to TDX guests that the MAPGPA operation should
>> be retried the next time the guest is scheduled.
>>
>> This is potentially a breaking change since if userspace sets
>> hypercall.ret to a value other than EBUSY or EINVAL an EINVAL error code
>> will be returned to userspace. As of now QEMU never sets hypercall.ret
>> to a non-zero value after handling KVM_EXIT_HYPERCALL so this change
>> should be safe.
>>
>> Signed-off-by: Vishal Annapurve <vannapurve@google.com>
>> Co-developed-by: Sagi Shahar <sagis@google.com>
>> Signed-off-by: Sagi Shahar <sagis@google.com>
>> ---
>> Documentation/virt/kvm/api.rst | 3 +++
>> arch/x86/kvm/vmx/tdx.c | 15 +++++++++++++--
>> arch/x86/kvm/x86.h | 6 ++++++
>> 3 files changed, 22 insertions(+), 2 deletions(-)
>>
>> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
>> index 01a3abef8abb..9978cd9d897e 100644
>> --- a/Documentation/virt/kvm/api.rst
>> +++ b/Documentation/virt/kvm/api.rst
>> @@ -8679,6 +8679,9 @@ block sizes is exposed in KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES as a
>>
>> This capability, if enabled, will cause KVM to exit to userspace
>> with KVM_EXIT_HYPERCALL exit reason to process some hypercalls.
>> +Userspace may fail the hypercall by setting hypercall.ret to EINVAL
>> +or may request the hypercall to be retried the next time the guest run
>> +by setting hypercall.ret to EAGAIN.
>>
>> Calling KVM_CHECK_EXTENSION for this capability will return a bitmask
>> of hypercalls that can be configured to exit to userspace.
>> diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
>> index 2d7a4d52ccfb..056a44b9d78b 100644
>> --- a/arch/x86/kvm/vmx/tdx.c
>> +++ b/arch/x86/kvm/vmx/tdx.c
>> @@ -1186,10 +1186,21 @@ static void __tdx_map_gpa(struct vcpu_tdx *tdx);
>>
>> static int tdx_complete_vmcall_map_gpa(struct kvm_vcpu *vcpu)
>> {
>> + u64 hypercall_ret = READ_ONCE(vcpu->run->hypercall.ret);
>> struct vcpu_tdx *tdx = to_tdx(vcpu);
>>
>> - if (vcpu->run->hypercall.ret) {
>> - tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
>> + if (hypercall_ret) {
>> + if (hypercall_ret == EAGAIN) {
>> + tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
>> + } else if (vcpu->run->hypercall.ret == EINVAL) {
>> + tdvmcall_set_return_code(
>> + vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
>> + } else {
>> + WARN_ON_ONCE(
>> + kvm_is_valid_map_gpa_range_ret(hypercall_ret));
>> + return -EINVAL;
>> + }
>> +
>> tdx->vp_enter_args.r11 = tdx->map_gpa_next;
>> return 1;
>> }
>
> Maybe slightly more readable?
>
> switch (hypercall_ret) {
> case EAGAIN:
> tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
> /* fallthrough */
I think you want a break here, not a fallthrough, so that you don't set
the return code twice with the last one not being correct for EAGAIN.
Thanks,
Tom
> case EINVAL:
> tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> /* fallthrough */
> case 0:
> break;
> case default:
> WARN_ON_ONCE(kvm_is_valid_map_gpa_range_ret(hypercall_ret));
> return -EINVAL;
> }
>
> tdx->vp_enter_args.r11 = tdx->map_gpa_next;
> return 1;
>
> Either way:
>
> Reviewed-by: Michael Roth <michael.roth@amd.com>
>
>> diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
>> index fdab0ad49098..3d464d12423a 100644
>> --- a/arch/x86/kvm/x86.h
>> +++ b/arch/x86/kvm/x86.h
>> @@ -706,6 +706,12 @@ int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size,
>> unsigned int port, void *data, unsigned int count,
>> int in);
>>
>> +static inline bool kvm_is_valid_map_gpa_range_ret(u64 hypercall_ret)
>> +{
>> + return !hypercall_ret || hypercall_ret == EINVAL ||
>> + hypercall_ret == EAGAIN;
>> +}
>> +
>> static inline bool user_exit_on_hypercall(struct kvm *kvm, unsigned long hc_nr)
>> {
>> return kvm->arch.hypercall_exit_enabled & BIT(hc_nr);
>> --
>> 2.53.0.rc2.204.g2597b5adb4-goog
>>
^ permalink raw reply
* Re: [PATCH v3 2/2] KVM: SEV: Restrict userspace return codes for KVM_HC_MAP_GPA_RANGE
From: Michael Roth @ 2026-02-17 18:19 UTC (permalink / raw)
To: Sagi Shahar
Cc: Sean Christopherson, Paolo Bonzini, Dave Hansen, Kiryl Shutsemau,
Rick Edgecombe, Thomas Gleixner, Borislav Petkov, H. Peter Anvin,
Tom Lendacky, x86, kvm, linux-kernel, linux-coco
In-Reply-To: <20260206222829.3758171-3-sagis@google.com>
On Fri, Feb 06, 2026 at 10:28:29PM +0000, Sagi Shahar wrote:
> To align with the updated TDX api that allows userspace to request
> that guests retry MAP_GPA operations, make sure that userspace is only
> returning EINVAL or EAGAIN as possible error codes.
>
> Signed-off-by: Sagi Shahar <sagis@google.com>
Reviewed-by: Michael Roth <michael.roth@amd.com>
> ---
> arch/x86/kvm/svm/sev.c | 12 ++++++++++--
> 1 file changed, 10 insertions(+), 2 deletions(-)
>
> diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
> index f59c65abe3cf..5f78e4c3eb5d 100644
> --- a/arch/x86/kvm/svm/sev.c
> +++ b/arch/x86/kvm/svm/sev.c
> @@ -3722,9 +3722,13 @@ static int snp_rmptable_psmash(kvm_pfn_t pfn)
>
> static int snp_complete_psc_msr(struct kvm_vcpu *vcpu)
> {
> + u64 hypercall_ret = READ_ONCE(vcpu->run->hypercall.ret);
> struct vcpu_svm *svm = to_svm(vcpu);
>
> - if (vcpu->run->hypercall.ret)
> + if (!kvm_is_valid_map_gpa_range_ret(hypercall_ret))
> + return -EINVAL;
> +
> + if (hypercall_ret)
> set_ghcb_msr(svm, GHCB_MSR_PSC_RESP_ERROR);
> else
> set_ghcb_msr(svm, GHCB_MSR_PSC_RESP);
> @@ -3815,10 +3819,14 @@ static void __snp_complete_one_psc(struct vcpu_svm *svm)
>
> static int snp_complete_one_psc(struct kvm_vcpu *vcpu)
> {
> + u64 hypercall_ret = READ_ONCE(vcpu->run->hypercall.ret);
> struct vcpu_svm *svm = to_svm(vcpu);
> struct psc_buffer *psc = svm->sev_es.ghcb_sa;
>
> - if (vcpu->run->hypercall.ret) {
> + if (!kvm_is_valid_map_gpa_range_ret(hypercall_ret))
> + return -EINVAL;
> +
> + if (hypercall_ret) {
> snp_complete_psc(svm, VMGEXIT_PSC_ERROR_GENERIC);
> return 1; /* resume guest */
> }
> --
> 2.53.0.rc2.204.g2597b5adb4-goog
>
^ permalink raw reply
* Re: [PATCH v3 1/2] KVM: TDX: Allow userspace to return errors to guest for MAPGPA
From: Michael Roth @ 2026-02-17 18:05 UTC (permalink / raw)
To: Sagi Shahar
Cc: Sean Christopherson, Paolo Bonzini, Dave Hansen, Kiryl Shutsemau,
Rick Edgecombe, Thomas Gleixner, Borislav Petkov, H. Peter Anvin,
Tom Lendacky, x86, kvm, linux-kernel, linux-coco,
Vishal Annapurve
In-Reply-To: <20260206222829.3758171-2-sagis@google.com>
On Fri, Feb 06, 2026 at 10:28:28PM +0000, Sagi Shahar wrote:
> From: Vishal Annapurve <vannapurve@google.com>
>
> MAPGPA request from TDX VMs gets split into chunks by KVM using a loop
> of userspace exits until the complete range is handled.
>
> In some cases userspace VMM might decide to break the MAPGPA operation
> and continue it later. For example: in the case of intrahost migration
> userspace might decide to continue the MAPGPA operation after the
> migration is completed.
>
> Allow userspace to signal to TDX guests that the MAPGPA operation should
> be retried the next time the guest is scheduled.
>
> This is potentially a breaking change since if userspace sets
> hypercall.ret to a value other than EBUSY or EINVAL an EINVAL error code
> will be returned to userspace. As of now QEMU never sets hypercall.ret
> to a non-zero value after handling KVM_EXIT_HYPERCALL so this change
> should be safe.
>
> Signed-off-by: Vishal Annapurve <vannapurve@google.com>
> Co-developed-by: Sagi Shahar <sagis@google.com>
> Signed-off-by: Sagi Shahar <sagis@google.com>
> ---
> Documentation/virt/kvm/api.rst | 3 +++
> arch/x86/kvm/vmx/tdx.c | 15 +++++++++++++--
> arch/x86/kvm/x86.h | 6 ++++++
> 3 files changed, 22 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> index 01a3abef8abb..9978cd9d897e 100644
> --- a/Documentation/virt/kvm/api.rst
> +++ b/Documentation/virt/kvm/api.rst
> @@ -8679,6 +8679,9 @@ block sizes is exposed in KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES as a
>
> This capability, if enabled, will cause KVM to exit to userspace
> with KVM_EXIT_HYPERCALL exit reason to process some hypercalls.
> +Userspace may fail the hypercall by setting hypercall.ret to EINVAL
> +or may request the hypercall to be retried the next time the guest run
> +by setting hypercall.ret to EAGAIN.
>
> Calling KVM_CHECK_EXTENSION for this capability will return a bitmask
> of hypercalls that can be configured to exit to userspace.
> diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
> index 2d7a4d52ccfb..056a44b9d78b 100644
> --- a/arch/x86/kvm/vmx/tdx.c
> +++ b/arch/x86/kvm/vmx/tdx.c
> @@ -1186,10 +1186,21 @@ static void __tdx_map_gpa(struct vcpu_tdx *tdx);
>
> static int tdx_complete_vmcall_map_gpa(struct kvm_vcpu *vcpu)
> {
> + u64 hypercall_ret = READ_ONCE(vcpu->run->hypercall.ret);
> struct vcpu_tdx *tdx = to_tdx(vcpu);
>
> - if (vcpu->run->hypercall.ret) {
> - tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> + if (hypercall_ret) {
> + if (hypercall_ret == EAGAIN) {
> + tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
> + } else if (vcpu->run->hypercall.ret == EINVAL) {
> + tdvmcall_set_return_code(
> + vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> + } else {
> + WARN_ON_ONCE(
> + kvm_is_valid_map_gpa_range_ret(hypercall_ret));
> + return -EINVAL;
> + }
> +
> tdx->vp_enter_args.r11 = tdx->map_gpa_next;
> return 1;
> }
Maybe slightly more readable?
switch (hypercall_ret) {
case EAGAIN:
tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_RETRY);
/* fallthrough */
case EINVAL:
tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
/* fallthrough */
case 0:
break;
case default:
WARN_ON_ONCE(kvm_is_valid_map_gpa_range_ret(hypercall_ret));
return -EINVAL;
}
tdx->vp_enter_args.r11 = tdx->map_gpa_next;
return 1;
Either way:
Reviewed-by: Michael Roth <michael.roth@amd.com>
> diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
> index fdab0ad49098..3d464d12423a 100644
> --- a/arch/x86/kvm/x86.h
> +++ b/arch/x86/kvm/x86.h
> @@ -706,6 +706,12 @@ int kvm_sev_es_string_io(struct kvm_vcpu *vcpu, unsigned int size,
> unsigned int port, void *data, unsigned int count,
> int in);
>
> +static inline bool kvm_is_valid_map_gpa_range_ret(u64 hypercall_ret)
> +{
> + return !hypercall_ret || hypercall_ret == EINVAL ||
> + hypercall_ret == EAGAIN;
> +}
> +
> static inline bool user_exit_on_hypercall(struct kvm *kvm, unsigned long hc_nr)
> {
> return kvm->arch.hypercall_exit_enabled & BIT(hc_nr);
> --
> 2.53.0.rc2.204.g2597b5adb4-goog
>
^ permalink raw reply
* Re: [PATCH v12 00/46] arm64: Support for Arm CCA in KVM
From: Mathieu Poirier @ 2026-02-17 17:47 UTC (permalink / raw)
To: Steven Price
Cc: kvm, kvmarm, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <9140efaa-dc54-4a1b-8936-3ef876ca9121@arm.com>
On Mon, Feb 16, 2026 at 02:27:02PM +0000, Steven Price wrote:
> On 16/02/2026 12:33, Steven Price wrote:
> > On 12/02/2026 17:48, Mathieu Poirier wrote:
> >> Hi Steven,
> >>
> >> On Wed, Dec 17, 2025 at 10:10:37AM +0000, Steven Price wrote:
> >>> This series adds support for running protected VMs using KVM under the
> >>> Arm Confidential Compute Architecture (CCA). I've changed the uAPI
> >>> following feedback from Marc.
> >>>
> >>> The main change is that rather than providing a multiplex CAP and
> >>> expecting the VMM to drive the different stages of realm construction,
> >>> there's now just a minimal interface and KVM performs the necessary
> >>> operations when needed.
> >>>
> >>> This series is lightly tested and is meant as a demonstration of the new
> >>> uAPI. There are a number of (known) rough corners in the implementation
> >>> that I haven't dealt with properly.
> >>>
> >>> In particular please note that this series is still targetting RMM v1.0.
> >>> There is an alpha quality version of RMM v2.0 available[1]. Feedback was
> >>> that there are a number of blockers for merging with RMM v1.0 and so I
> >>> expect to rework this series to support RMM v2.0 before it is merged.
> >>> That will necessarily involve reworking the implementation.
> >>>
> >>> Specifically I'm expecting improvements in:
> >>>
> >>> * GIC handling - passing state in registers, and allowing the host to
> >>> fully emulate the GIC by allowing trap bits to be set.
> >>>
> >>> * PMU handling - again providing flexibility to the host's emulation.
> >>>
> >>> * Page size/granule size mismatch. RMM v1.0 defines the granule as 4k,
> >>> RMM v2.0 provide the option for the host to change the granule size.
> >>> The intention is that Linux would simply set the granule size equal
> >>> to its page size which will significantly simplify the management of
> >>> granules.
> >>>
> >>> * Some performance improvement from the use of range-based map/unmap
> >>> RMI calls.
> >>>
> >>> This series is based on v6.19-rc1. It is also available as a git
> >>> repository:
> >>>
> >>> https://gitlab.arm.com/linux-arm/linux-cca cca-host/v12
> >>>
> >>> Work in progress changes for kvmtool are available from the git
> >>> repository below:
> >>>
> >>> https://gitlab.arm.com/linux-arm/kvmtool-cca cca/v10
> >>
> >> The first thing to note is that branch cca/v10 does not compile due to function
> >> realm_configure_parameters() not being called anywhere. Marking the function as
> >> [[maybe_unused]] solved the problem on my side.
> >
> > This is in the kvmtool code - and yes I agree "work in progress" is a
> > bit generous for the current state of that code, "horrid ugly hacks to
> > get things working" is probably more accurate ;)
> >
> > The issue here is that the two things that realm_configure_parameters()
> > set up (hash algorithm and RPV) are currently unsupported with the Linux
> > changes, but will need to be reintroduced in the future. So the contents
> > of the functions which set this up (using the old uAPI) are just #if 0'd
> > out.
> >
> > Depending on the compile flags the code will compile with a warning, but
> > using __attribute__((unused)) would at least make this clear. I'd want
> > to avoid the "[[maybe_unused]]" as it's not used elsewhere in the code
> > base and limits compatibility.
> >
> >> Using the FVP emulator, booting a Realm that includes EDK2 in its boot stack
> >> worked. If EDK2 is not part of the boot stack and a kernel is booted directly
> >> from lkvm, mounting the initrd fails. Looking into this issue further, I see
> >> that from a Realm kernel's perspective, the content of the initrd is either
> >> encrypted or has been trampled on.
> >
> > I can reproduce that, a quick fix is to change INITRD_ALIGN:
> >
> > #define INITRD_ALIGN SZ_64K
> >
> > But the code was meant to be able to deal with an unaligned initrd -
> > I'll see if I can figure out what's going wrong.
>
> Looks like a simple bug in kvm_arm_realm_populate_ram() - it wasn't
> updating the source address when it had to align the start of the
> region. Simple fix below:
>
> ---8<---
> diff --git a/arm/aarch64/realm.c b/arm/aarch64/realm.c
> --- a/arm/aarch64/realm.c
> +++ b/arm/aarch64/realm.c
> @@ -104,7 +104,7 @@ void kvm_arm_realm_populate_ram(struct kvm *kvm,
> unsigned long start,
>
> new_region->start = ALIGN_DOWN(start, SZ_64K);
> new_region->file_end = ALIGN(start + size, SZ_64K);
> - new_region->source = source;
> + new_region->source = source - (start - new_region->start);
That also worked on my side.
>
> list_add_tail(&new_region->list, &realm_ram_regions);
> }
> ---8<---
>
> Thanks for the pointer, and I'll try to remember to include initrd
> testing in future.
>
Very well.
Thanks for looking into this.
> Steve
>
^ permalink raw reply
* Re: [PATCH v3 05/16] x86/virt: Force-clear X86_FEATURE_VMX if configuring root VMCS fails
From: Sean Christopherson @ 2026-02-17 16:31 UTC (permalink / raw)
To: dan.j.williams
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Peter Zijlstra, Arnaldo Carvalho de Melo,
Namhyung Kim, Paolo Bonzini, linux-kernel, linux-coco, kvm,
linux-perf-users, Chao Gao, Xu Yilun
In-Reply-To: <699383e5939ed_2f4a1006f@dwillia2-mobl4.notmuch>
On Mon, Feb 16, 2026, dan.j.williams@intel.com wrote:
> Sean Christopherson wrote:
> > If allocating and configuring a root VMCS fails, clear X86_FEATURE_VMX in
> > all CPUs so that KVM doesn't need to manually check root_vmcs. As added
> > bonuses, clearing VMX will reflect that VMX is unusable in /proc/cpuinfo,
> > and will avoid a futile auto-probe of kvm-intel.ko.
> >
> > WARN if allocating a root VMCS page fails, e.g. to help users figure out
> > why VMX is broken in the unlikely scenario something goes sideways during
> > boot (and because the allocation should succeed unless there's a kernel
> > bug). Tweak KVM's error message to suggest checking kernel logs if VMX is
> > unsupported (in addition to checking BIOS).
> >
> > Signed-off-by: Sean Christopherson <seanjc@google.com>
> [..]
> > diff --git a/arch/x86/virt/hw.c b/arch/x86/virt/hw.c
> > index 56972f594d90..40495872fdfb 100644
> > --- a/arch/x86/virt/hw.c
> > +++ b/arch/x86/virt/hw.c
> [..]
> > @@ -56,7 +56,7 @@ static __init int x86_vmx_init(void)
> > struct vmcs *vmcs;
> >
> > page = __alloc_pages_node(node, GFP_KERNEL | __GFP_ZERO, 0);
> > - if (!page) {
> > + if (WARN_ON_ONCE(!page)) {
>
> Is the warn_alloc() deep in this path not sufficient? Either way, this
> patch looks good to me.
Not sure, I don't have much experience with warn_alloc() in practice. Reading
the code, my initial reaction is that I don't want to rely on warn_alloc() since
it's ratelimited. Multiple allocation failures during boot seems unlikely, but
at the same time, the cost of the WARN_ON_ONCE() here is really just the handful
of bytes for the bug_table entry.
^ permalink raw reply
* Re: [PATCH v3 10/16] x86/virt/tdx: Drop the outdated requirement that TDX be enabled in IRQ context
From: Sean Christopherson @ 2026-02-17 15:25 UTC (permalink / raw)
To: Kai Huang
Cc: dave.hansen@linux.intel.com, kas@kernel.org, peterz@infradead.org,
x86@kernel.org, mingo@redhat.com, bp@alien8.de, tglx@kernel.org,
namhyung@kernel.org, acme@kernel.org, pbonzini@redhat.com,
yilun.xu@linux.intel.com, kvm@vger.kernel.org,
linux-coco@lists.linux.dev, Dan J Williams,
linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org,
Chao Gao
In-Reply-To: <4317ad31f4ef883daee264f72f032974c044c0cc.camel@intel.com>
On Tue, Feb 17, 2026, Kai Huang wrote:
> On Fri, 2026-02-13 at 17:26 -0800, Sean Christopherson wrote:
> > Remove TDX's outdated requirement that per-CPU enabling be done via IPI
> > function call, which was a stale artifact leftover from early versions of
> > the TDX enablement series. The requirement that IRQs be disabled should
> > have been dropped as part of the revamped series that relied on a the KVM
> > rework to enable VMX at module load.
> >
> > In other words, the kernel's "requirement" was never a requirement at all,
> > but instead a reflection of how KVM enabled VMX (via IPI callback) when
> > the TDX subsystem code was merged.
> >
> > Note, accessing per-CPU information is safe even without disabling IRQs,
> > as tdx_online_cpu() is invoked via a cpuhp callback, i.e. from a per-CPU
> > thread.
> >
> > Link: https://lore.kernel.org/all/ZyJOiPQnBz31qLZ7@google.com
> > Signed-off-by: Sean Christopherson <seanjc@google.com>
> >
>
> Hi Sean,
>
> The first call of tdx_cpu_enable() will also call into
> try_init_module_global() (in order to do TDH_SYS_INIT), which also has a
> lockdep_assert_irqs_disabled() + a raw spinlock to make sure TDH_SYS_INIT is
> only called once when tdx_cpu_enable() are called from IRQ disabled context.
>
> This patch only changes tdx_cpu_enable() but doesn't change
> try_init_module_global(), thus the first call of tdx_cpu_enable() will still
> trigger the lockdep_assert_irqs_disabled() failure warning.
>
> I've tried this series on my local and I did see such WARNING during
> boot[*]. We need to fix that too.
>
> But hmm, Chao's "Runtime TDX module update" series actually needs to call
> tdx_cpu_enable() when IRQ disabled, IIUC, since it is called via
> stop_machine_cpuslocked():
>
> https://lore.kernel.org/kvm/20260212143606.534586-18-chao.gao@intel.com/
>
> Maybe we can just keep tdx_cpu_enabled() as-is?
Can't we simply delete the lockdep assert there as well? It should be totally
fine to have a function that can be called from task or IRQ context, so long as
the function is prepared for that possibility. I.e. just because it _can_ be
called from IRQ context doesn't mean it _must_ be called from IRQ context.
E.g. as a fixup
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index bdee937b84d4..f8f5e046159b 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -106,8 +106,7 @@ static __always_inline int sc_retry_prerr(sc_func_t func,
/*
* Do the module global initialization once and return its result.
- * It can be done on any cpu. It's always called with interrupts
- * disabled.
+ * It can be done on any cpu, and from task or IRQ context.
*/
static int try_init_module_global(void)
{
@@ -116,8 +115,6 @@ static int try_init_module_global(void)
static bool sysinit_done;
static int sysinit_ret;
- lockdep_assert_irqs_disabled();
-
raw_spin_lock(&sysinit_lock);
if (sysinit_done)
^ permalink raw reply related
* SVSM Development Call February 18, 2026
From: Stefano Garzarella @ 2026-02-17 15:13 UTC (permalink / raw)
To: coconut-svsm, linux-coco
Hi,
Here is the call for agenda items for this week SVSM development call.
Please send any agenda items you have in mind as a reply to this email
or raise them in the meeting.
We will use the LF Zoom instance. Details of the meeting can be found in
our governance repository at:
https://github.com/coconut-svsm/governance
The link to the COCONUT-SVSM calendar is:
https://zoom-lfx.platform.linuxfoundation.org/meetings/coconut-svsm?view=week
The meeting will be recorded and the recording eventually published.
Ciao,
Stefano
^ permalink raw reply
* Re: [PATCH v3 10/16] x86/virt/tdx: Drop the outdated requirement that TDX be enabled in IRQ context
From: Huang, Kai @ 2026-02-17 11:29 UTC (permalink / raw)
To: seanjc@google.com, dave.hansen@linux.intel.com, kas@kernel.org,
peterz@infradead.org, x86@kernel.org, mingo@redhat.com,
bp@alien8.de, tglx@kernel.org, namhyung@kernel.org,
acme@kernel.org, pbonzini@redhat.com
Cc: yilun.xu@linux.intel.com, kvm@vger.kernel.org,
linux-coco@lists.linux.dev, Williams, Dan J,
linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org,
Gao, Chao
In-Reply-To: <20260214012702.2368778-11-seanjc@google.com>
On Fri, 2026-02-13 at 17:26 -0800, Sean Christopherson wrote:
> Remove TDX's outdated requirement that per-CPU enabling be done via IPI
> function call, which was a stale artifact leftover from early versions of
> the TDX enablement series. The requirement that IRQs be disabled should
> have been dropped as part of the revamped series that relied on a the KVM
> rework to enable VMX at module load.
>
> In other words, the kernel's "requirement" was never a requirement at all,
> but instead a reflection of how KVM enabled VMX (via IPI callback) when
> the TDX subsystem code was merged.
>
> Note, accessing per-CPU information is safe even without disabling IRQs,
> as tdx_online_cpu() is invoked via a cpuhp callback, i.e. from a per-CPU
> thread.
>
> Link: https://lore.kernel.org/all/ZyJOiPQnBz31qLZ7@google.com
> Signed-off-by: Sean Christopherson <seanjc@google.com>
>
Hi Sean,
The first call of tdx_cpu_enable() will also call into
try_init_module_global() (in order to do TDH_SYS_INIT), which also has a
lockdep_assert_irqs_disabled() + a raw spinlock to make sure TDH_SYS_INIT is
only called once when tdx_cpu_enable() are called from IRQ disabled context.
This patch only changes tdx_cpu_enable() but doesn't change
try_init_module_global(), thus the first call of tdx_cpu_enable() will still
trigger the lockdep_assert_irqs_disabled() failure warning.
I've tried this series on my local and I did see such WARNING during
boot[*]. We need to fix that too.
But hmm, Chao's "Runtime TDX module update" series actually needs to call
tdx_cpu_enable() when IRQ disabled, IIUC, since it is called via
stop_machine_cpuslocked():
https://lore.kernel.org/kvm/20260212143606.534586-18-chao.gao@intel.com/
Maybe we can just keep tdx_cpu_enabled() as-is?
[*] lockdep WARNING():
[ 7.755642] ------------[ cut here ]------------
[ 7.756639] __lockdep_enabled && this_cpu_read(hardirqs_enabled)
[ 7.756642] WARNING: arch/x86/virt/vmx/tdx/tdx.c:119 at
try_init_module_global+0x189/0x1c0, CPU#0: cpuhp/0/21
^ permalink raw reply
* Re: [PATCH v1 06/26] x86/virt/tdx: Add tdx_page_array helpers for new TDX Module objects
From: Tony Lindgren @ 2026-02-17 7:37 UTC (permalink / raw)
To: Xu Yilun
Cc: linux-coco, linux-pci, chao.gao, dave.jiang, baolu.lu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen,
dan.j.williams, kas, x86
In-Reply-To: <20251117022311.2443900-7-yilun.xu@linux.intel.com>
On Mon, Nov 17, 2025 at 10:22:50AM +0800, Xu Yilun wrote:
> @@ -296,6 +297,257 @@ static __init int build_tdx_memlist(struct list_head *tmb_list)
> return ret;
> }
>
> +#define TDX_PAGE_ARRAY_MAX_NENTS (PAGE_SIZE / sizeof(u64))
...
> +/*
> + * For holding less than TDX_PAGE_ARRAY_MAX_NENTS (512) pages.
The comment should be "For holding at most TDX_PAGE_ARRAY_MAX_NENTS.."
> + * If more pages are required, use tdx_page_array_alloc() and
> + * tdx_page_array_fill_root() to build tdx_page_array chunk by chunk.
> + */
> +struct tdx_page_array *tdx_page_array_create(unsigned int nr_pages)
> +{
> + int filled;
> +
> + if (nr_pages > TDX_PAGE_ARRAY_MAX_NENTS)
> + return NULL;
To match this check.
> + struct tdx_page_array *array __free(tdx_page_array_free) =
> + tdx_page_array_alloc(nr_pages);
> + if (!array)
> + return NULL;
> +
> + filled = tdx_page_array_fill_root(array, 0);
> + if (filled != nr_pages)
> + return NULL;
> +
> + return no_free_ptr(array);
> +}
> +EXPORT_SYMBOL_GPL(tdx_page_array_create);
Regards,
Tony
^ permalink raw reply
* Re: [PATCH v3 00/16] KVM: x86/tdx: Have TDX handle VMXON during bringup
From: dan.j.williams @ 2026-02-16 23:00 UTC (permalink / raw)
To: Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, Kiryl Shutsemau,
Peter Zijlstra, Arnaldo Carvalho de Melo, Namhyung Kim,
Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, linux-perf-users, Chao Gao,
Xu Yilun, Dan Williams
In-Reply-To: <20260214012702.2368778-1-seanjc@google.com>
Sean Christopherson wrote:
> Assuming I didn't break anything between v2 and v3, I think this is ready to
> rip. Given the scope of the KVM changes, and that they extend outside of x86,
> my preference is to take this through the KVM tree. But a stable topic branch
> in tip would work too, though I think we'd want it sooner than later so that
> it can be used as a base.
>
> Chao, I deliberately omitted your Tested-by, as I shuffled things around enough
> while splitting up the main patch that I'm not 100% positive I didn't regress
> anything relative to v2.
>
>
> The idea here is to extract _only_ VMXON+VMXOFF and EFER.SVME toggling. AFAIK
> there's no second user of SVM, i.e. no equivalent to TDX, but I wanted to keep
> things as symmetrical as possible.
>
> TDX isn't a hypervisor, and isn't trying to be a hypervisor. Specifically, TDX
> should _never_ have it's own VMCSes (that are visible to the host; the
> TDX-Module has it's own VMCSes to do SEAMCALL/SEAMRET), and so there is simply
> no reason to move that functionality out of KVM.
>
> With that out of the way, dealing with VMXON/VMXOFF and EFER.SVME is a fairly
> simple refcounting game.
>
> v3:
> - https://lore.kernel.org/all/20251206011054.494190-1-seanjc@google.com
> - Split up the move from KVM => virt into smaller patches. [Dan]
> - Collect reviews. [Dan, Chao, Dave]
> - Update sample dmesg output and hotplug angle in docs. [Chao]
> - Add comments in kvm_arch_shutdown() to try and explain the madness. [Dave]
> - Add a largely superfluous smp_wmb() in kvm_arch_shutdown() to provide a
> convienent location for documenting the flow. [Dave]
> - Disable preemption in x86_virt_{get,put}_ref() so that changes in how
> KVM and/or TDX use the APIs doesn't result in bugs. [Xu]
> - Add a patch to drop the bogus "IRQs must be disabled" rule in
> tdx_cpu_enable().
> - Tag more TDX helpers as __init. [Chao]
> - Don't treat loading kvm-intel.ko with tdx=1 as fatal if the system doesn't
> have a TDX-Module available. [Chao]
I went through the rest of the patches, the finer grained splits make
sense. No significant concerns, so for the series:
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
...I expect Chao or Yilun to have a chance to offer a Tested-by per your
comment above.
^ permalink raw reply
* Re: [PATCH v3 05/16] x86/virt: Force-clear X86_FEATURE_VMX if configuring root VMCS fails
From: dan.j.williams @ 2026-02-16 20:53 UTC (permalink / raw)
To: Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, Kiryl Shutsemau,
Peter Zijlstra, Arnaldo Carvalho de Melo, Namhyung Kim,
Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, linux-perf-users, Chao Gao,
Xu Yilun, Dan Williams
In-Reply-To: <20260214012702.2368778-6-seanjc@google.com>
Sean Christopherson wrote:
> If allocating and configuring a root VMCS fails, clear X86_FEATURE_VMX in
> all CPUs so that KVM doesn't need to manually check root_vmcs. As added
> bonuses, clearing VMX will reflect that VMX is unusable in /proc/cpuinfo,
> and will avoid a futile auto-probe of kvm-intel.ko.
>
> WARN if allocating a root VMCS page fails, e.g. to help users figure out
> why VMX is broken in the unlikely scenario something goes sideways during
> boot (and because the allocation should succeed unless there's a kernel
> bug). Tweak KVM's error message to suggest checking kernel logs if VMX is
> unsupported (in addition to checking BIOS).
>
> Signed-off-by: Sean Christopherson <seanjc@google.com>
[..]
> diff --git a/arch/x86/virt/hw.c b/arch/x86/virt/hw.c
> index 56972f594d90..40495872fdfb 100644
> --- a/arch/x86/virt/hw.c
> +++ b/arch/x86/virt/hw.c
[..]
> @@ -56,7 +56,7 @@ static __init int x86_vmx_init(void)
> struct vmcs *vmcs;
>
> page = __alloc_pages_node(node, GFP_KERNEL | __GFP_ZERO, 0);
> - if (!page) {
> + if (WARN_ON_ONCE(!page)) {
Is the warn_alloc() deep in this path not sufficient? Either way, this
patch looks good to me.
^ permalink raw reply
* Re: [PATCH v4 1/2] mm/memory_hotplug: Add support to accept memory during hot-add
From: Pratik R. Sampat @ 2026-02-16 14:45 UTC (permalink / raw)
To: Kiryl Shutsemau, David Hildenbrand (Arm)
Cc: linux-mm, linux-coco, x86, linux-kernel, tglx, mingo, bp,
dave.hansen, ardb, akpm, osalvador, thomas.lendacky, michael.roth
In-Reply-To: <aYXYRf4lxm_iQwjE@thinkstation>
On 2/6/26 6:03 AM, Kiryl Shutsemau wrote:
> On Thu, Feb 05, 2026 at 06:29:08PM +0100, David Hildenbrand (Arm) wrote:
>>> Ideally, we want to know on boot:
>>>
>>> - what memory ranges are unaccepted - we have it;
>>> - what memory range can be removed or added after boot - we don't have it
>>
>> The SRAT describes memory ranges where we can see hotplug memory. Is that
>> too late? We calculate max_possible_pfn based on that.
>
> The cleanest way would be to declare the ranges in EFI memory map, not
> SRAT. It should be doable.
>
Got it. I'm speaking to a few EFI folks on how that would work; if we'd
need a new type to specify this or we could piggyback off an existing
type with either the hotpluggable attribute or create a new one.
Thanks,
Pratik
^ permalink raw reply
* Re: [PATCH v12 00/46] arm64: Support for Arm CCA in KVM
From: Steven Price @ 2026-02-16 14:27 UTC (permalink / raw)
To: Mathieu Poirier
Cc: kvm, kvmarm, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <55fc4877-666c-4d5f-a167-5692f7cfbd0b@arm.com>
On 16/02/2026 12:33, Steven Price wrote:
> On 12/02/2026 17:48, Mathieu Poirier wrote:
>> Hi Steven,
>>
>> On Wed, Dec 17, 2025 at 10:10:37AM +0000, Steven Price wrote:
>>> This series adds support for running protected VMs using KVM under the
>>> Arm Confidential Compute Architecture (CCA). I've changed the uAPI
>>> following feedback from Marc.
>>>
>>> The main change is that rather than providing a multiplex CAP and
>>> expecting the VMM to drive the different stages of realm construction,
>>> there's now just a minimal interface and KVM performs the necessary
>>> operations when needed.
>>>
>>> This series is lightly tested and is meant as a demonstration of the new
>>> uAPI. There are a number of (known) rough corners in the implementation
>>> that I haven't dealt with properly.
>>>
>>> In particular please note that this series is still targetting RMM v1.0.
>>> There is an alpha quality version of RMM v2.0 available[1]. Feedback was
>>> that there are a number of blockers for merging with RMM v1.0 and so I
>>> expect to rework this series to support RMM v2.0 before it is merged.
>>> That will necessarily involve reworking the implementation.
>>>
>>> Specifically I'm expecting improvements in:
>>>
>>> * GIC handling - passing state in registers, and allowing the host to
>>> fully emulate the GIC by allowing trap bits to be set.
>>>
>>> * PMU handling - again providing flexibility to the host's emulation.
>>>
>>> * Page size/granule size mismatch. RMM v1.0 defines the granule as 4k,
>>> RMM v2.0 provide the option for the host to change the granule size.
>>> The intention is that Linux would simply set the granule size equal
>>> to its page size which will significantly simplify the management of
>>> granules.
>>>
>>> * Some performance improvement from the use of range-based map/unmap
>>> RMI calls.
>>>
>>> This series is based on v6.19-rc1. It is also available as a git
>>> repository:
>>>
>>> https://gitlab.arm.com/linux-arm/linux-cca cca-host/v12
>>>
>>> Work in progress changes for kvmtool are available from the git
>>> repository below:
>>>
>>> https://gitlab.arm.com/linux-arm/kvmtool-cca cca/v10
>>
>> The first thing to note is that branch cca/v10 does not compile due to function
>> realm_configure_parameters() not being called anywhere. Marking the function as
>> [[maybe_unused]] solved the problem on my side.
>
> This is in the kvmtool code - and yes I agree "work in progress" is a
> bit generous for the current state of that code, "horrid ugly hacks to
> get things working" is probably more accurate ;)
>
> The issue here is that the two things that realm_configure_parameters()
> set up (hash algorithm and RPV) are currently unsupported with the Linux
> changes, but will need to be reintroduced in the future. So the contents
> of the functions which set this up (using the old uAPI) are just #if 0'd
> out.
>
> Depending on the compile flags the code will compile with a warning, but
> using __attribute__((unused)) would at least make this clear. I'd want
> to avoid the "[[maybe_unused]]" as it's not used elsewhere in the code
> base and limits compatibility.
>
>> Using the FVP emulator, booting a Realm that includes EDK2 in its boot stack
>> worked. If EDK2 is not part of the boot stack and a kernel is booted directly
>> from lkvm, mounting the initrd fails. Looking into this issue further, I see
>> that from a Realm kernel's perspective, the content of the initrd is either
>> encrypted or has been trampled on.
>
> I can reproduce that, a quick fix is to change INITRD_ALIGN:
>
> #define INITRD_ALIGN SZ_64K
>
> But the code was meant to be able to deal with an unaligned initrd -
> I'll see if I can figure out what's going wrong.
Looks like a simple bug in kvm_arm_realm_populate_ram() - it wasn't
updating the source address when it had to align the start of the
region. Simple fix below:
---8<---
diff --git a/arm/aarch64/realm.c b/arm/aarch64/realm.c
--- a/arm/aarch64/realm.c
+++ b/arm/aarch64/realm.c
@@ -104,7 +104,7 @@ void kvm_arm_realm_populate_ram(struct kvm *kvm,
unsigned long start,
new_region->start = ALIGN_DOWN(start, SZ_64K);
new_region->file_end = ALIGN(start + size, SZ_64K);
- new_region->source = source;
+ new_region->source = source - (start - new_region->start);
list_add_tail(&new_region->list, &realm_ram_regions);
}
---8<---
Thanks for the pointer, and I'll try to remember to include initrd
testing in future.
Steve
^ permalink raw reply
* Re: [PATCH v12 00/46] arm64: Support for Arm CCA in KVM
From: Steven Price @ 2026-02-16 12:33 UTC (permalink / raw)
To: Mathieu Poirier
Cc: kvm, kvmarm, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <aY4Sf4lMlWd9LyTo@p14s>
On 12/02/2026 17:48, Mathieu Poirier wrote:
> Hi Steven,
>
> On Wed, Dec 17, 2025 at 10:10:37AM +0000, Steven Price wrote:
>> This series adds support for running protected VMs using KVM under the
>> Arm Confidential Compute Architecture (CCA). I've changed the uAPI
>> following feedback from Marc.
>>
>> The main change is that rather than providing a multiplex CAP and
>> expecting the VMM to drive the different stages of realm construction,
>> there's now just a minimal interface and KVM performs the necessary
>> operations when needed.
>>
>> This series is lightly tested and is meant as a demonstration of the new
>> uAPI. There are a number of (known) rough corners in the implementation
>> that I haven't dealt with properly.
>>
>> In particular please note that this series is still targetting RMM v1.0.
>> There is an alpha quality version of RMM v2.0 available[1]. Feedback was
>> that there are a number of blockers for merging with RMM v1.0 and so I
>> expect to rework this series to support RMM v2.0 before it is merged.
>> That will necessarily involve reworking the implementation.
>>
>> Specifically I'm expecting improvements in:
>>
>> * GIC handling - passing state in registers, and allowing the host to
>> fully emulate the GIC by allowing trap bits to be set.
>>
>> * PMU handling - again providing flexibility to the host's emulation.
>>
>> * Page size/granule size mismatch. RMM v1.0 defines the granule as 4k,
>> RMM v2.0 provide the option for the host to change the granule size.
>> The intention is that Linux would simply set the granule size equal
>> to its page size which will significantly simplify the management of
>> granules.
>>
>> * Some performance improvement from the use of range-based map/unmap
>> RMI calls.
>>
>> This series is based on v6.19-rc1. It is also available as a git
>> repository:
>>
>> https://gitlab.arm.com/linux-arm/linux-cca cca-host/v12
>>
>> Work in progress changes for kvmtool are available from the git
>> repository below:
>>
>> https://gitlab.arm.com/linux-arm/kvmtool-cca cca/v10
>
> The first thing to note is that branch cca/v10 does not compile due to function
> realm_configure_parameters() not being called anywhere. Marking the function as
> [[maybe_unused]] solved the problem on my side.
This is in the kvmtool code - and yes I agree "work in progress" is a
bit generous for the current state of that code, "horrid ugly hacks to
get things working" is probably more accurate ;)
The issue here is that the two things that realm_configure_parameters()
set up (hash algorithm and RPV) are currently unsupported with the Linux
changes, but will need to be reintroduced in the future. So the contents
of the functions which set this up (using the old uAPI) are just #if 0'd
out.
Depending on the compile flags the code will compile with a warning, but
using __attribute__((unused)) would at least make this clear. I'd want
to avoid the "[[maybe_unused]]" as it's not used elsewhere in the code
base and limits compatibility.
> Using the FVP emulator, booting a Realm that includes EDK2 in its boot stack
> worked. If EDK2 is not part of the boot stack and a kernel is booted directly
> from lkvm, mounting the initrd fails. Looking into this issue further, I see
> that from a Realm kernel's perspective, the content of the initrd is either
> encrypted or has been trampled on.
I can reproduce that, a quick fix is to change INITRD_ALIGN:
#define INITRD_ALIGN SZ_64K
But the code was meant to be able to deal with an unaligned initrd -
I'll see if I can figure out what's going wrong.
Thanks,
Steve
> I'd be happy to provide more details on the above, just let me know.
>
> Thanks,
> Mathieu
>
>>
>> [1] https://developer.arm.com/documentation/den0137/latest/
>>
>> Jean-Philippe Brucker (7):
>> arm64: RMI: Propagate number of breakpoints and watchpoints to
>> userspace
>> arm64: RMI: Set breakpoint parameters through SET_ONE_REG
>> arm64: RMI: Initialize PMCR.N with number counter supported by RMM
>> arm64: RMI: Propagate max SVE vector length from RMM
>> arm64: RMI: Configure max SVE vector length for a Realm
>> arm64: RMI: Provide register list for unfinalized RMI RECs
>> arm64: RMI: Provide accurate register list
>>
>> Joey Gouly (2):
>> arm64: RMI: allow userspace to inject aborts
>> arm64: RMI: support RSI_HOST_CALL
>>
>> Steven Price (34):
>> arm64: RME: Handle Granule Protection Faults (GPFs)
>> arm64: RMI: Add SMC definitions for calling the RMM
>> arm64: RMI: Add wrappers for RMI calls
>> arm64: RMI: Check for RMI support at KVM init
>> arm64: RMI: Define the user ABI
>> arm64: RMI: Basic infrastructure for creating a realm.
>> KVM: arm64: Allow passing machine type in KVM creation
>> arm64: RMI: RTT tear down
>> arm64: RMI: Activate realm on first VCPU run
>> arm64: RMI: Allocate/free RECs to match vCPUs
>> KVM: arm64: vgic: Provide helper for number of list registers
>> arm64: RMI: Support for the VGIC in realms
>> KVM: arm64: Support timers in realm RECs
>> arm64: RMI: Handle realm enter/exit
>> arm64: RMI: Handle RMI_EXIT_RIPAS_CHANGE
>> KVM: arm64: Handle realm MMIO emulation
>> KVM: arm64: Expose support for private memory
>> arm64: RMI: Allow populating initial contents
>> arm64: RMI: Set RIPAS of initial memslots
>> arm64: RMI: Create the realm descriptor
>> arm64: RMI: Add a VMID allocator for realms
>> arm64: RMI: Runtime faulting of memory
>> KVM: arm64: Handle realm VCPU load
>> KVM: arm64: Validate register access for a Realm VM
>> KVM: arm64: Handle Realm PSCI requests
>> KVM: arm64: WARN on injected undef exceptions
>> arm64: Don't expose stolen time for realm guests
>> arm64: RMI: Always use 4k pages for realms
>> arm64: RMI: Prevent Device mappings for Realms
>> HACK: Restore per-CPU cpu_armpmu pointer
>> arm_pmu: Provide a mechanism for disabling the physical IRQ
>> arm64: RMI: Enable PMU support with a realm guest
>> KVM: arm64: Expose KVM_ARM_VCPU_REC to user space
>> arm64: RMI: Enable realms to be created
>>
>> Suzuki K Poulose (3):
>> kvm: arm64: Include kvm_emulate.h in kvm/arm_psci.h
>> kvm: arm64: Don't expose unsupported capabilities for realm guests
>> arm64: RMI: Allow checking SVE on VM instance
>>
>> Documentation/virt/kvm/api.rst | 78 +-
>> arch/arm64/include/asm/kvm_emulate.h | 31 +
>> arch/arm64/include/asm/kvm_host.h | 13 +-
>> arch/arm64/include/asm/kvm_rmi.h | 137 +++
>> arch/arm64/include/asm/rmi_cmds.h | 508 ++++++++
>> arch/arm64/include/asm/rmi_smc.h | 269 +++++
>> arch/arm64/include/asm/virt.h | 1 +
>> arch/arm64/kernel/cpufeature.c | 1 +
>> arch/arm64/kvm/Kconfig | 2 +
>> arch/arm64/kvm/Makefile | 2 +-
>> arch/arm64/kvm/arch_timer.c | 37 +-
>> arch/arm64/kvm/arm.c | 179 ++-
>> arch/arm64/kvm/guest.c | 95 +-
>> arch/arm64/kvm/hypercalls.c | 4 +-
>> arch/arm64/kvm/inject_fault.c | 5 +-
>> arch/arm64/kvm/mmio.c | 16 +-
>> arch/arm64/kvm/mmu.c | 214 +++-
>> arch/arm64/kvm/pmu-emul.c | 6 +
>> arch/arm64/kvm/psci.c | 30 +
>> arch/arm64/kvm/reset.c | 13 +-
>> arch/arm64/kvm/rmi-exit.c | 207 ++++
>> arch/arm64/kvm/rmi.c | 1663 ++++++++++++++++++++++++++
>> arch/arm64/kvm/sys_regs.c | 53 +-
>> arch/arm64/kvm/vgic/vgic-init.c | 2 +-
>> arch/arm64/kvm/vgic/vgic-v2.c | 6 +-
>> arch/arm64/kvm/vgic/vgic-v3.c | 14 +-
>> arch/arm64/kvm/vgic/vgic.c | 55 +-
>> arch/arm64/kvm/vgic/vgic.h | 20 +-
>> arch/arm64/mm/fault.c | 28 +-
>> drivers/perf/arm_pmu.c | 20 +
>> include/kvm/arm_arch_timer.h | 2 +
>> include/kvm/arm_pmu.h | 4 +
>> include/kvm/arm_psci.h | 2 +
>> include/linux/perf/arm_pmu.h | 7 +
>> include/uapi/linux/kvm.h | 42 +-
>> 35 files changed, 3650 insertions(+), 116 deletions(-)
>> create mode 100644 arch/arm64/include/asm/kvm_rmi.h
>> create mode 100644 arch/arm64/include/asm/rmi_cmds.h
>> create mode 100644 arch/arm64/include/asm/rmi_smc.h
>> create mode 100644 arch/arm64/kvm/rmi-exit.c
>> create mode 100644 arch/arm64/kvm/rmi.c
>>
>> --
>> 2.43.0
>>
>>
^ permalink raw reply
* Re: [GIT PULL] Confidential Computing: Attestation Updates for 7.0
From: pr-tracker-bot @ 2026-02-15 18:58 UTC (permalink / raw)
To: dan.j.williams; +Cc: Linus Torvalds, linux-coco
In-Reply-To: <69912a6621326_2f4a1006b@dwillia2-mobl4.notmuch>
The pull request you sent on Sat, 14 Feb 2026 18:07:34 -0800:
> git://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm tags/tsm-for-7.0
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/c4f414becb6ac9c71ea80dd8b28478d357c62bb7
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply
* [GIT PULL] Confidential Computing: Attestation Updates for 7.0
From: dan.j.williams @ 2026-02-15 2:07 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-coco
Hi Linus, please pull from:
git://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm tags/tsm-for-7.0
...to receive a couple updates to the maximum buffer sizes supported for
the configfs-tsm-reports interface. Recall this interface is a common
transport that conveys the varied architecture specific launch
attestation reports for confidential VMs.
They have appeared in linux-next with no known reports.
---
The following changes since commit 18f7fcd5e69a04df57b563360b88be72471d6b62:
Linux 6.19-rc8 (2026-02-01 14:01:13 -0800)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm tags/tsm-for-7.0
for you to fetch changes up to 43185067c6fd55b548ecb648a69d9569fcf622b5:
configfs-tsm-report: tdx_guest: Increase Quote buffer size to 128KB (2026-02-10 18:24:09 -0800)
----------------------------------------------------------------
tsm for 7.0
- Prepare the configfs-tsm-reports interface for passing larger
attestation evidence blobs for "Device Identifier Composition Engine"
(DICE) and Post Quantum Crypto (PQC).
- Update the tdx-guest driver for DICE evidence (larger certificate
chains and the CBOR Web Token schema).
----------------------------------------------------------------
Kuppuswamy Sathyanarayanan (3):
configfs-tsm-report: Document size limits for outblob attributes
configfs-tsm-report: Increase TSM_REPORT_OUTBLOB_MAX to 16MB
configfs-tsm-report: tdx_guest: Increase Quote buffer size to 128KB
Documentation/ABI/testing/configfs-tsm-report | 16 ++++++++++++++++
drivers/virt/coco/tdx-guest/tdx-guest.c | 4 +++-
include/linux/tsm.h | 2 +-
3 files changed, 20 insertions(+), 2 deletions(-)
^ permalink raw reply
* [PATCH v3 16/16] KVM: TDX: Fold tdx_bringup() into tdx_hardware_setup()
From: Sean Christopherson @ 2026-02-14 1:27 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Peter Zijlstra, Arnaldo Carvalho de Melo,
Namhyung Kim, Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, linux-perf-users, Chao Gao,
Xu Yilun, Dan Williams
In-Reply-To: <20260214012702.2368778-1-seanjc@google.com>
Now that TDX doesn't need to manually enable virtualization through _KVM_
APIs during setup, fold tdx_bringup() into tdx_hardware_setup() where the
code belongs, e.g. so that KVM doesn't leave the S-EPT kvm_x86_ops wired
up when TDX is disabled.
The weird ordering (and naming) was necessary to allow KVM TDX to use
kvm_enable_virtualization(), which in turn had a hard dependency on
kvm_x86_ops.enable_virtualization_cpu and thus kvm_x86_vendor_init().
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/main.c | 19 ++++++++-----------
arch/x86/kvm/vmx/tdx.c | 39 +++++++++++++++------------------------
arch/x86/kvm/vmx/tdx.h | 8 ++------
3 files changed, 25 insertions(+), 41 deletions(-)
diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c
index a46ccd670785..dbebddf648be 100644
--- a/arch/x86/kvm/vmx/main.c
+++ b/arch/x86/kvm/vmx/main.c
@@ -29,10 +29,15 @@ static __init int vt_hardware_setup(void)
if (ret)
return ret;
+ return enable_tdx ? tdx_hardware_setup() : 0;
+}
+
+static void vt_hardware_unsetup(void)
+{
if (enable_tdx)
- tdx_hardware_setup();
+ tdx_hardware_unsetup();
- return 0;
+ vmx_hardware_unsetup();
}
static int vt_vm_init(struct kvm *kvm)
@@ -869,7 +874,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
.check_processor_compatibility = vmx_check_processor_compat,
- .hardware_unsetup = vmx_hardware_unsetup,
+ .hardware_unsetup = vt_op(hardware_unsetup),
.enable_virtualization_cpu = vmx_enable_virtualization_cpu,
.disable_virtualization_cpu = vt_op(disable_virtualization_cpu),
@@ -1029,7 +1034,6 @@ struct kvm_x86_init_ops vt_init_ops __initdata = {
static void __exit vt_exit(void)
{
kvm_exit();
- tdx_cleanup();
vmx_exit();
}
module_exit(vt_exit);
@@ -1043,11 +1047,6 @@ static int __init vt_init(void)
if (r)
return r;
- /* tdx_init() has been taken */
- r = tdx_bringup();
- if (r)
- goto err_tdx_bringup;
-
/*
* TDX and VMX have different vCPU structures. Calculate the
* maximum size/align so that kvm_init() can use the larger
@@ -1074,8 +1073,6 @@ static int __init vt_init(void)
return 0;
err_kvm_init:
- tdx_cleanup();
-err_tdx_bringup:
vmx_exit();
return r;
}
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index fea3dfc7ac8b..d354022ba9c9 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -3285,7 +3285,12 @@ int tdx_gmem_max_mapping_level(struct kvm *kvm, kvm_pfn_t pfn, bool is_private)
return PG_LEVEL_4K;
}
-static int __init __tdx_bringup(void)
+void tdx_hardware_unsetup(void)
+{
+ misc_cg_set_capacity(MISC_CG_RES_TDX, 0);
+}
+
+static int __init __tdx_hardware_setup(void)
{
const struct tdx_sys_info_td_conf *td_conf;
int i;
@@ -3359,7 +3364,7 @@ static int __init __tdx_bringup(void)
return 0;
}
-int __init tdx_bringup(void)
+int __init tdx_hardware_setup(void)
{
int r, i;
@@ -3395,7 +3400,7 @@ int __init tdx_bringup(void)
goto success_disable_tdx;
}
- r = __tdx_bringup();
+ r = __tdx_hardware_setup();
if (r) {
/*
* Disable TDX only but don't fail to load module if the TDX
@@ -3409,31 +3414,12 @@ int __init tdx_bringup(void)
*/
if (r == -ENODEV)
goto success_disable_tdx;
+
+ return r;
}
- return r;
-
-success_disable_tdx:
- enable_tdx = 0;
- return 0;
-}
-
-void tdx_cleanup(void)
-{
- if (!enable_tdx)
- return;
-
- misc_cg_set_capacity(MISC_CG_RES_TDX, 0);
-}
-
-void __init tdx_hardware_setup(void)
-{
KVM_SANITY_CHECK_VM_STRUCT_SIZE(kvm_tdx);
- /*
- * Note, if the TDX module can't be loaded, KVM TDX support will be
- * disabled but KVM will continue loading (see tdx_bringup()).
- */
vt_x86_ops.vm_size = max_t(unsigned int, vt_x86_ops.vm_size, sizeof(struct kvm_tdx));
vt_x86_ops.link_external_spt = tdx_sept_link_private_spt;
@@ -3441,4 +3427,9 @@ void __init tdx_hardware_setup(void)
vt_x86_ops.free_external_spt = tdx_sept_free_private_spt;
vt_x86_ops.remove_external_spte = tdx_sept_remove_private_spte;
vt_x86_ops.protected_apic_has_interrupt = tdx_protected_apic_has_interrupt;
+ return 0;
+
+success_disable_tdx:
+ enable_tdx = 0;
+ return 0;
}
diff --git a/arch/x86/kvm/vmx/tdx.h b/arch/x86/kvm/vmx/tdx.h
index 45b5183ccb36..b5cd2ffb303e 100644
--- a/arch/x86/kvm/vmx/tdx.h
+++ b/arch/x86/kvm/vmx/tdx.h
@@ -8,9 +8,8 @@
#ifdef CONFIG_KVM_INTEL_TDX
#include "common.h"
-void tdx_hardware_setup(void);
-int tdx_bringup(void);
-void tdx_cleanup(void);
+int tdx_hardware_setup(void);
+void tdx_hardware_unsetup(void);
extern bool enable_tdx;
@@ -187,9 +186,6 @@ TDX_BUILD_TDVPS_ACCESSORS(8, MANAGEMENT, management);
TDX_BUILD_TDVPS_ACCESSORS(64, STATE_NON_ARCH, state_non_arch);
#else
-static inline int tdx_bringup(void) { return 0; }
-static inline void tdx_cleanup(void) {}
-
#define enable_tdx 0
struct kvm_tdx {
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related
* [PATCH v3 15/16] KVM: Bury kvm_{en,dis}able_virtualization() in kvm_main.c once more
From: Sean Christopherson @ 2026-02-14 1:27 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Peter Zijlstra, Arnaldo Carvalho de Melo,
Namhyung Kim, Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, linux-perf-users, Chao Gao,
Xu Yilun, Dan Williams
In-Reply-To: <20260214012702.2368778-1-seanjc@google.com>
Now that TDX handles doing VMXON without KVM's involvement, bury the
top-level APIs to enable and disable virtualization back in kvm_main.c.
No functional change intended.
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Chao Gao <chao.gao@intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
include/linux/kvm_host.h | 8 --------
virt/kvm/kvm_main.c | 17 +++++++++++++----
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 981b55c0a3a7..760e0ec2c8eb 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2605,12 +2605,4 @@ long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
struct kvm_pre_fault_memory *range);
#endif
-#ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
-int kvm_enable_virtualization(void);
-void kvm_disable_virtualization(void);
-#else
-static inline int kvm_enable_virtualization(void) { return 0; }
-static inline void kvm_disable_virtualization(void) { }
-#endif
-
#endif
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index e081e7244299..737b74b15bb5 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -1112,6 +1112,9 @@ static inline struct kvm_io_bus *kvm_get_bus_for_destruction(struct kvm *kvm,
!refcount_read(&kvm->users_count));
}
+static int kvm_enable_virtualization(void);
+static void kvm_disable_virtualization(void);
+
static struct kvm *kvm_create_vm(unsigned long type, const char *fdname)
{
struct kvm *kvm = kvm_arch_alloc_vm();
@@ -5704,7 +5707,7 @@ static struct syscore kvm_syscore = {
.ops = &kvm_syscore_ops,
};
-int kvm_enable_virtualization(void)
+static int kvm_enable_virtualization(void)
{
int r;
@@ -5749,9 +5752,8 @@ int kvm_enable_virtualization(void)
--kvm_usage_count;
return r;
}
-EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_enable_virtualization);
-void kvm_disable_virtualization(void)
+static void kvm_disable_virtualization(void)
{
guard(mutex)(&kvm_usage_lock);
@@ -5762,7 +5764,6 @@ void kvm_disable_virtualization(void)
cpuhp_remove_state(CPUHP_AP_KVM_ONLINE);
kvm_arch_disable_virtualization();
}
-EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_disable_virtualization);
static int kvm_init_virtualization(void)
{
@@ -5778,6 +5779,14 @@ static void kvm_uninit_virtualization(void)
kvm_disable_virtualization();
}
#else /* CONFIG_KVM_GENERIC_HARDWARE_ENABLING */
+static int kvm_enable_virtualization(void)
+{
+ return 0;
+}
+static void kvm_disable_virtualization(void)
+{
+
+}
static int kvm_init_virtualization(void)
{
return 0;
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related
* [PATCH v3 14/16] x86/virt/tdx: Use ida_is_empty() to detect if any TDs may be running
From: Sean Christopherson @ 2026-02-14 1:27 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Peter Zijlstra, Arnaldo Carvalho de Melo,
Namhyung Kim, Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, linux-perf-users, Chao Gao,
Xu Yilun, Dan Williams
In-Reply-To: <20260214012702.2368778-1-seanjc@google.com>
Drop nr_configured_hkid and instead use ida_is_empty() to detect if any
HKIDs have been allocated/configured.
Suggested-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Chao Gao <chao.gao@intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 17 ++++-------------
1 file changed, 4 insertions(+), 13 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index ddbab87d2467..bdee937b84d4 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -59,8 +59,6 @@ static LIST_HEAD(tdx_memlist);
static struct tdx_sys_info tdx_sysinfo __ro_after_init;
static bool tdx_module_initialized __ro_after_init;
-static atomic_t nr_configured_hkid;
-
typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args);
static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args)
@@ -194,7 +192,7 @@ static int tdx_offline_cpu(unsigned int cpu)
int i;
/* No TD is running. Allow any cpu to be offline. */
- if (!atomic_read(&nr_configured_hkid))
+ if (ida_is_empty(&tdx_guest_keyid_pool))
goto done;
/*
@@ -1545,22 +1543,15 @@ EXPORT_SYMBOL_FOR_KVM(tdx_get_nr_guest_keyids);
int tdx_guest_keyid_alloc(void)
{
- int ret;
-
- ret = ida_alloc_range(&tdx_guest_keyid_pool, tdx_guest_keyid_start,
- tdx_guest_keyid_start + tdx_nr_guest_keyids - 1,
- GFP_KERNEL);
- if (ret >= 0)
- atomic_inc(&nr_configured_hkid);
-
- return ret;
+ return ida_alloc_range(&tdx_guest_keyid_pool, tdx_guest_keyid_start,
+ tdx_guest_keyid_start + tdx_nr_guest_keyids - 1,
+ GFP_KERNEL);
}
EXPORT_SYMBOL_FOR_KVM(tdx_guest_keyid_alloc);
void tdx_guest_keyid_free(unsigned int keyid)
{
ida_free(&tdx_guest_keyid_pool, keyid);
- atomic_dec(&nr_configured_hkid);
}
EXPORT_SYMBOL_FOR_KVM(tdx_guest_keyid_free);
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related
* [PATCH v3 13/16] x86/virt/tdx: KVM: Consolidate TDX CPU hotplug handling
From: Sean Christopherson @ 2026-02-14 1:26 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Peter Zijlstra, Arnaldo Carvalho de Melo,
Namhyung Kim, Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, linux-perf-users, Chao Gao,
Xu Yilun, Dan Williams
In-Reply-To: <20260214012702.2368778-1-seanjc@google.com>
From: Chao Gao <chao.gao@intel.com>
The core kernel registers a CPU hotplug callback to do VMX and TDX init
and deinit while KVM registers a separate CPU offline callback to block
offlining the last online CPU in a socket.
Splitting TDX-related CPU hotplug handling across two components is odd
and adds unnecessary complexity.
Consolidate TDX-related CPU hotplug handling by integrating KVM's
tdx_offline_cpu() to the one in the core kernel.
Also move nr_configured_hkid to the core kernel because tdx_offline_cpu()
references it. Since HKID allocation and free are handled in the core
kernel, it's more natural to track used HKIDs there.
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Chao Gao <chao.gao@intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/tdx.c | 67 +------------------------------------
arch/x86/virt/vmx/tdx/tdx.c | 49 +++++++++++++++++++++++++--
2 files changed, 47 insertions(+), 69 deletions(-)
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index 0ac01c119336..fea3dfc7ac8b 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -59,8 +59,6 @@ module_param_named(tdx, enable_tdx, bool, 0444);
#define TDX_SHARED_BIT_PWL_5 gpa_to_gfn(BIT_ULL(51))
#define TDX_SHARED_BIT_PWL_4 gpa_to_gfn(BIT_ULL(47))
-static enum cpuhp_state tdx_cpuhp_state __ro_after_init;
-
static const struct tdx_sys_info *tdx_sysinfo;
void tdh_vp_rd_failed(struct vcpu_tdx *tdx, char *uclass, u32 field, u64 err)
@@ -219,8 +217,6 @@ static int init_kvm_tdx_caps(const struct tdx_sys_info_td_conf *td_conf,
*/
static DEFINE_MUTEX(tdx_lock);
-static atomic_t nr_configured_hkid;
-
static bool tdx_operand_busy(u64 err)
{
return (err & TDX_SEAMCALL_STATUS_MASK) == TDX_OPERAND_BUSY;
@@ -268,7 +264,6 @@ static inline void tdx_hkid_free(struct kvm_tdx *kvm_tdx)
{
tdx_guest_keyid_free(kvm_tdx->hkid);
kvm_tdx->hkid = -1;
- atomic_dec(&nr_configured_hkid);
misc_cg_uncharge(MISC_CG_RES_TDX, kvm_tdx->misc_cg, 1);
put_misc_cg(kvm_tdx->misc_cg);
kvm_tdx->misc_cg = NULL;
@@ -2399,8 +2394,6 @@ static int __tdx_td_init(struct kvm *kvm, struct td_params *td_params,
ret = -ENOMEM;
- atomic_inc(&nr_configured_hkid);
-
tdr_page = alloc_page(GFP_KERNEL);
if (!tdr_page)
goto free_hkid;
@@ -3292,51 +3285,10 @@ int tdx_gmem_max_mapping_level(struct kvm *kvm, kvm_pfn_t pfn, bool is_private)
return PG_LEVEL_4K;
}
-static int tdx_online_cpu(unsigned int cpu)
-{
- return 0;
-}
-
-static int tdx_offline_cpu(unsigned int cpu)
-{
- int i;
-
- /* No TD is running. Allow any cpu to be offline. */
- if (!atomic_read(&nr_configured_hkid))
- return 0;
-
- /*
- * In order to reclaim TDX HKID, (i.e. when deleting guest TD), need to
- * call TDH.PHYMEM.PAGE.WBINVD on all packages to program all memory
- * controller with pconfig. If we have active TDX HKID, refuse to
- * offline the last online cpu.
- */
- for_each_online_cpu(i) {
- /*
- * Found another online cpu on the same package.
- * Allow to offline.
- */
- if (i != cpu && topology_physical_package_id(i) ==
- topology_physical_package_id(cpu))
- return 0;
- }
-
- /*
- * This is the last cpu of this package. Don't offline it.
- *
- * Because it's hard for human operator to understand the
- * reason, warn it.
- */
-#define MSG_ALLPKG_ONLINE \
- "TDX requires all packages to have an online CPU. Delete all TDs in order to offline all CPUs of a package.\n"
- pr_warn_ratelimited(MSG_ALLPKG_ONLINE);
- return -EBUSY;
-}
-
static int __init __tdx_bringup(void)
{
const struct tdx_sys_info_td_conf *td_conf;
- int r, i;
+ int i;
for (i = 0; i < ARRAY_SIZE(tdx_uret_msrs); i++) {
/*
@@ -3404,23 +3356,7 @@ static int __init __tdx_bringup(void)
if (misc_cg_set_capacity(MISC_CG_RES_TDX, tdx_get_nr_guest_keyids()))
return -EINVAL;
- /*
- * TDX-specific cpuhp callback to disallow offlining the last CPU in a
- * packing while KVM is running one or more TDs. Reclaiming HKIDs
- * requires doing PAGE.WBINVD on every package, i.e. offlining all CPUs
- * of a package would prevent reclaiming the HKID.
- */
- r = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "kvm/cpu/tdx:online",
- tdx_online_cpu, tdx_offline_cpu);
- if (r < 0)
- goto err_cpuhup;
-
- tdx_cpuhp_state = r;
return 0;
-
-err_cpuhup:
- misc_cg_set_capacity(MISC_CG_RES_TDX, 0);
- return r;
}
int __init tdx_bringup(void)
@@ -3488,7 +3424,6 @@ void tdx_cleanup(void)
return;
misc_cg_set_capacity(MISC_CG_RES_TDX, 0);
- cpuhp_remove_state(tdx_cpuhp_state);
}
void __init tdx_hardware_setup(void)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 05d634caa4e8..ddbab87d2467 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -59,6 +59,8 @@ static LIST_HEAD(tdx_memlist);
static struct tdx_sys_info tdx_sysinfo __ro_after_init;
static bool tdx_module_initialized __ro_after_init;
+static atomic_t nr_configured_hkid;
+
typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args);
static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args)
@@ -189,6 +191,40 @@ static int tdx_online_cpu(unsigned int cpu)
static int tdx_offline_cpu(unsigned int cpu)
{
+ int i;
+
+ /* No TD is running. Allow any cpu to be offline. */
+ if (!atomic_read(&nr_configured_hkid))
+ goto done;
+
+ /*
+ * In order to reclaim TDX HKID, (i.e. when deleting guest TD), need to
+ * call TDH.PHYMEM.PAGE.WBINVD on all packages to program all memory
+ * controller with pconfig. If we have active TDX HKID, refuse to
+ * offline the last online cpu.
+ */
+ for_each_online_cpu(i) {
+ /*
+ * Found another online cpu on the same package.
+ * Allow to offline.
+ */
+ if (i != cpu && topology_physical_package_id(i) ==
+ topology_physical_package_id(cpu))
+ goto done;
+ }
+
+ /*
+ * This is the last cpu of this package. Don't offline it.
+ *
+ * Because it's hard for human operator to understand the
+ * reason, warn it.
+ */
+#define MSG_ALLPKG_ONLINE \
+ "TDX requires all packages to have an online CPU. Delete all TDs in order to offline all CPUs of a package.\n"
+ pr_warn_ratelimited(MSG_ALLPKG_ONLINE);
+ return -EBUSY;
+
+done:
x86_virt_put_ref(X86_FEATURE_VMX);
return 0;
}
@@ -1509,15 +1545,22 @@ EXPORT_SYMBOL_FOR_KVM(tdx_get_nr_guest_keyids);
int tdx_guest_keyid_alloc(void)
{
- return ida_alloc_range(&tdx_guest_keyid_pool, tdx_guest_keyid_start,
- tdx_guest_keyid_start + tdx_nr_guest_keyids - 1,
- GFP_KERNEL);
+ int ret;
+
+ ret = ida_alloc_range(&tdx_guest_keyid_pool, tdx_guest_keyid_start,
+ tdx_guest_keyid_start + tdx_nr_guest_keyids - 1,
+ GFP_KERNEL);
+ if (ret >= 0)
+ atomic_inc(&nr_configured_hkid);
+
+ return ret;
}
EXPORT_SYMBOL_FOR_KVM(tdx_guest_keyid_alloc);
void tdx_guest_keyid_free(unsigned int keyid)
{
ida_free(&tdx_guest_keyid_pool, keyid);
+ atomic_dec(&nr_configured_hkid);
}
EXPORT_SYMBOL_FOR_KVM(tdx_guest_keyid_free);
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related
* [PATCH v3 12/16] x86/virt/tdx: Tag a pile of functions as __init, and globals as __ro_after_init
From: Sean Christopherson @ 2026-02-14 1:26 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Peter Zijlstra, Arnaldo Carvalho de Melo,
Namhyung Kim, Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, linux-perf-users, Chao Gao,
Xu Yilun, Dan Williams
In-Reply-To: <20260214012702.2368778-1-seanjc@google.com>
Now that TDX-Module initialization is done during subsys init, tag all
related functions as __init, and relevant data as __ro_after_init.
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Chao Gao <chao.gao@intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 119 ++++++++++----------
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 10 +-
2 files changed, 66 insertions(+), 63 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index feea8dd6920d..05d634caa4e8 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -56,8 +56,8 @@ static struct tdmr_info_list tdx_tdmr_list;
/* All TDX-usable memory regions. Protected by mem_hotplug_lock. */
static LIST_HEAD(tdx_memlist);
-static struct tdx_sys_info tdx_sysinfo;
-static bool tdx_module_initialized;
+static struct tdx_sys_info tdx_sysinfo __ro_after_init;
+static bool tdx_module_initialized __ro_after_init;
typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args);
@@ -229,8 +229,9 @@ static struct syscore tdx_syscore = {
* all memory regions are added in address ascending order and don't
* overlap.
*/
-static int add_tdx_memblock(struct list_head *tmb_list, unsigned long start_pfn,
- unsigned long end_pfn, int nid)
+static __init int add_tdx_memblock(struct list_head *tmb_list,
+ unsigned long start_pfn,
+ unsigned long end_pfn, int nid)
{
struct tdx_memblock *tmb;
@@ -248,7 +249,7 @@ static int add_tdx_memblock(struct list_head *tmb_list, unsigned long start_pfn,
return 0;
}
-static void free_tdx_memlist(struct list_head *tmb_list)
+static __init void free_tdx_memlist(struct list_head *tmb_list)
{
/* @tmb_list is protected by mem_hotplug_lock */
while (!list_empty(tmb_list)) {
@@ -266,7 +267,7 @@ static void free_tdx_memlist(struct list_head *tmb_list)
* ranges off in a secondary structure because memblock is modified
* in memory hotplug while TDX memory regions are fixed.
*/
-static int build_tdx_memlist(struct list_head *tmb_list)
+static __init int build_tdx_memlist(struct list_head *tmb_list)
{
unsigned long start_pfn, end_pfn;
int i, nid, ret;
@@ -298,7 +299,7 @@ static int build_tdx_memlist(struct list_head *tmb_list)
return ret;
}
-static int read_sys_metadata_field(u64 field_id, u64 *data)
+static __init int read_sys_metadata_field(u64 field_id, u64 *data)
{
struct tdx_module_args args = {};
int ret;
@@ -320,7 +321,7 @@ static int read_sys_metadata_field(u64 field_id, u64 *data)
#include "tdx_global_metadata.c"
-static int check_features(struct tdx_sys_info *sysinfo)
+static __init int check_features(struct tdx_sys_info *sysinfo)
{
u64 tdx_features0 = sysinfo->features.tdx_features0;
@@ -333,7 +334,7 @@ static int check_features(struct tdx_sys_info *sysinfo)
}
/* Calculate the actual TDMR size */
-static int tdmr_size_single(u16 max_reserved_per_tdmr)
+static __init int tdmr_size_single(u16 max_reserved_per_tdmr)
{
int tdmr_sz;
@@ -347,8 +348,8 @@ static int tdmr_size_single(u16 max_reserved_per_tdmr)
return ALIGN(tdmr_sz, TDMR_INFO_ALIGNMENT);
}
-static int alloc_tdmr_list(struct tdmr_info_list *tdmr_list,
- struct tdx_sys_info_tdmr *sysinfo_tdmr)
+static __init int alloc_tdmr_list(struct tdmr_info_list *tdmr_list,
+ struct tdx_sys_info_tdmr *sysinfo_tdmr)
{
size_t tdmr_sz, tdmr_array_sz;
void *tdmr_array;
@@ -379,7 +380,7 @@ static int alloc_tdmr_list(struct tdmr_info_list *tdmr_list,
return 0;
}
-static void free_tdmr_list(struct tdmr_info_list *tdmr_list)
+static __init void free_tdmr_list(struct tdmr_info_list *tdmr_list)
{
free_pages_exact(tdmr_list->tdmrs,
tdmr_list->max_tdmrs * tdmr_list->tdmr_sz);
@@ -408,8 +409,8 @@ static inline u64 tdmr_end(struct tdmr_info *tdmr)
* preallocated @tdmr_list, following all the special alignment
* and size rules for TDMR.
*/
-static int fill_out_tdmrs(struct list_head *tmb_list,
- struct tdmr_info_list *tdmr_list)
+static __init int fill_out_tdmrs(struct list_head *tmb_list,
+ struct tdmr_info_list *tdmr_list)
{
struct tdx_memblock *tmb;
int tdmr_idx = 0;
@@ -485,8 +486,8 @@ static int fill_out_tdmrs(struct list_head *tmb_list,
* Calculate PAMT size given a TDMR and a page size. The returned
* PAMT size is always aligned up to 4K page boundary.
*/
-static unsigned long tdmr_get_pamt_sz(struct tdmr_info *tdmr, int pgsz,
- u16 pamt_entry_size)
+static __init unsigned long tdmr_get_pamt_sz(struct tdmr_info *tdmr, int pgsz,
+ u16 pamt_entry_size)
{
unsigned long pamt_sz, nr_pamt_entries;
@@ -517,7 +518,7 @@ static unsigned long tdmr_get_pamt_sz(struct tdmr_info *tdmr, int pgsz,
* PAMT. This node will have some memory covered by the TDMR. The
* relative amount of memory covered is not considered.
*/
-static int tdmr_get_nid(struct tdmr_info *tdmr, struct list_head *tmb_list)
+static __init int tdmr_get_nid(struct tdmr_info *tdmr, struct list_head *tmb_list)
{
struct tdx_memblock *tmb;
@@ -546,9 +547,9 @@ static int tdmr_get_nid(struct tdmr_info *tdmr, struct list_head *tmb_list)
* Allocate PAMTs from the local NUMA node of some memory in @tmb_list
* within @tdmr, and set up PAMTs for @tdmr.
*/
-static int tdmr_set_up_pamt(struct tdmr_info *tdmr,
- struct list_head *tmb_list,
- u16 pamt_entry_size[])
+static __init int tdmr_set_up_pamt(struct tdmr_info *tdmr,
+ struct list_head *tmb_list,
+ u16 pamt_entry_size[])
{
unsigned long pamt_base[TDX_PS_NR];
unsigned long pamt_size[TDX_PS_NR];
@@ -618,7 +619,7 @@ static void tdmr_get_pamt(struct tdmr_info *tdmr, unsigned long *pamt_base,
*pamt_size = pamt_sz;
}
-static void tdmr_do_pamt_func(struct tdmr_info *tdmr,
+static __init void tdmr_do_pamt_func(struct tdmr_info *tdmr,
void (*pamt_func)(unsigned long base, unsigned long size))
{
unsigned long pamt_base, pamt_size;
@@ -635,17 +636,17 @@ static void tdmr_do_pamt_func(struct tdmr_info *tdmr,
pamt_func(pamt_base, pamt_size);
}
-static void free_pamt(unsigned long pamt_base, unsigned long pamt_size)
+static __init void free_pamt(unsigned long pamt_base, unsigned long pamt_size)
{
free_contig_range(pamt_base >> PAGE_SHIFT, pamt_size >> PAGE_SHIFT);
}
-static void tdmr_free_pamt(struct tdmr_info *tdmr)
+static __init void tdmr_free_pamt(struct tdmr_info *tdmr)
{
tdmr_do_pamt_func(tdmr, free_pamt);
}
-static void tdmrs_free_pamt_all(struct tdmr_info_list *tdmr_list)
+static __init void tdmrs_free_pamt_all(struct tdmr_info_list *tdmr_list)
{
int i;
@@ -654,9 +655,9 @@ static void tdmrs_free_pamt_all(struct tdmr_info_list *tdmr_list)
}
/* Allocate and set up PAMTs for all TDMRs */
-static int tdmrs_set_up_pamt_all(struct tdmr_info_list *tdmr_list,
- struct list_head *tmb_list,
- u16 pamt_entry_size[])
+static __init int tdmrs_set_up_pamt_all(struct tdmr_info_list *tdmr_list,
+ struct list_head *tmb_list,
+ u16 pamt_entry_size[])
{
int i, ret = 0;
@@ -705,12 +706,13 @@ void tdx_quirk_reset_page(struct page *page)
}
EXPORT_SYMBOL_FOR_KVM(tdx_quirk_reset_page);
-static void tdmr_quirk_reset_pamt(struct tdmr_info *tdmr)
+static __init void tdmr_quirk_reset_pamt(struct tdmr_info *tdmr)
+
{
tdmr_do_pamt_func(tdmr, tdx_quirk_reset_paddr);
}
-static void tdmrs_quirk_reset_pamt_all(struct tdmr_info_list *tdmr_list)
+static __init void tdmrs_quirk_reset_pamt_all(struct tdmr_info_list *tdmr_list)
{
int i;
@@ -718,7 +720,7 @@ static void tdmrs_quirk_reset_pamt_all(struct tdmr_info_list *tdmr_list)
tdmr_quirk_reset_pamt(tdmr_entry(tdmr_list, i));
}
-static unsigned long tdmrs_count_pamt_kb(struct tdmr_info_list *tdmr_list)
+static __init unsigned long tdmrs_count_pamt_kb(struct tdmr_info_list *tdmr_list)
{
unsigned long pamt_size = 0;
int i;
@@ -733,8 +735,8 @@ static unsigned long tdmrs_count_pamt_kb(struct tdmr_info_list *tdmr_list)
return pamt_size / 1024;
}
-static int tdmr_add_rsvd_area(struct tdmr_info *tdmr, int *p_idx, u64 addr,
- u64 size, u16 max_reserved_per_tdmr)
+static __init int tdmr_add_rsvd_area(struct tdmr_info *tdmr, int *p_idx,
+ u64 addr, u64 size, u16 max_reserved_per_tdmr)
{
struct tdmr_reserved_area *rsvd_areas = tdmr->reserved_areas;
int idx = *p_idx;
@@ -767,10 +769,10 @@ static int tdmr_add_rsvd_area(struct tdmr_info *tdmr, int *p_idx, u64 addr,
* those holes fall within @tdmr, set up a TDMR reserved area to cover
* the hole.
*/
-static int tdmr_populate_rsvd_holes(struct list_head *tmb_list,
- struct tdmr_info *tdmr,
- int *rsvd_idx,
- u16 max_reserved_per_tdmr)
+static __init int tdmr_populate_rsvd_holes(struct list_head *tmb_list,
+ struct tdmr_info *tdmr,
+ int *rsvd_idx,
+ u16 max_reserved_per_tdmr)
{
struct tdx_memblock *tmb;
u64 prev_end;
@@ -831,10 +833,10 @@ static int tdmr_populate_rsvd_holes(struct list_head *tmb_list,
* overlaps with @tdmr, set up a TDMR reserved area to cover the
* overlapping part.
*/
-static int tdmr_populate_rsvd_pamts(struct tdmr_info_list *tdmr_list,
- struct tdmr_info *tdmr,
- int *rsvd_idx,
- u16 max_reserved_per_tdmr)
+static __init int tdmr_populate_rsvd_pamts(struct tdmr_info_list *tdmr_list,
+ struct tdmr_info *tdmr,
+ int *rsvd_idx,
+ u16 max_reserved_per_tdmr)
{
int i, ret;
@@ -869,7 +871,7 @@ static int tdmr_populate_rsvd_pamts(struct tdmr_info_list *tdmr_list,
}
/* Compare function called by sort() for TDMR reserved areas */
-static int rsvd_area_cmp_func(const void *a, const void *b)
+static __init int rsvd_area_cmp_func(const void *a, const void *b)
{
struct tdmr_reserved_area *r1 = (struct tdmr_reserved_area *)a;
struct tdmr_reserved_area *r2 = (struct tdmr_reserved_area *)b;
@@ -888,10 +890,10 @@ static int rsvd_area_cmp_func(const void *a, const void *b)
* Populate reserved areas for the given @tdmr, including memory holes
* (via @tmb_list) and PAMTs (via @tdmr_list).
*/
-static int tdmr_populate_rsvd_areas(struct tdmr_info *tdmr,
- struct list_head *tmb_list,
- struct tdmr_info_list *tdmr_list,
- u16 max_reserved_per_tdmr)
+static __init int tdmr_populate_rsvd_areas(struct tdmr_info *tdmr,
+ struct list_head *tmb_list,
+ struct tdmr_info_list *tdmr_list,
+ u16 max_reserved_per_tdmr)
{
int ret, rsvd_idx = 0;
@@ -916,9 +918,9 @@ static int tdmr_populate_rsvd_areas(struct tdmr_info *tdmr,
* Populate reserved areas for all TDMRs in @tdmr_list, including memory
* holes (via @tmb_list) and PAMTs.
*/
-static int tdmrs_populate_rsvd_areas_all(struct tdmr_info_list *tdmr_list,
- struct list_head *tmb_list,
- u16 max_reserved_per_tdmr)
+static __init int tdmrs_populate_rsvd_areas_all(struct tdmr_info_list *tdmr_list,
+ struct list_head *tmb_list,
+ u16 max_reserved_per_tdmr)
{
int i;
@@ -939,9 +941,9 @@ static int tdmrs_populate_rsvd_areas_all(struct tdmr_info_list *tdmr_list,
* to cover all TDX memory regions in @tmb_list based on the TDX module
* TDMR global information in @sysinfo_tdmr.
*/
-static int construct_tdmrs(struct list_head *tmb_list,
- struct tdmr_info_list *tdmr_list,
- struct tdx_sys_info_tdmr *sysinfo_tdmr)
+static __init int construct_tdmrs(struct list_head *tmb_list,
+ struct tdmr_info_list *tdmr_list,
+ struct tdx_sys_info_tdmr *sysinfo_tdmr)
{
u16 pamt_entry_size[TDX_PS_NR] = {
sysinfo_tdmr->pamt_4k_entry_size,
@@ -973,7 +975,8 @@ static int construct_tdmrs(struct list_head *tmb_list,
return ret;
}
-static int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid)
+static __init int config_tdx_module(struct tdmr_info_list *tdmr_list,
+ u64 global_keyid)
{
struct tdx_module_args args = {};
u64 *tdmr_pa_array;
@@ -1008,7 +1011,7 @@ static int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid)
return ret;
}
-static int do_global_key_config(void *unused)
+static __init int do_global_key_config(void *unused)
{
struct tdx_module_args args = {};
@@ -1026,7 +1029,7 @@ static int do_global_key_config(void *unused)
* KVM) can ensure success by ensuring sufficient CPUs are online and
* can run SEAMCALLs.
*/
-static int config_global_keyid(void)
+static __init int config_global_keyid(void)
{
cpumask_var_t packages;
int cpu, ret = -EINVAL;
@@ -1066,7 +1069,7 @@ static int config_global_keyid(void)
return ret;
}
-static int init_tdmr(struct tdmr_info *tdmr)
+static __init int init_tdmr(struct tdmr_info *tdmr)
{
u64 next;
@@ -1097,7 +1100,7 @@ static int init_tdmr(struct tdmr_info *tdmr)
return 0;
}
-static int init_tdmrs(struct tdmr_info_list *tdmr_list)
+static __init int init_tdmrs(struct tdmr_info_list *tdmr_list)
{
int i;
@@ -1116,7 +1119,7 @@ static int init_tdmrs(struct tdmr_info_list *tdmr_list)
return 0;
}
-static int init_tdx_module(void)
+static __init int init_tdx_module(void)
{
int ret;
@@ -1197,7 +1200,7 @@ static int init_tdx_module(void)
goto out_put_tdxmem;
}
-static int tdx_enable(void)
+static __init int tdx_enable(void)
{
enum cpuhp_state state;
int ret;
diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
index 13ad2663488b..360963bc9328 100644
--- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -7,7 +7,7 @@
* Include this file to other C file instead.
*/
-static int get_tdx_sys_info_features(struct tdx_sys_info_features *sysinfo_features)
+static __init int get_tdx_sys_info_features(struct tdx_sys_info_features *sysinfo_features)
{
int ret = 0;
u64 val;
@@ -18,7 +18,7 @@ static int get_tdx_sys_info_features(struct tdx_sys_info_features *sysinfo_featu
return ret;
}
-static int get_tdx_sys_info_tdmr(struct tdx_sys_info_tdmr *sysinfo_tdmr)
+static __init int get_tdx_sys_info_tdmr(struct tdx_sys_info_tdmr *sysinfo_tdmr)
{
int ret = 0;
u64 val;
@@ -37,7 +37,7 @@ static int get_tdx_sys_info_tdmr(struct tdx_sys_info_tdmr *sysinfo_tdmr)
return ret;
}
-static int get_tdx_sys_info_td_ctrl(struct tdx_sys_info_td_ctrl *sysinfo_td_ctrl)
+static __init int get_tdx_sys_info_td_ctrl(struct tdx_sys_info_td_ctrl *sysinfo_td_ctrl)
{
int ret = 0;
u64 val;
@@ -52,7 +52,7 @@ static int get_tdx_sys_info_td_ctrl(struct tdx_sys_info_td_ctrl *sysinfo_td_ctrl
return ret;
}
-static int get_tdx_sys_info_td_conf(struct tdx_sys_info_td_conf *sysinfo_td_conf)
+static __init int get_tdx_sys_info_td_conf(struct tdx_sys_info_td_conf *sysinfo_td_conf)
{
int ret = 0;
u64 val;
@@ -85,7 +85,7 @@ static int get_tdx_sys_info_td_conf(struct tdx_sys_info_td_conf *sysinfo_td_conf
return ret;
}
-static int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
+static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
{
int ret = 0;
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related
* [PATCH v3 11/16] KVM: x86/tdx: Do VMXON and TDX-Module initialization during subsys init
From: Sean Christopherson @ 2026-02-14 1:26 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Peter Zijlstra, Arnaldo Carvalho de Melo,
Namhyung Kim, Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, linux-perf-users, Chao Gao,
Xu Yilun, Dan Williams
In-Reply-To: <20260214012702.2368778-1-seanjc@google.com>
Now that VMXON can be done without bouncing through KVM, do TDX-Module
initialization during subsys init (specifically before module_init() so
that it runs before KVM when both are built-in). Aside from the obvious
benefits of separating core TDX code from KVM, this will allow tagging a
pile of TDX functions and globals as being __init and __ro_after_init.
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
Documentation/arch/x86/tdx.rst | 36 +------
arch/x86/include/asm/tdx.h | 4 -
arch/x86/kvm/vmx/tdx.c | 148 ++++++-----------------------
arch/x86/virt/vmx/tdx/tdx.c | 168 +++++++++++++++++++--------------
arch/x86/virt/vmx/tdx/tdx.h | 8 --
5 files changed, 130 insertions(+), 234 deletions(-)
diff --git a/Documentation/arch/x86/tdx.rst b/Documentation/arch/x86/tdx.rst
index 61670e7df2f7..ff6b110291bc 100644
--- a/Documentation/arch/x86/tdx.rst
+++ b/Documentation/arch/x86/tdx.rst
@@ -60,44 +60,18 @@ Besides initializing the TDX module, a per-cpu initialization SEAMCALL
must be done on one cpu before any other SEAMCALLs can be made on that
cpu.
-The kernel provides two functions, tdx_enable() and tdx_cpu_enable() to
-allow the user of TDX to enable the TDX module and enable TDX on local
-cpu respectively.
-
-Making SEAMCALL requires VMXON has been done on that CPU. Currently only
-KVM implements VMXON. For now both tdx_enable() and tdx_cpu_enable()
-don't do VMXON internally (not trivial), but depends on the caller to
-guarantee that.
-
-To enable TDX, the caller of TDX should: 1) temporarily disable CPU
-hotplug; 2) do VMXON and tdx_enable_cpu() on all online cpus; 3) call
-tdx_enable(). For example::
-
- cpus_read_lock();
- on_each_cpu(vmxon_and_tdx_cpu_enable());
- ret = tdx_enable();
- cpus_read_unlock();
- if (ret)
- goto no_tdx;
- // TDX is ready to use
-
-And the caller of TDX must guarantee the tdx_cpu_enable() has been
-successfully done on any cpu before it wants to run any other SEAMCALL.
-A typical usage is do both VMXON and tdx_cpu_enable() in CPU hotplug
-online callback, and refuse to online if tdx_cpu_enable() fails.
-
User can consult dmesg to see whether the TDX module has been initialized.
If the TDX module is initialized successfully, dmesg shows something
like below::
[..] virt/tdx: 262668 KBs allocated for PAMT
- [..] virt/tdx: module initialized
+ [..] virt/tdx: TDX-Module initialized
If the TDX module failed to initialize, dmesg also shows it failed to
initialize::
- [..] virt/tdx: module initialization failed ...
+ [..] virt/tdx: TDX-Module initialization failed ...
TDX Interaction to Other Kernel Components
------------------------------------------
@@ -129,9 +103,9 @@ CPU Hotplug
~~~~~~~~~~~
TDX module requires the per-cpu initialization SEAMCALL must be done on
-one cpu before any other SEAMCALLs can be made on that cpu. The kernel
-provides tdx_cpu_enable() to let the user of TDX to do it when the user
-wants to use a new cpu for TDX task.
+one cpu before any other SEAMCALLs can be made on that cpu. The kernel,
+via the CPU hotplug framework, performs the necessary initialization when
+a CPU is first brought online.
TDX doesn't support physical (ACPI) CPU hotplug. During machine boot,
TDX verifies all boot-time present logical CPUs are TDX compatible before
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 6b338d7f01b7..a149740b24e8 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -145,8 +145,6 @@ static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
#define seamcall(_fn, _args) sc_retry(__seamcall, (_fn), (_args))
#define seamcall_ret(_fn, _args) sc_retry(__seamcall_ret, (_fn), (_args))
#define seamcall_saved_ret(_fn, _args) sc_retry(__seamcall_saved_ret, (_fn), (_args))
-int tdx_cpu_enable(void);
-int tdx_enable(void);
const char *tdx_dump_mce_info(struct mce *m);
const struct tdx_sys_info *tdx_get_sysinfo(void);
@@ -223,8 +221,6 @@ u64 tdh_phymem_page_wbinvd_tdr(struct tdx_td *td);
u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, struct page *page);
#else
static inline void tdx_init(void) { }
-static inline int tdx_cpu_enable(void) { return -ENODEV; }
-static inline int tdx_enable(void) { return -ENODEV; }
static inline u32 tdx_get_nr_guest_keyids(void) { return 0; }
static inline const char *tdx_dump_mce_info(struct mce *m) { return NULL; }
static inline const struct tdx_sys_info *tdx_get_sysinfo(void) { return NULL; }
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index 582469118b79..0ac01c119336 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -59,7 +59,7 @@ module_param_named(tdx, enable_tdx, bool, 0444);
#define TDX_SHARED_BIT_PWL_5 gpa_to_gfn(BIT_ULL(51))
#define TDX_SHARED_BIT_PWL_4 gpa_to_gfn(BIT_ULL(47))
-static enum cpuhp_state tdx_cpuhp_state;
+static enum cpuhp_state tdx_cpuhp_state __ro_after_init;
static const struct tdx_sys_info *tdx_sysinfo;
@@ -3294,10 +3294,7 @@ int tdx_gmem_max_mapping_level(struct kvm *kvm, kvm_pfn_t pfn, bool is_private)
static int tdx_online_cpu(unsigned int cpu)
{
- /* Sanity check CPU is already in post-VMXON */
- WARN_ON_ONCE(!(cr4_read_shadow() & X86_CR4_VMXE));
-
- return tdx_cpu_enable();
+ return 0;
}
static int tdx_offline_cpu(unsigned int cpu)
@@ -3336,51 +3333,6 @@ static int tdx_offline_cpu(unsigned int cpu)
return -EBUSY;
}
-static void __do_tdx_cleanup(void)
-{
- /*
- * Once TDX module is initialized, it cannot be disabled and
- * re-initialized again w/o runtime update (which isn't
- * supported by kernel). Only need to remove the cpuhp here.
- * The TDX host core code tracks TDX status and can handle
- * 'multiple enabling' scenario.
- */
- WARN_ON_ONCE(!tdx_cpuhp_state);
- cpuhp_remove_state_nocalls_cpuslocked(tdx_cpuhp_state);
- tdx_cpuhp_state = 0;
-}
-
-static void __tdx_cleanup(void)
-{
- cpus_read_lock();
- __do_tdx_cleanup();
- cpus_read_unlock();
-}
-
-static int __init __do_tdx_bringup(void)
-{
- int r;
-
- /*
- * TDX-specific cpuhp callback to call tdx_cpu_enable() on all
- * online CPUs before calling tdx_enable(), and on any new
- * going-online CPU to make sure it is ready for TDX guest.
- */
- r = cpuhp_setup_state_cpuslocked(CPUHP_AP_ONLINE_DYN,
- "kvm/cpu/tdx:online",
- tdx_online_cpu, tdx_offline_cpu);
- if (r < 0)
- return r;
-
- tdx_cpuhp_state = r;
-
- r = tdx_enable();
- if (r)
- __do_tdx_cleanup();
-
- return r;
-}
-
static int __init __tdx_bringup(void)
{
const struct tdx_sys_info_td_conf *td_conf;
@@ -3400,34 +3352,18 @@ static int __init __tdx_bringup(void)
}
}
- /*
- * Enabling TDX requires enabling hardware virtualization first,
- * as making SEAMCALLs requires CPU being in post-VMXON state.
- */
- r = kvm_enable_virtualization();
- if (r)
- return r;
-
- cpus_read_lock();
- r = __do_tdx_bringup();
- cpus_read_unlock();
-
- if (r)
- goto tdx_bringup_err;
-
- r = -EINVAL;
/* Get TDX global information for later use */
tdx_sysinfo = tdx_get_sysinfo();
- if (WARN_ON_ONCE(!tdx_sysinfo))
- goto get_sysinfo_err;
+ if (!tdx_sysinfo)
+ return -ENODEV;
/* Check TDX module and KVM capabilities */
if (!tdx_get_supported_attrs(&tdx_sysinfo->td_conf) ||
!tdx_get_supported_xfam(&tdx_sysinfo->td_conf))
- goto get_sysinfo_err;
+ return -EINVAL;
if (!(tdx_sysinfo->features.tdx_features0 & MD_FIELD_ID_FEATURES0_TOPOLOGY_ENUM))
- goto get_sysinfo_err;
+ return -EINVAL;
/*
* TDX has its own limit of maximum vCPUs it can support for all
@@ -3462,34 +3398,31 @@ static int __init __tdx_bringup(void)
if (td_conf->max_vcpus_per_td < num_present_cpus()) {
pr_err("Disable TDX: MAX_VCPU_PER_TD (%u) smaller than number of logical CPUs (%u).\n",
td_conf->max_vcpus_per_td, num_present_cpus());
- goto get_sysinfo_err;
+ return -EINVAL;
}
if (misc_cg_set_capacity(MISC_CG_RES_TDX, tdx_get_nr_guest_keyids()))
- goto get_sysinfo_err;
+ return -EINVAL;
/*
- * Leave hardware virtualization enabled after TDX is enabled
- * successfully. TDX CPU hotplug depends on this.
+ * TDX-specific cpuhp callback to disallow offlining the last CPU in a
+ * packing while KVM is running one or more TDs. Reclaiming HKIDs
+ * requires doing PAGE.WBINVD on every package, i.e. offlining all CPUs
+ * of a package would prevent reclaiming the HKID.
*/
+ r = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "kvm/cpu/tdx:online",
+ tdx_online_cpu, tdx_offline_cpu);
+ if (r < 0)
+ goto err_cpuhup;
+
+ tdx_cpuhp_state = r;
return 0;
-get_sysinfo_err:
- __tdx_cleanup();
-tdx_bringup_err:
- kvm_disable_virtualization();
+err_cpuhup:
+ misc_cg_set_capacity(MISC_CG_RES_TDX, 0);
return r;
}
-void tdx_cleanup(void)
-{
- if (enable_tdx) {
- misc_cg_set_capacity(MISC_CG_RES_TDX, 0);
- __tdx_cleanup();
- kvm_disable_virtualization();
- }
-}
-
int __init tdx_bringup(void)
{
int r, i;
@@ -3521,39 +3454,11 @@ int __init tdx_bringup(void)
goto success_disable_tdx;
}
- if (!cpu_feature_enabled(X86_FEATURE_MOVDIR64B)) {
- pr_err("tdx: MOVDIR64B is required for TDX\n");
- goto success_disable_tdx;
- }
-
- if (!cpu_feature_enabled(X86_FEATURE_SELFSNOOP)) {
- pr_err("Self-snoop is required for TDX\n");
- goto success_disable_tdx;
- }
-
if (!cpu_feature_enabled(X86_FEATURE_TDX_HOST_PLATFORM)) {
- pr_err("tdx: no TDX private KeyIDs available\n");
+ pr_err("TDX not supported by the host platform\n");
goto success_disable_tdx;
}
- if (!enable_virt_at_load) {
- pr_err("tdx: tdx requires kvm.enable_virt_at_load=1\n");
- goto success_disable_tdx;
- }
-
- /*
- * Ideally KVM should probe whether TDX module has been loaded
- * first and then try to bring it up. But TDX needs to use SEAMCALL
- * to probe whether the module is loaded (there is no CPUID or MSR
- * for that), and making SEAMCALL requires enabling virtualization
- * first, just like the rest steps of bringing up TDX module.
- *
- * So, for simplicity do everything in __tdx_bringup(); the first
- * SEAMCALL will return -ENODEV when the module is not loaded. The
- * only complication is having to make sure that initialization
- * SEAMCALLs don't return TDX_SEAMCALL_VMFAILINVALID in other
- * cases.
- */
r = __tdx_bringup();
if (r) {
/*
@@ -3568,8 +3473,6 @@ int __init tdx_bringup(void)
*/
if (r == -ENODEV)
goto success_disable_tdx;
-
- enable_tdx = 0;
}
return r;
@@ -3579,6 +3482,15 @@ int __init tdx_bringup(void)
return 0;
}
+void tdx_cleanup(void)
+{
+ if (!enable_tdx)
+ return;
+
+ misc_cg_set_capacity(MISC_CG_RES_TDX, 0);
+ cpuhp_remove_state(tdx_cpuhp_state);
+}
+
void __init tdx_hardware_setup(void)
{
KVM_SANITY_CHECK_VM_STRUCT_SIZE(kvm_tdx);
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index dfd82fac0498..feea8dd6920d 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -28,6 +28,7 @@
#include <linux/log2.h>
#include <linux/acpi.h>
#include <linux/suspend.h>
+#include <linux/syscore_ops.h>
#include <linux/idr.h>
#include <linux/kvm_types.h>
#include <asm/page.h>
@@ -39,6 +40,7 @@
#include <asm/cpu_device_id.h>
#include <asm/processor.h>
#include <asm/mce.h>
+#include <asm/virt.h>
#include "tdx.h"
static u32 tdx_global_keyid __ro_after_init;
@@ -51,13 +53,11 @@ static DEFINE_PER_CPU(bool, tdx_lp_initialized);
static struct tdmr_info_list tdx_tdmr_list;
-static enum tdx_module_status_t tdx_module_status;
-static DEFINE_MUTEX(tdx_module_lock);
-
/* All TDX-usable memory regions. Protected by mem_hotplug_lock. */
static LIST_HEAD(tdx_memlist);
static struct tdx_sys_info tdx_sysinfo;
+static bool tdx_module_initialized;
typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args);
@@ -142,22 +142,15 @@ static int try_init_module_global(void)
}
/**
- * tdx_cpu_enable - Enable TDX on local cpu
- *
- * Do one-time TDX module per-cpu initialization SEAMCALL (and TDX module
- * global initialization SEAMCALL if not done) on local cpu to make this
- * cpu be ready to run any other SEAMCALLs.
- *
- * Return 0 on success, otherwise errors.
+ * Enable VMXON and then do one-time TDX module per-cpu initialization SEAMCALL
+ * (and TDX module global initialization SEAMCALL if not done) on local cpu to
+ * make this cpu be ready to run any other SEAMCALLs.
*/
-int tdx_cpu_enable(void)
+static int tdx_cpu_enable(void)
{
struct tdx_module_args args = {};
int ret;
- if (!boot_cpu_has(X86_FEATURE_TDX_HOST_PLATFORM))
- return -ENODEV;
-
if (__this_cpu_read(tdx_lp_initialized))
return 0;
@@ -178,7 +171,58 @@ int tdx_cpu_enable(void)
return 0;
}
-EXPORT_SYMBOL_FOR_KVM(tdx_cpu_enable);
+
+static int tdx_online_cpu(unsigned int cpu)
+{
+ int ret;
+
+ ret = x86_virt_get_ref(X86_FEATURE_VMX);
+ if (ret)
+ return ret;
+
+ ret = tdx_cpu_enable();
+ if (ret)
+ x86_virt_put_ref(X86_FEATURE_VMX);
+
+ return ret;
+}
+
+static int tdx_offline_cpu(unsigned int cpu)
+{
+ x86_virt_put_ref(X86_FEATURE_VMX);
+ return 0;
+}
+
+static void tdx_shutdown_cpu(void *ign)
+{
+ x86_virt_put_ref(X86_FEATURE_VMX);
+}
+
+static void tdx_shutdown(void *ign)
+{
+ on_each_cpu(tdx_shutdown_cpu, NULL, 1);
+}
+
+static int tdx_suspend(void *ign)
+{
+ x86_virt_put_ref(X86_FEATURE_VMX);
+ return 0;
+}
+
+static void tdx_resume(void *ign)
+{
+ WARN_ON_ONCE(x86_virt_get_ref(X86_FEATURE_VMX));
+}
+
+static const struct syscore_ops tdx_syscore_ops = {
+ .suspend = tdx_suspend,
+ .resume = tdx_resume,
+ .shutdown = tdx_shutdown,
+};
+
+static struct syscore tdx_syscore = {
+ .ops = &tdx_syscore_ops,
+};
/*
* Add a memory region as a TDX memory block. The caller must make sure
@@ -1153,67 +1197,50 @@ static int init_tdx_module(void)
goto out_put_tdxmem;
}
-static int __tdx_enable(void)
+static int tdx_enable(void)
{
+ enum cpuhp_state state;
int ret;
+ if (!cpu_feature_enabled(X86_FEATURE_TDX_HOST_PLATFORM)) {
+ pr_err("TDX not supported by the host platform\n");
+ return -ENODEV;
+ }
+
+ if (!cpu_feature_enabled(X86_FEATURE_XSAVE)) {
+ pr_err("XSAVE is required for TDX\n");
+ return -EINVAL;
+ }
+
+ if (!cpu_feature_enabled(X86_FEATURE_MOVDIR64B)) {
+ pr_err("MOVDIR64B is required for TDX\n");
+ return -EINVAL;
+ }
+
+ if (!cpu_feature_enabled(X86_FEATURE_SELFSNOOP)) {
+ pr_err("Self-snoop is required for TDX\n");
+ return -ENODEV;
+ }
+
+ state = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "virt/tdx:online",
+ tdx_online_cpu, tdx_offline_cpu);
+ if (state < 0)
+ return state;
+
ret = init_tdx_module();
if (ret) {
- pr_err("module initialization failed (%d)\n", ret);
- tdx_module_status = TDX_MODULE_ERROR;
+ pr_err("TDX-Module initialization failed (%d)\n", ret);
+ cpuhp_remove_state(state);
return ret;
}
- pr_info("module initialized\n");
- tdx_module_status = TDX_MODULE_INITIALIZED;
+ register_syscore(&tdx_syscore);
+ tdx_module_initialized = true;
+ pr_info("TDX-Module initialized\n");
return 0;
}
-
-/**
- * tdx_enable - Enable TDX module to make it ready to run TDX guests
- *
- * This function assumes the caller has: 1) held read lock of CPU hotplug
- * lock to prevent any new cpu from becoming online; 2) done both VMXON
- * and tdx_cpu_enable() on all online cpus.
- *
- * This function requires there's at least one online cpu for each CPU
- * package to succeed.
- *
- * This function can be called in parallel by multiple callers.
- *
- * Return 0 if TDX is enabled successfully, otherwise error.
- */
-int tdx_enable(void)
-{
- int ret;
-
- if (!boot_cpu_has(X86_FEATURE_TDX_HOST_PLATFORM))
- return -ENODEV;
-
- lockdep_assert_cpus_held();
-
- mutex_lock(&tdx_module_lock);
-
- switch (tdx_module_status) {
- case TDX_MODULE_UNINITIALIZED:
- ret = __tdx_enable();
- break;
- case TDX_MODULE_INITIALIZED:
- /* Already initialized, great, tell the caller. */
- ret = 0;
- break;
- default:
- /* Failed to initialize in the previous attempts */
- ret = -EINVAL;
- break;
- }
-
- mutex_unlock(&tdx_module_lock);
-
- return ret;
-}
-EXPORT_SYMBOL_FOR_KVM(tdx_enable);
+subsys_initcall(tdx_enable);
static bool is_pamt_page(unsigned long phys)
{
@@ -1464,15 +1491,10 @@ void __init tdx_init(void)
const struct tdx_sys_info *tdx_get_sysinfo(void)
{
- const struct tdx_sys_info *p = NULL;
+ if (!tdx_module_initialized)
+ return NULL;
- /* Make sure all fields in @tdx_sysinfo have been populated */
- mutex_lock(&tdx_module_lock);
- if (tdx_module_status == TDX_MODULE_INITIALIZED)
- p = (const struct tdx_sys_info *)&tdx_sysinfo;
- mutex_unlock(&tdx_module_lock);
-
- return p;
+ return (const struct tdx_sys_info *)&tdx_sysinfo;
}
EXPORT_SYMBOL_FOR_KVM(tdx_get_sysinfo);
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 82bb82be8567..dde219c823b4 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -91,14 +91,6 @@ struct tdmr_info {
* Do not put any hardware-defined TDX structure representations below
* this comment!
*/
-
-/* Kernel defined TDX module status during module initialization. */
-enum tdx_module_status_t {
- TDX_MODULE_UNINITIALIZED,
- TDX_MODULE_INITIALIZED,
- TDX_MODULE_ERROR
-};
-
struct tdx_memblock {
struct list_head list;
unsigned long start_pfn;
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related
* [PATCH v3 10/16] x86/virt/tdx: Drop the outdated requirement that TDX be enabled in IRQ context
From: Sean Christopherson @ 2026-02-14 1:26 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Peter Zijlstra, Arnaldo Carvalho de Melo,
Namhyung Kim, Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, linux-perf-users, Chao Gao,
Xu Yilun, Dan Williams
In-Reply-To: <20260214012702.2368778-1-seanjc@google.com>
Remove TDX's outdated requirement that per-CPU enabling be done via IPI
function call, which was a stale artifact leftover from early versions of
the TDX enablement series. The requirement that IRQs be disabled should
have been dropped as part of the revamped series that relied on a the KVM
rework to enable VMX at module load.
In other words, the kernel's "requirement" was never a requirement at all,
but instead a reflection of how KVM enabled VMX (via IPI callback) when
the TDX subsystem code was merged.
Note, accessing per-CPU information is safe even without disabling IRQs,
as tdx_online_cpu() is invoked via a cpuhp callback, i.e. from a per-CPU
thread.
Link: https://lore.kernel.org/all/ZyJOiPQnBz31qLZ7@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/tdx.c | 9 +--------
arch/x86/virt/vmx/tdx/tdx.c | 4 ----
2 files changed, 1 insertion(+), 12 deletions(-)
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index 0c790eb0bfa6..582469118b79 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -3294,17 +3294,10 @@ int tdx_gmem_max_mapping_level(struct kvm *kvm, kvm_pfn_t pfn, bool is_private)
static int tdx_online_cpu(unsigned int cpu)
{
- unsigned long flags;
- int r;
-
/* Sanity check CPU is already in post-VMXON */
WARN_ON_ONCE(!(cr4_read_shadow() & X86_CR4_VMXE));
- local_irq_save(flags);
- r = tdx_cpu_enable();
- local_irq_restore(flags);
-
- return r;
+ return tdx_cpu_enable();
}
static int tdx_offline_cpu(unsigned int cpu)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 5ce4ebe99774..dfd82fac0498 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -148,8 +148,6 @@ static int try_init_module_global(void)
* global initialization SEAMCALL if not done) on local cpu to make this
* cpu be ready to run any other SEAMCALLs.
*
- * Always call this function via IPI function calls.
- *
* Return 0 on success, otherwise errors.
*/
int tdx_cpu_enable(void)
@@ -160,8 +158,6 @@ int tdx_cpu_enable(void)
if (!boot_cpu_has(X86_FEATURE_TDX_HOST_PLATFORM))
return -ENODEV;
- lockdep_assert_irqs_disabled();
-
if (__this_cpu_read(tdx_lp_initialized))
return 0;
--
2.53.0.310.g728cabbaf7-goog
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox