* Re: [PATCH 2/2] x86/tdx: Accept hotplugged memory before online
From: Edgecombe, Rick P @ 2026-03-26 20:40 UTC (permalink / raw)
To: pbonzini@redhat.com
Cc: x86@kernel.org, dave.hansen@linux.intel.com,
marcandre.lureau@redhat.com, kas@kernel.org, hpa@zytor.com,
linux-kernel@vger.kernel.org, mingo@redhat.com, bp@alien8.de,
Qiang, Chenyi, tglx@kernel.org, linux-coco@lists.linux.dev,
kvm@vger.kernel.org
In-Reply-To: <CABgObfZ7_w8Q-dW=Sd4YA3P==BuN1edPv7Ty4EpPyU8ctW6RLg@mail.gmail.com>
Hi Paolo!
On Thu, 2026-03-26 at 19:25 +0100, Paolo Bonzini wrote:
> > Another option could be to perform a TDG.MEM.PAGE.RELEASE TDCALL from
> > the guest when it unplugs the memory, to put it in an unaccepted state.
> > This would be more robust to buggy VMM behavior. But working around
> > buggy VM behavior would need a high bar.
>
> Wouldn't it actually be a very low bar? Just from these two paragraphs
> of yours, it's clear that the line between buggy and malicious is
> fine, in fact I think userspace should not care at all about removing
> the memory. Only the guest cares about acceptance state.
>
> Doing a RELEASE TDCALL seems more robust and not hard.
I mean I guess the contract is a bit fuzzy. The reason why I was thinking it was
a host userspace bug is because the conventional bare metal behavior of
unplugging memory should be that it is no longer accessible, right? If the guest
could still use the unplugged memory, it could be surprising for userspace and
the guest. Also, ideally I'd think the behavior wouldn't cover up guest bugs
where it tried to keep using the memory. So forgetting about TDX, isn't it
better behavior in general for unplugging memory, to actually pull it from the
guest? Did I look at that wrong?
As for the bar to change the guest, I was first imagining it would be the size
of the accept memory plumbing. Which was not a small effort and has had a steady
stream of bugs to squash where the accept was missed.
But I didn't actually POC anything to check the scope so maybe that was a bit
hasty. Should we do a POC? But considering the scope, I wonder if SNP has the
same problem.
^ permalink raw reply
* Re: [PATCH v2 09/19] PCI/TSM: Support creating encrypted MMIO descriptors via TDISP Report
From: Alexey Kardashevskiy @ 2026-03-26 23:38 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: Xu Yilun, Aneesh Kumar K.V, Dan Williams, linux-coco, linux-pci,
gregkh, bhelgaas, alistair23, lukas, Arnd Bergmann
In-Reply-To: <20260323182014.GR7340@nvidia.com>
On 24/3/26 05:20, Jason Gunthorpe wrote:
> On Mon, Mar 16, 2026 at 04:19:30PM +1100, Alexey Kardashevskiy wrote:
>
>> and btw this only works if the entity generating the MMIO reporting
>> offset (==TSM) knows about BARs sizes, which is not the case for AMD
>> - the FW has no access to the config space (so the HV needs to feed
>> this to the FW? may be). Thanks,
>
> Then your platform just shouldn't use the mmio offset feature. Set it
> to 0 always.
pcie r7, Table 11-16 TDI Report Structure, MMIO_RANGE:
"Each MMIO Range of the TDI is reported with the MMIO reporting offset added."
My english struggles here - can the above be interpreted as "Each reported MMIO Range ..."?
as if it is each (except msix), then I know where msix is and can amend the report inside the VM if msix is not locked. Thanks,
--
Alexey
^ permalink raw reply
* Re: [PATCH 2/2] x86/tdx: Accept hotplugged memory before online
From: Chenyi Qiang @ 2026-03-27 3:05 UTC (permalink / raw)
To: Marc-André Lureau, Edgecombe, Rick P
Cc: bp@alien8.de, kas@kernel.org, hpa@zytor.com, mingo@redhat.com,
x86@kernel.org, tglx@kernel.org, dave.hansen@linux.intel.com,
kvm@vger.kernel.org, linux-coco@lists.linux.dev,
linux-kernel@vger.kernel.org, Bonzini, Paolo,
David Hildenbrand (Arm)
In-Reply-To: <CAMxuvaytU-pM+rviUbNGWMhvbAYutwvaSXW_O=sn+QfOzF35Xw@mail.gmail.com>
On 3/25/2026 6:29 PM, Marc-André Lureau wrote:
> Hi
>
> On Wed, Mar 25, 2026 at 2:04 AM Edgecombe, Rick P
> <rick.p.edgecombe@intel.com> wrote:
>>
>> On Tue, 2026-03-24 at 19:21 +0400, Marc-André Lureau wrote:
>>> In TDX guests, hotplugged memory (e.g., via virtio-mem) is never
>>> accepted before use. The first access triggers a fatal "SEPT entry in
>>> PENDING state" EPT violation and KVM terminates the guest.
>>>
>>> Fix this by registering a MEM_GOING_ONLINE memory hotplug notifier that
>>> calls tdx_accept_memory() for the range being onlined.
>>>
>>> The notifier returns NOTIFY_BAD on acceptance failure, preventing the
>>> memory from going online.
>>
>> Does this depend on patch 1 somehow?
>
> Yes, if I plug, unplug and plug again I get this without PATCH 1:
> [root@rhel10-server ~]# [ 5707.392231] virtio_mem virtio5: plugged
> size: 0x80000000
> [ 5707.395583] virtio_mem virtio5: requested size: 0x0
>
> [root@rhel10-server ~]# [ 5714.648501] virtio_mem virtio5: plugged
> size: 0x2e00000
> [ 5714.651808] virtio_mem virtio5: requested size: 0x80000000
> [ 5714.676296] tdx: Failed to accept memory [0x108000000, 0x110000000)
> [ 5714.683980] tdx: Failed to accept memory [0x110000000, 0x118000000)
> [ 5714.686997] tdx: Failed to accept memory [0x140000000, 0x148000000)
> [ 5714.689989] tdx: Failed to accept memory [0x128000000, 0x130000000)
> [ 5714.694981] tdx: Failed to accept memory [0x148000000, 0x150000000)
> [ 5714.704064] tdx: Failed to accept memory [0x138000000, 0x140000000)
> [ 5714.710144] tdx: Failed to accept memory [0x118000000, 0x120000000)
> [ 5714.722532] tdx: Failed to accept memory [0x130000000, 0x138000000)
>
> My understanding is that QEMU should eventually unplug the memory and
> PUNCH_HOLE then KVM should TDH.MEM.PAGE.REMOVE, but that doesn't seem
> to happen.
I guess it doesn't happen because virtio-mem in QEMU only PUNCH_HOLE the
shared memory by ram_block_discard_range() but it doesn't touch the private
memory which should be discarded by ram_block_discard_guest_memfd_range().
Is this strictly required? According to the specification,
> it may not be.
>
>
^ permalink raw reply
* Re: [PATCH 1/2] x86/virt/tdx: Use PFN directly for mapping guest private memory
From: Yan Zhao @ 2026-03-27 7:03 UTC (permalink / raw)
To: Edgecombe, Rick P
Cc: Hansen, Dave, kvm@vger.kernel.org, linux-coco@lists.linux.dev,
Huang, Kai, Li, Xiaoyao, dave.hansen@linux.intel.com,
kas@kernel.org, seanjc@google.com, mingo@redhat.com,
pbonzini@redhat.com, binbin.wu@linux.intel.com,
ackerleytng@google.com, linux-kernel@vger.kernel.org,
Yamahata, Isaku, sagis@google.com, Annapurve, Vishal,
bp@alien8.de, tglx@kernel.org, yilun.xu@linux.intel.com,
x86@kernel.org
In-Reply-To: <189f00877360117ab91ec3a6cb8b8239f4fff06a.camel@intel.com>
On Thu, Mar 26, 2026 at 12:57:26AM +0800, Edgecombe, Rick P wrote:
> On Wed, 2026-03-25 at 17:10 +0800, Yan Zhao wrote:
> > > I don't really understand what this is saying.
> > >
> > > Is the concern that KVM might want to set up page tables for memory
> > > that differ from how it was allocated? I'm a bit worried that this
> > > assumes something about folios that doesn't always hold.
> > >
> > > I think the hugetlbfs gigantic support uses folios in at least a
> > > few spots today.
> > Below is the background of this problem. I'll try to include a short
> > summary in the next version's patch logs.
>
> While this patchset is kind of pre-work for TDX huge pages, the reason
> to separate it out and push it earlier is because it has some value on
> it's own. So I'd think to focus mostly on the impact of the change
> today.
>
> How about this justification:
> 1. Because KVM handles guest memory as PFNs, and the SEAMCALLs under
> discussion are only used there, PFN is more natural.
>
> 2. The struct page was partly making sure we didn't pass a wrong arg
> (typical type safety) and partly ensuring that KVM doesn't pass non-
> convertible memory, however the SEAMCALLs themselves can check this for
> the kernel. So the case is already covered by warnings.
>
> In conclusion, the PFN is more natural and the original purpose of
> struct page is already covered.
>
>
> Sean said somewhere IIRC that he would have NAKed the struct page thing
> if he had seen it, for even the base support. And the two points above
> don't actually require discussion of even huge pages. So does it
> actually add any value to dive into the issues you list below?
I wanted to mention the issues listed below because I'm not sure if anyone has
the same question as me: why do we have to convert struct page to PFN if they
can both achieve the same purpose, given that currently all private memory
allocated by gmem has struct page backing?
The background can also reduce confusion if the patch log mentions hugepage and
single folio.
So, maybe also avoid mentioning hugepage and single folio things if you think
it's better to just mention the above two points?
> > In TDX huge page v3, I added logic that assumes PFNs are contained in
> > a single folio in both TDX's map/unmap paths [1][2]:
> > if (start_idx + npages > folio_nr_pages(folio))
> > return TDX_OPERAND_INVALID;
> > This not only assumes the PFNs have corresponding struct page, but
> > also assumes they must be contained in a single folio, since with
> > only base_page + npages, it's not easy to get the ith page's pointer
> > without first ensuring the pages are contained in a single folio.
> >
> > This should work since current KVM/guest_memfd only allocates memory
> > with struct page and maps them into S-EPT at a level lower than or
> > equal to the backend folio size. That is, a single S-EPT mapping
> > cannot span multiple backend folios.
> >
> > However, Ackerley's 1G hugetlb-based gmem splits the backend folio
> > [3] ahead of splitting/unmapping them from S-EPT [4], due to
> > implementation limitations mentioned at [5]. It makes the warning in
> > [1] hit upon invoking TDX's unmap callback.
> >
> > Moreover, Google's future gmem may manage PFNs independently in the
> > future,
>
> I think we can adapt to such changes when they eventually come up. It's
> not just about not merging code to be used by out-of-tree code. It also
> shrinks what we have to consider at each stage. So we can eventually
> get there.
Ok.
> > so TDX's private memory may have no corresponding struct page, and
> > KVM would map them via VM_PFNMAP, similar to mapping pass-through
> > MMIOs or other PFNs without struct page or with non-refcounted struct
> > page in normal VMs. Given that KVM has
> > suffered a lot from handling VM_PFNMAP memory for non-refcounted
> > struct page [6] in normal VMs, and TDX mapping/unmapping callbacks
> > have no semantic reason to dictate where and how KVM/guest_memfd
> > should allocate and map memory, Sean suggested dropping the
> > unnecessary assumption that memory to be mapped/unmapped to/from S-
> > EPT must be contained in a single folio (though he didn't object
> > reasonable sanity checks on if the PFNs are TDX convertible).
> >
> >
> > [1]
> > https://lore.kernel.org/kvm/20260106101929.24937-1-yan.y.zhao@intel.com
> > [2]
> > https://lore.kernel.org/kvm/20260106101826.24870-1-yan.y.zhao@intel.com
> > [3]
> > https://github.com/googleprodkernel/linux-cc/blob/wip-gmem-conversions-hugetlb-restructuring-12-08-25/virt/kvm/guest_memfd.c#L909
> > [4]
> > https://github.com/googleprodkernel/linux-cc/blob/wip-gmem-conversions-hugetlb-restructuring-12-08-25/virt/kvm/guest_memfd.c#L918
> > [5] https://lore.kernel.org/kvm/diqzqzrzdfvh.fsf@google.com/
> > [6]
> > https://lore.kernel.org/all/20241010182427.1434605-1-seanjc@google.com
>
^ permalink raw reply
* Re: [PATCH v2 16/19] samples/devsec: Introduce a "Device Security TSM" sample driver
From: Lai, Yi @ 2026-03-27 8:44 UTC (permalink / raw)
To: Dan Williams
Cc: linux-coco, linux-pci, gregkh, aik, aneesh.kumar, yilun.xu,
bhelgaas, alistair23, lukas, jgg, yi1.lai
In-Reply-To: <20260303000207.1836586-17-dan.j.williams@intel.com>
On Mon, Mar 02, 2026 at 04:02:04PM -0800, Dan Williams wrote:
> There are 2 sides to a TEE Security Manager (TSM), the 'link' TSM, and the
> 'devsec' TSM. The 'link' TSM, outside the TEE, establishes physical link
> confidentiality and integerity, and a secure session for transporting
> commands the manage the security state of devices. The 'devsec' TSM, within
> the TEE, issues requests for confidential devices to lock their
> configuration and transition to secure operation.
>
> Implement a sample implementation of a 'devsec' TSM. This leverages the PCI
> core's ability to register multiple TSMs at a time to load a sample
> devsec_tsm module alongside the existing devsec_link_tsm module. When both
> are loaded the TSM personality is selected by choosing to 'connect' vs
> 'lock' the device.
>
> Drivers like tdx_guest, sev_guest, or arm-cca-guest are examples of "Device
> Security TSM" drivers.
>
> A devsec_pci driver is included to test the device_cc_probe() helper for
> drivers that need to coordinate some configuration before 'lock' and
> 'accept'.
>
> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
> ---
> samples/devsec/Makefile | 6 ++
> samples/devsec/pci.c | 39 +++++++++++++
> samples/devsec/tsm.c | 124 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 169 insertions(+)
> create mode 100644 samples/devsec/pci.c
> create mode 100644 samples/devsec/tsm.c
>
> diff --git a/samples/devsec/Makefile b/samples/devsec/Makefile
> index da122eb8d23d..0c52448a629f 100644
> --- a/samples/devsec/Makefile
> +++ b/samples/devsec/Makefile
> @@ -8,3 +8,9 @@ devsec_bus-y := bus.o
>
> obj-$(CONFIG_SAMPLE_DEVSEC) += devsec_link_tsm.o
> devsec_link_tsm-y := link_tsm.o
> +
> +obj-$(CONFIG_SAMPLE_DEVSEC) += devsec_tsm.o
> +devsec_tsm-y := tsm.o
> +
> +obj-$(CONFIG_SAMPLE_DEVSEC) += devsec_pci.o
> +devsec_pci-y := pci.o
> diff --git a/samples/devsec/pci.c b/samples/devsec/pci.c
> new file mode 100644
> index 000000000000..50519be412ed
> --- /dev/null
> +++ b/samples/devsec/pci.c
> @@ -0,0 +1,39 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (C) 2024 - 2026 Intel Corporation */
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +
> +static int devsec_pci_probe(struct pci_dev *pdev,
> + const struct pci_device_id *id)
> +{
> + void __iomem *base;
> + int rc;
> +
> + rc = pcim_enable_device(pdev);
> + if (rc)
> + return dev_err_probe(&pdev->dev, rc, "enable failed\n");
> +
> + base = pcim_iomap_region(pdev, 0, KBUILD_MODNAME);
> + if (IS_ERR(base))
> + return dev_err_probe(&pdev->dev, PTR_ERR(base),
> + "iomap failed\n");
> +
> + dev_dbg(&pdev->dev, "attach\n");
> + return 0;
> +}
> +
> +static const struct pci_device_id devsec_pci_ids[] = {
> + { PCI_DEVICE(0x8086, 0xffff), .override_only = 1, },
> + { }
> +};
> +
> +static struct pci_driver devsec_pci_driver = {
> + .name = "devsec_pci",
> + .probe = devsec_pci_probe,
> + .id_table = devsec_pci_ids,
> +};
> +
> +module_pci_driver(devsec_pci_driver);
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Device Security Sample Infrastructure: Secure PCI Driver");
> diff --git a/samples/devsec/tsm.c b/samples/devsec/tsm.c
> new file mode 100644
> index 000000000000..46dbe668945a
> --- /dev/null
> +++ b/samples/devsec/tsm.c
> @@ -0,0 +1,124 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (C) 2024 - 2026 Intel Corporation */
> +
> +#define dev_fmt(fmt) "devsec: " fmt
> +#include <linux/device/faux.h>
> +#include <linux/pci-tsm.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/tsm.h>
> +#include "devsec.h"
> +
> +struct devsec_dev_data {
> + struct pci_tsm_devsec pci;
> +};
> +
> +static struct devsec_dev_data *to_devsec_data(struct pci_tsm *tsm)
> +{
> + return container_of(tsm, struct devsec_dev_data, pci.base_tsm);
> +}
> +
> +static struct pci_tsm *devsec_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
> +{
> + int rc;
> +
> + struct devsec_dev_data *devsec_data __free(kfree) =
> + kzalloc(sizeof(*devsec_data), GFP_KERNEL);
> + if (!devsec_data)
> + return ERR_PTR(-ENOMEM);
> +
> + rc = pci_tsm_devsec_constructor(pdev, &devsec_data->pci, tsm_dev);
> + if (rc)
> + return ERR_PTR(rc);
> +
> + return &no_free_ptr(devsec_data)->pci.base_tsm;
> +}
> +
> +static void devsec_tsm_unlock(struct pci_tsm *tsm)
> +{
> + struct devsec_dev_data *devsec_data = to_devsec_data(tsm);
> + struct pci_tsm_devsec *devsec_tsm = to_pci_tsm_devsec(tsm);
> +
> + pci_tsm_mmio_teardown(devsec_tsm->mmio);
> + kfree(devsec_tsm->mmio);
> + kfree(devsec_data);
> +}
> +
Hi Dan,
While validating devsec mode transitions, I hit a reproducible crash in the
sample devsec driver.
Reproducer:
1. lock with devsec tsm
2. unlock
Observed: NULL pointer dereference in the MMIO teardown path
Expected: unlock from LOCKED should return to UNLOCKED safely.
My understanding is that this is a sample driver implementation bug - missing
NULL guard before MMIO teardown.
A follow-up question: do you prefer current design and each device
security TSM driver is responsible for MMIO check, or should tsm/core
adds a NULL guard to avoid potential crash?
Regards,
Yi Lai
> +static int devsec_tsm_accept(struct pci_dev *pdev)
> +{
> + struct pci_tsm_devsec *devsec_tsm = to_pci_tsm_devsec(pdev->tsm);
> + int rc;
> +
> + struct pci_tsm_mmio *mmio __free(kfree) =
> + kzalloc(struct_size(mmio, mmio, PCI_NUM_RESOURCES), GFP_KERNEL);
> + if (!mmio)
> + return -ENOMEM;
> +
> + /*
> + * Typically this range request would come from the TDISP Interface
> + * Report. For this sample, just request all BARs be marked encrypted
> + */
> + for (int i = 0; i < PCI_NUM_RESOURCES; i++) {
> + struct resource *res = pci_tsm_mmio_resource(mmio, mmio->nr);
> +
> + if (pci_resource_len(pdev, i) == 0 ||
> + !(pci_resource_flags(pdev, i) & IORESOURCE_MEM))
> + continue;
> + res->start = pci_resource_start(pdev, i);
> + res->end = pci_resource_end(pdev, i);
> + mmio->nr++;
> + }
> +
> + rc = pci_tsm_mmio_setup(pdev, mmio);
> + if (rc)
> + return rc;
> + devsec_tsm->mmio = no_free_ptr(mmio);
> + return 0;
> +}
> +
> +static struct pci_tsm_ops devsec_pci_ops = {
> + .lock = devsec_tsm_lock,
> + .unlock = devsec_tsm_unlock,
> + .accept = devsec_tsm_accept,
> +};
> +
> +static void devsec_tsm_remove(void *tsm_dev)
> +{
> + tsm_unregister(tsm_dev);
> +}
> +
> +static int devsec_tsm_probe(struct faux_device *fdev)
> +{
> + struct tsm_dev *tsm_dev;
> +
> + tsm_dev = tsm_register(&fdev->dev, &devsec_pci_ops);
> + if (IS_ERR(tsm_dev))
> + return PTR_ERR(tsm_dev);
> +
> + return devm_add_action_or_reset(&fdev->dev, devsec_tsm_remove,
> + tsm_dev);
> +}
> +
> +static struct faux_device *devsec_tsm;
> +
> +static const struct faux_device_ops devsec_device_ops = {
> + .probe = devsec_tsm_probe,
> +};
> +
> +static int __init devsec_tsm_init(void)
> +{
> + devsec_tsm = faux_device_create("devsec_tsm", NULL, &devsec_device_ops);
> + if (!devsec_tsm)
> + return -ENOMEM;
> + return 0;
> +}
> +module_init(devsec_tsm_init);
> +
> +static void __exit devsec_tsm_exit(void)
> +{
> + faux_device_destroy(devsec_tsm);
> +}
> +module_exit(devsec_tsm_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Device Security Sample Infrastructure: Device Security TSM Driver");
> --
> 2.52.0
>
^ permalink raw reply
* Re: [PATCH 2/2] x86/tdx: Accept hotplugged memory before online
From: David Hildenbrand (Arm) @ 2026-03-27 8:49 UTC (permalink / raw)
To: Chenyi Qiang, Marc-André Lureau, Edgecombe, Rick P
Cc: bp@alien8.de, kas@kernel.org, hpa@zytor.com, mingo@redhat.com,
x86@kernel.org, tglx@kernel.org, dave.hansen@linux.intel.com,
kvm@vger.kernel.org, linux-coco@lists.linux.dev,
linux-kernel@vger.kernel.org, Bonzini, Paolo
In-Reply-To: <da98d671-eb7e-4e78-97b0-fedeb9d01f69@intel.com>
On 3/27/26 04:05, Chenyi Qiang wrote:
>
>
> On 3/25/2026 6:29 PM, Marc-André Lureau wrote:
>> Hi
>>
>> On Wed, Mar 25, 2026 at 2:04 AM Edgecombe, Rick P
>> <rick.p.edgecombe@intel.com> wrote:
>>>
>>>
>>> Does this depend on patch 1 somehow?
>>
>> Yes, if I plug, unplug and plug again I get this without PATCH 1:
>> [root@rhel10-server ~]# [ 5707.392231] virtio_mem virtio5: plugged
>> size: 0x80000000
>> [ 5707.395583] virtio_mem virtio5: requested size: 0x0
>>
>> [root@rhel10-server ~]# [ 5714.648501] virtio_mem virtio5: plugged
>> size: 0x2e00000
>> [ 5714.651808] virtio_mem virtio5: requested size: 0x80000000
>> [ 5714.676296] tdx: Failed to accept memory [0x108000000, 0x110000000)
>> [ 5714.683980] tdx: Failed to accept memory [0x110000000, 0x118000000)
>> [ 5714.686997] tdx: Failed to accept memory [0x140000000, 0x148000000)
>> [ 5714.689989] tdx: Failed to accept memory [0x128000000, 0x130000000)
>> [ 5714.694981] tdx: Failed to accept memory [0x148000000, 0x150000000)
>> [ 5714.704064] tdx: Failed to accept memory [0x138000000, 0x140000000)
>> [ 5714.710144] tdx: Failed to accept memory [0x118000000, 0x120000000)
>> [ 5714.722532] tdx: Failed to accept memory [0x130000000, 0x138000000)
>>
>> My understanding is that QEMU should eventually unplug the memory and
>> PUNCH_HOLE then KVM should TDH.MEM.PAGE.REMOVE, but that doesn't seem
>> to happen.
>
> I guess it doesn't happen because virtio-mem in QEMU only PUNCH_HOLE the
> shared memory by ram_block_discard_range() but it doesn't touch the private
> memory which should be discarded by ram_block_discard_guest_memfd_range().
>
> Is this strictly required? According to the specification,
So far nobody specified how virtio-mem should behave in a CoCo environment.
I assume that we need enhancements on the driver and the device side.
In Linux, we should not be accepting memory during memory
onlining/offlining through notifiers, as we might only hot(un)plug parts
of a memory block etc.
We need some explicit calls into the core before we hand hotplugged
memory to the core, and before we hand back unplugged memory to the device.
In QEMU, I would similarly assume that we might have to perform some
additional work when converting memory blocks. *maybe* that would just
be done by the guest that converts memory from private to shared before
unplug etc.
--
Cheers,
David
^ permalink raw reply
* Re: [PATCH 2/2] x86/tdx: Accept hotplugged memory before online
From: Yan Zhao @ 2026-03-27 8:28 UTC (permalink / raw)
To: Marc-André Lureau
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Kiryl Shutsemau, Rick Edgecombe, Chenyi Qiang,
linux-kernel, linux-coco, kvm
In-Reply-To: <20260324-tdx-hotplug-fixes-v1-2-8f29f2c17278@redhat.com>
On Tue, Mar 24, 2026 at 07:21:48PM +0400, Marc-André Lureau wrote:
> In TDX guests, hotplugged memory (e.g., via virtio-mem) is never
> accepted before use. The first access triggers a fatal "SEPT entry in
> PENDING state" EPT violation and KVM terminates the guest.
>
> Fix this by registering a MEM_GOING_ONLINE memory hotplug notifier that
> calls tdx_accept_memory() for the range being onlined.
>
> The notifier returns NOTIFY_BAD on acceptance failure, preventing the
> memory from going online.
>
> Assisted-by: Claude:claude-opus-4-6
> Reported-by: Chenyi Qiang <chenyi.qiang@intel.com>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> arch/x86/coco/tdx/tdx.c | 38 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 38 insertions(+)
>
> diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c
> index 7b2833705d475..89f90bc303258 100644
> --- a/arch/x86/coco/tdx/tdx.c
> +++ b/arch/x86/coco/tdx/tdx.c
> @@ -8,6 +8,7 @@
> #include <linux/export.h>
> #include <linux/io.h>
> #include <linux/kexec.h>
> +#include <linux/memory.h>
> #include <asm/coco.h>
> #include <asm/tdx.h>
> #include <asm/vmx.h>
> @@ -1194,3 +1195,40 @@ void __init tdx_early_init(void)
>
> tdx_announce();
> }
> +
> +#ifdef CONFIG_MEMORY_HOTPLUG
> +static int tdx_guest_memory_notifier(struct notifier_block *nb,
> + unsigned long action, void *v)
> +{
> + struct memory_notify *mn = v;
> + phys_addr_t start, end;
> +
> + if (action != MEM_GOING_ONLINE)
> + return NOTIFY_OK;
> +
> + start = PFN_PHYS(mn->start_pfn);
> + end = start + PFN_PHYS(mn->nr_pages);
> +
> + if (!tdx_accept_memory(start, end)) {
> + pr_err("Failed to accept memory [0x%llx, 0x%llx)\n",
> + (unsigned long long)start,
> + (unsigned long long)end);
> + return NOTIFY_BAD;
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static struct notifier_block tdx_guest_memory_nb = {
> + .notifier_call = tdx_guest_memory_notifier,
> +};
> +
> +static int __init tdx_guest_memory_init(void)
> +{
> + if (!cpu_feature_enabled(X86_FEATURE_TDX_GUEST))
> + return 0;
> +
> + return register_memory_notifier(&tdx_guest_memory_nb);
> +}
If I read the code correctly,
online_pages
1. memory_notify(MEM_GOING_ONLINE, &mem_arg);
2. online_pages_range(pfn, nr_pages);
(*online_page_callback)(page, order);
generic_online_page
__free_pages_core(page, order, MEMINIT_HOTPLUG);
In __free_pages_core(), there's accept_memory() already:
if (page_contains_unaccepted(page, order)) {
if (order == MAX_PAGE_ORDER && __free_unaccepted(page))
return;
accept_memory(page_to_phys(page), PAGE_SIZE << order);
}
__free_unaccepted() also adds the pages to the unaccepted_pages list, so
cond_accept_memory() will accept the memory later:
So, is it because the virtio mem sets online_page_callback to
virtio_mem_online_page_cb, which doesn't invoke __free_pages_core() properly?
Or am I missing something that makes the memory notifier approach necessary?
Thanks
Yan
> +core_initcall(tdx_guest_memory_init);
> +#endif
>
> --
> 2.53.0
>
>
^ permalink raw reply
* COCONUT-SVSM Development Release v2026.03-devel
From: Jörg Rödel @ 2026-03-27 9:21 UTC (permalink / raw)
To: coconut-svsm, linux-coco
Hi,
The COCONUT-SVSM development release for March is now tagged. It features 48
non-merge commits since the February release, among them changes from
first-time contributors.
The highlights of this release are:
- More improvements to the boot flow to get closer to a minimal stage2
implementation.
- Memory management improvements.
- Lots of changes to our CI workflows to improve their security, speed,
and coverage.
The shortlog is attached. Happy testing!
-Joerg
Carlos López (14):
mm/alloc: remove recursion from HeapMemoryRegion::free_page_order()
mm/alloc: remove recursion from HeapMemoryRegion::refill_page_list()
mm/alloc/tests: verify error variant in test_page_alloc_oom()
Makefile: add Miri target
Documentation: INSTALL: document --no-detdev option
Documentation: add TESTING.md
kernel: mm/pgtable: homogenize PageTable::map_4k()
kernel: mm/pgtable: homogenize PageTable::map_2m()
boot: bootimg/elf: page-align kernel ELF size
virtio-drivers: remove enumn dependency
libtcgtpm: disable bindgen unneeded features
github/workflows: do not add rust-src component
github/workflows: run builds in parallel
github/workflows: update actions to Node.js 24
Joerg Roedel (7):
github/workflows: Implement security best practices
github/workflows: Add a dependency review workflow
docs: Document GitHub workflow security requirements
workflows/publish-docs: Limit permissions even more
workflows/manual-verify: Do not use pre-built verusfmt
workflows/publish-docs: Install mkdocs from packages
COCONUT-SVSM Release 2026.03-devel
Jon Lange (16):
boot_params: remove vtom from boot parameter block
stage2: make VTOM a register parameter
boot_params: remove stage1 info from boot params
stage1: remove non-TDX logic
sev: remove SEV metadata generation
Merge pull request #989 from 00xc/mm/pgtable/map_4k
idt: allocate kernel IDT in the boot image
kernel: enable suppression of VTOM in the SVSM
Merge pull request #1003 from joergroedel/gh-workflows
bootimg: map the kernel heap with 2 MB pages
kernel: simplify memory launch parameters
svsm: dynamically expand the kernel region from the kernel
Merge pull request #1007 from luigix25/makefile_cleanup
workflows: move CI test artifacts to a separate directory
svsm: fix heap size calculation after kernel region expansion
svsm: validate memory correctly during kernel region expansion
Jörg Rödel (15):
Merge pull request #970 from msft-jlange/stage1
Merge pull request #967 from 00xc/mm/alloc
Merge pull request #993 from TanyaAgarwal25/tanya/add-nocc-doc
Merge pull request #995 from 00xc/boot/bootimg/align_heap
Merge pull request #996 from luigix25/add_missing_spdx
Merge pull request #991 from osteffenrh/igvmmeasure-error-msg
Merge pull request #999 from luigix25/remove_sudo
Merge pull request #1000 from n-ramacciotti/update_gdbstub
Merge pull request #1008 from msft-jlange/no_vtom
Merge pull request #1001 from 00xc/ci/parallel-v2
Merge pull request #1009 from nhandt64/chore/ignore-vscode
Merge pull request #1017 from msft-jlange/qemu_ci
Merge pull request #1016 from 00xc/ci/actions-node24
Merge pull request #1013 from joergroedel/gh-workflows
Merge pull request #1019 from msft-jlange/heap_info
Luigi Leonardi (4):
block: remove `VirtIOBlkDevice` abstraction
workspace: add missing SPDX headers
scripts/launch_guest: avoid sudo when using TCG acceleration
Makefile: remove broken targets
Nhan Dang (1):
gitignore: ignore .vscode folder
Nicola Ramacciotti (1):
cargo.toml: Update gdbstub to 0.7.10
Oliver Steffen (5):
igvmmeasure: Report the value of a wrong CR0 setting
igvmmeasure: Report unexpected VMSA GPA values
igvmmeasure: KVM check: allow real-mode
igvmmeasure: Split off KVM check errors
igvmmeasure: Reformat error message
Peter Fang (1):
Merge pull request #1005 from msft-jlange/dynamic_heap
Stefano Garzarella (4):
Merge pull request #979 from 00xc/docs/dev/testing
Merge pull request #980 from luigix25/blk_refactor
Merge pull request #983 from TanyaAgarwal25/main
Merge pull request #985 from TanyaAgarwal25/tanya/use-prefix
Tanya Agarwal (3):
doc/INSTALL: fix PKG_CONFIG_PATH typo in configure command
doc/INSTALL: simplify QEMU build instructions
docs: add native mode instructions to run SVSM without coco hardware
^ permalink raw reply
* Re: [PATCH v5 0/2] dma-buf: heaps: system: add an option to allocate explicitly shared/decrypted memory
From: Marek Szyprowski @ 2026-03-27 9:38 UTC (permalink / raw)
To: Jiri Pirko, dri-devel, linaro-mm-sig, iommu, linux-media
Cc: sumit.semwal, benjamin.gaignard, Brian.Starkey, jstultz,
tjmercier, christian.koenig, robin.murphy, jgg, leon,
sean.anderson, ptesarik, catalin.marinas, aneesh.kumar,
suzuki.poulose, steven.price, thomas.lendacky, john.allen,
ashish.kalra, suravee.suthikulpanit, linux-coco
In-Reply-To: <20260325192352.437608-1-jiri@resnulli.us>
On 25.03.2026 20:23, Jiri Pirko wrote:
> From: Jiri Pirko <jiri@nvidia.com>
>
> Confidential computing (CoCo) VMs/guests, such as AMD SEV and Intel TDX,
> run with private/encrypted memory which creates a challenge
> for devices that do not support DMA to it (no TDISP support).
>
> For kernel-only DMA operations, swiotlb bounce buffering provides a
> transparent solution by copying data through shared memory.
> However, the only way to get this memory into userspace is via the DMA
> API's dma_alloc_pages()/dma_mmap_pages() type interfaces which limits
> the use of the memory to a single DMA device, and is incompatible with
> pin_user_pages().
>
> These limitations are particularly problematic for the RDMA subsystem
> which makes heavy use of pin_user_pages() and expects flexible memory
> usage between many different DMA devices.
>
> This patch series enables userspace to explicitly request shared
> (decrypted) memory allocations from new dma-buf system_cc_shared heap.
> Userspace can mmap this memory and pass the dma-buf fd to other
> existing importers such as RDMA or DRM devices to access the
> memory. The DMA API is improved to allow the dma heap exporter to DMA
> map the shared memory to each importing device.
>
> Based on dma-mapping-for-next e7442a68cd1ee797b585f045d348781e9c0dde0d
I would like to merge this to dma-mapping-next, but I feel a bit
uncomfortable with my lack of knowledge about CoCo and friends. Could
those who know a bit more about it provide some Reviewed-by tags?
Best regards
--
Marek Szyprowski, PhD
Samsung R&D Institute Poland
^ permalink raw reply
* Re: [PATCH v2 09/19] PCI/TSM: Support creating encrypted MMIO descriptors via TDISP Report
From: Jason Gunthorpe @ 2026-03-27 11:49 UTC (permalink / raw)
To: Alexey Kardashevskiy
Cc: Xu Yilun, Aneesh Kumar K.V, Dan Williams, linux-coco, linux-pci,
gregkh, bhelgaas, alistair23, lukas, Arnd Bergmann
In-Reply-To: <fa1c6e1e-068c-46d7-a623-58c225bb03f4@amd.com>
On Fri, Mar 27, 2026 at 10:38:15AM +1100, Alexey Kardashevskiy wrote:
>
>
> On 24/3/26 05:20, Jason Gunthorpe wrote:
> > On Mon, Mar 16, 2026 at 04:19:30PM +1100, Alexey Kardashevskiy wrote:
> >
> > > and btw this only works if the entity generating the MMIO reporting
> > > offset (==TSM) knows about BARs sizes, which is not the case for AMD
> > > - the FW has no access to the config space (so the HV needs to feed
> > > this to the FW? may be). Thanks,
> >
> > Then your platform just shouldn't use the mmio offset feature. Set it
> > to 0 always.
>
> pcie r7, Table 11-16 TDI Report Structure, MMIO_RANGE:
>
> "Each MMIO Range of the TDI is reported with the MMIO reporting offset added."
>
> My english struggles here - can the above be interpreted as "Each reported MMIO Range ..."?
>
> as if it is each (except msix), then I know where msix is and can
> amend the report inside the VM if msix is not locked. Thanks,
To do this you must be convert between the offset'd and phys_addr_t
versions otherwise you have no idea where the translated ones fall
within the BAR, so you can't figure out if msix is covered or not.
Jason
^ permalink raw reply
* Re: [PATCH v5 0/2] dma-buf: heaps: system: add an option to allocate explicitly shared/decrypted memory
From: Jason Gunthorpe @ 2026-03-27 12:10 UTC (permalink / raw)
To: Marek Szyprowski, T.J. Mercier
Cc: Jiri Pirko, dri-devel, linaro-mm-sig, iommu, linux-media,
sumit.semwal, benjamin.gaignard, Brian.Starkey, jstultz,
tjmercier, christian.koenig, robin.murphy, leon, sean.anderson,
ptesarik, catalin.marinas, aneesh.kumar, suzuki.poulose,
steven.price, thomas.lendacky, john.allen, ashish.kalra,
suravee.suthikulpanit, linux-coco
In-Reply-To: <f2047cd7-91a8-4f6a-b6b9-0e4f143f6854@samsung.com>
On Fri, Mar 27, 2026 at 10:38:10AM +0100, Marek Szyprowski wrote:
> On 25.03.2026 20:23, Jiri Pirko wrote:
> > From: Jiri Pirko <jiri@nvidia.com>
> >
> > Confidential computing (CoCo) VMs/guests, such as AMD SEV and Intel TDX,
> > run with private/encrypted memory which creates a challenge
> > for devices that do not support DMA to it (no TDISP support).
> >
> > For kernel-only DMA operations, swiotlb bounce buffering provides a
> > transparent solution by copying data through shared memory.
> > However, the only way to get this memory into userspace is via the DMA
> > API's dma_alloc_pages()/dma_mmap_pages() type interfaces which limits
> > the use of the memory to a single DMA device, and is incompatible with
> > pin_user_pages().
> >
> > These limitations are particularly problematic for the RDMA subsystem
> > which makes heavy use of pin_user_pages() and expects flexible memory
> > usage between many different DMA devices.
> >
> > This patch series enables userspace to explicitly request shared
> > (decrypted) memory allocations from new dma-buf system_cc_shared heap.
> > Userspace can mmap this memory and pass the dma-buf fd to other
> > existing importers such as RDMA or DRM devices to access the
> > memory. The DMA API is improved to allow the dma heap exporter to DMA
> > map the shared memory to each importing device.
> >
> > Based on dma-mapping-for-next e7442a68cd1ee797b585f045d348781e9c0dde0d
>
> I would like to merge this to dma-mapping-next, but I feel a bit
> uncomfortable with my lack of knowledge about CoCo and friends. Could
> those who know a bit more about it provide some Reviewed-by tags?
I'm confident in the CC stuff, I was hoping to see someone from dmabuf
heap land ack that the uAPI design is OK.. TJ?
Jason
^ permalink raw reply
* Re: SVSM Development Call March 25, 2026
From: Jörg Rödel @ 2026-03-27 12:30 UTC (permalink / raw)
To: coconut-svsm, linux-coco
In-Reply-To: <iuafzrd25l3whbifc3m7tcxf2bq3hqe4bymuwrqhtpmcujipfc@2qxd6c547gz2>
Meeting minutes are ready for review:
https://github.com/coconut-svsm/governance/pull/101
-Joerg
^ permalink raw reply
* [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
This series is based on mainline v7.0-rc2 and targets v7.1 (quite
aggressive though). The merge path will be through tsm.git with tip.git
acks where needed. I know there are several parallel series on the fly,
so Dave you can wait for Dan to review, or ack/nak as you have time,
thanks. No KVM change, no acks from kvm.git is needed.
== Overview ==
This series adds a PCI/TSM low-level driver implementation for TDX
Connect (the TEE I/O architecture for Intel platforms). PCI/TSM is the
Linux PCI core subsystem [1][2] that supports Link Encryption & trust
establishment between CoCo-VM and assigned devices, allowing CoCo-VM to
accept devices for private memory access (private DMA). This series
only implements Link Encryption. It is a pre-requisite for trusted
device assignment in TDX system.
Two protocols, SPDM (Security Protocol and Data Model) and PCI
IDE (Integrity and Data Encryption) work together to establish the Link
Encryption. SPDM creates trust on untrusted transit for key exchanging.
IDE performs the actual real-time encryption for data traffic. In TSM
world, they are managed by secure firmwares, e.g. TDX Module.
To manage these protocols, TDX Module introduces Extensions to support
long running / hard-irq preemptible flows inside. Host invokes these
flows via Extension-SEAMCALLs.
This series has 2 distinct parts:
Patches 1-13: TDX core cleanups and TDX Module Extensions enabling
Patches 14-31: tdx_host TSM driver for PCIe Link Encryption
[1]: https://lore.kernel.org/linux-coco/20251031212902.2256310-1-dan.j.williams@intel.com/
[2]: https://lore.kernel.org/linux-coco/20251105040055.2832866-1-dan.j.williams@intel.com/
== Merge notes ==
- Merge conflicts with parallel series:
Sean's VMXON: https://lore.kernel.org/all/20260214012702.2368778-1-seanjc@google.com/
Chao's runtime update: https://lore.kernel.org/all/20260326084448.29947-1-chao.gao@intel.com/
- Picked several patches from parallel series:
Patch 1: https://lore.kernel.org/all/20260323-fuller_tdx_kexec_support-v2-1-87a36409e051@intel.com/
Patch 14: https://lore.kernel.org/all/20260303000207.1836586-2-dan.j.williams@intel.com/
Patch 15: https://lore.kernel.org/all/20260326084448.29947-3-chao.gao@intel.com/
== Changelog ==
v2:
- Subject change. previously it was:
"PCI/TSM: TDX Connect: SPDM Session and IDE Establishment"
- Remove __free() for core TDX and refactor all tdx_ext functions
- Use kzalloc(PAGE_SIZE, ...) instead of alloc_page() in TDX core
- Check feature0 support before reading optional global metadata
- Split the TDX Module Extensions enabling into small patches
- Enable TDX Module Extensions along with Basic TDX enabling
- Refactor SEAMCALL version handling
- For tdx_page_array, make page allocation method configurable
- For TDX Module Extensions, use contiguous page allocation
- For IOMMU_MT, use a custom page allocation
- Print TDX Extensions memory usage
- Various Changelog & comments refine
v1: https://lore.kernel.org/all/20251117022311.2443900-1-yilun.xu@linux.intel.com/
- No tdx_enable() needed in tdx-host
- Simplify tdx_page_array kAPI, no singleton mode input
- Refactor the handling of TDX_INTERRUPTED_RESUMABLE
- Refine the usage of scope-based cleanup in tdx-host
- Set nr_stream_id in tdx-host, not in PCI ACPI initialization
- Use KEYP table + ECAP bit50 to decide Domain ID reservation
- Refactor IDE Address Association Register setup
- Remove prototype patches
- Refactor tdx_enable_ext() locking because of Sean's change
- Pick ACPICA KEYP patch from ACPICA repo
- Select TDX Connect feature for TDH.SYS.CONFIG, remove temporary
solution for TDH.SYS.INIT
- Use Rick's tdx_errno.h movement patch [6]
- Factor out scope-based cleanup patches in mm
- Remove redunant header files, add header files only when first used
- Use dev_err_probe() when possible
- keyp_info_match() refactor
- Use bitfield.h macros for PAGE_LIST_INFO & HPA_ARRAY_T raw value
- Remove reserved fields for spdm_config_info_t
- Simplify return for tdh_ide_stream_block()
- Other small fixes for Jonathan's comments
RFC: https://lore.kernel.org/linux-coco/20250919142237.418648-1-dan.j.williams@intel.com/
Chao Gao (1):
coco/tdx-host: Introduce a "tdx_host" device
Dan Williams (1):
PCI/TSM: Report active IDE streams per host bridge
Dave Jiang (1):
acpi: Add KEYP support to fw_table parsing
Kiryl Shutsemau (1):
x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h>
Lu Baolu (2):
iommu/vt-d: Cache max domain ID to avoid redundant calculation
iommu/vt-d: Reserve the MSB domain ID bit for the TDX module
Xu Yilun (21):
x86/virt/tdx: Move bit definitions of TDX_FEATURES0 to public header
x86/virt/tdx: Add tdx_page_array helpers for new TDX Module objects
x86/virt/tdx: Support allocating contiguous pages for tdx_page_array
x86/virt/tdx: Extend tdx_page_array to support IOMMU_MT
x86/virt/tdx: Read global metadata for TDX Module Extensions/Connect
x86/virt/tdx: Embed version info in SEAMCALL leaf function definitions
x86/virt/tdx: Configure TDX Module with optional TDX Connect feature
x86/virt/tdx: Move tdx_clflush_page() up in the file
x86/virt/tdx: Add extra memory to TDX Module for Extensions
x86/virt/tdx: Make TDX Module initialize Extensions
x86/virt/tdx: Enable the Extensions after basic TDX Module init
x86/virt/tdx: Extend tdx_clflush_page() to handle compound pages
coco/tdx-host: Support Link TSM for TDX host
x86/virt/tdx: Add a helper to loop on TDX_INTERRUPTED_RESUMABLE
iommu/vt-d: Export a helper to do function for each dmar_drhd_unit
coco/tdx-host: Setup all trusted IOMMUs on TDX Connect init
mm: Add __free() support for __free_page()
coco/tdx-host: Parse ACPI KEYP table to init IDE for PCI host bridges
x86/virt/tdx: Add SEAMCALL wrappers for IDE stream management
coco/tdx-host: Implement IDE stream setup/teardown
coco/tdx-host: Finally enable SPDM session and IDE Establishment
Zhenzhong Duan (4):
x86/virt/tdx: Add SEAMCALL wrappers for trusted IOMMU setup and clear
coco/tdx-host: Add a helper to exchange SPDM messages through DOE
x86/virt/tdx: Add SEAMCALL wrappers for SPDM management
coco/tdx-host: Implement SPDM session setup
drivers/virt/coco/Kconfig | 2 +
drivers/virt/coco/tdx-host/Kconfig | 16 +
drivers/virt/coco/Makefile | 1 +
drivers/virt/coco/tdx-host/Makefile | 1 +
Documentation/ABI/testing/sysfs-class-tsm | 13 +
arch/x86/include/asm/shared/tdx.h | 1 +
.../vmx => include/asm/shared}/tdx_errno.h | 30 +-
arch/x86/include/asm/tdx.h | 95 +-
arch/x86/include/asm/tdx_global_metadata.h | 14 +
arch/x86/kvm/vmx/tdx.h | 1 -
arch/x86/virt/vmx/tdx/tdx.h | 42 +-
drivers/iommu/intel/iommu.h | 2 +
include/linux/acpi.h | 3 +
include/linux/dmar.h | 2 +
include/linux/fw_table.h | 1 +
include/linux/gfp.h | 1 +
include/linux/pci-ide.h | 4 +
include/linux/tsm.h | 3 +
arch/x86/virt/vmx/tdx/tdx.c | 839 ++++++++++++++-
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 36 +
drivers/acpi/tables.c | 12 +-
drivers/iommu/intel/dmar.c | 67 ++
drivers/iommu/intel/iommu.c | 10 +-
drivers/pci/ide.c | 9 +-
drivers/virt/coco/tdx-host/tdx-host.c | 952 ++++++++++++++++++
drivers/virt/coco/tsm-core.c | 97 ++
lib/fw_table.c | 9 +
27 files changed, 2202 insertions(+), 61 deletions(-)
create mode 100644 drivers/virt/coco/tdx-host/Kconfig
create mode 100644 drivers/virt/coco/tdx-host/Makefile
rename arch/x86/{kvm/vmx => include/asm/shared}/tdx_errno.h (61%)
create mode 100644 drivers/virt/coco/tdx-host/tdx-host.c
base-commit: 11439c4635edd669ae435eec308f4ab8a0804808
--
2.25.1
^ permalink raw reply
* [PATCH v2 01/31] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h>
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
From: "Kirill A. Shutemov" <kirill.shutemov@linux.intel.com>
Today there are two separate locations where TDX error codes are defined:
arch/x86/include/asm/tdx.h
arch/x86/kvm/vmx/tdx_errno.h
They have some overlap that is already defined similarly. Reduce the
duplication and prepare to introduce some helpers for these error codes in
the central place by unifying them. Join them at:
asm/shared/tdx_errno.h
...and update the headers that contained the duplicated definitions to
include the new unified header.
"asm/shared" is used for sharing TDX code between the early compressed
code and the normal kernel code. While the compressed code for the guest
doesn't use these error code header definitions today, it does make the
types of calls that return the values they define. So place the defines in
"shared" location so that it can, but leave such cleanups for future
changes.
Also, adjust BITUL() -> _BITULL() to address 32 bit build errors after the
move.
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
[enhance log]
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
Reviewed-by: Chao Gao <chao.gao@intel.com>
---
arch/x86/include/asm/shared/tdx.h | 1 +
.../vmx => include/asm/shared}/tdx_errno.h | 28 +++++++++++++++----
arch/x86/include/asm/tdx.h | 21 --------------
arch/x86/kvm/vmx/tdx.h | 1 -
4 files changed, 23 insertions(+), 28 deletions(-)
rename arch/x86/{kvm/vmx => include/asm/shared}/tdx_errno.h (64%)
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index 8bc074c8d7c6..6a1646fc2b2f 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -4,6 +4,7 @@
#include <linux/bits.h>
#include <linux/types.h>
+#include <asm/shared/tdx_errno.h>
#define TDX_HYPERCALL_STANDARD 0
diff --git a/arch/x86/kvm/vmx/tdx_errno.h b/arch/x86/include/asm/shared/tdx_errno.h
similarity index 64%
rename from arch/x86/kvm/vmx/tdx_errno.h
rename to arch/x86/include/asm/shared/tdx_errno.h
index 6ff4672c4181..8bf6765cf082 100644
--- a/arch/x86/kvm/vmx/tdx_errno.h
+++ b/arch/x86/include/asm/shared/tdx_errno.h
@@ -1,14 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
-/* architectural status code for SEAMCALL */
-
-#ifndef __KVM_X86_TDX_ERRNO_H
-#define __KVM_X86_TDX_ERRNO_H
+#ifndef _ASM_X86_SHARED_TDX_ERRNO_H
+#define _ASM_X86_SHARED_TDX_ERRNO_H
+#include <asm/trapnr.h>
+/* Upper 32 bit of the TDX error code encodes the status */
#define TDX_SEAMCALL_STATUS_MASK 0xFFFFFFFF00000000ULL
/*
- * TDX SEAMCALL Status Codes (returned in RAX)
+ * TDX Status Codes (returned in RAX)
*/
+#define TDX_SUCCESS 0ULL
#define TDX_NON_RECOVERABLE_VCPU 0x4000000100000000ULL
#define TDX_NON_RECOVERABLE_TD 0x4000000200000000ULL
#define TDX_NON_RECOVERABLE_TD_NON_ACCESSIBLE 0x6000000500000000ULL
@@ -17,6 +18,7 @@
#define TDX_OPERAND_INVALID 0xC000010000000000ULL
#define TDX_OPERAND_BUSY 0x8000020000000000ULL
#define TDX_PREVIOUS_TLB_EPOCH_BUSY 0x8000020100000000ULL
+#define TDX_RND_NO_ENTROPY 0x8000020300000000ULL
#define TDX_PAGE_METADATA_INCORRECT 0xC000030000000000ULL
#define TDX_VCPU_NOT_ASSOCIATED 0x8000070200000000ULL
#define TDX_KEY_GENERATION_FAILED 0x8000080000000000ULL
@@ -28,6 +30,20 @@
#define TDX_EPT_ENTRY_STATE_INCORRECT 0xC0000B0D00000000ULL
#define TDX_METADATA_FIELD_NOT_READABLE 0xC0000C0200000000ULL
+/*
+ * SW-defined error codes.
+ *
+ * Bits 47:40 == 0xFF indicate Reserved status code class that never used by
+ * TDX module.
+ */
+#define TDX_ERROR _BITULL(63)
+#define TDX_NON_RECOVERABLE _BITULL(62)
+#define TDX_SW_ERROR (TDX_ERROR | GENMASK_ULL(47, 40))
+#define TDX_SEAMCALL_VMFAILINVALID (TDX_SW_ERROR | _ULL(0xFFFF0000))
+
+#define TDX_SEAMCALL_GP (TDX_SW_ERROR | X86_TRAP_GP)
+#define TDX_SEAMCALL_UD (TDX_SW_ERROR | X86_TRAP_UD)
+
/*
* TDX module operand ID, appears in 31:0 part of error code as
* detail information
@@ -37,4 +53,4 @@
#define TDX_OPERAND_ID_SEPT 0x92
#define TDX_OPERAND_ID_TD_EPOCH 0xa9
-#endif /* __KVM_X86_TDX_ERRNO_H */
+#endif /* _ASM_X86_SHARED_TDX_ERRNO_H */
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 6b338d7f01b7..e040e0467ae4 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -9,29 +9,8 @@
#include <asm/errno.h>
#include <asm/ptrace.h>
-#include <asm/trapnr.h>
#include <asm/shared/tdx.h>
-/*
- * SW-defined error codes.
- *
- * Bits 47:40 == 0xFF indicate Reserved status code class that never used by
- * TDX module.
- */
-#define TDX_ERROR _BITUL(63)
-#define TDX_NON_RECOVERABLE _BITUL(62)
-#define TDX_SW_ERROR (TDX_ERROR | GENMASK_ULL(47, 40))
-#define TDX_SEAMCALL_VMFAILINVALID (TDX_SW_ERROR | _UL(0xFFFF0000))
-
-#define TDX_SEAMCALL_GP (TDX_SW_ERROR | X86_TRAP_GP)
-#define TDX_SEAMCALL_UD (TDX_SW_ERROR | X86_TRAP_UD)
-
-/*
- * TDX module SEAMCALL leaf function error codes
- */
-#define TDX_SUCCESS 0ULL
-#define TDX_RND_NO_ENTROPY 0x8000020300000000ULL
-
#ifndef __ASSEMBLER__
#include <uapi/asm/mce.h>
diff --git a/arch/x86/kvm/vmx/tdx.h b/arch/x86/kvm/vmx/tdx.h
index 45b5183ccb36..ce2720a028ad 100644
--- a/arch/x86/kvm/vmx/tdx.h
+++ b/arch/x86/kvm/vmx/tdx.h
@@ -3,7 +3,6 @@
#define __KVM_X86_VMX_TDX_H
#include "tdx_arch.h"
-#include "tdx_errno.h"
#ifdef CONFIG_KVM_INTEL_TDX
#include "common.h"
--
2.25.1
^ permalink raw reply related
* [PATCH v2 02/31] x86/virt/tdx: Move bit definitions of TDX_FEATURES0 to public header
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
Move bit definitions of TDX_FEATURES0 to TDX core public header.
Kernel users get TDX_FEATURES0 bitmap via tdx_get_sysinfo(). It is
reasonable to also public the definitions of each bit. TDX Connect (a
new TDX feature to enable Trusted I/O virtualization) will add new bits
and check them in separate kernel modules.
Take the opportunity to change its type to BIT_ULL since TDX_FEATURES0
is explicitly defined as 64-bit in both TDX Module Specification and
TDX core code.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 4 ++++
arch/x86/virt/vmx/tdx/tdx.h | 3 ---
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index e040e0467ae4..65c4da396450 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -127,6 +127,10 @@ static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
int tdx_cpu_enable(void);
int tdx_enable(void);
const char *tdx_dump_mce_info(struct mce *m);
+
+/* Bit definitions of TDX_FEATURES0 metadata field */
+#define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18)
+
const struct tdx_sys_info *tdx_get_sysinfo(void);
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 82bb82be8567..c641b4632826 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -84,9 +84,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.25.1
^ permalink raw reply related
* [PATCH v2 03/31] x86/virt/tdx: Add tdx_page_array helpers for new TDX Module objects
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
Add struct tdx_page_array definition for new TDX Module object
types - HPA_ARRAY_T and HPA_LIST_INFO. They are used as input/output
parameters in newly defined SEAMCALLs. Also define some helpers to
allocate, setup and free tdx_page_array.
HPA_ARRAY_T and HPA_LIST_INFO are similar in most aspects. They both
represent a list of pages for TDX Module accessing. There are several
use cases for these 2 structures:
- As SEAMCALL inputs. They are claimed by TDX Module as control pages.
Control pages are private pages for TDX Module to hold its internal
control structures or private data. TDR, TDCS, TDVPR... are existing
control pages, just not added via tdx_page_array.
- As SEAMCALL outputs. They were TDX Module control pages and now are
released.
- As SEAMCALL inputs. They are just temporary buffers for exchanging
data blobs in one SEAMCALL. TDX Module will not hold them for long
time.
The 2 structures both need a 'root page' which contains a list of HPAs.
They collapse the HPA of the root page and the number of valid HPAs
into a 64 bit raw value for SEAMCALL parameters. The root page is
always a medium for passing data pages, TDX Module never keeps the
root page.
A main difference is HPA_ARRAY_T requires singleton mode when
containing just 1 functional page (page0). In this mode the root page is
not needed and the HPA field of the raw value directly points to the
page0. But in this patch, root page is always allocated for user
friendly kAPIs.
Another small difference is HPA_LIST_INFO contains a 'first entry' field
which could be filled by TDX Module. This simplifies host by providing
the same structure when re-invoke the interrupted SEAMCALL. No need for
host to touch this field.
Typical usages of the tdx_page_array:
1. Add control pages:
- struct tdx_page_array *array = tdx_page_array_create(nr_pages);
- seamcall(TDH_XXX_CREATE, array, ...);
2. Release control pages:
- seamcall(TDX_XXX_DELETE, array, &nr_released, &released_hpa);
- tdx_page_array_ctrl_release(array, nr_released, released_hpa);
3. Exchange data blobs:
- struct tdx_page_array *array = tdx_page_array_create(nr_pages);
- seamcall(TDX_XXX, array, ...);
- Read data from array.
- tdx_page_array_free(array);
4. Note the root page contains 512 HPAs at most, if more pages are
required, re-populate the tdx_page_array is needed.
- struct tdx_page_array *array = tdx_page_array_alloc(nr_pages);
- for each 512-page bulk
- tdx_page_array_populate(array, offset);
- seamcall(TDH_XXX_ADD, array, ...);
In case 2, SEAMCALLs output the released page array in the form of
HPA_ARRAY_T or PAGE_LIST_INFO. Use tdx_page_array_ctrl_release() to
check if the output pages match the original input pages. If failed,
TDX Module is buggy. In this case the safer way is to leak the
control pages, call tdx_page_array_ctrl_leak().
The usage of tdx_page_array will be in following patches.
Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 37 +++++
arch/x86/virt/vmx/tdx/tdx.c | 299 ++++++++++++++++++++++++++++++++++++
2 files changed, 336 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 65c4da396450..9173a432b312 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -139,6 +139,43 @@ void tdx_guest_keyid_free(unsigned int keyid);
void tdx_quirk_reset_page(struct page *page);
+/**
+ * struct tdx_page_array - Represents a list of pages for TDX Module access
+ * @nr_pages: Total number of data pages in the collection
+ * @pages: Array of data page pointers containing all the data
+ *
+ * @offset: Internal: The starting index in @pages, positions the currently
+ * populated page window in @root.
+ * @nents: Internal: Number of valid HPAs for the page window in @root
+ * @root: Internal: A single 4KB page holding the 8-byte HPAs of the page
+ * window. The page window max size is constrained by the root page,
+ * which is 512 HPAs.
+ *
+ * This structure abstracts several TDX Module defined object types, e.g.,
+ * HPA_ARRAY_T and HPA_LIST_INFO. Typically they all use a "root page" as the
+ * medium to exchange a list of data pages between host and TDX Module. This
+ * structure serves as a unified parameter type for SEAMCALL wrappers, where
+ * these hardware object types are needed.
+ */
+struct tdx_page_array {
+ /* public: */
+ unsigned int nr_pages;
+ struct page **pages;
+
+ /* private: */
+ unsigned int offset;
+ unsigned int nents;
+ u64 *root;
+};
+
+void tdx_page_array_free(struct tdx_page_array *array);
+DEFINE_FREE(tdx_page_array_free, struct tdx_page_array *, if (_T) tdx_page_array_free(_T))
+struct tdx_page_array *tdx_page_array_create(unsigned int nr_pages);
+void tdx_page_array_ctrl_leak(struct tdx_page_array *array);
+int tdx_page_array_ctrl_release(struct tdx_page_array *array,
+ unsigned int nr_released,
+ u64 released_hpa);
+
struct tdx_td {
/* TD root structure: */
struct page *tdr_page;
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 8b8e165a2001..a3021e7e2490 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -30,6 +30,7 @@
#include <linux/suspend.h>
#include <linux/idr.h>
#include <linux/kvm_types.h>
+#include <linux/bitfield.h>
#include <asm/page.h>
#include <asm/special_insns.h>
#include <asm/msr-index.h>
@@ -258,6 +259,304 @@ static int build_tdx_memlist(struct list_head *tmb_list)
return ret;
}
+#define TDX_PAGE_ARRAY_MAX_NENTS (PAGE_SIZE / sizeof(u64))
+
+static int tdx_page_array_populate(struct tdx_page_array *array,
+ unsigned int offset)
+{
+ u64 *entries;
+ int i;
+
+ if (offset >= array->nr_pages)
+ return 0;
+
+ array->offset = offset;
+ array->nents = umin(array->nr_pages - offset,
+ TDX_PAGE_ARRAY_MAX_NENTS);
+
+ entries = array->root;
+ for (i = 0; i < array->nents; i++)
+ entries[i] = page_to_phys(array->pages[offset + i]);
+
+ return array->nents;
+}
+
+static void tdx_free_pages_bulk(unsigned int nr_pages, struct page **pages)
+{
+ int i;
+
+ for (i = 0; i < nr_pages; i++)
+ __free_page(pages[i]);
+}
+
+static int tdx_alloc_pages_bulk(unsigned int nr_pages, struct page **pages)
+{
+ unsigned int filled, done = 0;
+
+ do {
+ filled = alloc_pages_bulk(GFP_KERNEL, nr_pages - done,
+ pages + done);
+ if (!filled) {
+ tdx_free_pages_bulk(done, pages);
+ return -ENOMEM;
+ }
+
+ done += filled;
+ } while (done != nr_pages);
+
+ return 0;
+}
+
+/**
+ * tdx_page_array_free() - Free all memory for a tdx_page_array
+ * @array: The tdx_page_array to be freed.
+ *
+ * Free all associated pages and the container itself.
+ */
+void tdx_page_array_free(struct tdx_page_array *array)
+{
+ if (!array)
+ return;
+
+ tdx_free_pages_bulk(array->nr_pages, array->pages);
+ kfree(array->pages);
+ kfree(array->root);
+ kfree(array);
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_free);
+
+static struct tdx_page_array *
+tdx_page_array_alloc(unsigned int nr_pages)
+{
+ struct tdx_page_array *array = NULL;
+ struct page **pages = NULL;
+ u64 *root = NULL;
+ int ret;
+
+ if (!nr_pages)
+ return NULL;
+
+ array = kzalloc_obj(*array);
+ if (!array)
+ goto out_free;
+
+ root = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!root)
+ goto out_free;
+
+ pages = kcalloc(nr_pages, sizeof(*pages), GFP_KERNEL);
+ if (!pages)
+ goto out_free;
+
+ ret = tdx_alloc_pages_bulk(nr_pages, pages);
+ if (ret)
+ goto out_free;
+
+ array->nr_pages = nr_pages;
+ array->pages = pages;
+ array->root = root;
+
+ return array;
+
+out_free:
+ kfree(pages);
+ kfree(root);
+ kfree(array);
+
+ return NULL;
+}
+
+/**
+ * tdx_page_array_create() - Create a small tdx_page_array (up to 512 pages)
+ * @nr_pages: Number of pages to allocate (must be <= 512).
+ *
+ * Allocate and populate a tdx_page_array in a single step. This is intended
+ * for small collections that fit within a single root page. The allocated
+ * pages are all order-0 pages. This is the most common use case for a list of
+ * TDX control pages.
+ *
+ * If more pages are required, use tdx_page_array_alloc() and
+ * tdx_page_array_populate() to build tdx_page_array chunk by chunk.
+ *
+ * Return: Fully populated tdx_page_array or NULL on failure.
+ */
+struct tdx_page_array *tdx_page_array_create(unsigned int nr_pages)
+{
+ struct tdx_page_array *array;
+ int populated;
+
+ if (nr_pages > TDX_PAGE_ARRAY_MAX_NENTS)
+ return NULL;
+
+ array = tdx_page_array_alloc(nr_pages);
+ if (!array)
+ return NULL;
+
+ populated = tdx_page_array_populate(array, 0);
+ if (populated != nr_pages)
+ goto out_free;
+
+ return array;
+
+out_free:
+ tdx_page_array_free(array);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_create);
+
+/**
+ * tdx_page_array_ctrl_leak() - Leak data pages and free the container
+ * @array: The tdx_page_array to be leaked.
+ *
+ * Call this function when failed to reclaim the control pages. Free the root
+ * page and the holding structures, but orphan the data pages, to prevent the
+ * host from re-allocating and accessing memory that the hardware may still
+ * consider private.
+ */
+void tdx_page_array_ctrl_leak(struct tdx_page_array *array)
+{
+ if (!array)
+ return;
+
+ kfree(array->pages);
+ kfree(array->root);
+ kfree(array);
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_ctrl_leak);
+
+static bool tdx_page_array_validate_release(struct tdx_page_array *array,
+ unsigned int offset,
+ unsigned int nr_released,
+ u64 released_hpa)
+{
+ unsigned int nents;
+
+ if (offset >= array->nr_pages)
+ return false;
+
+ nents = umin(array->nr_pages - offset, TDX_PAGE_ARRAY_MAX_NENTS);
+
+ if (nents != nr_released) {
+ pr_err("%s nr_released [%d] doesn't match page array nents [%d]\n",
+ __func__, nr_released, nents);
+ return false;
+ }
+
+ /*
+ * Unfortunately TDX has multiple page allocation protocols, check the
+ * "singleton" case required for HPA_ARRAY_T.
+ */
+ if (page_to_phys(array->pages[0]) == released_hpa &&
+ array->nr_pages == 1)
+ return true;
+
+ /* Then check the "non-singleton" case */
+ if (virt_to_phys(array->root) == released_hpa) {
+ u64 *entries = array->root;
+ int i;
+
+ for (i = 0; i < nents; i++) {
+ struct page *page = array->pages[offset + i];
+ u64 val = page_to_phys(page);
+
+ if (val != entries[i]) {
+ pr_err("%s entry[%d] [0x%llx] doesn't match page hpa [0x%llx]\n",
+ __func__, i, entries[i], val);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ pr_err("%s failed to validate, released_hpa [0x%llx], root page hpa [0x%llx], page0 hpa [%#llx], number pages %u\n",
+ __func__, released_hpa, virt_to_phys(array->root),
+ page_to_phys(array->pages[0]), array->nr_pages);
+
+ return false;
+}
+
+/**
+ * tdx_page_array_ctrl_release() - Verify and release TDX control pages
+ * @array: The tdx_page_array used to originally create control pages.
+ * @nr_released: Number of HPAs the TDX Module reported as released.
+ * @released_hpa: The HPA list the TDX Module reported as released.
+ *
+ * TDX Module can at most release 512 control pages, so this function only
+ * accepts small tdx_page_array (up to 512 pages), usually created by
+ * tdx_page_array_create().
+ *
+ * Return: 0 on success, -errno on page release protocol error.
+ */
+int tdx_page_array_ctrl_release(struct tdx_page_array *array,
+ unsigned int nr_released,
+ u64 released_hpa)
+{
+ int i;
+
+ /*
+ * The only case where ->nr_pages is allowed to be >
+ * TDX_PAGE_ARRAY_MAX_NENTS is a case where those pages are never
+ * expected to be released by this function.
+ */
+ if (WARN_ON(array->nr_pages > TDX_PAGE_ARRAY_MAX_NENTS))
+ return -EINVAL;
+
+ if (WARN_ONCE(!tdx_page_array_validate_release(array, 0, nr_released,
+ released_hpa),
+ "page release protocol error, consider reboot and replace TDX Module.\n"))
+ return -EFAULT;
+
+ for (i = 0; i < array->nr_pages; i++) {
+ u64 r;
+
+ r = tdh_phymem_page_wbinvd_hkid(tdx_global_keyid,
+ array->pages[i]);
+ if (WARN_ON(r))
+ return -EFAULT;
+ }
+
+ tdx_page_array_free(array);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_ctrl_release);
+
+#define HPA_LIST_INFO_FIRST_ENTRY GENMASK_U64(11, 3)
+#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
+#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
+
+static u64 __maybe_unused hpa_list_info_assign_raw(struct tdx_page_array *array)
+{
+ return FIELD_PREP(HPA_LIST_INFO_FIRST_ENTRY, 0) |
+ FIELD_PREP(HPA_LIST_INFO_PFN,
+ PFN_DOWN(virt_to_phys(array->root))) |
+ FIELD_PREP(HPA_LIST_INFO_LAST_ENTRY, array->nents - 1);
+}
+
+#define HPA_ARRAY_T_PFN GENMASK_U64(51, 12)
+#define HPA_ARRAY_T_SIZE GENMASK_U64(63, 55)
+
+static u64 __maybe_unused hpa_array_t_assign_raw(struct tdx_page_array *array)
+{
+ unsigned long pfn;
+
+ if (array->nents == 1)
+ pfn = page_to_pfn(array->pages[array->offset]);
+ else
+ pfn = PFN_DOWN(virt_to_phys(array->root));
+
+ return FIELD_PREP(HPA_ARRAY_T_PFN, pfn) |
+ FIELD_PREP(HPA_ARRAY_T_SIZE, array->nents - 1);
+}
+
+static u64 __maybe_unused hpa_array_t_release_raw(struct tdx_page_array *array)
+{
+ if (array->nents == 1)
+ return 0;
+
+ return virt_to_phys(array->root);
+}
+
static int read_sys_metadata_field(u64 field_id, u64 *data)
{
struct tdx_module_args args = {};
--
2.25.1
^ permalink raw reply related
* [PATCH v2 04/31] x86/virt/tdx: Support allocating contiguous pages for tdx_page_array
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
The current tdx_page_array implementation allocates scattered order-0
pages. However, some TDX Module operations benefit from contiguous
physical memory. E.g. Enabling TDX Module Extensions (an optional TDX
feature) requires ~50MB memory and never returns. Such allocation
would at worst cause ~25GB permanently fragmented memory if each
allocated page is from a different 2M region.
Support allocating contiguous pages for tdx_page_array by making the
allocation method configurable. Change the tdx_page_array_alloc() to
accept a custom allocation function pointer and a context parameter.
Wrap the specific allocation into a tdx_page_array_alloc_contig()
helper.
The foreseeable caller will allocate ~50MB memory with this helper,
exceeding the maximum HPAs (512) a root page can hold, the typical usage
will be:
- struct tdx_page_array *array = tdx_page_array_alloc_contig(nr_pages);
- for each 512-page bulk
- tdx_page_array_populate(array, offset);
- seamcall(TDH_XXX_ADD, array, ...);
The configurable allocation method would also benefit more
tdx_page_array usages. TDX Module may require more specific memory
layouts encoded in the root page. Will introduce them in following
patches.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 42 +++++++++++++++++++++++++++++++++----
1 file changed, 38 insertions(+), 4 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index a3021e7e2490..6c4ed80e8e5a 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -289,7 +289,8 @@ static void tdx_free_pages_bulk(unsigned int nr_pages, struct page **pages)
__free_page(pages[i]);
}
-static int tdx_alloc_pages_bulk(unsigned int nr_pages, struct page **pages)
+static int tdx_alloc_pages_bulk(unsigned int nr_pages, struct page **pages,
+ void *data)
{
unsigned int filled, done = 0;
@@ -326,7 +327,10 @@ void tdx_page_array_free(struct tdx_page_array *array)
EXPORT_SYMBOL_GPL(tdx_page_array_free);
static struct tdx_page_array *
-tdx_page_array_alloc(unsigned int nr_pages)
+tdx_page_array_alloc(unsigned int nr_pages,
+ int (*alloc_fn)(unsigned int nr_pages,
+ struct page **pages, void *data),
+ void *data)
{
struct tdx_page_array *array = NULL;
struct page **pages = NULL;
@@ -348,7 +352,7 @@ tdx_page_array_alloc(unsigned int nr_pages)
if (!pages)
goto out_free;
- ret = tdx_alloc_pages_bulk(nr_pages, pages);
+ ret = alloc_fn(nr_pages, pages, data);
if (ret)
goto out_free;
@@ -388,7 +392,7 @@ struct tdx_page_array *tdx_page_array_create(unsigned int nr_pages)
if (nr_pages > TDX_PAGE_ARRAY_MAX_NENTS)
return NULL;
- array = tdx_page_array_alloc(nr_pages);
+ array = tdx_page_array_alloc(nr_pages, tdx_alloc_pages_bulk, NULL);
if (!array)
return NULL;
@@ -521,6 +525,36 @@ int tdx_page_array_ctrl_release(struct tdx_page_array *array,
}
EXPORT_SYMBOL_GPL(tdx_page_array_ctrl_release);
+static int tdx_alloc_pages_contig(unsigned int nr_pages, struct page **pages,
+ void *data)
+{
+ struct page *page;
+ int i;
+
+ page = alloc_contig_pages(nr_pages, GFP_KERNEL, numa_mem_id(),
+ &node_online_map);
+ if (!page)
+ return -ENOMEM;
+
+ for (i = 0; i < nr_pages; i++)
+ pages[i] = page + i;
+
+ return 0;
+}
+
+/*
+ * For holding large number of contiguous pages, usually larger than
+ * TDX_PAGE_ARRAY_MAX_NENTS (512).
+ *
+ * Similar to tdx_page_array_alloc(), after allocating with this
+ * function, call tdx_page_array_populate() to populate the tdx_page_array.
+ */
+static __maybe_unused struct tdx_page_array *
+tdx_page_array_alloc_contig(unsigned int nr_pages)
+{
+ return tdx_page_array_alloc(nr_pages, tdx_alloc_pages_contig, NULL);
+}
+
#define HPA_LIST_INFO_FIRST_ENTRY GENMASK_U64(11, 3)
#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
--
2.25.1
^ permalink raw reply related
* [PATCH v2 05/31] x86/virt/tdx: Extend tdx_page_array to support IOMMU_MT
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
IOMMU_MT is another TDX Module defined structure similar to HPA_ARRAY_T
and HPA_LIST_INFO. The difference is it requires multi-order contiguous
pages for some entries. It adds an additional NUM_PAGES field for every
multi-order page entry.
Add a dedicated allocation helper for IOMMU_MT. Fortunately put_page()
works well for both single pages and multi-order folios, simplifying the
cleanup logic for all allocation methods.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 2 +
arch/x86/virt/vmx/tdx/tdx.c | 90 +++++++++++++++++++++++++++++++++++--
2 files changed, 89 insertions(+), 3 deletions(-)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 9173a432b312..d5f1d7b7d1e7 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -175,6 +175,8 @@ void tdx_page_array_ctrl_leak(struct tdx_page_array *array);
int tdx_page_array_ctrl_release(struct tdx_page_array *array,
unsigned int nr_released,
u64 released_hpa);
+struct tdx_page_array *
+tdx_page_array_create_iommu_mt(unsigned int iq_order, unsigned int nr_mt_pages);
struct tdx_td {
/* TD root structure: */
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 6c4ed80e8e5a..2b17e0f73dac 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -275,8 +275,15 @@ static int tdx_page_array_populate(struct tdx_page_array *array,
TDX_PAGE_ARRAY_MAX_NENTS);
entries = array->root;
- for (i = 0; i < array->nents; i++)
- entries[i] = page_to_phys(array->pages[offset + i]);
+ for (i = 0; i < array->nents; i++) {
+ struct page *page = array->pages[offset + i];
+
+ entries[i] = page_to_phys(page);
+
+ /* Now only for iommu_mt */
+ if (compound_nr(page) > 1)
+ entries[i] |= compound_nr(page);
+ }
return array->nents;
}
@@ -286,7 +293,7 @@ static void tdx_free_pages_bulk(unsigned int nr_pages, struct page **pages)
int i;
for (i = 0; i < nr_pages; i++)
- __free_page(pages[i]);
+ put_page(pages[i]);
}
static int tdx_alloc_pages_bulk(unsigned int nr_pages, struct page **pages,
@@ -463,6 +470,10 @@ static bool tdx_page_array_validate_release(struct tdx_page_array *array,
struct page *page = array->pages[offset + i];
u64 val = page_to_phys(page);
+ /* Now only for iommu_mt */
+ if (compound_nr(page) > 1)
+ val |= compound_nr(page);
+
if (val != entries[i]) {
pr_err("%s entry[%d] [0x%llx] doesn't match page hpa [0x%llx]\n",
__func__, i, entries[i], val);
@@ -555,6 +566,79 @@ tdx_page_array_alloc_contig(unsigned int nr_pages)
return tdx_page_array_alloc(nr_pages, tdx_alloc_pages_contig, NULL);
}
+static int tdx_alloc_pages_iommu_mt(unsigned int nr_pages, struct page **pages,
+ void *data)
+{
+ unsigned int iq_order = (unsigned int)(long)data;
+ struct folio *t_iq, *t_ctxiq;
+ int ret;
+
+ /* TODO: folio_alloc_node() is preferred, but need numa info */
+ t_iq = folio_alloc(GFP_KERNEL | __GFP_ZERO, iq_order);
+ if (!t_iq)
+ return -ENOMEM;
+
+ t_ctxiq = folio_alloc(GFP_KERNEL | __GFP_ZERO, iq_order);
+ if (!t_ctxiq) {
+ ret = -ENOMEM;
+ goto out_t_iq;
+ }
+
+ ret = tdx_alloc_pages_bulk(nr_pages - 2, pages + 2, NULL);
+ if (ret)
+ goto out_t_ctxiq;
+
+ pages[0] = folio_page(t_iq, 0);
+ pages[1] = folio_page(t_ctxiq, 0);
+
+ return 0;
+
+out_t_ctxiq:
+ folio_put(t_ctxiq);
+out_t_iq:
+ folio_put(t_iq);
+
+ return ret;
+}
+
+/**
+ * tdx_page_array_create_iommu_mt() - Create a page array for IOMMU Memory Tables
+ * @iq_order: The allocation order for the IOMMU Invalidation Queue.
+ * @nr_mt_pages: Number of additional order-0 pages for the MT.
+ *
+ * Allocate and populate a specialized tdx_page_array for IOMMU_MT structures.
+ * The resulting array consists of two multi-order folios (at index 0 and 1)
+ * followed by the requested number of order-0 pages.
+ *
+ * Return: Fully populated tdx_page_array or NULL on failure.
+ */
+struct tdx_page_array *
+tdx_page_array_create_iommu_mt(unsigned int iq_order, unsigned int nr_mt_pages)
+{
+ unsigned int nr_pages = nr_mt_pages + 2;
+ struct tdx_page_array *array;
+ int populated;
+
+ if (nr_pages > TDX_PAGE_ARRAY_MAX_NENTS)
+ return NULL;
+
+ array = tdx_page_array_alloc(nr_pages, tdx_alloc_pages_iommu_mt,
+ (void *)(long)iq_order);
+ if (!array)
+ return NULL;
+
+ populated = tdx_page_array_populate(array, 0);
+ if (populated != nr_pages)
+ goto out_free;
+
+ return array;
+
+out_free:
+ tdx_page_array_free(array);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_create_iommu_mt);
+
#define HPA_LIST_INFO_FIRST_ENTRY GENMASK_U64(11, 3)
#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
--
2.25.1
^ permalink raw reply related
* [PATCH v2 06/31] x86/virt/tdx: Read global metadata for TDX Module Extensions/Connect
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
Add reading of the global metadata for TDX Module Extensions & TDX
Connect. Add them in a batch as TDX Connect is currently the only user
of TDX Module Extensions and no way to initialize TDX Module Extensions
without firstly enabling TDX Connect.
TDX Module Extensions & TDX Connect are optional features enumerated by
TDX_FEATURES0. Check the TDX_FEATURES0 before reading these metadata to
avoid failing the whole TDX initialization.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 2 ++
arch/x86/include/asm/tdx_global_metadata.h | 14 ++++++++
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 36 +++++++++++++++++++++
3 files changed, 52 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index d5f1d7b7d1e7..d7605235aa9b 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -129,7 +129,9 @@ int tdx_enable(void);
const char *tdx_dump_mce_info(struct mce *m);
/* Bit definitions of TDX_FEATURES0 metadata field */
+#define TDX_FEATURES0_TDXCONNECT BIT_ULL(6)
#define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18)
+#define TDX_FEATURES0_EXT BIT_ULL(39)
const struct tdx_sys_info *tdx_get_sysinfo(void);
diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h
index 060a2ad744bf..e7948bca671a 100644
--- a/arch/x86/include/asm/tdx_global_metadata.h
+++ b/arch/x86/include/asm/tdx_global_metadata.h
@@ -34,11 +34,25 @@ struct tdx_sys_info_td_conf {
u64 cpuid_config_values[128][2];
};
+struct tdx_sys_info_ext {
+ u16 memory_pool_required_pages;
+ u8 ext_required;
+};
+
+struct tdx_sys_info_connect {
+ u16 ide_mt_page_count;
+ u16 spdm_mt_page_count;
+ u16 iommu_mt_page_count;
+ u16 spdm_max_dev_info_pages;
+};
+
struct tdx_sys_info {
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_ext ext;
+ struct tdx_sys_info_connect connect;
};
#endif
diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
index 13ad2663488b..a07f1e7b18e8 100644
--- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -85,6 +85,36 @@ 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_ext(struct tdx_sys_info_ext *sysinfo_ext)
+{
+ int ret = 0;
+ u64 val;
+
+ if (!ret && !(ret = read_sys_metadata_field(0x3100000100000000, &val)))
+ sysinfo_ext->memory_pool_required_pages = val;
+ if (!ret && !(ret = read_sys_metadata_field(0x3100000100000001, &val)))
+ sysinfo_ext->ext_required = val;
+
+ return ret;
+}
+
+static int get_tdx_sys_info_connect(struct tdx_sys_info_connect *sysinfo_connect)
+{
+ int ret = 0;
+ u64 val;
+
+ if (!ret && !(ret = read_sys_metadata_field(0x3000000100000001, &val)))
+ sysinfo_connect->ide_mt_page_count = val;
+ if (!ret && !(ret = read_sys_metadata_field(0x3000000100000002, &val)))
+ sysinfo_connect->spdm_mt_page_count = val;
+ if (!ret && !(ret = read_sys_metadata_field(0x3000000100000003, &val)))
+ sysinfo_connect->iommu_mt_page_count = val;
+ if (!ret && !(ret = read_sys_metadata_field(0x3000000100000007, &val)))
+ sysinfo_connect->spdm_max_dev_info_pages = val;
+
+ return ret;
+}
+
static int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
{
int ret = 0;
@@ -94,5 +124,11 @@ static int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
ret = ret ?: get_tdx_sys_info_td_ctrl(&sysinfo->td_ctrl);
ret = ret ?: get_tdx_sys_info_td_conf(&sysinfo->td_conf);
+ if (sysinfo->features.tdx_features0 & TDX_FEATURES0_EXT)
+ ret = ret ?: get_tdx_sys_info_ext(&sysinfo->ext);
+
+ if (sysinfo->features.tdx_features0 & TDX_FEATURES0_TDXCONNECT)
+ ret = ret ?: get_tdx_sys_info_connect(&sysinfo->connect);
+
return ret;
}
--
2.25.1
^ permalink raw reply related
* [PATCH v2 07/31] x86/virt/tdx: Embed version info in SEAMCALL leaf function definitions
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
Embed version information in SEAMCALL leaf function definitions rather
than let the caller open code them. For now, only TDH.VP.INIT is
involved.
Don't bother the caller to choose the SEAMCALL version if unnecessary.
New version SEAMCALLs are guaranteed to be backward compatible, so
ideally kernel doesn't need to keep version history and only uses the
latest version SEAMCALLs.
The concern is some old TDX Modules don't recognize new version
SEAMCALLs. Multiple SEAMCALL versions co-exist when kernel should
support these old Modules. As time goes by, the old Modules deprecate
and old version SEAMCALL definitions should disappear.
The old TDX Modules that only support TDH.VP.INIT v0 are all deprecated,
so only provide the latest (v1) definition.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.h | 23 ++++++++++++++---------
arch/x86/virt/vmx/tdx/tdx.c | 4 ++--
2 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index c641b4632826..e5a9331df451 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -2,6 +2,7 @@
#ifndef _X86_VIRT_TDX_H
#define _X86_VIRT_TDX_H
+#include <linux/bitfield.h>
#include <linux/bits.h>
/*
@@ -11,6 +12,18 @@
* architectural definitions come first.
*/
+/*
+ * SEAMCALL leaf:
+ *
+ * Bit 15:0 Leaf number
+ * Bit 23:16 Version number
+ */
+#define SEAMCALL_LEAF GENMASK(15, 0)
+#define SEAMCALL_VER GENMASK(23, 16)
+
+#define SEAMCALL_LEAF_VER(l, v) (FIELD_PREP(SEAMCALL_LEAF, l) | \
+ FIELD_PREP(SEAMCALL_VER, v))
+
/*
* TDX module SEAMCALL leaf functions
*/
@@ -31,7 +44,7 @@
#define TDH_VP_CREATE 10
#define TDH_MNG_KEY_FREEID 20
#define TDH_MNG_INIT 21
-#define TDH_VP_INIT 22
+#define TDH_VP_INIT SEAMCALL_LEAF_VER(22, 1)
#define TDH_PHYMEM_PAGE_RDMD 24
#define TDH_VP_RD 26
#define TDH_PHYMEM_PAGE_RECLAIM 28
@@ -47,14 +60,6 @@
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG 45
-/*
- * SEAMCALL leaf:
- *
- * Bit 15:0 Leaf number
- * Bit 23:16 Version number
- */
-#define TDX_VERSION_SHIFT 16
-
/* TDX page types */
#define PT_NDA 0x0
#define PT_RSVD 0x1
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 2b17e0f73dac..130214933c2f 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -2202,8 +2202,8 @@ u64 tdh_vp_init(struct tdx_vp *vp, u64 initial_rcx, u32 x2apicid)
.r8 = x2apicid,
};
- /* apicid requires version == 1. */
- return seamcall(TDH_VP_INIT | (1ULL << TDX_VERSION_SHIFT), &args);
+ /* apicid requires version == 1. See TDH_VP_INIT definition.*/
+ return seamcall(TDH_VP_INIT, &args);
}
EXPORT_SYMBOL_FOR_KVM(tdh_vp_init);
--
2.25.1
^ permalink raw reply related
* [PATCH v2 08/31] x86/virt/tdx: Configure TDX Module with optional TDX Connect feature
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
TDX Module supports optional TDX features (e.g. TDX Connect & TDX Module
Extensions) that won't be enabled by default. It extends TDH.SYS.CONFIG
for host to choose to enable them on bootup.
Call TDH.SYS.CONFIG with a new bitmap input parameter to specify which
features to enable. The bitmap uses the same definitions as
TDX_FEATURES0. But note not all bits in TDX_FEATURES0 are valid for
configuration, e.g. TDX Module Extensions is a service that supports TDX
Connect, it is implicitly enabled when TDX Connect is enabled. Setting
TDX_FEATURES0_EXT in the bitmap has no effect.
TDX Module advances the version of TDH.SYS.CONFIG for the change, so
use the latest version (v1) for optional feature enabling. But
supporting existing Modules which only support v0 is still necessary
until they are deprecated, enumerate via TDX_FEATURES0 to decide which
version to use.
TDX Module updates global metadata when optional features are enabled.
Host should update the cached tdx_sysinfo to reflect these changes.
Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.h | 3 ++-
arch/x86/virt/vmx/tdx/tdx.c | 16 +++++++++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index e5a9331df451..870bb75da3ba 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -58,7 +58,8 @@
#define TDH_PHYMEM_CACHE_WB 40
#define TDH_PHYMEM_PAGE_WBINVD 41
#define TDH_VP_WR 43
-#define TDH_SYS_CONFIG 45
+#define TDH_SYS_CONFIG_V0 45
+#define TDH_SYS_CONFIG SEAMCALL_LEAF_VER(TDH_SYS_CONFIG_V0, 1)
/* TDX page types */
#define PT_NDA 0x0
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 130214933c2f..0c5d6bdd810f 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1353,6 +1353,7 @@ static int construct_tdmrs(struct list_head *tmb_list,
static int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid)
{
struct tdx_module_args args = {};
+ u64 seamcall_fn = TDH_SYS_CONFIG_V0;
u64 *tdmr_pa_array;
size_t array_sz;
int i, ret;
@@ -1377,7 +1378,15 @@ static int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid)
args.rcx = __pa(tdmr_pa_array);
args.rdx = tdmr_list->nr_consumed_tdmrs;
args.r8 = global_keyid;
- ret = seamcall_prerr(TDH_SYS_CONFIG, &args);
+
+ if (tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_TDXCONNECT) {
+ args.r9 |= TDX_FEATURES0_TDXCONNECT;
+ args.r11 = ktime_get_real_seconds();
+ /* These parameters requires version >= 1 */
+ seamcall_fn = TDH_SYS_CONFIG;
+ }
+
+ ret = seamcall_prerr(seamcall_fn, &args);
/* Free the array as it is not required anymore. */
kfree(tdmr_pa_array);
@@ -1537,6 +1546,11 @@ static int init_tdx_module(void)
if (ret)
goto err_free_pamts;
+ /* configuration to tdx module may change tdx_sysinfo, update it */
+ ret = get_tdx_sys_info(&tdx_sysinfo);
+ if (ret)
+ goto err_reset_pamts;
+
/* Config the key of global KeyID on all packages */
ret = config_global_keyid();
if (ret)
--
2.25.1
^ permalink raw reply related
* [PATCH v2 09/31] x86/virt/tdx: Move tdx_clflush_page() up in the file
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
Prepare to add more callers earlier in this file, so move this
function up in advance.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 0c5d6bdd810f..4fb56bb442f0 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1502,6 +1502,17 @@ static int init_tdmrs(struct tdmr_info_list *tdmr_list)
return 0;
}
+/*
+ * The TDX module exposes a CLFLUSH_BEFORE_ALLOC bit to specify whether
+ * a CLFLUSH of pages is required before handing them to the TDX module.
+ * Be conservative and make the code simpler by doing the CLFLUSH
+ * unconditionally.
+ */
+static void tdx_clflush_page(struct page *page)
+{
+ clflush_cache_range(page_to_virt(page), PAGE_SIZE);
+}
+
static int init_tdx_module(void)
{
int ret;
@@ -1936,17 +1947,6 @@ static inline u64 tdx_tdr_pa(struct tdx_td *td)
return page_to_phys(td->tdr_page);
}
-/*
- * The TDX module exposes a CLFLUSH_BEFORE_ALLOC bit to specify whether
- * a CLFLUSH of pages is required before handing them to the TDX module.
- * Be conservative and make the code simpler by doing the CLFLUSH
- * unconditionally.
- */
-static void tdx_clflush_page(struct page *page)
-{
- clflush_cache_range(page_to_virt(page), PAGE_SIZE);
-}
-
noinstr u64 tdh_vp_enter(struct tdx_vp *td, struct tdx_module_args *args)
{
args->rcx = td->tdvpr_pa;
--
2.25.1
^ permalink raw reply related
* [PATCH v2 10/31] x86/virt/tdx: Add extra memory to TDX Module for Extensions
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
Adding more memory to TDX Module is the first step to enable Extensions.
Currently, TDX Module memory use is relatively static. But, some new
features (called "TDX Module Extensions") need to use memory more
dynamically. While 'static' here means the kernel provides necessary
amount of memory to TDX Module for its basic functionalities, 'dynamic'
means extra memory is needed only if new optional features are to be
enabled. So add a new memory feeding process backed by a new SEAMCALL
TDH.EXT.MEM.ADD.
The process is mostly the same as adding PAMT. The kernel queries TDX
Module how much memory needed, allocates it, hands it over, and never
gets it back.
TDH.EXT.MEM.ADD uses tdx_page_array to provide control (private) pages
to TDX Module. Introduce a tdx_clflush_page_array() helper to flush
shared cache before SEAMCALL, to avoid shared cache write back damages
these private pages.
For now, TDX Module Extensions consume relatively large amount of
memory (~50MB). Use contiguous page allocation to avoid permanently
fragment too much memory. Print this readout value on TDX Module
Extensions initialization for visibility.
Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 92 ++++++++++++++++++++++++++++++++++++-
2 files changed, 91 insertions(+), 2 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 870bb75da3ba..31ccdfcf518c 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -60,6 +60,7 @@
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG_V0 45
#define TDH_SYS_CONFIG SEAMCALL_LEAF_VER(TDH_SYS_CONFIG_V0, 1)
+#define TDH_EXT_MEM_ADD 61
/* TDX page types */
#define PT_NDA 0x0
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 4fb56bb442f0..5fae17c13191 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -560,7 +560,7 @@ static int tdx_alloc_pages_contig(unsigned int nr_pages, struct page **pages,
* Similar to tdx_page_array_alloc(), after allocating with this
* function, call tdx_page_array_populate() to populate the tdx_page_array.
*/
-static __maybe_unused struct tdx_page_array *
+static struct tdx_page_array *
tdx_page_array_alloc_contig(unsigned int nr_pages)
{
return tdx_page_array_alloc(nr_pages, tdx_alloc_pages_contig, NULL);
@@ -643,7 +643,7 @@ EXPORT_SYMBOL_GPL(tdx_page_array_create_iommu_mt);
#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
-static u64 __maybe_unused hpa_list_info_assign_raw(struct tdx_page_array *array)
+static u64 hpa_list_info_assign_raw(struct tdx_page_array *array)
{
return FIELD_PREP(HPA_LIST_INFO_FIRST_ENTRY, 0) |
FIELD_PREP(HPA_LIST_INFO_PFN,
@@ -1513,6 +1513,94 @@ static void tdx_clflush_page(struct page *page)
clflush_cache_range(page_to_virt(page), PAGE_SIZE);
}
+static void tdx_clflush_page_array(struct tdx_page_array *array)
+{
+ for (int i = 0; i < array->nents; i++)
+ tdx_clflush_page(array->pages[array->offset + i]);
+}
+
+static int tdx_ext_mem_add(struct tdx_page_array *ext_mem)
+{
+ struct tdx_module_args args = {
+ .rcx = hpa_list_info_assign_raw(ext_mem),
+ };
+ u64 r;
+
+ tdx_clflush_page_array(ext_mem);
+
+ do {
+ r = seamcall_ret(TDH_EXT_MEM_ADD, &args);
+ cond_resched();
+ } while (r == TDX_INTERRUPTED_RESUMABLE);
+
+ if (r != TDX_SUCCESS)
+ return -EFAULT;
+
+ return 0;
+}
+
+static int tdx_ext_mem_setup(struct tdx_page_array *ext_mem)
+{
+ unsigned int populated, offset = 0;
+ int ret;
+
+ /*
+ * tdx_page_array's root page can hold 512 HPAs at most. We have ~50MB
+ * memory to add, re-populate the array and add pages bulk by bulk.
+ */
+ while (1) {
+ populated = tdx_page_array_populate(ext_mem, offset);
+ if (!populated)
+ break;
+
+ ret = tdx_ext_mem_add(ext_mem);
+ if (ret)
+ return ret;
+
+ offset += populated;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused init_tdx_ext(void)
+{
+ struct tdx_page_array *ext_mem = NULL;
+ unsigned int nr_pages;
+ int ret;
+
+ if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT))
+ return 0;
+
+ nr_pages = tdx_sysinfo.ext.memory_pool_required_pages;
+ /*
+ * memory_pool_required_pages == 0 means no need to add more pages,
+ * skip the memory setup.
+ */
+ if (nr_pages) {
+ ext_mem = tdx_page_array_alloc_contig(nr_pages);
+ if (!ext_mem)
+ return -ENOMEM;
+
+ ret = tdx_ext_mem_setup(ext_mem);
+ if (ret)
+ goto out_ext_mem;
+ }
+
+ /* Extension memory is never reclaimed once assigned */
+ tdx_page_array_ctrl_leak(ext_mem);
+
+ pr_info("%lu KB allocated for TDX Module Extensions\n",
+ nr_pages * PAGE_SIZE / 1024);
+
+ return 0;
+
+out_ext_mem:
+ tdx_page_array_free(ext_mem);
+
+ return ret;
+}
+
static int init_tdx_module(void)
{
int ret;
--
2.25.1
^ permalink raw reply related
* [PATCH v2 11/31] x86/virt/tdx: Make TDX Module initialize Extensions
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
After providing all required memory to TDX Module, initialize the
Extensions via TDH.EXT.INIT, and then Extension-SEAMCALLs can be used.
The initialization of Extensions touches the required memory (previously
provided by TDH.EXT.MEM.ADD) in private manner. If failed, flush cache
before freeing these memory, to avoid private cache write back damages
the shared pages.
TDX should use movdir64b to clear private pages when reclaiming them on
older platforms with the X86_BUG_TDX_PW_MCE erratum. For simplicity,
don't expect this errata on any TDX Extensions supported platform. So
TDX Extensions & all features that require TDX Extensions (e.g. TDX
Connect) will not call the clearing helpers.
Note the "ext_required" global metadata specifies if TDH.EXT.INIT call
is needed. If 0, the Extensions are already working, so skip the SEAMCALL.
Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 45 +++++++++++++++++++++++++++++++++++++
2 files changed, 46 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 31ccdfcf518c..a26fe94c07ff 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -60,6 +60,7 @@
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG_V0 45
#define TDH_SYS_CONFIG SEAMCALL_LEAF_VER(TDH_SYS_CONFIG_V0, 1)
+#define TDH_EXT_INIT 60
#define TDH_EXT_MEM_ADD 61
/* TDX page types */
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 5fae17c13191..4134f92425da 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1519,6 +1519,23 @@ static void tdx_clflush_page_array(struct tdx_page_array *array)
tdx_clflush_page(array->pages[array->offset + i]);
}
+/* Initialize the TDX Module Extensions then Extension-SEAMCALLs can be used */
+static int tdx_ext_init(void)
+{
+ struct tdx_module_args args = {};
+ u64 r;
+
+ do {
+ r = seamcall(TDH_EXT_INIT, &args);
+ cond_resched();
+ } while (r == TDX_INTERRUPTED_RESUMABLE);
+
+ if (r != TDX_SUCCESS)
+ return -EFAULT;
+
+ return 0;
+}
+
static int tdx_ext_mem_add(struct tdx_page_array *ext_mem)
{
struct tdx_module_args args = {
@@ -1572,6 +1589,17 @@ static int __maybe_unused init_tdx_ext(void)
if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT))
return 0;
+ /*
+ * With this errata, TDX should use movdir64b to clear private pages
+ * when reclaiming them. See tdx_quirk_reset_paddr().
+ *
+ * Don't expect this errata on any TDX Extensions supported platform.
+ * All features require TDX Extensions (including TDX Extensions
+ * itself) will never call tdx_quirk_reset_paddr().
+ */
+ if (boot_cpu_has_bug(X86_BUG_TDX_PW_MCE))
+ return -ENXIO;
+
nr_pages = tdx_sysinfo.ext.memory_pool_required_pages;
/*
* memory_pool_required_pages == 0 means no need to add more pages,
@@ -1587,6 +1615,20 @@ static int __maybe_unused init_tdx_ext(void)
goto out_ext_mem;
}
+ /*
+ * ext_required == 0 means no need to call TDH.EXT.INIT, the Extensions
+ * are already working.
+ */
+ if (tdx_sysinfo.ext.ext_required) {
+ ret = tdx_ext_init();
+ /*
+ * Some pages may have been touched by the TDX module.
+ * Flush cache before returning these pages to kernel.
+ */
+ if (ret)
+ goto out_flush;
+ }
+
/* Extension memory is never reclaimed once assigned */
tdx_page_array_ctrl_leak(ext_mem);
@@ -1595,6 +1637,9 @@ static int __maybe_unused init_tdx_ext(void)
return 0;
+out_flush:
+ if (ext_mem)
+ wbinvd_on_all_cpus();
out_ext_mem:
tdx_page_array_free(ext_mem);
--
2.25.1
^ permalink raw reply related
* [PATCH v2 12/31] x86/virt/tdx: Enable the Extensions after basic TDX Module init
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
The detailed initialization flow for TDX Module Extensions has been
fully implemented. Enable the flow after basic TDX Module
initialization.
Theoretically, the Extensions can be initialized later when the first
usage of the Extension-SEAMCALL comes. That would save or postpone the
usage of ~50M memory. But it isn't worth the complexity, the needs for
Extensions are vast but the savings are little for a typical TDX capable
system (about 0.001% of memory). So just enable it along with the basic
TDX.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 4134f92425da..0e1ad793e648 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1580,7 +1580,7 @@ static int tdx_ext_mem_setup(struct tdx_page_array *ext_mem)
return 0;
}
-static int __maybe_unused init_tdx_ext(void)
+static int init_tdx_ext(void)
{
struct tdx_page_array *ext_mem = NULL;
unsigned int nr_pages;
@@ -1705,6 +1705,10 @@ static int init_tdx_module(void)
if (ret)
goto err_reset_pamts;
+ ret = init_tdx_ext();
+ if (ret)
+ goto err_reset_pamts;
+
pr_info("%lu KB allocated for PAMT\n", tdmrs_count_pamt_kb(&tdx_tdmr_list));
out_put_tdxmem:
--
2.25.1
^ 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