* Re: [PATCH v1 12/14] x86/boot: tweak a20.c for better code generation
From: Maciej W. Rozycki @ 2026-01-24 3:00 UTC (permalink / raw)
To: David Laight
Cc: H. Peter Anvin, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, Uros Bizjak, Petr Mladek, Andrew Morton, Kees Cook,
Peter Zijlstra (Intel), Nathan Chancellor, Kiryl Shutsemau,
Rick Edgecombe, linux-kernel, linux-coco, x86
In-Reply-To: <20260121114911.6adc2838@pumpkin>
On Wed, 21 Jan 2026, David Laight wrote:
> No loops needed.
A loop is needed because there can be a considerable delay from issuing
the I/O request to flip the A20 gate till the circuitry responding. This
is particularly true with the command issued to the 8042 device, which is
a microcontroller running its own firmware that needs it time to process
an incoming request to drive one of the microcontroller's GPIOs. There
was a reason for port 0x92 circuitry later added to the PC architecture
with the IBM PS/2 being called the "fast A20 gate".
Maciej
^ permalink raw reply
* Re: [PATCH kernel 2/2] crypto/ccp: Allow multiple streams on the same root bridge
From: dan.j.williams @ 2026-01-23 22:59 UTC (permalink / raw)
To: Alexey Kardashevskiy, linux-crypto
Cc: linux-kernel, Ashish Kalra, Tom Lendacky, John Allen, Herbert Xu,
David S. Miller, Dan Williams, Alexey Kardashevskiy, x86,
linux-coco, Pratik R . Sampat
In-Reply-To: <20260123053057.1350569-3-aik@amd.com>
Alexey Kardashevskiy wrote:
> IDE stream IDs are responsibility of a platform and in some cases TSM
> allocates the numbers. AMD SEV TIO though leaves it to the host OS.
> Mistakenly stream ID is hard coded to be the same as a traffic class.
I scratched my head at this comment, but now realize that you are saying
the existing code used the local @tc, not that the hardware stream ID is
in any way related to traffic class, right?
It would help to detail what the end user visible effects of this bug
are. The TSM framework does not allow for multiple streams per PF, so I
wonder what scenario is being fixed?
Lastly, are you expecting tsm.git#fixes to pick this up? I am assuming
that this goes through crypto.git and tsm.git can just stay focused on
core fixes.
^ permalink raw reply
* Re: [PATCH kernel 1/2] crypto/ccp: Use PCI bridge defaults for IDE
From: dan.j.williams @ 2026-01-23 22:48 UTC (permalink / raw)
To: Alexey Kardashevskiy, linux-crypto
Cc: linux-kernel, Ashish Kalra, Tom Lendacky, John Allen, Herbert Xu,
David S. Miller, Dan Williams, Alexey Kardashevskiy, x86,
linux-coco, Pratik R . Sampat
In-Reply-To: <20260123053057.1350569-2-aik@amd.com>
Alexey Kardashevskiy wrote:
> The current number of streams in AMD TSM is 1 which is too little,
> the core uses 255. Also, even if the module parameter is increased,
> calling pci_ide_set_nr_streams() second time triggers WARN_ON.
>
> Simplify the code by sticking to the PCI core defaults.
>
> Fixes: 4be423572da1 ("crypto/ccp: Implement SEV-TIO PCIe IDE (phase1)")
> Signed-off-by: Alexey Kardashevskiy <aik@amd.com>
> ---
> drivers/crypto/ccp/sev-dev-tsm.c | 12 ------------
> 1 file changed, 12 deletions(-)
>
> diff --git a/drivers/crypto/ccp/sev-dev-tsm.c b/drivers/crypto/ccp/sev-dev-tsm.c
> index ea29cd5d0ff9..7407b77c2ef2 100644
> --- a/drivers/crypto/ccp/sev-dev-tsm.c
> +++ b/drivers/crypto/ccp/sev-dev-tsm.c
> @@ -19,12 +19,6 @@
>
> MODULE_IMPORT_NS("PCI_IDE");
>
> -#define TIO_DEFAULT_NR_IDE_STREAMS 1
> -
> -static uint nr_ide_streams = TIO_DEFAULT_NR_IDE_STREAMS;
> -module_param_named(ide_nr, nr_ide_streams, uint, 0644);
> -MODULE_PARM_DESC(ide_nr, "Set the maximum number of IDE streams per PHB");
> -
Yes, happy to see any reduction in ABI surface, especially module
parameters.
Acked-by: Dan Williams <dan.j.williams@intel.com>
^ permalink raw reply
* Re: [PATCH v12 22/46] arm64: RMI: Create the realm descriptor
From: Alper Gun @ 2026-01-23 18:57 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, Aneesh Kumar K . V, Emi Kisanuki,
Vishal Annapurve
In-Reply-To: <20251217101125.91098-23-steven.price@arm.com>
On Wed, Dec 17, 2025 at 2:13 AM Steven Price <steven.price@arm.com> wrote:
>
> Creating a realm involves first creating a realm descriptor (RD). This
> involves passing the configuration information to the RMM. Do this as
> part of realm_ensure_created() so that the realm is created when it is
> first needed.
>
> Signed-off-by: Steven Price <steven.price@arm.com>
> ---
> New patch for v12
> ---
> arch/arm64/kvm/rmi.c | 117 ++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 115 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
> index b51e68e56d56..18edc7eeb5fa 100644
> --- a/arch/arm64/kvm/rmi.c
> +++ b/arch/arm64/kvm/rmi.c
> @@ -500,6 +500,106 @@ static void realm_unmap_shared_range(struct kvm *kvm,
> start, end);
> }
>
> +/* Calculate the number of s2 root rtts needed */
> +static int realm_num_root_rtts(struct realm *realm)
> +{
> + unsigned int ipa_bits = realm->ia_bits;
> + unsigned int levels = 4 - get_start_level(realm);
> + unsigned int sl_ipa_bits = levels * (RMM_PAGE_SHIFT - 3) +
> + RMM_PAGE_SHIFT;
> +
> + if (sl_ipa_bits >= ipa_bits)
> + return 1;
> +
> + return 1 << (ipa_bits - sl_ipa_bits);
> +}
> +
> +static int realm_create_rd(struct kvm *kvm)
> +{
> + struct realm *realm = &kvm->arch.realm;
> + struct realm_params *params = realm->params;
> + void *rd = NULL;
> + phys_addr_t rd_phys, params_phys;
> + size_t pgd_size = kvm_pgtable_stage2_pgd_size(kvm->arch.mmu.vtcr);
> + int i, r;
> + int rtt_num_start;
> +
> + realm->ia_bits = VTCR_EL2_IPA(kvm->arch.mmu.vtcr);
> + rtt_num_start = realm_num_root_rtts(realm);
> +
> + if (WARN_ON(realm->rd || !realm->params))
> + return -EEXIST;
> +
> + if (pgd_size / RMM_PAGE_SIZE < rtt_num_start)
> + return -EINVAL;
> +
> + rd = (void *)__get_free_page(GFP_KERNEL);
> + if (!rd)
> + return -ENOMEM;
> +
> + rd_phys = virt_to_phys(rd);
> + if (rmi_granule_delegate(rd_phys)) {
> + r = -ENXIO;
> + goto free_rd;
> + }
> +
> + for (i = 0; i < pgd_size; i += RMM_PAGE_SIZE) {
> + phys_addr_t pgd_phys = kvm->arch.mmu.pgd_phys + i;
> +
> + if (rmi_granule_delegate(pgd_phys)) {
> + r = -ENXIO;
> + goto out_undelegate_tables;
> + }
> + }
> +
> + params->s2sz = VTCR_EL2_IPA(kvm->arch.mmu.vtcr);
> + params->rtt_level_start = get_start_level(realm);
> + params->rtt_num_start = rtt_num_start;
> + params->rtt_base = kvm->arch.mmu.pgd_phys;
> + params->vmid = realm->vmid;
I don't see a way to configure rpv and hash_algo anymore. I assume they
are gone for a minimal userspace interface. Will there be a way to set
them going forward?
> +
> + params_phys = virt_to_phys(params);
> +
> + if (rmi_realm_create(rd_phys, params_phys)) {
> + r = -ENXIO;
> + goto out_undelegate_tables;
> + }
> +
> + if (WARN_ON(rmi_rec_aux_count(rd_phys, &realm->num_aux))) {
> + WARN_ON(rmi_realm_destroy(rd_phys));
> + r = -ENXIO;
> + goto out_undelegate_tables;
> + }
> +
> + realm->rd = rd;
> + WRITE_ONCE(realm->state, REALM_STATE_NEW);
> + /* The realm is up, free the parameters. */
> + free_page((unsigned long)realm->params);
> + realm->params = NULL;
> +
> + return 0;
> +
> +out_undelegate_tables:
> + while (i > 0) {
> + i -= RMM_PAGE_SIZE;
> +
> + phys_addr_t pgd_phys = kvm->arch.mmu.pgd_phys + i;
> +
> + if (WARN_ON(rmi_granule_undelegate(pgd_phys))) {
> + /* Leak the pages if they cannot be returned */
> + kvm->arch.mmu.pgt = NULL;
> + break;
> + }
> + }
> + if (WARN_ON(rmi_granule_undelegate(rd_phys))) {
> + /* Leak the page if it isn't returned */
> + return r;
> + }
> +free_rd:
> + free_page((unsigned long)rd);
> + return r;
> +}
> +
> static int realm_unmap_private_page(struct realm *realm,
> unsigned long ipa,
> unsigned long *next_addr)
> @@ -803,8 +903,21 @@ static int realm_init_ipa_state(struct kvm *kvm,
>
> static int realm_ensure_created(struct kvm *kvm)
> {
> - /* Provided in later patch */
> - return -ENXIO;
> + int ret;
> +
> + switch (kvm_realm_state(kvm)) {
> + case REALM_STATE_NONE:
> + break;
> + case REALM_STATE_NEW:
> + return 0;
> + case REALM_STATE_DEAD:
> + return -ENXIO;
> + default:
> + return -EBUSY;
> + }
> +
> + ret = realm_create_rd(kvm);
> + return ret;
> }
>
> static int set_ripas_of_protected_regions(struct kvm *kvm)
> --
> 2.43.0
>
^ permalink raw reply
* Re: [PATCH v2] KVM: TDX: Allow userspace to return errors to guest for MAPGPA
From: Sean Christopherson @ 2026-01-23 17:42 UTC (permalink / raw)
To: Sagi Shahar
Cc: Paolo Bonzini, Dave Hansen, Kiryl Shutsemau, Rick Edgecombe,
Thomas Gleixner, Borislav Petkov, H. Peter Anvin, x86, kvm,
linux-kernel, linux-coco, Vishal Annapurve, Michael Roth,
Tom Lendacky
In-Reply-To: <20260115225238.2837449-1-sagis@google.com>
+Mike and Tom
On Thu, Jan 15, 2026, 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>
> ---
> arch/x86/kvm/vmx/tdx.c | 8 +++++++-
> 1 file changed, 7 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
> index 2d7a4d52ccfb..9bd4ffbdfecf 100644
> --- a/arch/x86/kvm/vmx/tdx.c
> +++ b/arch/x86/kvm/vmx/tdx.c
> @@ -1189,7 +1189,13 @@ static int tdx_complete_vmcall_map_gpa(struct kvm_vcpu *vcpu)
> struct vcpu_tdx *tdx = to_tdx(vcpu);
>
> if (vcpu->run->hypercall.ret) {
> - tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND);
> + if (vcpu->run->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
> + return -EINVAL;
> +
Because no good deed goes unpunished, please update the KVM_CAP_EXIT_HYPERCALL
section in Documentation/virt/kvm/api.rst.
We also need to give snp_complete_psc_msr() and snp_complete_one_psc() similar
treatment (and update docs accordingly, too). AFAICT, SNP doesn't have a "retry"
error code, so I think all we can do is restrict userspace to EAGAIN and EINVAL?
(Restricting SNP guests to EINVAL seems like it would create unnecessary pain for
userspace)
E.g. something like this?
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index f9aad5c1447e..14ad4daefaf7 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -3732,9 +3732,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);
@@ -3825,10 +3829,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 */
}
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index 2d7a4d52ccfb..4aa1edfef698 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -1186,10 +1186,19 @@ 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;
}
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index fdab0ad49098..5c2c1924addf 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -706,6 +706,13 @@ 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);
^ permalink raw reply related
* Re: [PATCH v7 1/2] KVM: Introduce KVM_EXIT_SNP_REQ_CERTS for SNP certificate-fetching
From: Sean Christopherson @ 2026-01-23 17:17 UTC (permalink / raw)
To: Michael Roth
Cc: kvm, linux-coco, linux-kernel, pbonzini, jroedel, thomas.lendacky,
liam.merwick, huibo.wang, Dionna Glaze
In-Reply-To: <20260109231732.1160759-2-michael.roth@amd.com>
On Fri, Jan 09, 2026, Michael Roth wrote:
> +KVM_EXIT_SNP_REQ_CERTS indicates an SEV-SNP guest with certificate-fetching
> +enabled (see KVM_SEV_SNP_ENABLE_REQ_CERTS) has generated an Extended Guest
> +Request NAE #VMGEXIT (SNP_GUEST_REQUEST) with message type MSG_REPORT_REQ,
> +i.e. has requested an attestation report from firmware, and would like the
> +certificate data corresponding the attestation report signature to be
^
|
to
I'll fixup when applying. Thanks for the *fantastic* documentation!
^ permalink raw reply
* Re: [PATCH v12 06/46] arm64: RMI: Define the user ABI
From: Suzuki K Poulose @ 2026-01-23 16:47 UTC (permalink / raw)
To: Steven Price, kvm, kvmarm
Cc: Catalin Marinas, Marc Zyngier, Will Deacon, James Morse,
Oliver Upton, 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: <20251217101125.91098-7-steven.price@arm.com>
Hi Steven
On 17/12/2025 10:10, Steven Price wrote:
> There is one CAP which identified the presence of CCA, and two ioctls.
> One ioctl is used to populate memory and the other is used when user
> space is providing the PSCI implementation to identify the target of the
> operation.
>
Could we split these changes to the corresponding implementation patches
? That might give us a full picture of how these UAPIs fit in the bigger
picture.
> Signed-off-by: Steven Price <steven.price@arm.com>
> ---
> Changes since v11:
> * Completely reworked to be more implicit. Rather than having explicit
> CAP operations to progress the realm construction these operations
> are done when needed (on populating and on first vCPU run).
> * Populate and PSCI complete are promoted to proper ioctls.
> Changes since v10:
> * Rename symbols from RME to RMI.
> Changes since v9:
> * Improvements to documentation.
> * Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
> Changes since v8:
> * Minor improvements to documentation following review.
> * Bump the magic numbers to avoid conflicts.
> Changes since v7:
> * Add documentation of new ioctls
> * Bump the magic numbers to avoid conflicts
> Changes since v6:
> * Rename some of the symbols to make their usage clearer and avoid
> repetition.
> Changes from v5:
> * Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
> KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
> ---
> Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
> include/uapi/linux/kvm.h | 23 ++++++++++++++
> 2 files changed, 80 insertions(+)
>
> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> index 01a3abef8abb..2d5dc7e48954 100644
> --- a/Documentation/virt/kvm/api.rst
> +++ b/Documentation/virt/kvm/api.rst
> @@ -6517,6 +6517,54 @@ the capability to be present.
>
> `flags` must currently be zero.
>
> +4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE
> +------------------------------------
> +
> +:Capability: KVM_CAP_ARM_RMI
> +:Architectures: arm64
> +:Type: vcpu ioctl
> +:Parameters: struct kvm_arm_rmi_psci_complete (in)
> +:Returns: 0 if successful, < 0 on error
> +
> +::
> +
> + struct kvm_arm_rmi_psci_complete {
> + __u64 target_mpidr;
> + __u32 psci_status;
> + __u32 padding[3];
> + };
> +
> +Where PSCI functions are handled by user space, the RMM needs to be informed of
> +the target of the operation using `target_mpidr`, along with the status
> +(`psci_status`). The RMM v1.0 specification defines two functions that require
> +this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.
> +
> +If the kernel is handling PSCI then this is done automatically and the VMM
> +doesn't need to call this ioctl.
> +
> +4.145 KVM_ARM_RMI_POPULATE
> +--------------------------
> +
> +:Capability: KVM_CAP_ARM_RMI
> +:Architectures: arm64
> +:Type: vm ioctl
> +:Parameters: struct kvm_arm_rmi_populate (in)
> +:Returns: number of bytes populated, < 0 on error
> +
> +::
> +
> + struct kvm_arm_rmi_populate {
> + __u64 base;
> + __u64 size;
> + __u64 source_uaddr;
> + __u32 flags;
> + __u32 reserved;
> + };
> +
> +Populate a region of protected address space by copying the data from the user
> +space pointer provided. This is only valid before any VCPUs have been run.
> +The ioctl might not populate the entire region and user space may have to
> +repeatedly call it (with updated pointers) to populate the entire region.
It may be a good idea to spill out the restrictions on calling this,
right ? We expect that the source_uaddr is a separate memory area
from the "DRAM" (in shared mode) ?
Can this be called before/after converting the entire memory of the
Guest to Private ? I believe, it must be done after the "initial
change all of DRAM to private" ?
Suzuki
>
> .. _kvm_run:
>
> @@ -8765,6 +8813,15 @@ helpful if user space wants to emulate instructions which are not
> This capability can be enabled dynamically even if VCPUs were already
> created and are running.
>
> +7.47 KVM_CAP_ARM_RMI
> +--------------------
> +
> +:Architectures: arm64
> +:Target: VM
> +:Parameters: None
> +
> +This capability indicates that support for CCA realms is available.
> +
> 8. Other capabilities.
> ======================
>
> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
> index dddb781b0507..8e66ba9c81db 100644
> --- a/include/uapi/linux/kvm.h
> +++ b/include/uapi/linux/kvm.h
> @@ -974,6 +974,7 @@ struct kvm_enable_cap {
> #define KVM_CAP_GUEST_MEMFD_FLAGS 244
> #define KVM_CAP_ARM_SEA_TO_USER 245
> #define KVM_CAP_S390_USER_OPEREXEC 246
> +#define KVM_CAP_ARM_RMI 247
>
> struct kvm_irq_routing_irqchip {
> __u32 irqchip;
> @@ -1628,4 +1629,26 @@ struct kvm_pre_fault_memory {
> __u64 padding[5];
> };
>
> +/* Available with KVM_CAP_ARM_RMI, only for VMs with KVM_VM_TYPE_ARM_REALM */
> +struct kvm_arm_rmi_psci_complete {
> + __u64 target_mpidr;
> + __u32 psci_status;
> + __u32 padding[3];
> +};
> +
> +#define KVM_ARM_VCPU_RMI_PSCI_COMPLETE _IOW(KVMIO, 0xd6, struct kvm_arm_rmi_psci_complete)
> +
> +/* Available with KVM_CAP_ARM_RMI, only for VMs with KVM_VM_TYPE_ARM_REALM */
> +struct kvm_arm_rmi_populate {
> + __u64 base;
> + __u64 size;
> + __u64 source_uaddr;
> + __u32 flags;
> + __u32 reserved;
> +};
> +
> +#define KVM_ARM_RMI_POPULATE_FLAGS_MEASURE (1 << 0)
> +
> +#define KVM_ARM_RMI_POPULATE _IOW(KVMIO, 0xd7, struct kvm_arm_rmi_populate)
> +
> #endif /* __LINUX_KVM_H */
^ permalink raw reply
* Re: [PATCH kernel 0/2] crypto/ccp: Fixes for PCI IDE
From: Tom Lendacky @ 2026-01-23 16:01 UTC (permalink / raw)
To: Alexey Kardashevskiy, linux-crypto
Cc: linux-kernel, Ashish Kalra, John Allen, Herbert Xu,
David S. Miller, Dan Williams, x86, linux-coco, Pratik R . Sampat
In-Reply-To: <20260123053057.1350569-1-aik@amd.com>
On 1/22/26 23:30, Alexey Kardashevskiy wrote:
> A couple of fixes for bugs discovered recently as we got more of
> these devices and tested more configurations with multiple devices
> on same and different bridges.
>
>
> This is based on sha1
> 0499add8efd7 Paolo Bonzini Merge tag 'kvm-x86-fixes-6.19-rc1' of htts://github.com/kvm-x86/linux into HEAD
>
> Please comment. Thanks.
>
You might want to send a patch that adds you as a maintainer of the SEV
TIO files/support, too.
Acked-by: Tom Lendacky <thomas.lendacky@amd.com>
>
>
> Alexey Kardashevskiy (2):
> crypto/ccp: Use PCI bridge defaults for IDE
> crypto/ccp: Allow multiple streams on the same root bridge
>
> drivers/crypto/ccp/sev-dev-tsm.c | 15 +--------------
> 1 file changed, 1 insertion(+), 14 deletions(-)
>
^ permalink raw reply
* [PATCH v3 26/26] coco/tdx-host: Set and document TDX Module update expectations
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
In rare cases, TDX Module updates may cause TD management operations to
fail if they occur during phases of the TD lifecycle that are sensitive
to update compatibility.
But not all combinations of P-SEAMLDR, kernel, and TDX Module have the
capability to detect and prevent said incompatibilities. Completely
disabling TDX Module updates on platforms without the capability would
be overkill, as these incompatibility cases are rare and can be
addressed by userspace through coordinated scheduling of updates and TD
management operations.
To set clear expectations for TDX Module updates, expose the capability
to detect and prevent these incompatibility cases via sysfs and
document the compatibility criteria and indications when those criteria
are violated.
Signed-off-by: Chao Gao <chao.gao@intel.com>
---
v3:
- new, based on a reference patch from Dan Williams
---
.../ABI/testing/sysfs-devices-faux-tdx-host | 45 +++++++++++++++++++
drivers/virt/coco/tdx-host/tdx-host.c | 13 ++++++
2 files changed, 58 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-devices-faux-tdx-host b/Documentation/ABI/testing/sysfs-devices-faux-tdx-host
index a3f155977016..81cb13e91f2a 100644
--- a/Documentation/ABI/testing/sysfs-devices-faux-tdx-host
+++ b/Documentation/ABI/testing/sysfs-devices-faux-tdx-host
@@ -29,3 +29,48 @@ Description: (RO) Report the number of remaining updates that can be performed.
4.2 "SEAMLDR.INSTALL" for more information. The documentation is
available at:
https://cdrdv2-public.intel.com/739045/intel-tdx-seamldr-interface-specification.pdf
+
+What: /sys/devices/faux/tdx_host/firmware/seamldr_upload
+Contact: linux-coco@lists.linux.dev
+Description: (Directory) The seamldr_upload directory implements the
+ fw_upload sysfs ABI, see
+ Documentation/ABI/testing/sysfs-class-firmware for the general
+ description of the attributes @data, @cancel, @error, @loading,
+ @remaining_size, and @status. This ABI facilitates "Compatible
+ TDX Module Updates". A compatible update is one that meets the
+ following criteria:
+
+ Does not interrupt or interfere with any current TDX
+ operation or TD VM.
+
+ Does not invalidate any previously consumed Module metadata
+ values outside of the TEE_TCB_SVN_2 field (updated Security
+ Version Number) in TD Quotes.
+
+ Does not require validation of new Module metadata fields. By
+ implication, new Module features and capabilities are only
+ available by installing the Module at reboot (BIOS or EFI
+ helper loaded).
+
+ See tdx_host/compat_capable and
+ tdx_host/firmware/seamldr_upload/error. For details.
+
+What: /sys/devices/faux/tdx_host/compat_capable
+Contact: linux-coco@lists.linux.dev
+Description: (RO) When present this attribute returns "1" to indicate that
+ the current seamldr, kernel, and TDX Module combination can
+ detect when an update conforms with the "Compatible TDX Module
+ Updates" criteria in the tdx_host/firmware/seamldr_upload description.
+ When this attribute is missing it is indeterminate whether an
+ update will violate the criteria.
+
+What: /sys/devices/faux/tdx_host/firmware/seamldr_upload/error
+Contact: linux-coco@lists.linux.dev
+Description: (RO) See Documentation/ABI/testing/sysfs-class-firmware for
+ baseline expectations for this file. Updates that fail
+ compatibility checks end with the "device-busy" error in the
+ <STATUS>:<ERROR> format of this attribute. When this is
+ signalled current TDs and the current TDX Module stay running.
+ Other failures may result in all TDs being lost and further
+ TDX operations becoming impossible. This occurs when
+ /sys/devices/faux/tdx_host/version becomes unreadable.
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index 06487de2ebfe..8cc48e276533 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -45,8 +45,21 @@ static ssize_t version_show(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RO(version);
+static ssize_t compat_capable_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
+
+ if (!tdx_sysinfo)
+ return -ENXIO;
+
+ return sysfs_emit(buf, "%i\n", tdx_supports_update_compatibility(tdx_sysinfo));
+}
+static DEVICE_ATTR_RO(compat_capable);
+
static struct attribute *tdx_host_attrs[] = {
&dev_attr_version.attr,
+ &dev_attr_compat_capable.attr,
NULL,
};
--
2.47.3
^ permalink raw reply related
* [PATCH v3 25/26] x86/virt/tdx: Avoid updates during update-sensitive operations
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin,
Paolo Bonzini
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
TDX Module updates may cause TD management operations to fail if they
occur during phases of the TD lifecycle that are sensitive to update
compatibility.
Currently, there are two update-sensitive scenarios:
- TD build, where TD Measurement Register (TDMR) accumulates over multiple
TDH.MEM.PAGE.ADD, TDH.MR.EXTEND and TDH.MR.FINALIZE calls.
- TD migration, where an intermediate crypto state is saved if a state
migration function (TDH.EXPORT.STATE.* or TDH.IMPORT.STATE.*) is
interrupted and restored when the function is resumed.
For example, if an update races with TD build operations, the TD
Measurement Register will become incorrect, causing the TD to fail
attestation.
The TDX Module offers two solutions:
1. Avoid updates during update-sensitive times
The host VMM can instruct TDH.SYS.SHUTDOWN to fail if any of the TDs
are currently in any update-sensitive cases.
2. Detect incompatibility after updates
On TDH.SYS.UPDATE, the host VMM can configure the TDX Module to detect
actual incompatibility cases. The TDX Module will then return a special
error to signal the incompatibility, allowing the host VMM to restart
the update-sensitive operations.
Implement option #1 to fail updates if the feature is available. Also,
distinguish this update failure from other failures by returning -EBUSY,
which will be converted to a firmware update error code indicating that the
firmware is busy.
Options like "do nothing" or option #2 are not viable [1] because the
former allows damage to propagate to multiple, potentially unknown
components (adding significant complexity to the whole ecosystem), while
the latter may make existing KVM ioctls unstable.
Based on a reference patch by Vishal [2].
Signed-off-by: Chao Gao <chao.gao@intel.com>
Link: https://lore.kernel.org/linux-coco/aQIbM5m09G0FYTzE@google.com/ # [1]
Link: https://lore.kernel.org/linux-coco/CAGtprH_oR44Vx9Z0cfxvq5-QbyLmy_+Gn3tWm3wzHPmC1nC0eg@mail.gmail.com/ # [2]
---
arch/x86/include/asm/tdx.h | 13 +++++++++++--
arch/x86/kvm/vmx/tdx_errno.h | 2 --
arch/x86/virt/vmx/tdx/tdx.c | 23 +++++++++++++++++++----
3 files changed, 30 insertions(+), 8 deletions(-)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 0cd408f902f4..85746de7c528 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -26,15 +26,19 @@
#define TDX_SEAMCALL_GP (TDX_SW_ERROR | X86_TRAP_GP)
#define TDX_SEAMCALL_UD (TDX_SW_ERROR | X86_TRAP_UD)
+#define TDX_SEAMCALL_STATUS_MASK 0xFFFFFFFF00000000ULL
+
/*
* TDX module SEAMCALL leaf function error codes
*/
-#define TDX_SUCCESS 0ULL
-#define TDX_RND_NO_ENTROPY 0x8000020300000000ULL
+#define TDX_SUCCESS 0ULL
+#define TDX_RND_NO_ENTROPY 0x8000020300000000ULL
+#define TDX_UPDATE_COMPAT_SENSITIVE 0x8000051200000000ULL
/* Bit definitions of TDX_FEATURES0 metadata field */
#define TDX_FEATURES0_TD_PRESERVING BIT(1)
#define TDX_FEATURES0_NO_RBP_MOD BIT(18)
+#define TDX_FEATURES0_UPDATE_COMPAT BIT_ULL(47)
#ifndef __ASSEMBLER__
#include <uapi/asm/mce.h>
@@ -111,6 +115,11 @@ static inline bool tdx_supports_runtime_update(const struct tdx_sys_info *sysinf
return sysinfo->features.tdx_features0 & TDX_FEATURES0_TD_PRESERVING;
}
+static inline bool tdx_supports_update_compatibility(const struct tdx_sys_info *sysinfo)
+{
+ return sysinfo->features.tdx_features0 & TDX_FEATURES0_UPDATE_COMPAT;
+}
+
int tdx_guest_keyid_alloc(void);
u32 tdx_get_nr_guest_keyids(void);
void tdx_guest_keyid_free(unsigned int keyid);
diff --git a/arch/x86/kvm/vmx/tdx_errno.h b/arch/x86/kvm/vmx/tdx_errno.h
index 6ff4672c4181..215c00d76a94 100644
--- a/arch/x86/kvm/vmx/tdx_errno.h
+++ b/arch/x86/kvm/vmx/tdx_errno.h
@@ -4,8 +4,6 @@
#ifndef __KVM_X86_TDX_ERRNO_H
#define __KVM_X86_TDX_ERRNO_H
-#define TDX_SEAMCALL_STATUS_MASK 0xFFFFFFFF00000000ULL
-
/*
* TDX SEAMCALL Status Codes (returned in RAX)
*/
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 5d3f3f3eeb7d..5b562255630b 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1175,10 +1175,13 @@ int tdx_enable(void)
}
EXPORT_SYMBOL_FOR_KVM(tdx_enable);
+#define TDX_SYS_SHUTDOWN_AVOID_COMPAT_SENSITIVE BIT(16)
+
int tdx_module_shutdown(void)
{
struct tdx_module_args args = {};
- int ret, cpu;
+ u64 ret;
+ int cpu;
/*
* Shut down the TDX Module and prepare handoff data for the next
@@ -1189,9 +1192,21 @@ int tdx_module_shutdown(void)
* hand-off version.
*/
args.rcx = tdx_sysinfo.handoff.module_hv;
- ret = seamcall_prerr(TDH_SYS_SHUTDOWN, &args);
- if (ret)
- return ret;
+
+ if (tdx_supports_update_compatibility(&tdx_sysinfo))
+ args.rcx |= TDX_SYS_SHUTDOWN_AVOID_COMPAT_SENSITIVE;
+
+ ret = seamcall(TDH_SYS_SHUTDOWN, &args);
+
+ /*
+ * Return -EBUSY to signal that there is one or more ongoing flows
+ * which may not be compatible with an updated TDX module, so that
+ * userspace can retry on this error.
+ */
+ if ((ret & TDX_SEAMCALL_STATUS_MASK) == TDX_UPDATE_COMPAT_SENSITIVE)
+ return -EBUSY;
+ else if (ret)
+ return -EIO;
tdx_module_status = TDX_MODULE_UNINITIALIZED;
sysinit_done = false;
--
2.47.3
^ permalink raw reply related
* [PATCH v3 24/26] x86/virt/seamldr: Extend sigstruct to 16KB
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
Currently, each TDX Module has a 4KB sigstruct that is passed to the
P-SEAMLDR during module updates to authenticate the TDX Module binary.
Future TDX Module versions will pack additional information into the
sigstruct, which will exceed the current 4KB size limit.
To accommodate this, the sigstruct is being extended to support up to
16KB. Update seamldr_params and tdx-blob structures to handle the larger
sigstruct size.
Signed-off-by: Chao Gao <chao.gao@intel.com>
---
arch/x86/virt/vmx/tdx/seamldr.c | 28 +++++++++++++++++++---------
1 file changed, 19 insertions(+), 9 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index d2d85114d6c4..9e77b24f659c 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -29,6 +29,8 @@
/* P-SEAMLDR can accept up to 496 4KB pages for TDX module binary */
#define SEAMLDR_MAX_NR_MODULE_4KB_PAGES 496
+#define SEAMLDR_MAX_NR_SIG_4KB_PAGES 4
+
/* scenario field in struct seamldr_params */
#define SEAMLDR_SCENARIO_UPDATE 1
@@ -40,8 +42,8 @@
struct seamldr_params {
u32 version;
u32 scenario;
- u64 sigstruct_pa;
- u8 reserved[104];
+ u64 sigstruct_pa[SEAMLDR_MAX_NR_SIG_4KB_PAGES];
+ u8 reserved[80];
u64 num_module_pages;
u64 mod_pages_pa_list[SEAMLDR_MAX_NR_MODULE_4KB_PAGES];
} __packed;
@@ -121,7 +123,10 @@ static struct seamldr_params *alloc_seamldr_params(const void *module, unsigned
if (module_size > SEAMLDR_MAX_NR_MODULE_4KB_PAGES * SZ_4K)
return ERR_PTR(-EINVAL);
- if (!IS_ALIGNED(module_size, SZ_4K) || sig_size != SZ_4K ||
+ if (sig_size > SEAMLDR_MAX_NR_SIG_4KB_PAGES * SZ_4K)
+ return ERR_PTR(-EINVAL);
+
+ if (!IS_ALIGNED(module_size, SZ_4K) || !IS_ALIGNED(sig_size, SZ_4K) ||
!IS_ALIGNED((unsigned long)module, SZ_4K) ||
!IS_ALIGNED((unsigned long)sig, SZ_4K))
return ERR_PTR(-EINVAL);
@@ -132,12 +137,17 @@ static struct seamldr_params *alloc_seamldr_params(const void *module, unsigned
params->scenario = SEAMLDR_SCENARIO_UPDATE;
- /*
- * Don't assume @sig is page-aligned although it is 4KB-aligned.
- * Always add the in-page offset to get the physical address.
- */
- params->sigstruct_pa = (vmalloc_to_pfn(sig) << PAGE_SHIFT) +
- ((unsigned long)sig & ~PAGE_MASK);
+ ptr = sig;
+ for (i = 0; i < sig_size / SZ_4K; i++) {
+ /*
+ * Don't assume @sig is page-aligned although it is 4KB-aligned.
+ * Always add the in-page offset to get the physical address.
+ */
+ params->sigstruct_pa[i] = (vmalloc_to_pfn(ptr) << PAGE_SHIFT) +
+ ((unsigned long)ptr & ~PAGE_MASK);
+ ptr += SZ_4K;
+ }
+
params->num_module_pages = module_size / SZ_4K;
ptr = module;
--
2.47.3
^ permalink raw reply related
* [PATCH v3 23/26] x86/virt/tdx: Enable TDX Module runtime updates
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
All pieces of TDX Module runtime updates are in place. Enable it if it
is supported.
Signed-off-by: Chao Gao <chao.gao@intel.com>
---
arch/x86/include/asm/tdx.h | 5 ++++-
arch/x86/virt/vmx/tdx/tdx.h | 3 ---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index ffadbf64d0c1..0cd408f902f4 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -32,6 +32,9 @@
#define TDX_SUCCESS 0ULL
#define TDX_RND_NO_ENTROPY 0x8000020300000000ULL
+/* Bit definitions of TDX_FEATURES0 metadata field */
+#define TDX_FEATURES0_TD_PRESERVING BIT(1)
+#define TDX_FEATURES0_NO_RBP_MOD BIT(18)
#ifndef __ASSEMBLER__
#include <uapi/asm/mce.h>
@@ -105,7 +108,7 @@ const struct tdx_sys_info *tdx_get_sysinfo(void);
static inline bool tdx_supports_runtime_update(const struct tdx_sys_info *sysinfo)
{
- return false; /* To be enabled when kernel is ready */
+ return sysinfo->features.tdx_features0 & TDX_FEATURES0_TD_PRESERVING;
}
int tdx_guest_keyid_alloc(void);
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index d1807a476d3b..749f4d74cb2c 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -88,9 +88,6 @@ struct tdmr_info {
DECLARE_FLEX_ARRAY(struct tdmr_reserved_area, reserved_areas);
} __packed __aligned(TDMR_INFO_ALIGNMENT);
-/* Bit definitions of TDX_FEATURES0 metadata field */
-#define TDX_FEATURES0_NO_RBP_MOD BIT(18)
-
/*
* Do not put any hardware-defined TDX structure representations below
* this comment!
--
2.47.3
^ permalink raw reply related
* [PATCH v3 22/26] x86/virt/tdx: Update tdx_sysinfo and check features post-update
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
tdx_sysinfo contains all metadata of the active TDX module, including
versions, supported features, and TDMR/TDCS/TDVPS information. These
elements may change over updates. Blindly refreshing the entire tdx_sysinfo
could disrupt running software, as it may subtly rely on the previous state
unless proven otherwise.
Adopt a conservative approach, like microcode updates, by only refreshing
version information that does not affect functionality, while ignoring
all other changes. This is acceptable as new modules are required to
maintain backward compatibility.
Any updates to metadata beyond versions should be justified and reviewed on
a case-by-case basis.
Note that preallocating a tdx_sys_info buffer before updates is to avoid
having to handle -ENOMEM when updating tdx_sysinfo after a successful
update.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
v3:
- use 'old' instead of 'cur' as the local variable to represent the
sysinfo of the previous module [Binbin]
- combine if(ret) and WARN_ONCE(1, ...) to WARN_ONCE(ret, ...) [Binbin]
- Improve the print log messages after detecting new features from updates.
[Binbin]
v2:
- don't add a separate function for version and feature checks. Do them
directly in tdx_module_post_update()
- add a comment about preallocating a tdx_sys_info buffer in
seamldr_install_module().
---
arch/x86/virt/vmx/tdx/seamldr.c | 11 ++++++++-
arch/x86/virt/vmx/tdx/tdx.c | 43 +++++++++++++++++++++++++++++++++
arch/x86/virt/vmx/tdx/tdx.h | 3 +++
3 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index 7fa68c0c6ce4..d2d85114d6c4 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -357,6 +357,15 @@ int seamldr_install_module(const u8 *data, u32 size)
if (!is_vmalloc_addr(data))
return -EINVAL;
+ /*
+ * Preallocating a tdx_sys_info buffer before updates is to avoid having to
+ * handle -ENOMEM when updating tdx_sysinfo after a successful update.
+ */
+ struct tdx_sys_info *sysinfo __free(kfree) = kzalloc(sizeof(*sysinfo),
+ GFP_KERNEL);
+ if (!sysinfo)
+ return -ENOMEM;
+
struct seamldr_params *params __free(free_seamldr_params) =
init_seamldr_params(data, size);
if (IS_ERR(params))
@@ -374,6 +383,6 @@ int seamldr_install_module(const u8 *data, u32 size)
if (ret)
return ret;
- return 0;
+ return tdx_module_post_update(sysinfo);
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host");
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 2654aa169dda..5d3f3f3eeb7d 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1218,6 +1218,49 @@ int tdx_module_run_update(void)
return 0;
}
+/*
+ * Update tdx_sysinfo and check if any TDX module features changed after
+ * updates
+ */
+int tdx_module_post_update(struct tdx_sys_info *info)
+{
+ struct tdx_sys_info_version *old, *new;
+ int ret;
+
+ /* Shouldn't fail as the update has succeeded */
+ ret = get_tdx_sys_info(info);
+ if (WARN_ONCE(ret, "version retrieval failed after update, replace TDX Module\n"))
+ return ret;
+
+ old = &tdx_sysinfo.version;
+ new = &info->version;
+ pr_info("version %u.%u.%02u -> %u.%u.%02u\n", old->major_version,
+ old->minor_version,
+ old->update_version,
+ new->major_version,
+ new->minor_version,
+ new->update_version);
+
+ /*
+ * Blindly refreshing the entire tdx_sysinfo could disrupt running
+ * software, as it may subtly rely on the previous state unless
+ * proven otherwise.
+ *
+ * Only refresh version information (including handoff version)
+ * that does not affect functionality, and ignore all other
+ * changes.
+ */
+ tdx_sysinfo.version = info->version;
+ tdx_sysinfo.handoff = info->handoff;
+
+ if (!memcmp(&tdx_sysinfo, info, sizeof(*info)))
+ return 0;
+
+ pr_info("TDX module features have changed after updates, but might not take effect.\n");
+ pr_info("Please consider updating your BIOS to install the TDX Module.\n");
+ return 0;
+}
+
static bool is_pamt_page(unsigned long phys)
{
struct tdmr_info_list *tdmr_list = &tdx_tdmr_list;
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 0887debfd139..d1807a476d3b 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -4,6 +4,8 @@
#include <linux/bits.h>
+#include <asm/tdx_global_metadata.h>
+
/*
* This file contains both macros and data structures defined by the TDX
* architecture and Linux defined software data structures and functions.
@@ -122,5 +124,6 @@ struct tdmr_info_list {
int tdx_module_shutdown(void);
int tdx_module_run_update(void);
+int tdx_module_post_update(struct tdx_sys_info *info);
#endif
--
2.47.3
^ permalink raw reply related
* [PATCH v3 21/26] x86/virt/tdx: Establish contexts for the new TDX Module
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
After being installed, the new TDX Module shouldn't re-configure the
global HKID, TDMRs or PAMTs. Instead, to preserve running TDs, it should
import the handoff data from the old module to establish all necessary
contexts.
Once the import is done, the TDX Module update is complete, and the new
module is ready to handle requests from the VMM and guests.
Call the TDH.SYS.UPDATE SEAMCALL to import the handoff data from the old
module.
Note that the location and the format of handoff data is defined by the
TDX Module. The new module knows where to get the handoff data and how to
parse it. The kernel doesn't need to provide its location, format etc.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
v3:
- use seamcall_prerr() rather than raw seamcall() [Binbin]
- use pr_err() to print error message [Binbin]
---
arch/x86/virt/vmx/tdx/seamldr.c | 5 +++++
arch/x86/virt/vmx/tdx/tdx.c | 16 ++++++++++++++++
arch/x86/virt/vmx/tdx/tdx.h | 2 ++
3 files changed, 23 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index ee672f381dd5..7fa68c0c6ce4 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -240,6 +240,7 @@ enum tdp_state {
TDP_SHUTDOWN,
TDP_CPU_INSTALL,
TDP_CPU_INIT,
+ TDP_RUN_UPDATE,
TDP_DONE,
};
@@ -307,6 +308,10 @@ static int do_seamldr_install_module(void *seamldr_params)
case TDP_CPU_INIT:
ret = tdx_cpu_enable();
break;
+ case TDP_RUN_UPDATE:
+ if (primary)
+ ret = tdx_module_run_update();
+ break;
default:
break;
}
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 2763c1869b78..2654aa169dda 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1202,6 +1202,22 @@ int tdx_module_shutdown(void)
return 0;
}
+int tdx_module_run_update(void)
+{
+ struct tdx_module_args args = {};
+ int ret;
+
+ ret = seamcall_prerr(TDH_SYS_UPDATE, &args);
+ if (ret) {
+ pr_err("TDX-Module update failed (%d)\n", ret);
+ tdx_module_status = TDX_MODULE_ERROR;
+ return ret;
+ }
+
+ tdx_module_status = TDX_MODULE_INITIALIZED;
+ return 0;
+}
+
static bool is_pamt_page(unsigned long phys)
{
struct tdmr_info_list *tdmr_list = &tdx_tdmr_list;
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 1c4da9540ae0..0887debfd139 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -47,6 +47,7 @@
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG 45
#define TDH_SYS_SHUTDOWN 52
+#define TDH_SYS_UPDATE 53
/*
* SEAMCALL leaf:
@@ -120,5 +121,6 @@ struct tdmr_info_list {
};
int tdx_module_shutdown(void);
+int tdx_module_run_update(void);
#endif
--
2.47.3
^ permalink raw reply related
* [PATCH v3 20/26] x86/virt/seamldr: Do TDX per-CPU initialization after updates
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
After installing the new TDX module, each CPU should be initialized
again to make the CPU ready to run any other SEAMCALLs. So, call
tdx_cpu_enable() on all CPUs.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
arch/x86/virt/vmx/tdx/seamldr.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index 13c34e6378e0..ee672f381dd5 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -239,6 +239,7 @@ enum tdp_state {
TDP_START,
TDP_SHUTDOWN,
TDP_CPU_INSTALL,
+ TDP_CPU_INIT,
TDP_DONE,
};
@@ -303,6 +304,9 @@ static int do_seamldr_install_module(void *seamldr_params)
scoped_guard(raw_spinlock, &seamldr_lock)
ret = seamldr_call(P_SEAMLDR_INSTALL, &args);
break;
+ case TDP_CPU_INIT:
+ ret = tdx_cpu_enable();
+ break;
default:
break;
}
--
2.47.3
^ permalink raw reply related
* [PATCH v3 19/26] x86/virt/seamldr: Install a new TDX Module
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
After shutting down the running TDX module, the next step is to install the
new TDX Module supplied by userspace.
P-SEAMLDR provides the SEAMLDR.INSTALL SEAMCALL for that. The SEAMCALL
accepts the seamldr_params struct and should be called serially on all
CPUs.
Invoke the SEAMLDR.INSTALL SEAMCALL serially on all CPUs and add a new
spinlock to enforce serialization.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
arch/x86/virt/vmx/tdx/seamldr.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index b497fa72ebb6..13c34e6378e0 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -13,6 +13,7 @@
#include <linux/mm.h>
#include <linux/nmi.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
#include <linux/stop_machine.h>
#include <linux/types.h>
@@ -23,6 +24,7 @@
/* P-SEAMLDR SEAMCALL leaf function */
#define P_SEAMLDR_INFO 0x8000000000000000
+#define P_SEAMLDR_INSTALL 0x8000000000000001
/* P-SEAMLDR can accept up to 496 4KB pages for TDX module binary */
#define SEAMLDR_MAX_NR_MODULE_4KB_PAGES 496
@@ -45,6 +47,7 @@ struct seamldr_params {
} __packed;
static struct seamldr_info seamldr_info __aligned(256);
+static DEFINE_RAW_SPINLOCK(seamldr_lock);
static inline int seamldr_call(u64 fn, struct tdx_module_args *args)
{
@@ -235,6 +238,7 @@ static struct seamldr_params *init_seamldr_params(const u8 *data, u32 size)
enum tdp_state {
TDP_START,
TDP_SHUTDOWN,
+ TDP_CPU_INSTALL,
TDP_DONE,
};
@@ -272,9 +276,10 @@ static void print_update_failure_message(void)
* See multi_cpu_stop() from where this multi-cpu state-machine was
* adopted, and the rationale for touch_nmi_watchdog()
*/
-static int do_seamldr_install_module(void *params)
+static int do_seamldr_install_module(void *seamldr_params)
{
enum tdp_state newstate, curstate = TDP_START;
+ struct tdx_module_args args = {};
int cpu = smp_processor_id();
bool primary;
int ret = 0;
@@ -293,6 +298,11 @@ static int do_seamldr_install_module(void *params)
if (primary)
ret = tdx_module_shutdown();
break;
+ case TDP_CPU_INSTALL:
+ args.rcx = __pa(seamldr_params);
+ scoped_guard(raw_spinlock, &seamldr_lock)
+ ret = seamldr_call(P_SEAMLDR_INSTALL, &args);
+ break;
default:
break;
}
--
2.47.3
^ permalink raw reply related
* [PATCH v3 18/26] x86/virt/seamldr: Log TDX Module update failures
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
If failures occur after the TDX Module has been successfully shut down,
they are unrecoverable. The kernel cannot restore the previous TDX
Module to a running state. All subsequent SEAMCALLs to the TDX Module
will fail, so TDs cannot continue to run.
Log a message to clarify that SEAMCALL errors are expected in this case.
To prevent TDX Module update failures, admins are encouraged to use the
user space tool [1] that will perform compatibility and integrity checks
that guarantee TDX Module update success.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
Link: https://github.com/intel/tdx-module-binaries/blob/main/version_select_and_load.py # [1]
---
v3:
- Rephrase the changelog to eliminate the confusing uses of 'i.e.' and 'e.g.'
[Dave/Yilun]
---
arch/x86/virt/vmx/tdx/seamldr.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index 76f404d1115c..b497fa72ebb6 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -260,6 +260,14 @@ static void ack_state(void)
set_target_state(tdp_data.state + 1);
}
+static void print_update_failure_message(void)
+{
+ static atomic_t printed = ATOMIC_INIT(0);
+
+ if (atomic_inc_return(&printed) == 1)
+ pr_err("update failed, SEAMCALLs will report failure until TDs killed\n");
+}
+
/*
* See multi_cpu_stop() from where this multi-cpu state-machine was
* adopted, and the rationale for touch_nmi_watchdog()
@@ -289,10 +297,13 @@ static int do_seamldr_install_module(void *params)
break;
}
- if (ret)
+ if (ret) {
atomic_inc(&tdp_data.failed);
- else
+ if (curstate > TDP_SHUTDOWN)
+ print_update_failure_message();
+ } else {
ack_state();
+ }
} else {
touch_nmi_watchdog();
rcu_momentary_eqs();
--
2.47.3
^ permalink raw reply related
* [PATCH v3 17/26] x86/virt/tdx: Reset software states after TDX module shutdown
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
The TDX module requires a one-time global initialization (TDH.SYS.INIT) and
per-CPU initialization (TDH.SYS.LP.INIT) before use. These initializations
are guarded by software flags to prevent repetition.
After TDX module updates, the new TDX module requires the same global and
per-CPU initializations, but the existing software flags prevent
re-initialization.
Reset all software flags guarding the initialization flows to allow the
global and per-CPU initializations to be triggered again after updates.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 8b36a80cf229..2763c1869b78 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -54,6 +54,8 @@ static struct tdmr_info_list tdx_tdmr_list;
static enum tdx_module_status_t tdx_module_status;
static DEFINE_MUTEX(tdx_module_lock);
+static bool sysinit_done;
+static int sysinit_ret;
/* All TDX-usable memory regions. Protected by mem_hotplug_lock. */
static LIST_HEAD(tdx_memlist);
@@ -69,8 +71,6 @@ static int try_init_module_global(void)
{
struct tdx_module_args args = {};
static DEFINE_RAW_SPINLOCK(sysinit_lock);
- static bool sysinit_done;
- static int sysinit_ret;
lockdep_assert_irqs_disabled();
@@ -1178,6 +1178,7 @@ EXPORT_SYMBOL_FOR_KVM(tdx_enable);
int tdx_module_shutdown(void)
{
struct tdx_module_args args = {};
+ int ret, cpu;
/*
* Shut down the TDX Module and prepare handoff data for the next
@@ -1188,7 +1189,17 @@ int tdx_module_shutdown(void)
* hand-off version.
*/
args.rcx = tdx_sysinfo.handoff.module_hv;
- return seamcall_prerr(TDH_SYS_SHUTDOWN, &args);
+ ret = seamcall_prerr(TDH_SYS_SHUTDOWN, &args);
+ if (ret)
+ return ret;
+
+ tdx_module_status = TDX_MODULE_UNINITIALIZED;
+ sysinit_done = false;
+ sysinit_ret = 0;
+
+ for_each_online_cpu(cpu)
+ per_cpu(tdx_lp_initialized, cpu) = false;
+ return 0;
}
static bool is_pamt_page(unsigned long phys)
--
2.47.3
^ permalink raw reply related
* [PATCH v3 16/26] x86/virt/seamldr: Shut down the current TDX module
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
TDX Module updates request shutting down the existing TDX module.
During this shutdown, the module generates hand-off data, which captures
the module's states essential for preserving running TDs. The new TDX
Module can utilize this hand-off data to establish its states.
Invoke the TDH_SYS_SHUTDOWN SEAMCALL on one CPU to perform the shutdown.
This SEAMCALL requires a hand-off module version. Use the module's own
hand-off version, as it is the highest version the module can produce and
is more likely to be compatible with new modules as new modules likely have
higher hand-off version.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
v3:
- remove autogeneration stuff in the changelog
v2:
- add a comment about how handoff version is chosen.
- remove the first !ret in get_tdx_sys_info_handoff() as we edited the
auto-generated code anyway
- remove !! when determining whether a CPU is the primary one
- remove unnecessary if-break nesting in TDP_SHUTDOWN
---
arch/x86/include/asm/tdx_global_metadata.h | 5 +++++
arch/x86/virt/vmx/tdx/seamldr.c | 10 ++++++++++
arch/x86/virt/vmx/tdx/tdx.c | 16 ++++++++++++++++
arch/x86/virt/vmx/tdx/tdx.h | 3 +++
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 13 +++++++++++++
5 files changed, 47 insertions(+)
diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h
index 40689c8dc67e..8a9ebd895e70 100644
--- a/arch/x86/include/asm/tdx_global_metadata.h
+++ b/arch/x86/include/asm/tdx_global_metadata.h
@@ -40,12 +40,17 @@ struct tdx_sys_info_td_conf {
u64 cpuid_config_values[128][2];
};
+struct tdx_sys_info_handoff {
+ u16 module_hv;
+};
+
struct tdx_sys_info {
struct tdx_sys_info_version version;
struct tdx_sys_info_features features;
struct tdx_sys_info_tdmr tdmr;
struct tdx_sys_info_td_ctrl td_ctrl;
struct tdx_sys_info_td_conf td_conf;
+ struct tdx_sys_info_handoff handoff;
};
#endif
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index a13d526b38a7..76f404d1115c 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -19,6 +19,7 @@
#include <asm/seamldr.h>
#include "seamcall.h"
+#include "tdx.h"
/* P-SEAMLDR SEAMCALL leaf function */
#define P_SEAMLDR_INFO 0x8000000000000000
@@ -233,6 +234,7 @@ static struct seamldr_params *init_seamldr_params(const u8 *data, u32 size)
*/
enum tdp_state {
TDP_START,
+ TDP_SHUTDOWN,
TDP_DONE,
};
@@ -265,8 +267,12 @@ static void ack_state(void)
static int do_seamldr_install_module(void *params)
{
enum tdp_state newstate, curstate = TDP_START;
+ int cpu = smp_processor_id();
+ bool primary;
int ret = 0;
+ primary = cpumask_first(cpu_online_mask) == cpu;
+
do {
/* Chill out and ensure we re-read tdp_data. */
cpu_relax();
@@ -275,6 +281,10 @@ static int do_seamldr_install_module(void *params)
if (newstate != curstate) {
curstate = newstate;
switch (curstate) {
+ case TDP_SHUTDOWN:
+ if (primary)
+ ret = tdx_module_shutdown();
+ break;
default:
break;
}
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index a0990c5dd78d..8b36a80cf229 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1175,6 +1175,22 @@ int tdx_enable(void)
}
EXPORT_SYMBOL_FOR_KVM(tdx_enable);
+int tdx_module_shutdown(void)
+{
+ struct tdx_module_args args = {};
+
+ /*
+ * Shut down the TDX Module and prepare handoff data for the next
+ * TDX Module. This SEAMCALL requires a hand-off module version.
+ * Use the module's own hand-off version, as it is the highest
+ * version the module can produce and is more likely to be
+ * compatible with new modules as new modules likely have higher
+ * hand-off version.
+ */
+ args.rcx = tdx_sysinfo.handoff.module_hv;
+ return seamcall_prerr(TDH_SYS_SHUTDOWN, &args);
+}
+
static bool is_pamt_page(unsigned long phys)
{
struct tdmr_info_list *tdmr_list = &tdx_tdmr_list;
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 82bb82be8567..1c4da9540ae0 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -46,6 +46,7 @@
#define TDH_PHYMEM_PAGE_WBINVD 41
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG 45
+#define TDH_SYS_SHUTDOWN 52
/*
* SEAMCALL leaf:
@@ -118,4 +119,6 @@ struct tdmr_info_list {
int max_tdmrs; /* How many 'tdmr_info's are allocated */
};
+int tdx_module_shutdown(void);
+
#endif
diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
index 4c9917a9c2c3..7f4ed9af1d8d 100644
--- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -100,6 +100,18 @@ 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_handoff(struct tdx_sys_info_handoff *sysinfo_handoff)
+{
+ int ret = 0;
+ u64 val;
+
+ if (tdx_supports_runtime_update(&tdx_sysinfo) &&
+ !(ret = read_sys_metadata_field(0x8900000100000000, &val)))
+ sysinfo_handoff->module_hv = val;
+
+ return ret;
+}
+
static int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
{
int ret = 0;
@@ -115,6 +127,7 @@ static int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
ret = ret ?: get_tdx_sys_info_tdmr(&sysinfo->tdmr);
ret = ret ?: get_tdx_sys_info_td_ctrl(&sysinfo->td_ctrl);
ret = ret ?: get_tdx_sys_info_td_conf(&sysinfo->td_conf);
+ ret = ret ?: get_tdx_sys_info_handoff(&sysinfo->handoff);
return ret;
}
--
2.47.3
^ permalink raw reply related
* [PATCH v3 14/26] x86/virt/seamldr: Introduce skeleton for TDX Module updates
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
The P-SEAMLDR requires that no TDX Module SEAMCALLs are invoked during a
runtime TDX Module update.
But currently, TDX Module SEAMCALLs are invoked in various contexts and in
parallel across CPUs. Additionally, considering the need to force all vCPUs
out of guest mode, no single lock primitive, except for stop_machine(), can
meet this requirement.
Perform TDX Module updates within stop_machine() as it achieves the
P-SEAMLDR requirements and is an existing well understood mechanism.
TDX Module updates consist of several steps: shutting down the old
module, installing the new module, and initializing the new one and etc.
Some steps must be executed on a single CPU, others serially across all
CPUs, and some can be performed concurrently on all CPUs and there are
ordering requirements between steps. So, all CPUs need to perform the work
in a step-locked manner.
In preparation for adding concrete steps for TDX Module updates,
establish the framework by mimicking multi_cpu_stop(). Specifically, use a
global state machine to control the work done on each CPU and require all
CPUs to acknowledge completion before proceeding to the next stage.
Potential alternative to stop_machine()
=======================================
An alternative approach is to lock all KVM entry points and kick all
vCPUs. Here, KVM entry points refer to KVM VM/vCPU ioctl entry points,
implemented in KVM common code (virt/kvm). Adding a locking mechanism
there would affect all architectures. And to lock only TDX vCPUs, new
logic would be needed to identify TDX vCPUs, which the common code
currently lacks. This would add significant complexity and maintenance
overhead for a TDX-specific use case.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
v2:
- refine the changlog to follow context-problem-solution structure
- move alternative discussions at the end of the changelog
- add a comment about state machine transition
- Move rcu_momentary_eqs() call to the else branch.
---
arch/x86/virt/vmx/tdx/seamldr.c | 71 ++++++++++++++++++++++++++++++++-
1 file changed, 70 insertions(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index d136ef89cd36..06080c648b02 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -11,7 +11,9 @@
#include <linux/cpumask.h>
#include <linux/irqflags.h>
#include <linux/mm.h>
+#include <linux/nmi.h>
#include <linux/slab.h>
+#include <linux/stop_machine.h>
#include <linux/types.h>
#include <asm/seamldr.h>
@@ -223,6 +225,68 @@ static struct seamldr_params *init_seamldr_params(const u8 *data, u32 size)
return alloc_seamldr_params(module, module_size, sig, sig_size);
}
+/*
+ * During a TDX Module update, all CPUs start from TDP_START and progress
+ * to TDP_DONE. Each state is associated with certain work. For some
+ * states, just one CPU needs to perform the work, while other CPUs just
+ * wait during those states.
+ */
+enum tdp_state {
+ TDP_START,
+ TDP_DONE,
+};
+
+static struct {
+ enum tdp_state state;
+ atomic_t thread_ack;
+} tdp_data;
+
+static void set_target_state(enum tdp_state state)
+{
+ /* Reset ack counter. */
+ atomic_set(&tdp_data.thread_ack, num_online_cpus());
+ /* Ensure thread_ack is updated before the new state */
+ smp_wmb();
+ WRITE_ONCE(tdp_data.state, state);
+}
+
+/* Last one to ack a state moves to the next state. */
+static void ack_state(void)
+{
+ if (atomic_dec_and_test(&tdp_data.thread_ack))
+ set_target_state(tdp_data.state + 1);
+}
+
+/*
+ * See multi_cpu_stop() from where this multi-cpu state-machine was
+ * adopted, and the rationale for touch_nmi_watchdog()
+ */
+static int do_seamldr_install_module(void *params)
+{
+ enum tdp_state newstate, curstate = TDP_START;
+ int ret = 0;
+
+ do {
+ /* Chill out and ensure we re-read tdp_data. */
+ cpu_relax();
+ newstate = READ_ONCE(tdp_data.state);
+
+ if (newstate != curstate) {
+ curstate = newstate;
+ switch (curstate) {
+ default:
+ break;
+ }
+ ack_state();
+ } else {
+ touch_nmi_watchdog();
+ rcu_momentary_eqs();
+ }
+ } while (curstate != TDP_DONE);
+
+ return ret;
+}
+
DEFINE_FREE(free_seamldr_params, struct seamldr_params *,
if (!IS_ERR_OR_NULL(_T)) free_seamldr_params(_T))
@@ -237,6 +301,7 @@ DEFINE_FREE(free_seamldr_params, struct seamldr_params *,
int seamldr_install_module(const u8 *data, u32 size)
{
const struct seamldr_info *info = seamldr_get_info();
+ int ret;
if (!info)
return -EIO;
@@ -258,7 +323,11 @@ int seamldr_install_module(const u8 *data, u32 size)
return -EBUSY;
}
- /* TODO: Update TDX Module here */
+ set_target_state(TDP_START + 1);
+ ret = stop_machine_cpuslocked(do_seamldr_install_module, params, cpu_online_mask);
+ if (ret)
+ return ret;
+
return 0;
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host");
--
2.47.3
^ permalink raw reply related
* [PATCH v3 15/26] x86/virt/seamldr: Abort updates if errors occurred midway
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
The TDX Module update process has multiple stages, each of which may
encounter failures.
The current state machine of updates proceeds to the next stage
regardless of errors. But continuing updates when errors occur midway
is pointless.
If a CPU encounters an error, abort the update by setting a flag and
exiting the execution loop. Note that this CPU doesn't acknowledge the
current stage. This will keep all other CPUs in the current stage until
they see the flag and exit the loop as well.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
v3:
- Instead of fast-forward to the final stage, exit the execution loop
directly.
---
arch/x86/virt/vmx/tdx/seamldr.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index 06080c648b02..a13d526b38a7 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -239,6 +239,7 @@ enum tdp_state {
static struct {
enum tdp_state state;
atomic_t thread_ack;
+ atomic_t failed;
} tdp_data;
static void set_target_state(enum tdp_state state)
@@ -277,12 +278,16 @@ static int do_seamldr_install_module(void *params)
default:
break;
}
- ack_state();
+
+ if (ret)
+ atomic_inc(&tdp_data.failed);
+ else
+ ack_state();
} else {
touch_nmi_watchdog();
rcu_momentary_eqs();
}
- } while (curstate != TDP_DONE);
+ } while (curstate != TDP_DONE && !atomic_read(&tdp_data.failed));
return ret;
}
@@ -323,6 +328,7 @@ int seamldr_install_module(const u8 *data, u32 size)
return -EBUSY;
}
+ atomic_set(&tdp_data.failed, 0);
set_target_state(TDP_START + 1);
ret = stop_machine_cpuslocked(do_seamldr_install_module, params, cpu_online_mask);
if (ret)
--
2.47.3
^ permalink raw reply related
* [PATCH v3 13/26] x86/virt/seamldr: Allocate and populate a module update request
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
A module update request is a struct used to describe information about
the TDX module to install. It is part of the P-SEAMLDR <-> kernel ABI
and is accepted by the SEAMLDR_INSTALL SEAMCALL.
The request includes pointers to pages that contain the module binary, a
pointer to a sigstruct file, and an update scenario.
Define the request struct according to the P-SEAMLDR spec [1], and parse
the bitstream from userspace to populate that struct for later module
updates.
Note that the bitstream format is specified in [2]. It consists of a
header, a sigstruct, a module binary, and reserved fields for future
extensions. The header includes fields like a simple checksum and a
signature for error detection.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
Link: https://cdrdv2.intel.com/v1/dl/getContent/733584 # [1]
Link: https://github.com/intel/tdx-module-binaries/blob/main/blob_structure.txt # [2]
---
v3:
- Print tdx_blob version in hex [Binbin]
- Drop redundant sigstruct alignment check [Yilun]
- Note buffers passed from firmware upload infrastructure are
vmalloc()'d above alloc_seamldr_params()
---
arch/x86/virt/vmx/tdx/seamldr.c | 158 ++++++++++++++++++++++++++++++++
1 file changed, 158 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index d1d4f96c4963..d136ef89cd36 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -6,10 +6,12 @@
*/
#define pr_fmt(fmt) "seamldr: " fmt
+#include <linux/cleanup.h>
#include <linux/cpuhplock.h>
#include <linux/cpumask.h>
#include <linux/irqflags.h>
#include <linux/mm.h>
+#include <linux/slab.h>
#include <linux/types.h>
#include <asm/seamldr.h>
@@ -19,6 +21,26 @@
/* P-SEAMLDR SEAMCALL leaf function */
#define P_SEAMLDR_INFO 0x8000000000000000
+/* P-SEAMLDR can accept up to 496 4KB pages for TDX module binary */
+#define SEAMLDR_MAX_NR_MODULE_4KB_PAGES 496
+
+/* scenario field in struct seamldr_params */
+#define SEAMLDR_SCENARIO_UPDATE 1
+
+/*
+ * Passed to P-SEAMLDR to describe information about the TDX module to install.
+ * Defined in "SEAM Loader (SEAMLDR) Interface Specification", Revision
+ * 343755-003, Section 3.2.
+ */
+struct seamldr_params {
+ u32 version;
+ u32 scenario;
+ u64 sigstruct_pa;
+ u8 reserved[104];
+ u64 num_module_pages;
+ u64 mod_pages_pa_list[SEAMLDR_MAX_NR_MODULE_4KB_PAGES];
+} __packed;
+
static struct seamldr_info seamldr_info __aligned(256);
static inline int seamldr_call(u64 fn, struct tdx_module_args *args)
@@ -73,6 +95,137 @@ const struct seamldr_info *seamldr_get_info(void)
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
+static void free_seamldr_params(struct seamldr_params *params)
+{
+ free_page((unsigned long)params);
+}
+
+/*
+ * Allocate and populate a seamldr_params.
+ * Note that both @module and @sig should be vmalloc'd memory.
+ */
+static struct seamldr_params *alloc_seamldr_params(const void *module, unsigned int module_size,
+ const void *sig, unsigned int sig_size)
+{
+ struct seamldr_params *params;
+ const u8 *ptr;
+ int i;
+
+ BUILD_BUG_ON(sizeof(struct seamldr_params) != SZ_4K);
+ if (module_size > SEAMLDR_MAX_NR_MODULE_4KB_PAGES * SZ_4K)
+ return ERR_PTR(-EINVAL);
+
+ if (!IS_ALIGNED(module_size, SZ_4K) || sig_size != SZ_4K ||
+ !IS_ALIGNED((unsigned long)module, SZ_4K) ||
+ !IS_ALIGNED((unsigned long)sig, SZ_4K))
+ return ERR_PTR(-EINVAL);
+
+ params = (struct seamldr_params *)get_zeroed_page(GFP_KERNEL);
+ if (!params)
+ return ERR_PTR(-ENOMEM);
+
+ params->scenario = SEAMLDR_SCENARIO_UPDATE;
+
+ /*
+ * Don't assume @sig is page-aligned although it is 4KB-aligned.
+ * Always add the in-page offset to get the physical address.
+ */
+ params->sigstruct_pa = (vmalloc_to_pfn(sig) << PAGE_SHIFT) +
+ ((unsigned long)sig & ~PAGE_MASK);
+ params->num_module_pages = module_size / SZ_4K;
+
+ ptr = module;
+ for (i = 0; i < params->num_module_pages; i++) {
+ params->mod_pages_pa_list[i] = (vmalloc_to_pfn(ptr) << PAGE_SHIFT) +
+ ((unsigned long)ptr & ~PAGE_MASK);
+ ptr += SZ_4K;
+ }
+
+ return params;
+}
+
+/*
+ * Intel TDX Module blob. Its format is defined at:
+ * https://github.com/intel/tdx-module-binaries/blob/main/blob_structure.txt
+ */
+struct tdx_blob {
+ u16 version;
+ u16 checksum;
+ u32 offset_of_module;
+ u8 signature[8];
+ u32 len;
+ u32 resv1;
+ u64 resv2[509];
+ u8 data[];
+} __packed;
+
+/*
+ * Verify that the checksum of the entire blob is zero. The checksum is
+ * calculated by summing up all 16-bit words, with carry bits dropped.
+ */
+static bool verify_checksum(const struct tdx_blob *blob)
+{
+ u32 size = blob->len;
+ u16 checksum = 0;
+ const u16 *p;
+ int i;
+
+ /* Handle the last byte if the size is odd */
+ if (size % 2) {
+ checksum += *((const u8 *)blob + size - 1);
+ size--;
+ }
+
+ p = (const u16 *)blob;
+ for (i = 0; i < size; i += 2) {
+ checksum += *p;
+ p++;
+ }
+
+ return !checksum;
+}
+
+static struct seamldr_params *init_seamldr_params(const u8 *data, u32 size)
+{
+ const struct tdx_blob *blob = (const void *)data;
+ int module_size, sig_size;
+ const void *sig, *module;
+
+ if (blob->version != 0x100) {
+ pr_err("unsupported blob version: %x\n", blob->version);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (blob->resv1 || memchr_inv(blob->resv2, 0, sizeof(blob->resv2))) {
+ pr_err("non-zero reserved fields\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* Split the given blob into a sigstruct and a module */
+ sig = blob->data;
+ sig_size = blob->offset_of_module - sizeof(struct tdx_blob);
+ module = data + blob->offset_of_module;
+ module_size = size - blob->offset_of_module;
+
+ if (sig_size <= 0 || module_size <= 0 || blob->len != size)
+ return ERR_PTR(-EINVAL);
+
+ if (memcmp(blob->signature, "TDX-BLOB", 8)) {
+ pr_err("invalid signature\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (!verify_checksum(blob)) {
+ pr_err("invalid checksum\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ return alloc_seamldr_params(module, module_size, sig, sig_size);
+}
+
+DEFINE_FREE(free_seamldr_params, struct seamldr_params *,
+ if (!IS_ERR_OR_NULL(_T)) free_seamldr_params(_T))
+
/**
* seamldr_install_module - Install a new TDX module
* @data: Pointer to the TDX module binary data. It should be vmalloc'd
@@ -94,6 +247,11 @@ int seamldr_install_module(const u8 *data, u32 size)
if (!is_vmalloc_addr(data))
return -EINVAL;
+ struct seamldr_params *params __free(free_seamldr_params) =
+ init_seamldr_params(data, size);
+ if (IS_ERR(params))
+ return PTR_ERR(params);
+
guard(cpus_read_lock)();
if (!cpumask_equal(cpu_online_mask, cpu_present_mask)) {
pr_err("Cannot update TDX module if any CPU is offline\n");
--
2.47.3
^ permalink raw reply related
* [PATCH v3 12/26] x86/virt/seamldr: Verify availability of slots for TDX Module updates
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
The CPU keeps track of TCB versions for each TDX Module that has been
loaded. Since this tracking database has finite capacity, there's a maximum
number of module updates that can be performed. After each successful
update, the number reduces by one. Once it reaches zero, further updates
will fail until next reboot.
Before updating the TDX Module, ensure that the limit on TDX Module updates
has not been exceeded to prevent update failures in a later phase where TDs
have to be killed.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
arch/x86/virt/vmx/tdx/seamldr.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index 88388aa0fb5f..d1d4f96c4963 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -83,6 +83,14 @@ EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
*/
int seamldr_install_module(const u8 *data, u32 size)
{
+ const struct seamldr_info *info = seamldr_get_info();
+
+ if (!info)
+ return -EIO;
+
+ if (!info->num_remaining_updates)
+ return -ENOSPC;
+
if (!is_vmalloc_addr(data))
return -EINVAL;
--
2.47.3
^ permalink raw reply related
* [PATCH v3 11/26] x86/virt/seamldr: Block TDX Module updates if any CPU is offline
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
P-SEAMLDR requires every CPU to call the SEAMLDR.INSTALL SEAMCALL during
updates. So, every CPU should be online.
Check if all CPUs are online and abort the update if any CPU is offline at
the very beginning. Without this check, P-SEAMLDR will report failure at a
later phase where the old TDX module is gone and TDs have to be killed.
Signed-off-by: Chao Gao <chao.gao@intel.com>
---
arch/x86/virt/vmx/tdx/seamldr.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index af7a6621e5e0..88388aa0fb5f 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -6,6 +6,8 @@
*/
#define pr_fmt(fmt) "seamldr: " fmt
+#include <linux/cpuhplock.h>
+#include <linux/cpumask.h>
#include <linux/irqflags.h>
#include <linux/mm.h>
#include <linux/types.h>
@@ -84,6 +86,12 @@ int seamldr_install_module(const u8 *data, u32 size)
if (!is_vmalloc_addr(data))
return -EINVAL;
+ guard(cpus_read_lock)();
+ if (!cpumask_equal(cpu_online_mask, cpu_present_mask)) {
+ pr_err("Cannot update TDX module if any CPU is offline\n");
+ return -EBUSY;
+ }
+
/* TODO: Update TDX Module here */
return 0;
}
--
2.47.3
^ permalink raw reply related
* [PATCH v3 10/26] coco/tdx-host: Implement FW_UPLOAD sysfs ABI for TDX Module updates
From: Chao Gao @ 2026-01-23 14:55 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, Chao Gao,
Farrah Chen, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
H. Peter Anvin
In-Reply-To: <20260123145645.90444-1-chao.gao@intel.com>
The firmware upload framework provides a standard mechanism for firmware
updates by allowing device drivers to expose sysfs interfaces for
user-initiated updates.
Register with this framework to expose sysfs interfaces for TDX Module
updates and implement operations to process data blobs supplied by
userspace.
Note that:
1. P-SEAMLDR processes the entire update at once rather than
chunk-by-chunk, so .write() is called only once per update; so the
offset should be always 0.
2. TDX Module Updates complete synchronously within .write(), meaning
.poll_complete() is only called after successful updates and therefore
always returns success
Why fw_upload instead of request_firmware()?
============================================
The explicit file selection capabilities of fw_upload is preferred over
the implicit file selection of request_firmware() for the following
reasons:
a. Intel distributes all versions of the TDX Module, allowing admins to
load any version rather than always defaulting to the latest. This
flexibility is necessary because future extensions may require reverting to
a previous version to clear fatal errors.
b. Some module version series are platform-specific. For example, the 1.5.x
series is for certain platform generations, while the 2.0.x series is
intended for others.
c. The update policy for TDX Module updates is non-linear at times. The
latest TDX Module may not be compatible. For example, TDX Module 1.5.x
may be updated to 1.5.y but not to 1.5.y+1. This policy is documented
separately in a file released along with each TDX Module release.
So, the default policy of "request_firmware()" of "always load latest", is
not suitable for TDX. Userspace needs to deploy a more sophisticated policy
check (e.g., latest may not be compatible), and there is potential
operator choice to consider.
Just have userspace pick rather than add kernel mechanism to change the
default policy of request_firmware().
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
v3:
- clear "cancel_request" in the "prepare" phase [Binbin]
- Don't fail the whole tdx-host device if seamldr_init() met an error
[Yilun]
- Add kdoc for seamldr_install_module() and verify that the input
buffer is vmalloc'd. [Yilun]
---
arch/x86/include/asm/seamldr.h | 2 +
arch/x86/include/asm/tdx.h | 5 ++
arch/x86/virt/vmx/tdx/seamldr.c | 19 ++++
drivers/virt/coco/tdx-host/Kconfig | 2 +
drivers/virt/coco/tdx-host/tdx-host.c | 124 +++++++++++++++++++++++++-
5 files changed, 151 insertions(+), 1 deletion(-)
diff --git a/arch/x86/include/asm/seamldr.h b/arch/x86/include/asm/seamldr.h
index d1e9f6e16e8d..692bde5e9bb4 100644
--- a/arch/x86/include/asm/seamldr.h
+++ b/arch/x86/include/asm/seamldr.h
@@ -20,8 +20,10 @@ struct seamldr_info {
#ifdef CONFIG_INTEL_TDX_MODULE_UPDATE
const struct seamldr_info *seamldr_get_info(void);
+int seamldr_install_module(const u8 *data, u32 size);
#else
static inline const struct seamldr_info *seamldr_get_info(void) { return NULL; }
+static inline int seamldr_install_module(const u8 *data, u32 size) { return -EOPNOTSUPP; }
#endif
#endif
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index cb2219302dfc..ffadbf64d0c1 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -103,6 +103,11 @@ int tdx_enable(void);
const char *tdx_dump_mce_info(struct mce *m);
const struct tdx_sys_info *tdx_get_sysinfo(void);
+static inline bool tdx_supports_runtime_update(const struct tdx_sys_info *sysinfo)
+{
+ return false; /* To be enabled when kernel is ready */
+}
+
int tdx_guest_keyid_alloc(void);
u32 tdx_get_nr_guest_keyids(void);
void tdx_guest_keyid_free(unsigned int keyid);
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index 6a83ae405fac..af7a6621e5e0 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -7,6 +7,7 @@
#define pr_fmt(fmt) "seamldr: " fmt
#include <linux/irqflags.h>
+#include <linux/mm.h>
#include <linux/types.h>
#include <asm/seamldr.h>
@@ -69,3 +70,21 @@ const struct seamldr_info *seamldr_get_info(void)
return seamldr_call(P_SEAMLDR_INFO, &args) ? NULL : &seamldr_info;
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
+
+/**
+ * seamldr_install_module - Install a new TDX module
+ * @data: Pointer to the TDX module binary data. It should be vmalloc'd
+ * memory.
+ * @size: Size of the TDX module binary data
+ *
+ * Returns 0 on success, negative error code on failure.
+ */
+int seamldr_install_module(const u8 *data, u32 size)
+{
+ if (!is_vmalloc_addr(data))
+ return -EINVAL;
+
+ /* TODO: Update TDX Module here */
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host");
diff --git a/drivers/virt/coco/tdx-host/Kconfig b/drivers/virt/coco/tdx-host/Kconfig
index 6a9199e6c2c6..59aaca2252b0 100644
--- a/drivers/virt/coco/tdx-host/Kconfig
+++ b/drivers/virt/coco/tdx-host/Kconfig
@@ -12,6 +12,8 @@ config TDX_HOST_SERVICES
config INTEL_TDX_MODULE_UPDATE
bool "Intel TDX module runtime update"
depends on TDX_HOST_SERVICES
+ select FW_LOADER
+ select FW_UPLOAD
help
This enables the kernel to support TDX module runtime update. This
allows the admin to update the TDX module to another compatible
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index f4ce89522806..06487de2ebfe 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -6,6 +6,7 @@
*/
#include <linux/device/faux.h>
+#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/sysfs.h>
@@ -20,6 +21,13 @@ static const struct x86_cpu_id tdx_host_ids[] = {
};
MODULE_DEVICE_TABLE(x86cpu, tdx_host_ids);
+struct tdx_fw_upload_status {
+ bool cancel_request;
+};
+
+struct fw_upload *tdx_fwl;
+static struct tdx_fw_upload_status tdx_fw_upload_status;
+
static ssize_t version_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -100,6 +108,120 @@ static const struct attribute_group *tdx_host_groups[] = {
NULL,
};
+static enum fw_upload_err tdx_fw_prepare(struct fw_upload *fwl,
+ const u8 *data, u32 size)
+{
+ struct tdx_fw_upload_status *status = fwl->dd_handle;
+
+ status->cancel_request = false;
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err tdx_fw_write(struct fw_upload *fwl, const u8 *data,
+ u32 offset, u32 size, u32 *written)
+{
+ struct tdx_fw_upload_status *status = fwl->dd_handle;
+ int ret;
+
+ if (status->cancel_request) {
+ status->cancel_request = false;
+ return FW_UPLOAD_ERR_CANCELED;
+ }
+
+ /*
+ * tdx_fw_write() always processes all data on the first call with
+ * offset == 0. Since it never returns partial success (it either
+ * succeeds completely or fails), there is no subsequent call with
+ * non-zero offsets.
+ */
+ WARN_ON_ONCE(offset);
+ ret = seamldr_install_module(data, size);
+ switch (ret) {
+ case 0:
+ *written = size;
+ return FW_UPLOAD_ERR_NONE;
+ case -EBUSY:
+ return FW_UPLOAD_ERR_BUSY;
+ case -EIO:
+ return FW_UPLOAD_ERR_HW_ERROR;
+ case -ENOSPC:
+ return FW_UPLOAD_ERR_WEAROUT;
+ case -ENOMEM:
+ return FW_UPLOAD_ERR_RW_ERROR;
+ default:
+ return FW_UPLOAD_ERR_FW_INVALID;
+ }
+}
+
+static enum fw_upload_err tdx_fw_poll_complete(struct fw_upload *fwl)
+{
+ /*
+ * TDX Module updates are completed in the previous phase
+ * (tdx_fw_write()). If any error occurred, the previous phase
+ * would return an error code to abort the update process. In
+ * other words, reaching this point means the update succeeded.
+ */
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static void tdx_fw_cancel(struct fw_upload *fwl)
+{
+ struct tdx_fw_upload_status *status = fwl->dd_handle;
+
+ status->cancel_request = true;
+}
+
+static const struct fw_upload_ops tdx_fw_ops = {
+ .prepare = tdx_fw_prepare,
+ .write = tdx_fw_write,
+ .poll_complete = tdx_fw_poll_complete,
+ .cancel = tdx_fw_cancel,
+};
+
+static void seamldr_init(struct device *dev)
+{
+ const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
+ int ret;
+
+ if (WARN_ON_ONCE(!tdx_sysinfo))
+ return;
+
+ if (!IS_ENABLED(CONFIG_INTEL_TDX_MODULE_UPDATE))
+ return;
+
+ if (!tdx_supports_runtime_update(tdx_sysinfo))
+ pr_info("Current TDX Module cannot be updated. Consider BIOS updates\n");
+
+ tdx_fwl = firmware_upload_register(THIS_MODULE, dev, "seamldr_upload",
+ &tdx_fw_ops, &tdx_fw_upload_status);
+ ret = PTR_ERR_OR_ZERO(tdx_fwl);
+ if (ret)
+ pr_err("failed to register module uploader %d\n", ret);
+}
+
+static void seamldr_deinit(void)
+{
+ if (tdx_fwl)
+ firmware_upload_unregister(tdx_fwl);
+}
+
+static int tdx_host_probe(struct faux_device *fdev)
+{
+ seamldr_init(&fdev->dev);
+ return 0;
+}
+
+static void tdx_host_remove(struct faux_device *fdev)
+{
+ seamldr_deinit();
+}
+
+static struct faux_device_ops tdx_host_ops = {
+ .probe = tdx_host_probe,
+ .remove = tdx_host_remove,
+};
+
static struct faux_device *fdev;
static int __init tdx_host_init(void)
@@ -107,7 +229,7 @@ static int __init tdx_host_init(void)
if (!x86_match_cpu(tdx_host_ids) || !tdx_get_sysinfo())
return -ENODEV;
- fdev = faux_device_create_with_groups(KBUILD_MODNAME, NULL, NULL, tdx_host_groups);
+ fdev = faux_device_create_with_groups(KBUILD_MODNAME, NULL, &tdx_host_ops, tdx_host_groups);
if (!fdev)
return -ENODEV;
--
2.47.3
^ 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