* Re: [PATCH v2 1/5] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h>
From: Chao Gao @ 2026-03-24 9:49 UTC (permalink / raw)
To: Vishal Verma
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Kiryl Shutsemau, Rick Edgecombe,
Sean Christopherson, Paolo Bonzini, linux-kernel, linux-coco, kvm
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-1-87a36409e051@intel.com>
On Mon, Mar 23, 2026 at 02:59:04PM -0600, Vishal Verma wrote:
>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>
^ permalink raw reply
* Re: [PATCH v2 00/16] fs,x86/resctrl: Add kernel-mode (e.g., PLZA) support to the resctrl subsystem
From: Askar Safin @ 2026-03-24 6:15 UTC (permalink / raw)
To: babu.moger; +Cc: kvm, linux-coco, linux-doc, linux-kernel, x86
In-Reply-To: <cover.1773347820.git.babu.moger@amd.com>
Please, remove me from CC list in future versions of this patchset
--
Askar Safin
^ permalink raw reply
* Re: [PATCH v2 09/19] PCI/TSM: Support creating encrypted MMIO descriptors via TDISP Report
From: Dan Williams @ 2026-03-24 3:26 UTC (permalink / raw)
To: Xu Yilun, Jason Gunthorpe
Cc: Aneesh Kumar K.V, Dan Williams, linux-coco, linux-pci, gregkh,
aik, bhelgaas, alistair23, lukas, Arnd Bergmann
In-Reply-To: <abji3A72ogqdRiGc@yilunxu-OptiPlex-7050>
Xu Yilun wrote:
[..]
> And I've no idea why unlocked MSIX/PBA must be hidden? How about other
> non-TEE ranges, must be hidden or mustn't? Is there a possibility we
> enforce DSM to present all ranges, then the layout is clear to OS?
Just to close this question, this was discussed at the last device
security call. Indeed the expectation is that Linux will assume that all
ranges besides MSIX/PBA must be present in the report, and that offset
is always aligned. If / when an implementation violates that expectation
they can help write the Linux quirk for that case or otherwise fix their
implementation.
^ permalink raw reply
* Re: [PATCH v2 03/19] device core: Introduce confidential device acceptance
From: Dan Williams @ 2026-03-24 2:18 UTC (permalink / raw)
To: Jason Gunthorpe, Dan Williams
Cc: Greg KH, linux-coco, linux-pci, aik, aneesh.kumar, yilun.xu,
bhelgaas, alistair23, lukas, Christoph Hellwig, Marek Szyprowski,
Robin Murphy, Roman Kisel, Samuel Ortiz, Rafael J. Wysocki,
Danilo Krummrich
In-Reply-To: <20260323181413.GP7340@nvidia.com>
Jason Gunthorpe wrote:
> On Fri, Mar 13, 2026 at 06:32:27PM -0700, Dan Williams wrote:
>
> > The problem is that for all the buses that do not currently have a
> > "device authorization" concept only userspace can decide that a device
> > should skip bind by default. For that, I propose module autoprobe policy
> > [1]. Not yet convinced the kernel needs its own per-device "no bind"
> > policy.
>
> I think it is just part of the broader definition of the level that
> extends into the iommu and so on. It makes sense to have this kind of
> no-binding security level, IMHO.
Easy to include for completeness.
In a CC VM userspace can set "$module autoprobe=0" to get a control
point to set @trust to zero, but @trust otherwise defaults to 1. This
allows for userspace policy to generically distrust devices, but without
needing to build a new mechanism to specify which devices start life at
trust == 0 (i.e. "device filter" proposal previously NAK'd).
> > > The DMA API just wants a flag in the struct device that says if the
> > > device can access encrypted memory or only decrypted.
> >
> > You mean separate "trusted to access private" and "currently enabled to
> > access private" properties? I am trying to think of a situation where
> > "dev->trust >= 3" and a flag saying "disable bouncing for encrypted
> > memory" would ever disagree.
>
> I'm steering the trust level toward more of an acceptance criteria.
> If the trust level is you have access to private memory but the device
> can't actually do that then fail the trust level change.
>
> Same for the reverse, if the trust level says no private memory and the
> device is T=1 then fail the trust level change.
Ok, so the uapi for PCI/TDISP would be:
echo $tsm > $pdev/tsm/lock
<gather evidence, validate with relying party>
echo 3 > $pdev/trust
...where that @trust attribute is a generic device semantic, but in the
case of PCI device connected to a given TSM it invokes the TSM hypercall
to transition the device to the RUN state and the TSM local call to
unblock DMA to private memory.
So, userspace can generically understand what privileges come with which
trust levels, but the mechanism to get those privileges remains bus
specific.
> > That bit though has lock-to-run consistency expectations. So if the
> > kernel does not yet fully trust the device by time the relying party is
> > satisfied, and the uAPI to transition the device into the TCB (level 3)
> > is driver-core generic it raises TOCTOU issues in my mind. The
> > driver-core would need to ask the bus "user now trusts this device, do
> > you?".
>
> Huh? No, there is no concept of trust in the kernel. The userspace
> setting level 3 is "I now ack that this device is trusted", there is
> no further trust cross check. If TSM side says it is in RUN/T=1 then
> we are done.
Ok, so maybe I misunderstood your point. Per the above, if the trust
setting is what kicks the bus to finalize T=1 then it makes sense. If
that kick fails, the user's trust setting request fails.
What I expect is unwanted / surprising is device has already been
transitioned to T=1 state ahead of the trust setting, it is a
synchronous mechanism.
> If we fall out of RUN then the level auto-resets back to 0 and
> userspace has to go around and fix it again. (ignoring driver RAS)
Yes, at the moment that the bus detects that an event like SPDM session
loss, IDE link loss, TDISP ERROR state entry has occurred it can
downgrade trust and notify. That notification fits well with netlink
because all of those events are downstream of evidence validation.
> > Aneesh and I are currently debating on Discord whether the kernel needs
> > to protect against guest userspace confusing itself.
>
> Userspace that controls acceptance must be part of the TCB or the
> whole model is fully broken. If your guest userspace is so security
> broken it can accept devices it doesn't mean to then just forget it.
Agree, it is a non-problem. If guest userspace confuses itself by racing
sysfs operations then the relying party should not trust that userspace.
> > However, to Aneesh's point we could protect against that with a
> > transactional uAPI like netlink that can express "trust if and only if
> > the device has not been relocked before final accept" by passing a
> > cookie obtained at lock to accept. That would be awkward to coordinate
> > with driver-core generic uAPI for trust.
>
> You could, but why make it so complicated? The whole LOCKED/RUN thing
> is already supposed to deal with TOCTOU, doesn't it?
Right, the threat vector is the guest accepting something it has had no
chance to validate. Guest userspace confusion is not that. Guest
userspace asking the device to be re-locked in a way that confuses an
ongoing evidence validation sequence in another thread is a "you get to
keep the pieces" event.
> The CSP cannot trick a device to fall out of LOCKED an the re-enter
> LOCKED without the VM knowing.
>
> The VM attacking itself on something as security critical as device
> accepance can't be in scope :\
The complication vs benefit tradeoff is indeed not mathing, but wanted
to do justice to Aneesh's proposal and the suitability of the sysfs
uapi.
> > > This way nothing is coupled and the kernel can offer all kinds of
> > > different uAPI for device verification. Userspaces picks the
> > > appropriate one and acks it with the level change.
> >
> > Thunderbolt already has authorized uAPI. I expect adding dev->trust
> > support to thunderbolt is more related to ATS privilege and private
> > memory privilege.
>
> It brings it into the whole 'measure the device and then decide what
> to do with it' framework. The trust level is still the generic ack
> that the device is allowed the participate in the system with whatever
> level of security.
Some thought experiments to confirm alignment...
'Generic ack' is a synchronous mechanism for the bus to evaluate. So if
@trust appears for any device, and by consequence alongside @authorized
for a thunderbolt device, it should be the case that these operations
are equivalent:
# echo 1 > $dev/trust
# echo 1 > $dev/authorized
...and the result is cross-reflected for comptability:
# echo 1 > $dev/trust
# cat $dev/authorized
1
Consequently this:
# echo 2 > $dev/trust
...would be equivalent to authorizing the device and unblocking ATS (if
such a thing existed).
For bare metal PCI device security the TSM 'connection' needs to be
established in order to enable device evidence collection.
echo $tsm > $pdev/tsm/connect
<validate device evidence>
echo 2 > $pdev/trust
Now, I question whether 5 trust levels instead of 4. This would be to
explicitly only trust devices where the TSM has established physical
link encryption, or the TSM has asserted that the link is protected by
other means. So the trust levels are:
0 disconnected: bus does not attach drivers
1 limited: core code deploys hostile device mitigations like disable
ATS, CC force shared memory bouncing.
2 DMA: full DMA access, driver responsible for protecting against
adverarial devices.
3 Link: mitigations against physical integrity and snooping attacks
deployed
4 TCB: full device interface validation via a protocol like TDISP,
CC private memory access granted.
Where the native Rust library based SPDM driver only offers trust level
2, bare metal TSMs can support trust level 3, and the TSM interfaces in
CC VMs can support trust level 4. I could see squashing 2 and 3 and
making it a documentation problem to understand the capabilities of the
various TSM drivers.
^ permalink raw reply
* Re: [PATCH v4 2/2] dma-buf: heaps: system: add system_cc_decrypted heap for explicitly decrypted memory
From: T.J. Mercier @ 2026-03-23 23:25 UTC (permalink / raw)
To: Jiri Pirko
Cc: dri-devel, linaro-mm-sig, iommu, linux-media, sumit.semwal,
benjamin.gaignard, Brian.Starkey, jstultz, christian.koenig,
m.szyprowski, 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: <20260316125857.617836-3-jiri@resnulli.us>
On Mon, Mar 16, 2026 at 5:59 AM Jiri Pirko <jiri@resnulli.us> wrote:
>
> From: Jiri Pirko <jiri@nvidia.com>
>
> Add a new "system_cc_decrypted" dma-buf heap to allow userspace to
> allocate decrypted (shared) memory for confidential computing (CoCo)
> VMs.
>
> On CoCo VMs, guest memory is encrypted by default. The hardware uses an
> encryption bit in page table entries (C-bit on AMD SEV, "shared" bit on
> Intel TDX) to control whether a given memory access is encrypted or
> decrypted. The kernel's direct map is set up with encryption enabled,
> so pages returned by alloc_pages() are encrypted in the direct map
> by default. To make this memory usable for devices that do not support
> DMA to encrypted memory (no TDISP support), it has to be explicitly
> decrypted. A couple of things are needed to properly handle
> decrypted memory for the dma-buf use case:
>
> - set_memory_decrypted() on the direct map after allocation:
> Besides clearing the encryption bit in the direct map PTEs, this
> also notifies the hypervisor about the page state change. On free,
> the inverse set_memory_encrypted() must be called before returning
> pages to the allocator. If re-encryption fails, pages
> are intentionally leaked to prevent decrypted memory from being
> reused as private.
>
> - pgprot_decrypted() for userspace and kernel virtual mappings:
> Any new mapping of the decrypted pages, be it to userspace via
> mmap or to kernel vmalloc space via vmap, creates PTEs independent
> of the direct map. These must also have the encryption bit cleared,
> otherwise accesses through them would see encrypted (garbage) data.
>
> - DMA_ATTR_CC_DECRYPTED for DMA mapping:
> Since the pages are already decrypted, the DMA API needs to be
> informed via DMA_ATTR_CC_DECRYPTED so it can map them correctly
> as unencrypted for device access.
>
> On non-CoCo VMs, the system_cc_decrypted heap is not registered
> to prevent misuse by userspace that does not understand
> the security implications of explicitly decrypted memory.
>
> Signed-off-by: Jiri Pirko <jiri@nvidia.com>
> ---
> v2->v3:
> - removed couple of leftovers from headers
> v1->v2:
> - fixed build errors on s390 by including mem_encrypt.h
> - converted system heap flag implementation to a separate heap
> ---
> drivers/dma-buf/heaps/system_heap.c | 103 ++++++++++++++++++++++++++--
> 1 file changed, 98 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/dma-buf/heaps/system_heap.c b/drivers/dma-buf/heaps/system_heap.c
> index b3650d8fd651..a525e9aaaffa 100644
> --- a/drivers/dma-buf/heaps/system_heap.c
> +++ b/drivers/dma-buf/heaps/system_heap.c
> @@ -10,17 +10,25 @@
> * Andrew F. Davis <afd@ti.com>
> */
>
> +#include <linux/cc_platform.h>
> #include <linux/dma-buf.h>
> #include <linux/dma-mapping.h>
> #include <linux/dma-heap.h>
> #include <linux/err.h>
> #include <linux/highmem.h>
> +#include <linux/mem_encrypt.h>
> #include <linux/mm.h>
> +#include <linux/set_memory.h>
> #include <linux/module.h>
> +#include <linux/pgtable.h>
> #include <linux/scatterlist.h>
> #include <linux/slab.h>
> #include <linux/vmalloc.h>
>
> +struct system_heap_priv {
> + bool decrypted;
> +};
Hi Jiri,
I wonder if it'd better to call this cc_decrypted (or I guess
cc_shared based on Robin's comment in the previous patch) like the DMA
attr? There's a separate effort for "restricted" heaps with TEE for
(encrypted) video playback, which doesn't involve VMs or RDMA. I think
the cc_ prefix might help avoid any confusion between the usecase here
and restricted heaps.
> +
> struct system_heap_buffer {
> struct dma_heap *heap;
> struct list_head attachments;
> @@ -29,6 +37,7 @@ struct system_heap_buffer {
> struct sg_table sg_table;
> int vmap_cnt;
> void *vaddr;
> + bool decrypted;
> };
>
> struct dma_heap_attachment {
> @@ -36,6 +45,7 @@ struct dma_heap_attachment {
> struct sg_table table;
> struct list_head list;
> bool mapped;
> + bool decrypted;
> };
>
> #define LOW_ORDER_GFP (GFP_HIGHUSER | __GFP_ZERO)
> @@ -52,6 +62,34 @@ static gfp_t order_flags[] = {HIGH_ORDER_GFP, HIGH_ORDER_GFP, LOW_ORDER_GFP};
> static const unsigned int orders[] = {8, 4, 0};
> #define NUM_ORDERS ARRAY_SIZE(orders)
>
> +static int system_heap_set_page_decrypted(struct page *page)
> +{
> + unsigned long addr = (unsigned long)page_address(page);
> + unsigned int nr_pages = 1 << compound_order(page);
> + int ret;
> +
> + ret = set_memory_decrypted(addr, nr_pages);
> + if (ret)
> + pr_warn_ratelimited("dma-buf system heap: failed to decrypt page at %p\n",
> + page_address(page));
> +
> + return ret;
> +}
> +
> +static int system_heap_set_page_encrypted(struct page *page)
> +{
> + unsigned long addr = (unsigned long)page_address(page);
> + unsigned int nr_pages = 1 << compound_order(page);
> + int ret;
> +
> + ret = set_memory_encrypted(addr, nr_pages);
> + if (ret)
> + pr_warn_ratelimited("dma-buf system heap: failed to re-encrypt page at %p, leaking memory\n",
> + page_address(page));
> +
> + return ret;
> +}
> +
> static int dup_sg_table(struct sg_table *from, struct sg_table *to)
> {
> struct scatterlist *sg, *new_sg;
> @@ -90,6 +128,7 @@ static int system_heap_attach(struct dma_buf *dmabuf,
> a->dev = attachment->dev;
> INIT_LIST_HEAD(&a->list);
> a->mapped = false;
> + a->decrypted = buffer->decrypted;
>
> attachment->priv = a;
>
> @@ -119,9 +158,11 @@ static struct sg_table *system_heap_map_dma_buf(struct dma_buf_attachment *attac
> {
> struct dma_heap_attachment *a = attachment->priv;
> struct sg_table *table = &a->table;
> + unsigned long attrs;
> int ret;
>
> - ret = dma_map_sgtable(attachment->dev, table, direction, 0);
> + attrs = a->decrypted ? DMA_ATTR_CC_DECRYPTED : 0;
> + ret = dma_map_sgtable(attachment->dev, table, direction, attrs);
> if (ret)
> return ERR_PTR(ret);
>
> @@ -188,8 +229,13 @@ static int system_heap_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
> unsigned long addr = vma->vm_start;
> unsigned long pgoff = vma->vm_pgoff;
> struct scatterlist *sg;
> + pgprot_t prot;
> int i, ret;
>
> + prot = vma->vm_page_prot;
> + if (buffer->decrypted)
> + prot = pgprot_decrypted(prot);
> +
> for_each_sgtable_sg(table, sg, i) {
> unsigned long n = sg->length >> PAGE_SHIFT;
>
> @@ -206,8 +252,7 @@ static int system_heap_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
> if (addr + size > vma->vm_end)
> size = vma->vm_end - addr;
>
> - ret = remap_pfn_range(vma, addr, page_to_pfn(page),
> - size, vma->vm_page_prot);
> + ret = remap_pfn_range(vma, addr, page_to_pfn(page), size, prot);
> if (ret)
> return ret;
>
> @@ -225,6 +270,7 @@ static void *system_heap_do_vmap(struct system_heap_buffer *buffer)
> struct page **pages = vmalloc(sizeof(struct page *) * npages);
> struct page **tmp = pages;
> struct sg_page_iter piter;
> + pgprot_t prot;
> void *vaddr;
>
> if (!pages)
> @@ -235,7 +281,10 @@ static void *system_heap_do_vmap(struct system_heap_buffer *buffer)
> *tmp++ = sg_page_iter_page(&piter);
> }
>
> - vaddr = vmap(pages, npages, VM_MAP, PAGE_KERNEL);
> + prot = PAGE_KERNEL;
> + if (buffer->decrypted)
> + prot = pgprot_decrypted(prot);
> + vaddr = vmap(pages, npages, VM_MAP, prot);
> vfree(pages);
>
> if (!vaddr)
> @@ -296,6 +345,14 @@ static void system_heap_dma_buf_release(struct dma_buf *dmabuf)
> for_each_sgtable_sg(table, sg, i) {
> struct page *page = sg_page(sg);
>
> + /*
> + * Intentionally leak pages that cannot be re-encrypted
> + * to prevent decrypted memory from being reused.
> + */
> + if (buffer->decrypted &&
> + system_heap_set_page_encrypted(page))
> + continue;
> +
> __free_pages(page, compound_order(page));
> }
> sg_free_table(table);
> @@ -347,6 +404,8 @@ static struct dma_buf *system_heap_allocate(struct dma_heap *heap,
> DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
> unsigned long size_remaining = len;
> unsigned int max_order = orders[0];
> + struct system_heap_priv *priv = dma_heap_get_drvdata(heap);
> + bool decrypted = priv->decrypted;
> struct dma_buf *dmabuf;
> struct sg_table *table;
> struct scatterlist *sg;
> @@ -362,6 +421,7 @@ static struct dma_buf *system_heap_allocate(struct dma_heap *heap,
> mutex_init(&buffer->lock);
> buffer->heap = heap;
> buffer->len = len;
> + buffer->decrypted = decrypted;
>
> INIT_LIST_HEAD(&pages);
> i = 0;
> @@ -396,6 +456,14 @@ static struct dma_buf *system_heap_allocate(struct dma_heap *heap,
> list_del(&page->lru);
> }
>
> + if (decrypted) {
> + for_each_sgtable_sg(table, sg, i) {
> + ret = system_heap_set_page_decrypted(sg_page(sg));
> + if (ret)
> + goto free_pages;
> + }
> + }
> +
> /* create the dmabuf */
> exp_info.exp_name = dma_heap_get_name(heap);
> exp_info.ops = &system_heap_buf_ops;
> @@ -413,6 +481,13 @@ static struct dma_buf *system_heap_allocate(struct dma_heap *heap,
> for_each_sgtable_sg(table, sg, i) {
> struct page *p = sg_page(sg);
>
> + /*
> + * Intentionally leak pages that cannot be re-encrypted
> + * to prevent decrypted memory from being reused.
> + */
> + if (buffer->decrypted &&
> + system_heap_set_page_encrypted(p))
> + continue;
> __free_pages(p, compound_order(p));
> }
> sg_free_table(table);
> @@ -428,6 +503,14 @@ static const struct dma_heap_ops system_heap_ops = {
> .allocate = system_heap_allocate,
> };
>
> +static struct system_heap_priv system_heap_priv = {
> + .decrypted = false,
> +};
> +
> +static struct system_heap_priv system_heap_cc_decrypted_priv = {
> + .decrypted = true,
> +};
> +
> static int __init system_heap_create(void)
> {
> struct dma_heap_export_info exp_info;
> @@ -435,8 +518,18 @@ static int __init system_heap_create(void)
>
> exp_info.name = "system";
> exp_info.ops = &system_heap_ops;
> - exp_info.priv = NULL;
> + exp_info.priv = &system_heap_priv;
> +
> + sys_heap = dma_heap_add(&exp_info);
> + if (IS_ERR(sys_heap))
> + return PTR_ERR(sys_heap);
> +
> + if (IS_ENABLED(CONFIG_HIGHMEM) ||
> + !cc_platform_has(CC_ATTR_MEM_ENCRYPT))
> + return 0;
>
> + exp_info.name = "system_cc_decrypted";
> + exp_info.priv = &system_heap_cc_decrypted_priv;
> sys_heap = dma_heap_add(&exp_info);
> if (IS_ERR(sys_heap))
> return PTR_ERR(sys_heap);
> --
> 2.51.1
>
^ permalink raw reply
* Re: [PATCH v2 5/5] x86/virt/tdx: Remove kexec docs
From: Huang, Kai @ 2026-03-23 22:41 UTC (permalink / raw)
To: Edgecombe, Rick P, seanjc@google.com, bp@alien8.de,
x86@kernel.org, kas@kernel.org, hpa@zytor.com, mingo@redhat.com,
Verma, Vishal L, dave.hansen@linux.intel.com, tglx@kernel.org,
pbonzini@redhat.com
Cc: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
linux-kernel@vger.kernel.org
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-5-87a36409e051@intel.com>
On Mon, 2026-03-23 at 14:59 -0600, Vishal Verma wrote:
> From: Rick Edgecombe <rick.p.edgecombe@intel.com>
>
> Recent changes have removed the hard limitations for using kexec and
> TDX together. So remove the section in the TDX docs.
>
> Users on partial write erratums will need an updated TDX module to
> handle the rare edge cases. The docs do not currently provide any
> guidance on recommended TDX module versions, so don't keep a whole
> section around to document this interaction.
>
> Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
> Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
>
Acked-by: Kai Huang <kai.huang@intel.com>
^ permalink raw reply
* Re: [PATCH v2 4/5] x86/tdx: Disable the TDX module during kexec and kdump
From: Huang, Kai @ 2026-03-23 22:41 UTC (permalink / raw)
To: Edgecombe, Rick P, seanjc@google.com, bp@alien8.de,
x86@kernel.org, kas@kernel.org, hpa@zytor.com, mingo@redhat.com,
Verma, Vishal L, dave.hansen@linux.intel.com, tglx@kernel.org,
pbonzini@redhat.com
Cc: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
linux-kernel@vger.kernel.org
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-4-87a36409e051@intel.com>
On Mon, 2026-03-23 at 14:59 -0600, Vishal Verma wrote:
> Use the TDH.SYS.DISABLE SEAMCALL, which disables the TDX module,
> reclaims all memory resources assigned to TDX, and clears any
> partial-write induced poison, to allow kexec and kdump on platforms with
> the partial write errata.
>
> On TDX-capable platforms with the partial write erratum, kexec has been
> disabled because the new kernel could hit a machine check reading a
> previously poisoned memory location.
>
> Later TDX modules support TDH.SYS.DISABLE, which disables the module and
> reclaims all TDX memory resources, allowing the new kernel to re-initialize
> TDX from scratch. This operation also clears the old memory, cleaning up
> any poison.
>
> Add tdx_sys_disable() to tdx_shutdown(), which is called in the
> syscore_shutdown path for kexec. This is done just before tdx_shutdown()
> disables VMX on all CPUs.
>
> For kdump, call tdx_sys_disable() in the crash path before
> x86_virt_emergency_disable_virtualization_cpu() does VMXOFF.
>
> Since this clears any poison on TDX-managed memory, remove the
> X86_BUG_TDX_PW_MCE check in machine_kexec() that blocked kexec on
> partial write errata platforms.
>
> Co-developed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
> Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
> Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
>
Acked-by: Kai Huang <kai.huang@intel.com>
^ permalink raw reply
* Re: [PATCH v2 3/5] x86/virt/tdx: Add SEAMCALL wrapper for TDH.SYS.DISABLE
From: Huang, Kai @ 2026-03-23 22:40 UTC (permalink / raw)
To: Edgecombe, Rick P, seanjc@google.com, bp@alien8.de,
x86@kernel.org, kas@kernel.org, hpa@zytor.com, mingo@redhat.com,
Verma, Vishal L, dave.hansen@linux.intel.com, tglx@kernel.org,
pbonzini@redhat.com
Cc: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
linux-kernel@vger.kernel.org
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-3-87a36409e051@intel.com>
On Mon, 2026-03-23 at 14:59 -0600, Vishal Verma wrote:
> Some early TDX-capable platforms have an erratum where a partial write
> to TDX private memory can cause a machine check on a subsequent read.
> On these platforms, kexec and kdump have been disabled in these cases,
> because the old kernel cannot safely hand off TDX state to the new
> kernel. Later TDX modules support the TDH.SYS.DISABLE SEAMCALL, which
> provides a way to cleanly disable TDX and allow kexec to proceed.
>
> The new SEAMCALL has an enumeration bit, but that is ignored. It is
> expected that users will be using the latest TDX module, and the failure
> mode for running the missing SEAMCALL on an older module is not fatal.
>
> This can be a long running operation, and the time needed largely
> depends on the amount of memory that has been allocated to TDs. If all
> TDs have been destroyed prior to the sys_disable call, then it is fast,
> with only needing to override the TDX module memory.
>
> After the SEAMCALL completes, the TDX module is disabled and all memory
> resources allocated to TDX are freed and reset. The next kernel can then
> re-initialize the TDX module from scratch via the normal TDX bring-up
> sequence.
>
> The SEAMCALL can return two different error codes that expect a retry.
> - TDX_INTERRUPTED_RESUMABLE can be returned in the case of a host
> interrupt. However, it will not return until it makes some forward
> progress, so we can expect to complete even in the case of interrupt
> storms.
> - TDX_SYS_BUSY will be returned on contention with other TDH.SYS.*
> SEAMCALLs, however a side effect of TDH.SYS.DISABLE is that it will
> block other SEAMCALLs once it gets going. So this contention will be
> short lived.
>
> So loop infinitely on either of these error codes, until success or other
> error.
>
> Co-developed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
> Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
> Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
>
Acked-by: Kai Huang <kai.huang@intel.com>
^ permalink raw reply
* Re: [PATCH v2 3/5] x86/virt/tdx: Add SEAMCALL wrapper for TDH.SYS.DISABLE
From: Verma, Vishal L @ 2026-03-23 21:54 UTC (permalink / raw)
To: Edgecombe, Rick P, seanjc@google.com, bp@alien8.de,
x86@kernel.org, kas@kernel.org, hpa@zytor.com, mingo@redhat.com,
dave.hansen@linux.intel.com, tglx@kernel.org, pbonzini@redhat.com
Cc: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
linux-kernel@vger.kernel.org
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-3-87a36409e051@intel.com>
On Mon, 2026-03-23 at 14:59 -0600, Vishal Verma wrote:
>
[..]
> +void tdx_sys_disable(void)
> +{
> + struct tdx_module_args args = {};
> + u64 ret;
> +
> + /*
> + * Don't loop forever.
> + * - TDX_INTERRUPTED_RESUMABLE guarantees forward progress between
> + * calls.
> + * - TDX_SYS_BUSY could transiently contend with TDH.SYS.* SEAMCALLs,
> + * but will lock out future ones.
> + *
> + * This is a 'destructive' SEAMCALL, in that no other SEAMCALL can be
> + * run after this until a full reinitialization is done.
> + */
> + do {
> + ret = seamcall(TDH_SYS_DISABLE, &args);
> + } while (ret == TDX_INTERRUPTED_RESUMABLE || ret == TDX_SYS_BUSY);
> +
> + /*
> + * Print SEAMCALL failures, but not SW-defined error codes
> + * (SEAMCALL faulted with #GP/#UD, TDX not supported).
> + */
> + if (ret && (ret & TDX_SW_ERROR) != TDX_SW_ERROR)
> + pr_err("TDH.SYS.DISABLE failed: 0x%016llx\n", ret);
> +}
Note - old TDX modules that don't implement this SEAMCALL produce a
message like:
virt/tdx: TDH.SYS.DISABLE failed: 0xc000010000000000
Where that code translates to TDX_OPERAND_INVALID.
This also serves as a nudge that the module should be updated.
It might be worth including a blurb about this in the commit message -
something like below. This could be included when applying, or I can
send an updated version with this if it is acceptable.
---
An error is printed if the SEAMCALL fails with anything other than the
error codes that cause retries, or 'synthesized' error codes produced
for #GP or #UD. e.g., an old module that has been properly initialized,
that doesn't implement SYS_DISABLE, returns TDX_OPERAND_INVALID. This
prints:
virt/tdx: TDH.SYS.DISABLE failed: 0xc000010000000000
But a system that doesn't have any TDX support at all doesn't print
anything.
^ permalink raw reply
* [PATCH v2 5/5] x86/virt/tdx: Remove kexec docs
From: Vishal Verma @ 2026-03-23 20:59 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Kiryl Shutsemau, Rick Edgecombe,
Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, Vishal Verma
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-0-87a36409e051@intel.com>
From: Rick Edgecombe <rick.p.edgecombe@intel.com>
Recent changes have removed the hard limitations for using kexec and
TDX together. So remove the section in the TDX docs.
Users on partial write erratums will need an updated TDX module to
handle the rare edge cases. The docs do not currently provide any
guidance on recommended TDX module versions, so don't keep a whole
section around to document this interaction.
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
---
Documentation/arch/x86/tdx.rst | 7 -------
1 file changed, 7 deletions(-)
diff --git a/Documentation/arch/x86/tdx.rst b/Documentation/arch/x86/tdx.rst
index ff6b110291bc..1a3b5bac1021 100644
--- a/Documentation/arch/x86/tdx.rst
+++ b/Documentation/arch/x86/tdx.rst
@@ -138,13 +138,6 @@ If the platform has such erratum, the kernel prints additional message in
machine check handler to tell user the machine check may be caused by
kernel bug on TDX private memory.
-Kexec
-~~~~~~~
-
-Currently kexec doesn't work on the TDX platforms with the aforementioned
-erratum. It fails when loading the kexec kernel image. Otherwise it
-works normally.
-
Interaction vs S3 and deeper states
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
2.53.0
^ permalink raw reply related
* [PATCH v2 4/5] x86/tdx: Disable the TDX module during kexec and kdump
From: Vishal Verma @ 2026-03-23 20:59 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Kiryl Shutsemau, Rick Edgecombe,
Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, Vishal Verma
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-0-87a36409e051@intel.com>
Use the TDH.SYS.DISABLE SEAMCALL, which disables the TDX module,
reclaims all memory resources assigned to TDX, and clears any
partial-write induced poison, to allow kexec and kdump on platforms with
the partial write errata.
On TDX-capable platforms with the partial write erratum, kexec has been
disabled because the new kernel could hit a machine check reading a
previously poisoned memory location.
Later TDX modules support TDH.SYS.DISABLE, which disables the module and
reclaims all TDX memory resources, allowing the new kernel to re-initialize
TDX from scratch. This operation also clears the old memory, cleaning up
any poison.
Add tdx_sys_disable() to tdx_shutdown(), which is called in the
syscore_shutdown path for kexec. This is done just before tdx_shutdown()
disables VMX on all CPUs.
For kdump, call tdx_sys_disable() in the crash path before
x86_virt_emergency_disable_virtualization_cpu() does VMXOFF.
Since this clears any poison on TDX-managed memory, remove the
X86_BUG_TDX_PW_MCE check in machine_kexec() that blocked kexec on
partial write errata platforms.
Co-developed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
---
arch/x86/kernel/crash.c | 2 ++
arch/x86/kernel/machine_kexec_64.c | 16 ----------------
arch/x86/virt/vmx/tdx/tdx.c | 1 +
3 files changed, 3 insertions(+), 16 deletions(-)
diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c
index cd796818d94d..623d4474631a 100644
--- a/arch/x86/kernel/crash.c
+++ b/arch/x86/kernel/crash.c
@@ -38,6 +38,7 @@
#include <linux/kdebug.h>
#include <asm/cpu.h>
#include <asm/reboot.h>
+#include <asm/tdx.h>
#include <asm/intel_pt.h>
#include <asm/crash.h>
#include <asm/cmdline.h>
@@ -112,6 +113,7 @@ void native_machine_crash_shutdown(struct pt_regs *regs)
crash_smp_send_stop();
+ tdx_sys_disable();
x86_virt_emergency_disable_virtualization_cpu();
/*
diff --git a/arch/x86/kernel/machine_kexec_64.c b/arch/x86/kernel/machine_kexec_64.c
index 0590d399d4f1..c3f4a389992d 100644
--- a/arch/x86/kernel/machine_kexec_64.c
+++ b/arch/x86/kernel/machine_kexec_64.c
@@ -347,22 +347,6 @@ int machine_kexec_prepare(struct kimage *image)
unsigned long reloc_end = (unsigned long)__relocate_kernel_end;
int result;
- /*
- * Some early TDX-capable platforms have an erratum. A kernel
- * partial write (a write transaction of less than cacheline
- * lands at memory controller) to TDX private memory poisons that
- * memory, and a subsequent read triggers a machine check.
- *
- * On those platforms the old kernel must reset TDX private
- * memory before jumping to the new kernel otherwise the new
- * kernel may see unexpected machine check. For simplicity
- * just fail kexec/kdump on those platforms.
- */
- if (boot_cpu_has_bug(X86_BUG_TDX_PW_MCE)) {
- pr_info_once("Not allowed on platform with tdx_pw_mce bug\n");
- return -EOPNOTSUPP;
- }
-
/* Setup the identity mapped 64bit page table */
result = init_pgtable(image, __pa(control_page));
if (result)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 3a76000dec7a..aaf22a87717a 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -252,6 +252,7 @@ static void tdx_shutdown_cpu(void *ign)
static void tdx_shutdown(void *ign)
{
+ tdx_sys_disable();
on_each_cpu(tdx_shutdown_cpu, NULL, 1);
}
--
2.53.0
^ permalink raw reply related
* [PATCH v2 3/5] x86/virt/tdx: Add SEAMCALL wrapper for TDH.SYS.DISABLE
From: Vishal Verma @ 2026-03-23 20:59 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Kiryl Shutsemau, Rick Edgecombe,
Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, Vishal Verma
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-0-87a36409e051@intel.com>
Some early TDX-capable platforms have an erratum where a partial write
to TDX private memory can cause a machine check on a subsequent read.
On these platforms, kexec and kdump have been disabled in these cases,
because the old kernel cannot safely hand off TDX state to the new
kernel. Later TDX modules support the TDH.SYS.DISABLE SEAMCALL, which
provides a way to cleanly disable TDX and allow kexec to proceed.
The new SEAMCALL has an enumeration bit, but that is ignored. It is
expected that users will be using the latest TDX module, and the failure
mode for running the missing SEAMCALL on an older module is not fatal.
This can be a long running operation, and the time needed largely
depends on the amount of memory that has been allocated to TDs. If all
TDs have been destroyed prior to the sys_disable call, then it is fast,
with only needing to override the TDX module memory.
After the SEAMCALL completes, the TDX module is disabled and all memory
resources allocated to TDX are freed and reset. The next kernel can then
re-initialize the TDX module from scratch via the normal TDX bring-up
sequence.
The SEAMCALL can return two different error codes that expect a retry.
- TDX_INTERRUPTED_RESUMABLE can be returned in the case of a host
interrupt. However, it will not return until it makes some forward
progress, so we can expect to complete even in the case of interrupt
storms.
- TDX_SYS_BUSY will be returned on contention with other TDH.SYS.*
SEAMCALLs, however a side effect of TDH.SYS.DISABLE is that it will
block other SEAMCALLs once it gets going. So this contention will be
short lived.
So loop infinitely on either of these error codes, until success or other
error.
Co-developed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
---
arch/x86/include/asm/shared/tdx_errno.h | 1 +
arch/x86/include/asm/tdx.h | 3 +++
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 28 ++++++++++++++++++++++++++++
4 files changed, 33 insertions(+)
diff --git a/arch/x86/include/asm/shared/tdx_errno.h b/arch/x86/include/asm/shared/tdx_errno.h
index 8bf6765cf082..246b4fd54a48 100644
--- a/arch/x86/include/asm/shared/tdx_errno.h
+++ b/arch/x86/include/asm/shared/tdx_errno.h
@@ -15,6 +15,7 @@
#define TDX_NON_RECOVERABLE_TD_NON_ACCESSIBLE 0x6000000500000000ULL
#define TDX_NON_RECOVERABLE_TD_WRONG_APIC_MODE 0x6000000700000000ULL
#define TDX_INTERRUPTED_RESUMABLE 0x8000000300000000ULL
+#define TDX_SYS_BUSY 0x8000020200000000ULL
#define TDX_OPERAND_INVALID 0xC000010000000000ULL
#define TDX_OPERAND_BUSY 0x8000020000000000ULL
#define TDX_PREVIOUS_TLB_EPOCH_BUSY 0x8000020100000000ULL
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 7674fc530090..a0a4a15142fc 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -172,6 +172,8 @@ static inline int pg_level_to_tdx_sept_level(enum pg_level level)
return level - 1;
}
+void tdx_sys_disable(void);
+
u64 tdh_vp_enter(struct tdx_vp *vp, struct tdx_module_args *args);
u64 tdh_mng_addcx(struct tdx_td *td, struct page *tdcs_page);
u64 tdh_mem_page_add(struct tdx_td *td, u64 gpa, struct page *page, struct page *source, u64 *ext_err1, u64 *ext_err2);
@@ -203,6 +205,7 @@ static inline void tdx_init(void) { }
static inline u32 tdx_get_nr_guest_keyids(void) { return 0; }
static inline const char *tdx_dump_mce_info(struct mce *m) { return NULL; }
static inline const struct tdx_sys_info *tdx_get_sysinfo(void) { return NULL; }
+static inline void tdx_sys_disable(void) { }
#endif /* CONFIG_INTEL_TDX_HOST */
#endif /* !__ASSEMBLER__ */
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index dde219c823b4..e2cf2dd48755 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -46,6 +46,7 @@
#define TDH_PHYMEM_PAGE_WBINVD 41
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG 45
+#define TDH_SYS_DISABLE 69
/*
* SEAMCALL leaf:
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 0802d0fd18a4..3a76000dec7a 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -37,6 +37,7 @@
#include <asm/msr.h>
#include <asm/cpufeature.h>
#include <asm/tdx.h>
+#include <asm/shared/tdx_errno.h>
#include <asm/cpu_device_id.h>
#include <asm/processor.h>
#include <asm/mce.h>
@@ -1940,3 +1941,30 @@ u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, struct page *page)
return seamcall(TDH_PHYMEM_PAGE_WBINVD, &args);
}
EXPORT_SYMBOL_FOR_KVM(tdh_phymem_page_wbinvd_hkid);
+
+void tdx_sys_disable(void)
+{
+ struct tdx_module_args args = {};
+ u64 ret;
+
+ /*
+ * Don't loop forever.
+ * - TDX_INTERRUPTED_RESUMABLE guarantees forward progress between
+ * calls.
+ * - TDX_SYS_BUSY could transiently contend with TDH.SYS.* SEAMCALLs,
+ * but will lock out future ones.
+ *
+ * This is a 'destructive' SEAMCALL, in that no other SEAMCALL can be
+ * run after this until a full reinitialization is done.
+ */
+ do {
+ ret = seamcall(TDH_SYS_DISABLE, &args);
+ } while (ret == TDX_INTERRUPTED_RESUMABLE || ret == TDX_SYS_BUSY);
+
+ /*
+ * Print SEAMCALL failures, but not SW-defined error codes
+ * (SEAMCALL faulted with #GP/#UD, TDX not supported).
+ */
+ if (ret && (ret & TDX_SW_ERROR) != TDX_SW_ERROR)
+ pr_err("TDH.SYS.DISABLE failed: 0x%016llx\n", ret);
+}
--
2.53.0
^ permalink raw reply related
* [PATCH v2 2/5] x86/virt/tdx: Pull kexec cache flush logic into arch/x86
From: Vishal Verma @ 2026-03-23 20:59 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Kiryl Shutsemau, Rick Edgecombe,
Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, Kai Huang, Vishal Verma
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-0-87a36409e051@intel.com>
From: Rick Edgecombe <rick.p.edgecombe@intel.com>
KVM tries to take care of some required cache flushing earlier in the
kexec path in order to be kind to some long standing races that can occur
later in the operation. Until recently, VMXOFF was handled within KVM.
Since VMX being enabled is required to make a SEAMCALL, it had the best
per-cpu scoped operation to plug the flushing into. So it is kicked off
from there.
This early kexec cache flushing in KVM happens via a syscore shutdown
callback. Now that VMX enablement control has moved to arch/x86, which has
grown its own syscore shutdown callback, it no longer make sense for it to
live in KVM. It fits better with the TDX enablement managing code.
In addition, future changes will add a SEAMCALL that happens immediately
before VMXOFF, which means the cache flush in KVM will be too late to
flush the cache before the last SEAMCALL. So move it to the newly added TDX
arch/x86 syscore shutdown handler.
Since tdx_cpu_flush_cache_for_kexec() is no longer needed by KVM, make it
static and remove the export. Since it is also not part of an operation
spread across disparate components, remove the redundant comments and
verbose naming.
In the existing KVM based code, CPU offline also funnels through
tdx_cpu_flush_cache_for_kexec(). So the centralization to the arch/x86
syscore shutdown callback elides this CPU offline time behavior. However,
WBINVD is already generally done at CPU offline as matter of course. So
don't bother adding TDX specific logic for this, and rely on the normal
WBINVD to handle it.
Acked-by: Kai Huang <kai.huang@intel.com>
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
---
arch/x86/include/asm/tdx.h | 6 ------
arch/x86/kvm/vmx/tdx.c | 10 ----------
arch/x86/virt/vmx/tdx/tdx.c | 39 ++++++++++++++++++++-------------------
3 files changed, 20 insertions(+), 35 deletions(-)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 2917b3451491..7674fc530090 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -205,11 +205,5 @@ static inline const char *tdx_dump_mce_info(struct mce *m) { return NULL; }
static inline const struct tdx_sys_info *tdx_get_sysinfo(void) { return NULL; }
#endif /* CONFIG_INTEL_TDX_HOST */
-#ifdef CONFIG_KEXEC_CORE
-void tdx_cpu_flush_cache_for_kexec(void);
-#else
-static inline void tdx_cpu_flush_cache_for_kexec(void) { }
-#endif
-
#endif /* !__ASSEMBLER__ */
#endif /* _ASM_X86_TDX_H */
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index b7264b533feb..50a5cfdbd33e 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -440,16 +440,6 @@ void tdx_disable_virtualization_cpu(void)
tdx_flush_vp(&arg);
}
local_irq_restore(flags);
-
- /*
- * Flush cache now if kexec is possible: this is necessary to avoid
- * having dirty private memory cachelines when the new kernel boots,
- * but WBINVD is a relatively expensive operation and doing it during
- * kexec can exacerbate races in native_stop_other_cpus(). Do it
- * now, since this is a safe moment and there is going to be no more
- * TDX activity on this CPU from this point on.
- */
- tdx_cpu_flush_cache_for_kexec();
}
#define TDX_SEAMCALL_RETRIES 10000
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index cb9b3210ab71..0802d0fd18a4 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -224,8 +224,28 @@ static int tdx_offline_cpu(unsigned int cpu)
return 0;
}
+static void tdx_cpu_flush_cache(void)
+{
+ lockdep_assert_preemption_disabled();
+
+ if (!this_cpu_read(cache_state_incoherent))
+ return;
+
+ wbinvd();
+ this_cpu_write(cache_state_incoherent, false);
+}
+
static void tdx_shutdown_cpu(void *ign)
{
+ /*
+ * Flush cache now if kexec is possible: this is necessary to avoid
+ * having dirty private memory cachelines when the new kernel boots,
+ * but WBINVD is a relatively expensive operation and doing it during
+ * kexec can exacerbate races in native_stop_other_cpus(). Do it
+ * now, since this is a safe moment and there is going to be no more
+ * TDX activity on this CPU from this point on.
+ */
+ tdx_cpu_flush_cache();
x86_virt_put_ref(X86_FEATURE_VMX);
}
@@ -1920,22 +1940,3 @@ u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, struct page *page)
return seamcall(TDH_PHYMEM_PAGE_WBINVD, &args);
}
EXPORT_SYMBOL_FOR_KVM(tdh_phymem_page_wbinvd_hkid);
-
-#ifdef CONFIG_KEXEC_CORE
-void tdx_cpu_flush_cache_for_kexec(void)
-{
- lockdep_assert_preemption_disabled();
-
- if (!this_cpu_read(cache_state_incoherent))
- return;
-
- /*
- * Private memory cachelines need to be clean at the time of
- * kexec. Write them back now, as the caller promises that
- * there should be no more SEAMCALLs on this CPU.
- */
- wbinvd();
- this_cpu_write(cache_state_incoherent, false);
-}
-EXPORT_SYMBOL_FOR_KVM(tdx_cpu_flush_cache_for_kexec);
-#endif
--
2.53.0
^ permalink raw reply related
* [PATCH v2 1/5] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h>
From: Vishal Verma @ 2026-03-23 20:59 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Kiryl Shutsemau, Rick Edgecombe,
Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, Vishal Verma, Kiryl Shutsemau,
Kiryl Shutsemau
In-Reply-To: <20260323-fuller_tdx_kexec_support-v2-0-87a36409e051@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>
---
arch/x86/include/asm/shared/tdx.h | 1 +
.../{kvm/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(-)
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 a149740b24e8..2917b3451491 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 b5cd2ffb303e..ac8323a68b16 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.53.0
^ permalink raw reply related
* [PATCH v2 0/5] Fuller TDX kexec support
From: Vishal Verma @ 2026-03-23 20:59 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Kiryl Shutsemau, Rick Edgecombe,
Sean Christopherson, Paolo Bonzini
Cc: linux-kernel, linux-coco, kvm, Vishal Verma, Kai Huang,
Kiryl Shutsemau
Hi,
This series adds a couple of cool things -
1. Allow kexec and kdump on systems with the partial write errata
2. Allow using TDX in the second (kexec'ed) kernel
It has been waiting for VMXON refactor to land because the implementation
is much cleaner on top of that.
KVM folks, just a few deletions on your side and the long discussed moving
of tdx_errno.h. Tip folks and reviewers, the changes here are pretty small.
Optimistically, I'm hoping we can iterate this quickly and see it off the
list in the next few weeks.
Background
==========
Some early TDX-capable platforms have an erratum where a partial write
to TDX private memory can cause a machine check on a subsequent read.
Currently, kexec and kdump are disabled on these platforms because the
new (or kdump) kernel may trip over these, causing a machine check.
Future TDX modules will support TDH.SYS.DISABLE SEAMCALL, which disables
the TDX module and reclaims all memory resources allocated to TDX, and
cleans up any poison. After this SEAMCALL succeeds, the new kernel
can also re-initialize the TDX module from scratch via the normal bring-up
sequence.
It is probably worth mentioning that this is a different kind of cleanup
than the WBINVD stuff that was the cause of all the fuss in the earlier
kexec enabling. The WBINVD is flushing private keyid cachelines so they
are not later written back over the new kernels memory. It needs to happen
after the last SEAMCALL that might have produced them. So this new
SEAMCALL is for something else, but also needs to be slotted with respect
to WBINVD.
Implementation
==============
The series adds:
1. A pre-requisite patch to move TDX error code definitions to a
shared location so that TDX_INTERRUPTED_RESUMABLE etc. are
accessible from arch/x86/virt/vmx/tdx/. This comes from the Dynamic
PAMT series [0], but is also needed by some other series, and can
benefit them all from an early merge.
2. A preparatory patch to move some straggling stuff into arch/x86 in the
wake of the VMXON series.
3. A tdx_sys_disable() helper that wraps calls TDH.SYS.DISABLE with a
retry loop to handle TDX_INTERRUPTED_RESUMABLE.
4. Integration into the kexec path: Remove the check for partial write
errata platforms as this is addressed by the SEAMCALL clearing any
poisoned memory locations. Call tdx_sys_disable() in tdx_shutdown
which is called via syscore ops in the kexec path. Call
tdx_sys_disable() in native_machine_crash_shutdown() to cover the
crash (kdump) path.
5. A patch to update (remove) the kexec section in TDX docs.
Testing
=======
The implementation is based on the draft TDX module spec available at
[1], and was smoke tested with an engineering build of the TDX module
that supports the new SEAMCALL. The new kernel was able to initialize
the TDX module successfully:
kvm: exiting hardware virtualization
kexec_core: Starting new kernel
Linux version 7.0.0-rc2-g0077f702b21c...
...
virt/tdx: 1034220 KB allocated for PAMT
virt/tdx: TDX-Module initialized
All the other TDX CI tests pass, and some other scenarios that were
manually tested and also pass or behave as expected:
- Running on a completely non-TDX system
- Running on a TDX capable system with an old module
- Running on a TDX capable system where the module hasn't been
initialized
During development further testing was done by mocking up the new
SEAMCALL to introduce delays and exercise the retry loops, combined with
kexec, kdump, reboot and shutdown flows.
Base
====
This series is based on the vmxon branch Sean pushed to kvm_x86,
kvm-x86-vmxon-2026.03.05.
[0]: https://lore.kernel.org/kvm/20260129011517.3545883-11-seanjc@google.com/
[1]: https://cdrdv2.intel.com/v1/dl/getContent/871617
---
Changes in v2:
- Use patch 1 from the DPAMT series with other feedback (Kai)
- Fix commit message typo (s/adjust_/adjust /)
- In patch 2, drop "too late to be helpful" in favor of something more
explicit (Kai)
- Fix commit message typo in patch 2 (s/both/bother/)
- In patch 2, add a bit about dropping the TDX specific WBINVD (Kai)
- Reword some commit logs to use the imperative mood (Chao)
- Kai raised offline that TDH.SYS.DISABLE can return TDX_SYS_BUSY too.
In theory this could happen if another SEAMCALL happens concurrently,
however that contention should be short lived. Update the loop to
continue on a TDX_SYS_BUSY error code too. (Kai)
- Patch 3: Add a print for SEAMCALL errors reported by the TDX module
(excluding SW errors like #UD and #GP) (Kiryl)
- Patch 3: Add a sentence to the log about skipping enumeration for the
new SEAMCALL (Kiryl)
- Adjust the patch 4 subject (Chao)
- Add a new patch to update the docs (Chao)
- Smoke test with TDX module engineering build with the new SEAMCALL.
Kiryl Shutsemau (1):
x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h>
Rick Edgecombe (2):
x86/virt/tdx: Pull kexec cache flush logic into arch/x86
x86/virt/tdx: Remove kexec docs
Vishal Verma (2):
x86/virt/tdx: Add SEAMCALL wrapper for TDH.SYS.DISABLE
x86/tdx: Disable the TDX module during kexec and kdump
Documentation/arch/x86/tdx.rst | 7 -------
arch/x86/include/asm/shared/tdx.h | 1 +
arch/x86/{kvm/vmx => include/asm/shared}/tdx_errno.h | 29 +++++++++++++++++++++++------
arch/x86/include/asm/tdx.h | 30 +++---------------------------
arch/x86/kvm/vmx/tdx.h | 1 -
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/kernel/crash.c | 2 ++
arch/x86/kernel/machine_kexec_64.c | 16 ----------------
arch/x86/kvm/vmx/tdx.c | 10 ----------
arch/x86/virt/vmx/tdx/tdx.c | 54 ++++++++++++++++++++++++++++++++++++++++++------------
10 files changed, 72 insertions(+), 79 deletions(-)
--
2.53.0
---
Kiryl Shutsemau (1):
x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h>
Rick Edgecombe (2):
x86/virt/tdx: Pull kexec cache flush logic into arch/x86
x86/virt/tdx: Remove kexec docs
Vishal Verma (2):
x86/virt/tdx: Add SEAMCALL wrapper for TDH.SYS.DISABLE
x86/tdx: Disable the TDX module during kexec and kdump
Documentation/arch/x86/tdx.rst | 7 ---
arch/x86/include/asm/shared/tdx.h | 1 +
.../{kvm/vmx => include/asm/shared}/tdx_errno.h | 29 +++++++++---
arch/x86/include/asm/tdx.h | 30 ++----------
arch/x86/kvm/vmx/tdx.h | 1 -
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/kernel/crash.c | 2 +
arch/x86/kernel/machine_kexec_64.c | 16 -------
arch/x86/kvm/vmx/tdx.c | 10 ----
arch/x86/virt/vmx/tdx/tdx.c | 54 +++++++++++++++++-----
10 files changed, 72 insertions(+), 79 deletions(-)
---
base-commit: f630de1f8d70d7e29e12bc25dc63f9c5f771dc59
change-id: 20260317-fuller_tdx_kexec_support-bc79694678be
Best regards,
--
Vishal Verma <vishal.l.verma@intel.com>
^ permalink raw reply
* Re: [PATCH v2 09/19] PCI/TSM: Support creating encrypted MMIO descriptors via TDISP Report
From: Jason Gunthorpe @ 2026-03-23 18:20 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: <228298b7-76bc-4ffb-bec0-0c35fd487c4c@amd.com>
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.
Jason
^ permalink raw reply
* Re: [PATCH v2 08/19] PCI/TSM: Add "evidence" support
From: Jason Gunthorpe @ 2026-03-23 18:18 UTC (permalink / raw)
To: Dan Williams
Cc: Lukas Wunner, Jakub Kicinski, linux-coco, linux-pci, gregkh, aik,
aneesh.kumar, yilun.xu, bhelgaas, alistair23, Donald Hunter
In-Reply-To: <69ba5abaf3ae7_7ee3100d@dwillia2-mobl4.notmuch>
On Wed, Mar 18, 2026 at 12:56:42AM -0700, Dan Williams wrote:
> Lukas Wunner wrote:
> [..]
> > At this point perhaps your conclusion is that netlink isn't the right
> > protocol for this job. It's great for transmitting sets of small items,
> > some of which may be optional, but it's obviously not well-suited for
> > large items.
>
> Right, and sysfs is not well suited for transaction in/out semantics.
>
> > Jason Gunthorpe was quite insistent that we use netlink and you know
>
> Jason can of course correct me, but the insistence was less that netlink
> was the right tool for the job, and more that sysfs was the wrong tool
> for the job.
+1
netlink is a good starting point, but if it isn't fitting well then
the next stop would be an ioctl char dev..
Jason
^ permalink raw reply
* Re: [PATCH v2 03/19] device core: Introduce confidential device acceptance
From: Jason Gunthorpe @ 2026-03-23 18:14 UTC (permalink / raw)
To: Dan Williams
Cc: Greg KH, linux-coco, linux-pci, aik, aneesh.kumar, yilun.xu,
bhelgaas, alistair23, lukas, Christoph Hellwig, Marek Szyprowski,
Robin Murphy, Roman Kisel, Samuel Ortiz, Rafael J. Wysocki,
Danilo Krummrich
In-Reply-To: <69b4baab2b950_b2b610013@dwillia2-mobl4.notmuch>
On Fri, Mar 13, 2026 at 06:32:27PM -0700, Dan Williams wrote:
> The problem is that for all the buses that do not currently have a
> "device authorization" concept only userspace can decide that a device
> should skip bind by default. For that, I propose module autoprobe policy
> [1]. Not yet convinced the kernel needs its own per-device "no bind"
> policy.
I think it is just part of the broader definition of the level that
extends into the iommu and so on. It makes sense to have this kind of
no-binding security level, IMHO.
> > The DMA API just wants a flag in the struct device that says if the
> > device can access encrypted memory or only decrypted.
>
> You mean separate "trusted to access private" and "currently enabled to
> access private" properties? I am trying to think of a situation where
> "dev->trust >= 3" and a flag saying "disable bouncing for encrypted
> memory" would ever disagree.
I'm steering the trust level toward more of an acceptance criteria.
If the trust level is you have access to private memory but the device
can't actually do that then fail the trust level change.
Same for the reverse, if the trust level says no private memory and the
device is T=1 then fail the trust level change.
> That bit though has lock-to-run consistency expectations. So if the
> kernel does not yet fully trust the device by time the relying party is
> satisfied, and the uAPI to transition the device into the TCB (level 3)
> is driver-core generic it raises TOCTOU issues in my mind. The
> driver-core would need to ask the bus "user now trusts this device, do
> you?".
Huh? No, there is no concept of trust in the kernel. The userspace
setting level 3 is "I now ack that this device is trusted", there is
no further trust cross check. If TSM side says it is in RUN/T=1 then
we are done.
If we fall out of RUN then the level auto-resets back to 0 and
userspace has to go around and fix it again. (ignoring driver RAS)
> Aneesh and I are currently debating on Discord whether the kernel needs
> to protect against guest userspace confusing itself.
Userspace that controls acceptance must be part of the TCB or the
whole model is fully broken. If your guest userspace is so security
broken it can accept devices it doesn't mean to then just forget it.
> However, to Aneesh's point we could protect against that with a
> transactional uAPI like netlink that can express "trust if and only if
> the device has not been relocked before final accept" by passing a
> cookie obtained at lock to accept. That would be awkward to coordinate
> with driver-core generic uAPI for trust.
You could, but why make it so complicated? The whole LOCKED/RUN thing
is already supposed to deal with TOCTOU, doesn't it? The CSP cannot
trick a device to fall out of LOCKED an the re-enter LOCKED without
the VM knowing.
The VM attacking itself on something as security critical as device
accepance can't be in scope :\
> > This way nothing is coupled and the kernel can offer all kinds of
> > different uAPI for device verification. Userspaces picks the
> > appropriate one and acks it with the level change.
>
> Thunderbolt already has authorized uAPI. I expect adding dev->trust
> support to thunderbolt is more related to ATS privilege and private
> memory privilege.
It brings it into the whole 'measure the device and then decide what
to do with it' framework. The trust level is still the generic ack
that the device is allowed the participate in the system with whatever
level of security.
Jason
^ permalink raw reply
* Re: [PATCH v3 2/3] swiotlb: dma: its: Enforce host page-size alignment for shared buffers
From: Aneesh Kumar K.V @ 2026-03-23 13:52 UTC (permalink / raw)
To: kernel test robot, linux-kernel, iommu, linux-coco,
linux-arm-kernel, kvmarm
Cc: llvm, oe-kbuild-all, Marc Zyngier, Thomas Gleixner,
Catalin Marinas, Will Deacon, Jason Gunthorpe, Marek Szyprowski,
Robin Murphy, Steven Price, Suzuki K Poulose
In-Reply-To: <202603092320.JgtItJg0-lkp@intel.com>
kernel test robot <lkp@intel.com> writes:
> Hi Aneesh,
>
> kernel test robot noticed the following build errors:
>
> [auto build test ERROR on arm64/for-next/core]
> [also build test ERROR on tip/irq/core arm/for-next arm/fixes kvmarm/next soc/for-next linus/master v7.0-rc3 next-20260306]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch#_base_tree_information]
>
> url: https://github.com/intel-lab-lkp/linux/commits/Aneesh-Kumar-K-V-Arm/dma-direct-swiotlb-handle-swiotlb-alloc-free-outside-__dma_direct_alloc_pages/20260309-182834
> base: https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-next/core
> patch link: https://lore.kernel.org/r/20260309102625.2315725-3-aneesh.kumar%40kernel.org
> patch subject: [PATCH v3 2/3] swiotlb: dma: its: Enforce host page-size alignment for shared buffers
> config: i386-randconfig-012-20260309 (https://download.01.org/0day-ci/archive/20260309/202603092320.JgtItJg0-lkp@intel.com/config)
> compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260309/202603092320.JgtItJg0-lkp@intel.com/reproduce)
>
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/oe-kbuild-all/202603092320.JgtItJg0-lkp@intel.com/
>
> All errors (new ones prefixed by >>):
>
> In file included from arch/x86/kernel/asm-offsets.c:9:
> In file included from include/linux/crypto.h:15:
> In file included from include/linux/completion.h:12:
> In file included from include/linux/swait.h:7:
> In file included from include/linux/spinlock.h:59:
> In file included from include/linux/irqflags.h:18:
> In file included from arch/x86/include/asm/irqflags.h:5:
> In file included from arch/x86/include/asm/processor-flags.h:6:
>>> include/linux/mem_encrypt.h:60:9: error: use of undeclared identifier 'PAGE_SIZE'
> 60 | return PAGE_SIZE;
> | ^
>>> include/linux/mem_encrypt.h:66:9: error: call to undeclared function 'ALIGN'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
> 66 | return ALIGN(size, mem_decrypt_granule_size());
> | ^
>
Fixed this by including
modified include/linux/mem_encrypt.h
@@ -11,6 +11,8 @@
#define __MEM_ENCRYPT_H__
#ifndef __ASSEMBLY__
+#include <linux/align.h>
+#include <vdso/page.h>
#ifdef CONFIG_ARCH_HAS_MEM_ENCRYPT
Will include this in the next patch update.
The other alternative is to switch that to a macro
modified include/linux/mem_encrypt.h
@@ -55,16 +55,10 @@
#endif
#ifndef mem_decrypt_granule_size
-static inline size_t mem_decrypt_granule_size(void)
-{
- return PAGE_SIZE;
-}
+#define mem_decrypt_granule_size() PAGE_SIZE
#endif
-static inline size_t mem_decrypt_align(size_t size)
-{
- return ALIGN(size, mem_decrypt_granule_size());
-}
+#define mem_decrypt_align(size) ALIGN((size), mem_decrypt_granule_size())
^ permalink raw reply
* Re: [PATCH v13 17/48] arm64: RMI: Allocate/free RECs to match vCPUs
From: Suzuki K Poulose @ 2026-03-23 11:56 UTC (permalink / raw)
To: Steven Price, kvm, kvmarm
Cc: Catalin Marinas, Marc Zyngier, Will Deacon, James Morse,
Oliver Upton, Zenghui Yu, linux-arm-kernel, linux-kernel,
Joey Gouly, Alexandru Elisei, Christoffer Dall, Fuad Tabba,
linux-coco, Ganapatrao Kulkarni, Gavin Shan, Shanker Donthineni,
Alper Gun, Aneesh Kumar K . V, Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-18-steven.price@arm.com>
Hi,
This is a NOTE for the fellow reviewers. This patch will undergo some
changes to handle how the AUX granules (metadata storage for RMM) for
the REC (aka vCPU) will be donated/reclaimed with RMM-v2.0.
Please see PATCH 48/48 for the changes with the new Stateful RMI
Operations (SRO), for REC create and destory.
I have tried to mark the areas affected below.
On 18/03/2026 15:53, Steven Price wrote:
> The RMM maintains a data structure known as the Realm Execution Context
> (or REC). It is similar to struct kvm_vcpu and tracks the state of the
> virtual CPUs. KVM must delegate memory and request the structures are
> created when vCPUs are created, and suitably tear down on destruction.
>
> RECs must also be supplied with addition pages - auxiliary (or AUX)
> granules - for storing the larger registers state (e.g. for SVE). The
> number of AUX granules for a REC depends on the parameters with which
> the Realm was created - the RMM makes this information available via the
> RMI_REC_AUX_COUNT call performed after creating the Realm Descriptor (RD).
>
> Note that only some of register state for the REC can be set by KVM, the
> rest is defined by the RMM (zeroed). The register state then cannot be
> changed by KVM after the REC is created (except when the guest
> explicitly requests this e.g. by performing a PSCI call).
>
> Signed-off-by: Steven Price <steven.price@arm.com>
> ---
> Changes since v12:
> * Use the new range-based delegation RMI.
> Changes since v11:
> * Remove the KVM_ARM_VCPU_REC feature. User space no longer needs to
> configure each VCPU separately, RECs are created on the first VCPU
> run of the guest.
> Changes since v9:
> * Size the aux_pages array according to the PAGE_SIZE of the host.
> Changes since v7:
> * Add comment explaining the aux_pages array.
> * Rename "undeleted_failed" variable to "should_free" to avoid a
> confusing double negative.
> Changes since v6:
> * Avoid reporting the KVM_ARM_VCPU_REC feature if the guest isn't a
> realm guest.
> * Support host page size being larger than RMM's granule size when
> allocating/freeing aux granules.
> Changes since v5:
> * Separate the concept of vcpu_is_rec() and
> kvm_arm_vcpu_rec_finalized() by using the KVM_ARM_VCPU_REC feature as
> the indication that the VCPU is a REC.
> Changes since v2:
> * Free rec->run earlier in kvm_destroy_realm() and adapt to previous patches.
> ---
> arch/arm64/include/asm/kvm_emulate.h | 2 +-
> arch/arm64/include/asm/kvm_host.h | 3 +
> arch/arm64/include/asm/kvm_rmi.h | 21 +++
> arch/arm64/kvm/arm.c | 10 +-
> arch/arm64/kvm/reset.c | 1 +
> arch/arm64/kvm/rmi.c | 196 +++++++++++++++++++++++++++
> 6 files changed, 230 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
> index 39310d9b4e16..d194d91fbc2a 100644
> --- a/arch/arm64/include/asm/kvm_emulate.h
> +++ b/arch/arm64/include/asm/kvm_emulate.h
> @@ -708,7 +708,7 @@ static inline bool kvm_realm_is_created(struct kvm *kvm)
>
> static inline bool vcpu_is_rec(struct kvm_vcpu *vcpu)
> {
> - return false;
> + return kvm_is_realm(vcpu->kvm);
> }
>
> #endif /* __ARM64_KVM_EMULATE_H__ */
> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> index 9267a2f2d65b..64304848aad4 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -924,6 +924,9 @@ struct kvm_vcpu_arch {
>
> /* Per-vcpu TLB for VNCR_EL2 -- NULL when !NV */
> struct vncr_tlb *vncr_tlb;
> +
> + /* Realm meta data */
> + struct realm_rec rec;
> };
>
> /*
> diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
> index 6c13847480f7..4e2c61e71a38 100644
> --- a/arch/arm64/include/asm/kvm_rmi.h
> +++ b/arch/arm64/include/asm/kvm_rmi.h
> @@ -63,6 +63,26 @@ struct realm {
> unsigned int ia_bits;
> };
>
> +/**
> + * struct realm_rec - Additional per VCPU data for a Realm
> + *
> + * @mpidr: MPIDR (Multiprocessor Affinity Register) value to identify this VCPU
> + * @rec_page: Kernel VA of the RMM's private page for this REC
> + * @aux_pages: Additional pages private to the RMM for this REC
> + * @run: Kernel VA of the RmiRecRun structure shared with the RMM
> + */
> +struct realm_rec {
> + unsigned long mpidr;
> + void *rec_page;
> + /*
> + * REC_PARAMS_AUX_GRANULES is the maximum number of 4K granules that
> + * the RMM can require. The array is sized to be large enough for the
> + * maximum number of host sized pages that could be required.
> + */
> + struct page *aux_pages[(REC_PARAMS_AUX_GRANULES * SZ_4K) >> PAGE_SHIFT];
> + struct rec_run *run;
> +};
> +
> void kvm_init_rmi(void);
> u32 kvm_realm_ipa_limit(void);
>
> @@ -70,6 +90,7 @@ int kvm_init_realm_vm(struct kvm *kvm);
> int kvm_activate_realm(struct kvm *kvm);
> void kvm_destroy_realm(struct kvm *kvm);
> void kvm_realm_destroy_rtts(struct kvm *kvm);
> +void kvm_destroy_rec(struct kvm_vcpu *vcpu);
>
> static inline bool kvm_realm_is_private_address(struct realm *realm,
> unsigned long addr)
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index c8e51ed009c0..8c50ebd9fba0 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -575,6 +575,8 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
> /* Force users to call KVM_ARM_VCPU_INIT */
> vcpu_clear_flag(vcpu, VCPU_INITIALIZED);
>
> + vcpu->arch.rec.mpidr = INVALID_HWID;
> +
> vcpu->arch.mmu_page_cache.gfp_zero = __GFP_ZERO;
>
> /* Set up the timer */
> @@ -1549,7 +1551,7 @@ int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_level,
> return -EINVAL;
> }
>
> -static unsigned long system_supported_vcpu_features(void)
> +static unsigned long system_supported_vcpu_features(struct kvm *kvm)
> {
> unsigned long features = KVM_VCPU_VALID_FEATURES;
>
> @@ -1587,7 +1589,7 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,
> return -ENOENT;
> }
>
> - if (features & ~system_supported_vcpu_features())
> + if (features & ~system_supported_vcpu_features(vcpu->kvm))
> return -EINVAL;
>
> /*
> @@ -1609,6 +1611,10 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,
> if (test_bit(KVM_ARM_VCPU_HAS_EL2, &features))
> return -EINVAL;
>
> + /* Realms are incompatible with AArch32 */
> + if (vcpu_is_rec(vcpu))
> + return -EINVAL;
> +
> return 0;
> }
>
> diff --git a/arch/arm64/kvm/reset.c b/arch/arm64/kvm/reset.c
> index 959532422d3a..4bbf58892928 100644
> --- a/arch/arm64/kvm/reset.c
> +++ b/arch/arm64/kvm/reset.c
> @@ -161,6 +161,7 @@ void kvm_arm_vcpu_destroy(struct kvm_vcpu *vcpu)
> free_page((unsigned long)vcpu->arch.ctxt.vncr_array);
> kfree(vcpu->arch.vncr_tlb);
> kfree(vcpu->arch.ccsidr);
> + kvm_destroy_rec(vcpu);
> }
>
> static void kvm_vcpu_reset_sve(struct kvm_vcpu *vcpu)
> diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
> index 937fababf960..6daf14c4b413 100644
> --- a/arch/arm64/kvm/rmi.c
> +++ b/arch/arm64/kvm/rmi.c
> @@ -207,6 +207,28 @@ static int get_start_level(struct realm *realm)
> return 4 - stage2_pgtable_levels(realm->ia_bits);
> }
>
> +static int delegate_range(phys_addr_t phys, unsigned long size)
> +{
> + unsigned long ret;
> + unsigned long top = phys + size;
> + unsigned long out_top;
> +
> + while (phys < top) {
> + ret = rmi_granule_range_delegate(phys, top, &out_top);
> + if (ret == RMI_SUCCESS)
> + phys = out_top;
> + else if (ret != RMI_BUSY && ret != RMI_BLOCKED)
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +static int delegate_page(phys_addr_t phys)
> +{
> + return delegate_range(phys, PAGE_SIZE);
> +}
> +
> static int undelegate_range(phys_addr_t phys, unsigned long size)
> {
> unsigned long ret;
> @@ -372,9 +394,177 @@ static int realm_ensure_created(struct kvm *kvm)
> return -ENXIO;
> }
>
--->8--- Cut here
> +static void free_rec_aux(struct page **aux_pages,
> + unsigned int num_aux)
> +{
> + unsigned int i;
> + unsigned int page_count = 0;
> +
> + for (i = 0; i < num_aux; i++) {
> + struct page *aux_page = aux_pages[page_count++];
> + phys_addr_t aux_page_phys = page_to_phys(aux_page);
> +
> + if (!WARN_ON(undelegate_page(aux_page_phys)))
> + __free_page(aux_page);
> + aux_page_phys += PAGE_SIZE;
> + }
> +}
> +
> +static int alloc_rec_aux(struct page **aux_pages,
> + u64 *aux_phys_pages,
> + unsigned int num_aux)
> +{
> + struct page *aux_page;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < num_aux; i++) {
> + phys_addr_t aux_page_phys;
> +
> + aux_page = alloc_page(GFP_KERNEL);
> + if (!aux_page) {
> + ret = -ENOMEM;
> + goto out_err;
> + }
> +
> + aux_page_phys = page_to_phys(aux_page);
> + if (delegate_page(aux_page_phys)) {
> + ret = -ENXIO;
> + goto err_undelegate;
> + }
> + aux_phys_pages[i] = aux_page_phys;
> + aux_pages[i] = aux_page;
> + }
> +
> + return 0;
> +err_undelegate:
> + while (i > 0) {
> + i--;
> + if (WARN_ON(undelegate_page(aux_phys_pages[i]))) {
> + /* Leak the page if the undelegate fails */
> + goto out_err;
> + }
> + }
> + __free_page(aux_page);
> +out_err:
> + free_rec_aux(aux_pages, i);
> + return ret;
> +}
> +
---8<---
> +static int kvm_create_rec(struct kvm_vcpu *vcpu)
> +{
...
---8>--- CUT here
> + r = alloc_rec_aux(rec->aux_pages, params->aux, realm->num_aux);
> + if (r)
> + goto out_undelegate_rmm_rec;
> +
> + params->num_rec_aux = realm->num_aux;
---8<---
> + params->mpidr = mpidr;
> +
> + if (rmi_rec_create(virt_to_phys(realm->rd),
> + rec_page_phys,
> + virt_to_phys(params))) {
> + r = -ENXIO;
> + goto out_free_rec_aux;
> + }
> +
> + rec->mpidr = mpidr;
> +
> + free_page((unsigned long)params);
> + return 0;
> +
> +out_free_rec_aux:
> + free_rec_aux(rec->aux_pages, realm->num_aux);
> +out_undelegate_rmm_rec:
> + if (WARN_ON(undelegate_page(rec_page_phys)))
> + rec->rec_page = NULL;
> +out_free_pages:
> + free_page((unsigned long)rec->run);
> + free_page((unsigned long)rec->rec_page);
> + free_page((unsigned long)params);
> + rec->run = NULL;
> + return r;
> +}
> +
> +void kvm_destroy_rec(struct kvm_vcpu *vcpu)
> +{
> + struct realm *realm = &vcpu->kvm->arch.realm;
> + struct realm_rec *rec = &vcpu->arch.rec;
> + unsigned long rec_page_phys;
> +
> + if (!vcpu_is_rec(vcpu))
> + return;
> +
> + if (!rec->run) {
> + /* Nothing to do if the VCPU hasn't been finalized */
> + return;
> + }
> +
> + free_page((unsigned long)rec->run);
> +
> + rec_page_phys = virt_to_phys(rec->rec_page);
> +
--8>-- Cut here
> + /*
> + * The REC and any AUX pages cannot be reclaimed until the REC is
> + * destroyed. So if the REC destroy fails then the REC page and any AUX
> + * pages will be leaked.
> + */
> + if (WARN_ON(rmi_rec_destroy(rec_page_phys)))
> + return;
> +
> + free_rec_aux(rec->aux_pages, realm->num_aux);
---8<---
Suzuki
> +
> + free_delegated_page(rec_page_phys);
> +}
> +
> int kvm_activate_realm(struct kvm *kvm)
> {
> struct realm *realm = &kvm->arch.realm;
> + struct kvm_vcpu *vcpu;
> + unsigned long i;
> int ret;
>
> if (kvm_realm_state(kvm) >= REALM_STATE_ACTIVE)
> @@ -397,6 +587,12 @@ int kvm_activate_realm(struct kvm *kvm)
> /* Mark state as dead in case we fail */
> WRITE_ONCE(realm->state, REALM_STATE_DEAD);
>
> + kvm_for_each_vcpu(i, vcpu, kvm) {
> + ret = kvm_create_rec(vcpu);
> + if (ret)
> + return ret;
> + }
> +
> ret = rmi_realm_activate(virt_to_phys(realm->rd));
> if (ret)
> return -ENXIO;
^ permalink raw reply
* Re: [PATCH v13 24/48] arm64: RMI: Allow populating initial contents
From: Suzuki K Poulose @ 2026-03-23 11:32 UTC (permalink / raw)
To: Steven Price, kvm, kvmarm
Cc: Catalin Marinas, Marc Zyngier, Will Deacon, James Morse,
Oliver Upton, Zenghui Yu, linux-arm-kernel, linux-kernel,
Joey Gouly, Alexandru Elisei, Christoffer Dall, Fuad Tabba,
linux-coco, Ganapatrao Kulkarni, Gavin Shan, Shanker Donthineni,
Alper Gun, Aneesh Kumar K . V, Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-25-steven.price@arm.com>
On 18/03/2026 15:53, Steven Price wrote:
> The VMM needs to populate the realm with some data before starting (e.g.
> a kernel and initrd). This is measured by the RMM and used as part of
> the attestation later on.
>
> Signed-off-by: Steven Price <steven.price@arm.com>
> ---
> Changes since v12:
> * The ioctl now updates the structure with the amount populated rather
> than returning this through the ioctl return code.
> * Use the new RMM v2.0 range based RMI calls.
> * Adapt to upstream changes in kvm_gmem_populate().
> Changes since v11:
> * The multiplex CAP is gone and there's a new ioctl which makes use of
> the generic kvm_gmem_populate() functionality.
> Changes since v7:
> * Improve the error codes.
> * Other minor changes from review.
> Changes since v6:
> * Handle host potentially having a larger page size than the RMM
> granule.
> * Drop historic "par" (protected address range) from
> populate_par_region() - it doesn't exist within the current
> architecture.
> * Add a cond_resched() call in kvm_populate_realm().
> Changes since v5:
> * Refactor to use PFNs rather than tracking struct page in
> realm_create_protected_data_page().
> * Pull changes from a later patch (in the v5 series) for accessing
> pages from a guest memfd.
> * Do the populate in chunks to avoid holding locks for too long and
> triggering RCU stall warnings.
> ---
> arch/arm64/include/asm/kvm_rmi.h | 4 ++
> arch/arm64/kvm/Kconfig | 1 +
> arch/arm64/kvm/arm.c | 13 ++++
> arch/arm64/kvm/rmi.c | 111 +++++++++++++++++++++++++++++++
> 4 files changed, 129 insertions(+)
>
> diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
> index 46b0cbe6c202..bf663bb240c4 100644
> --- a/arch/arm64/include/asm/kvm_rmi.h
> +++ b/arch/arm64/include/asm/kvm_rmi.h
> @@ -96,6 +96,10 @@ int kvm_rec_enter(struct kvm_vcpu *vcpu);
> int kvm_rec_pre_enter(struct kvm_vcpu *vcpu);
> int handle_rec_exit(struct kvm_vcpu *vcpu, int rec_run_status);
>
> +struct kvm_arm_rmi_populate;
> +
> +int kvm_arm_rmi_populate(struct kvm *kvm,
> + struct kvm_arm_rmi_populate *arg);
> void kvm_realm_unmap_range(struct kvm *kvm,
> unsigned long ipa,
> unsigned long size,
> diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
> index 1cac6dfc0972..b495dfd3a8b4 100644
> --- a/arch/arm64/kvm/Kconfig
> +++ b/arch/arm64/kvm/Kconfig
> @@ -39,6 +39,7 @@ menuconfig KVM
> select GUEST_PERF_EVENTS if PERF_EVENTS
> select KVM_GUEST_MEMFD
> select KVM_GENERIC_MEMORY_ATTRIBUTES
> + select HAVE_KVM_ARCH_GMEM_POPULATE
> help
> Support hosting virtualized guest machines.
>
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index badb94b398bc..43d05da7e694 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -2089,6 +2089,19 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
> return -EFAULT;
> return kvm_vm_ioctl_get_reg_writable_masks(kvm, &range);
> }
> + case KVM_ARM_RMI_POPULATE: {
> + struct kvm_arm_rmi_populate req;
> + int ret;
> +
> + if (!kvm_is_realm(kvm))
> + return -ENXIO;
> + if (copy_from_user(&req, argp, sizeof(req)))
> + return -EFAULT;
> + ret = kvm_arm_rmi_populate(kvm, &req);
> + if (copy_to_user(argp, &req, sizeof(req)))
> + return -EFAULT;
> + return ret;
> + }
> default:
> return -EINVAL;
> }
> diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
> index 13eed6f0b9eb..b48f4e12e4e0 100644
> --- a/arch/arm64/kvm/rmi.c
> +++ b/arch/arm64/kvm/rmi.c
> @@ -718,6 +718,80 @@ void kvm_realm_unmap_range(struct kvm *kvm, unsigned long start,
> realm_unmap_private_range(kvm, start, end, may_block);
> }
>
> +static int realm_create_protected_data_page(struct kvm *kvm,
minor nit: To align with the RMM ABI, could we rename this to :
realm_data_map_init() ?
> + unsigned long ipa,
> + kvm_pfn_t dst_pfn,
> + kvm_pfn_t src_pfn,
> + unsigned long flags)
> +{
> + struct realm *realm = &kvm->arch.realm;
> + phys_addr_t rd = virt_to_phys(realm->rd);
> + phys_addr_t dst_phys, src_phys;
> + int ret;
> +
> + dst_phys = __pfn_to_phys(dst_pfn);
> + src_phys = __pfn_to_phys(src_pfn);
> +
> + if (delegate_page(dst_phys))
> + return -ENXIO;
> +
> + ret = rmi_rtt_data_map_init(rd, dst_phys, ipa, src_phys, flags);
> + if (RMI_RETURN_STATUS(ret) == RMI_ERROR_RTT) {
> + /* Create missing RTTs and retry */
> + int level = RMI_RETURN_INDEX(ret);
> +
> + KVM_BUG_ON(level == RMM_RTT_MAX_LEVEL, kvm);
A buggy VMM can trigger this by calling RMI_POPULATE twice ? Should we
return -ENXIO here rather ? The delegate_page() above could prevent
normal cases, but is the VMM allowed to somehow trigger a "pfn" change
backing the KVM ? Either way, this need not be Fatal ?
Otherwise looks good to me.
Suzuki
> +
> + ret = realm_create_rtt_levels(realm, ipa, level,
> + RMM_RTT_MAX_LEVEL, NULL);
> + if (!ret) {
> + ret = rmi_rtt_data_map_init(rd, dst_phys, ipa, src_phys,
> + flags);
> + }
> + }
> +
> + if (ret) {
> + if (WARN_ON(undelegate_page(dst_phys))) {
> + /* Undelegate failed, so we leak the page */
> + get_page(pfn_to_page(dst_pfn));
> + }
> + }
> +
> + return ret;
> +}
> +
> +static int populate_region_cb(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
> + struct page *src_page, void *opaque)
> +{
> + unsigned long data_flags = *(unsigned long *)opaque;
> + phys_addr_t ipa = gfn_to_gpa(gfn);
> +
> + if (!src_page)
> + return -EOPNOTSUPP;
> +
> + return realm_create_protected_data_page(kvm, ipa, pfn,
> + page_to_pfn(src_page),
> + data_flags);
> +}
> +
> +static long populate_region(struct kvm *kvm,
> + gfn_t base_gfn,
> + unsigned long pages,
> + u64 uaddr,
> + unsigned long data_flags)
> +{
> + long ret = 0;
> +
> + mutex_lock(&kvm->slots_lock);
> + mmap_read_lock(current->mm);
> + ret = kvm_gmem_populate(kvm, base_gfn, u64_to_user_ptr(uaddr), pages,
> + populate_region_cb, &data_flags);
> + mmap_read_unlock(current->mm);
> + mutex_unlock(&kvm->slots_lock);
> +
> + return ret;
> +}
> +
> enum ripas_action {
> RIPAS_INIT,
> RIPAS_SET,
> @@ -815,6 +889,43 @@ static int realm_ensure_created(struct kvm *kvm)
> return -ENXIO;
> }
>
> +int kvm_arm_rmi_populate(struct kvm *kvm,
> + struct kvm_arm_rmi_populate *args)
> +{
> + unsigned long data_flags = 0;
> + unsigned long ipa_start = args->base;
> + unsigned long ipa_end = ipa_start + args->size;
> + long pages_populated;
> + int ret;
> +
> + if (args->reserved ||
> + (args->flags & ~KVM_ARM_RMI_POPULATE_FLAGS_MEASURE) ||
> + !IS_ALIGNED(ipa_start, PAGE_SIZE) ||
> + !IS_ALIGNED(ipa_end, PAGE_SIZE) ||
> + !IS_ALIGNED(args->source_uaddr, PAGE_SIZE))
> + return -EINVAL;
> +
> + ret = realm_ensure_created(kvm);
> + if (ret)
> + return ret;
> +
> + if (args->flags & KVM_ARM_RMI_POPULATE_FLAGS_MEASURE)
> + data_flags |= RMI_MEASURE_CONTENT;
> +
> + pages_populated = populate_region(kvm, gpa_to_gfn(ipa_start),
> + args->size >> PAGE_SHIFT,
> + args->source_uaddr, data_flags);
> +
> + if (pages_populated < 0)
> + return pages_populated;
> +
> + args->size -= pages_populated << PAGE_SHIFT;
> + args->source_uaddr += pages_populated << PAGE_SHIFT;
> + args->base += pages_populated << PAGE_SHIFT;
> +
> + return 0;
> +}
> +
> static void kvm_complete_ripas_change(struct kvm_vcpu *vcpu)
> {
> struct kvm *kvm = vcpu->kvm;
^ permalink raw reply
* Re: [PATCH v13 05/48] arm64: RMI: Temporarily add SMCs from RMM v1.0 spec
From: Suzuki K Poulose @ 2026-03-23 10:30 UTC (permalink / raw)
To: Marc Zyngier, Steven Price
Cc: kvm, kvmarm, Catalin Marinas, Will Deacon, James Morse,
Oliver Upton, Zenghui Yu, linux-arm-kernel, linux-kernel,
Joey Gouly, Alexandru Elisei, Christoffer Dall, Fuad Tabba,
linux-coco, Ganapatrao Kulkarni, Gavin Shan, Shanker Donthineni,
Alper Gun, Aneesh Kumar K . V, Emi Kisanuki, Vishal Annapurve
In-Reply-To: <87cy0xco7f.wl-maz@kernel.org>
Hi Marc,
On 21/03/2026 13:21, Marc Zyngier wrote:
> On Wed, 18 Mar 2026 15:53:29 +0000,
> Steven Price <steven.price@arm.com> wrote:
>>
>> Not all the functionality has been migrated to the v2.0 specification,
>> so for now we still rely on some v1.0 SMCs. This mixture is not
>> spec-compliant, but is necessary until an updated RMM is available.
>>
>
> This sort of things really makes it awkward to review the series.
I agree, that this makes it painful to review as it doesn't get you a
clear picture of what will stay or what will change and is not the best
use of the precious time of the Maintainers. We will make sure to flag
the appropriate portions in the next iteration. Apologies!
>
> Do you really expect the reviewers to mentally triage what is current
> and what is only throwaway code? I want to see patches that are merge
> candidates, not patches that are only there to cope with the broken
> state of the RMM.
Agree, we could clearly mark the "areas" of code that we anticipate to
change and by the next posting this will be clearly marked.
> If extra hacks are required to work with the current Franken-RMM, keep
> them as a separate series that doesn't pollute what is targeted at
> upstream.
In fact there are only a few areas that would change with a true RMM-
v2.0 spec compliant firmware. But yes, I acknowledge that feedback from
the maintainers would be fedback to the RMM spec and this might trigger
minor changes to align with them.
>
> It also means that any testing you have done will be invalidated when
> the one true RMM shows up...
Agreed, True, but not very much of the functionality/
spec are changing until we land in fully compliant 2.0 RMM.
See more on this below.
>
> "This is great."
>
> M.
> diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
> index 8a42b83218f8..049d71470486 100644
> --- a/arch/arm64/include/asm/rmi_smc.h
> +++ b/arch/arm64/include/asm/rmi_smc.h
> @@ -30,14 +30,15 @@
> #define SMC_RMI_REC_ENTER SMC_RMI_CALL(0x015c)
> #define SMC_RMI_RTT_CREATE SMC_RMI_CALL(0x015d)
> #define SMC_RMI_RTT_DESTROY SMC_RMI_CALL(0x015e)
> +#define SMC_RMI_RTT_MAP_UNPROTECTED SMC_RMI_CALL(0x015f) //
>
> #define SMC_RMI_RTT_READ_ENTRY SMC_RMI_CALL(0x0161)
> -
> +#define SMC_RMI_RTT_UNMAP_UNPROTECTED SMC_RMI_CALL(0x0162) //
The above two RMI commands help with mapping/unmapping Unprotected
memory and will be replaced with two renamed commands with "Range based"
ABI => SMC_RMI_RTT_UNPROT_{MAP,UNMAP}. So, as far as the KVM code is
concerned, we do the mapping a granule at a time (without hugetlb
support) and this is simply a change in the command in the true 2.0 RMM.
> #define SMC_RMI_RTT_DEV_VALIDATE SMC_RMI_CALL(0x0163)
> #define SMC_RMI_PSCI_COMPLETE SMC_RMI_CALL(0x0164)
> #define SMC_RMI_FEATURES SMC_RMI_CALL(0x0165)
> #define SMC_RMI_RTT_FOLD SMC_RMI_CALL(0x0166)
> -
> +#define SMC_RMI_REC_AUX_COUNT SMC_RMI_CALL(0x0167) //
This is part of the REC creation, where we donate AUXilliary granules
for the VCPU to save state. This is replaced by the SRO method, which we
have a WIP path at the top of the series and it will be cleaned up in
the next version.
We wanted to send this version out which is the first one with RMM-v2.0
spec, which has addressed the concerns and feedback around the RMM-v1.0
spec. But we have clearly felt short of marking "what is volatile" and
what is stable, to help the review. We will address this in the next
iteration.
Thanks for taking the time to respond.
Kind regards
Suzuki
^ permalink raw reply
* Re: [PATCH v13 20/48] arm64: RMI: Handle realm enter/exit
From: Suzuki K Poulose @ 2026-03-23 10:03 UTC (permalink / raw)
To: Steven Price, kvm, kvmarm
Cc: Catalin Marinas, Marc Zyngier, Will Deacon, James Morse,
Oliver Upton, Zenghui Yu, linux-arm-kernel, linux-kernel,
Joey Gouly, Alexandru Elisei, Christoffer Dall, Fuad Tabba,
linux-coco, Ganapatrao Kulkarni, Gavin Shan, Shanker Donthineni,
Alper Gun, Aneesh Kumar K . V, Emi Kisanuki, Vishal Annapurve
In-Reply-To: <02582577-3701-4d5c-88c6-3ce0b3328377@arm.com>
On 20/03/2026 16:32, Steven Price wrote:
> On 20/03/2026 14:08, Suzuki K Poulose wrote:
>> On 18/03/2026 15:53, Steven Price wrote:
>>> Entering a realm is done using a SMC call to the RMM. On exit the
>>> exit-codes need to be handled slightly differently to the normal KVM
>>> path so define our own functions for realm enter/exit and hook them
>>> in if the guest is a realm guest.
>>>
>>> Signed-off-by: Steven Price <steven.price@arm.com>
>>> Reviewed-by: Gavin Shan <gshan@redhat.com>
>>> ---
>>> Changes since v12:
>>> * Call guest_state_{enter,exit}_irqoff() around rmi_rec_enter().
>>> * Add handling of the IRQ exception case where IRQs need to be briefly
>>> enabled before exiting guest timing.
>>> Changes since v8:
>>> * Introduce kvm_rec_pre_enter() called before entering an atomic
>>> section to handle operations that might require memory allocation
>>> (specifically completing a RIPAS change introduced in a later patch).
>>> * Updates to align with upstream changes to hpfar_el2 which now
>>> (ab)uses
>>> HPFAR_EL2_NS as a valid flag.
>>> * Fix exit reason when racing with PSCI shutdown to return
>>> KVM_EXIT_SHUTDOWN rather than KVM_EXIT_UNKNOWN.
>>> Changes since v7:
>>> * A return of 0 from kvm_handle_sys_reg() doesn't mean the register has
>>> been read (although that can never happen in the current code). Tidy
>>> up the condition to handle any future refactoring.
>>> Changes since v6:
>>> * Use vcpu_err() rather than pr_err/kvm_err when there is an associated
>>> vcpu to the error.
>>> * Return -EFAULT for KVM_EXIT_MEMORY_FAULT as per the documentation for
>>> this exit type.
>>> * Split code handling a RIPAS change triggered by the guest to the
>>> following patch.
>>> Changes since v5:
>>> * For a RIPAS_CHANGE request from the guest perform the actual RIPAS
>>> change on next entry rather than immediately on the exit. This allows
>>> the VMM to 'reject' a RIPAS change by refusing to continue
>>> scheduling.
>>> Changes since v4:
>>> * Rename handle_rme_exit() to handle_rec_exit()
>>> * Move the loop to copy registers into the REC enter structure from the
>>> to rec_exit_handlers callbacks to kvm_rec_enter(). This fixes a bug
>>> where the handler exits to user space and user space wants to modify
>>> the GPRS.
>>> * Some code rearrangement in rec_exit_ripas_change().
>>> Changes since v2:
>>> * realm_set_ipa_state() now provides an output parameter for the
>>> top_iap that was changed. Use this to signal the VMM with the correct
>>> range that has been transitioned.
>>> * Adapt to previous patch changes.
>>> ---
>>> arch/arm64/include/asm/kvm_rmi.h | 4 +
>>> arch/arm64/kvm/Makefile | 2 +-
>>> arch/arm64/kvm/arm.c | 26 ++++-
>>> arch/arm64/kvm/rmi-exit.c | 178 +++++++++++++++++++++++++++++++
>>> arch/arm64/kvm/rmi.c | 43 ++++++++
>>> 5 files changed, 247 insertions(+), 6 deletions(-)
>>> create mode 100644 arch/arm64/kvm/rmi-exit.c
>>>
>>> diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/
>>> asm/kvm_rmi.h
>>> index 4e2c61e71a38..7bec3a3976e7 100644
>>> --- a/arch/arm64/include/asm/kvm_rmi.h
>>> +++ b/arch/arm64/include/asm/kvm_rmi.h
>>> @@ -92,6 +92,10 @@ void kvm_destroy_realm(struct kvm *kvm);
>>> void kvm_realm_destroy_rtts(struct kvm *kvm);
>>> void kvm_destroy_rec(struct kvm_vcpu *vcpu);
>>> +int kvm_rec_enter(struct kvm_vcpu *vcpu);
>>> +int kvm_rec_pre_enter(struct kvm_vcpu *vcpu);
>>> +int handle_rec_exit(struct kvm_vcpu *vcpu, int rec_run_status);
>>> +
>>> static inline bool kvm_realm_is_private_address(struct realm *realm,
>>> unsigned long addr)
>>> {
>>> diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
>>> index e17c4077d8e7..4b103bcbe760 100644
>>> --- a/arch/arm64/kvm/Makefile
>>> +++ b/arch/arm64/kvm/Makefile
>>> @@ -16,7 +16,7 @@ CFLAGS_handle_exit.o += -Wno-override-init
>>> kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
>>> inject_fault.o va_layout.o handle_exit.o config.o \
>>> guest.o debug.o reset.o sys_regs.o stacktrace.o \
>>> - vgic-sys-reg-v3.o fpsimd.o pkvm.o rmi.o \
>>> + vgic-sys-reg-v3.o fpsimd.o pkvm.o rmi.o rmi-exit.o \
>>> arch_timer.o trng.o vmid.o emulate-nested.o nested.o at.o \
>>> vgic/vgic.o vgic/vgic-init.o \
>>> vgic/vgic-irqfd.o vgic/vgic-v2.o \
>>> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
>>> index 45eff4c41cde..badb94b398bc 100644
>>> --- a/arch/arm64/kvm/arm.c
>>> +++ b/arch/arm64/kvm/arm.c
>>> @@ -1311,6 +1311,9 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
>>> if (ret > 0)
>>> ret = check_vcpu_requests(vcpu);
>>> + if (ret > 0 && vcpu_is_rec(vcpu))
>>> + ret = kvm_rec_pre_enter(vcpu);
>>> +
>>> /*
>>> * Preparing the interrupts to be injected also
>>> * involves poking the GIC, which must be done in a
>>> @@ -1358,7 +1361,10 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
>>> trace_kvm_entry(*vcpu_pc(vcpu));
>>> guest_timing_enter_irqoff();
>>> - ret = kvm_arm_vcpu_enter_exit(vcpu);
>>> + if (vcpu_is_rec(vcpu))
>>> + ret = kvm_rec_enter(vcpu);
>>> + else
>>> + ret = kvm_arm_vcpu_enter_exit(vcpu);
>>> vcpu->mode = OUTSIDE_GUEST_MODE;
>>> vcpu->stat.exits++;
>>> @@ -1404,7 +1410,9 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
>>> * context synchronization event) is necessary to ensure that
>>> * pending interrupts are taken.
>>> */
>>> - if (ARM_EXCEPTION_CODE(ret) == ARM_EXCEPTION_IRQ) {
>>> + if (ARM_EXCEPTION_CODE(ret) == ARM_EXCEPTION_IRQ ||
>>> + (vcpu_is_rec(vcpu) &&
>>> + vcpu->arch.rec.run->exit.exit_reason == RMI_EXIT_IRQ)) {
>>> local_irq_enable();
>>> isb();
>>> local_irq_disable();
>>> @@ -1416,8 +1424,13 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
>>> trace_kvm_exit(ret, kvm_vcpu_trap_get_class(vcpu),
>>> *vcpu_pc(vcpu));
>>> - /* Exit types that need handling before we can be preempted */
>>> - handle_exit_early(vcpu, ret);
>>> + if (!vcpu_is_rec(vcpu)) {
>>> + /*
>>> + * Exit types that need handling before we can be
>>> + * preempted
>>> + */
>>> + handle_exit_early(vcpu, ret);
>>> + }
>>> kvm_nested_sync_hwstate(vcpu);
>>> @@ -1442,7 +1455,10 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu
>>> *vcpu)
>>> ret = ARM_EXCEPTION_IL;
>>> }
>>> - ret = handle_exit(vcpu, ret);
>>> + if (vcpu_is_rec(vcpu))
>>> + ret = handle_rec_exit(vcpu, ret);
>>> + else
>>> + ret = handle_exit(vcpu, ret);
>>> }
>>> /* Tell userspace about in-kernel device output levels */
>>> diff --git a/arch/arm64/kvm/rmi-exit.c b/arch/arm64/kvm/rmi-exit.c
>>> new file mode 100644
>>> index 000000000000..f5701153dec0
>>> --- /dev/null
>>> +++ b/arch/arm64/kvm/rmi-exit.c
>>> @@ -0,0 +1,178 @@
>>> +// SPDX-License-Identifier: GPL-2.0-only
>>> +/*
>>> + * Copyright (C) 2023 ARM Ltd.
>>> + */
>>> +
>>> +#include <linux/kvm_host.h>
>>> +#include <kvm/arm_hypercalls.h>
>>> +#include <kvm/arm_psci.h>
>>> +
>>> +#include <asm/rmi_smc.h>
>>> +#include <asm/kvm_emulate.h>
>>> +#include <asm/kvm_rmi.h>
>>> +#include <asm/kvm_mmu.h>
>>> +
>>> +typedef int (*exit_handler_fn)(struct kvm_vcpu *vcpu);
>>> +
>>> +static int rec_exit_reason_notimpl(struct kvm_vcpu *vcpu)
>>> +{
>>> + struct realm_rec *rec = &vcpu->arch.rec;
>>> +
>>> + vcpu_err(vcpu, "Unhandled exit reason from realm (ESR: %#llx)\n",
>>> + rec->run->exit.esr);
>>> + return -ENXIO;
>>> +}
>>> +
>>> +static int rec_exit_sync_dabt(struct kvm_vcpu *vcpu)
>>> +{
>>> + return kvm_handle_guest_abort(vcpu);
>>> +}
>>> +
>>> +static int rec_exit_sync_iabt(struct kvm_vcpu *vcpu)
>>> +{
>>> + struct realm_rec *rec = &vcpu->arch.rec;
>>> +
>>> + vcpu_err(vcpu, "Unhandled instruction abort (ESR: %#llx).\n",
>>> + rec->run->exit.esr);
>>> + return -ENXIO;
>>> +}
>>> +
>>> +static int rec_exit_sys_reg(struct kvm_vcpu *vcpu)
>>> +{
>>> + struct realm_rec *rec = &vcpu->arch.rec;
>>> + unsigned long esr = kvm_vcpu_get_esr(vcpu);
>>> + int rt = kvm_vcpu_sys_get_rt(vcpu);
>>> + bool is_write = !(esr & 1);
>>> + int ret;
>>> +
>>> + if (is_write)
>>> + vcpu_set_reg(vcpu, rt, rec->run->exit.gprs[0]);
>>
>> The RMM has been fixed to indicate the correct value in ESR_ELx_SRT. So
>> this could be :
>> vcpu_set_reg(vcpu, rt, rec->run->ext.gprs[rt]); ?
>>
>>> +
>>> + ret = kvm_handle_sys_reg(vcpu);
>>> + if (!is_write)
>>> + rec->run->enter.gprs[0] = vcpu_get_reg(vcpu, rt);
>>
>> Same here ^
>
> True, although no functional change because it's always going to be 0.
>
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static exit_handler_fn rec_exit_handlers[] = {
>>> + [0 ... ESR_ELx_EC_MAX] = rec_exit_reason_notimpl,
>>> + [ESR_ELx_EC_SYS64] = rec_exit_sys_reg,
>>> + [ESR_ELx_EC_DABT_LOW] = rec_exit_sync_dabt,
>>> + [ESR_ELx_EC_IABT_LOW] = rec_exit_sync_iabt
>>> +};
>>> +
>>> +static int rec_exit_psci(struct kvm_vcpu *vcpu)
>>> +{
>>> + struct realm_rec *rec = &vcpu->arch.rec;
>>> + int i;
>>> +
>>> + for (i = 0; i < REC_RUN_GPRS; i++)
>>> + vcpu_set_reg(vcpu, i, rec->run->exit.gprs[i]);
>>> +
>>> + return kvm_smccc_call_handler(vcpu);
>>> +}
>>> +
>>> +static int rec_exit_ripas_change(struct kvm_vcpu *vcpu)
>>> +{
>>> + struct kvm *kvm = vcpu->kvm;
>>> + struct realm *realm = &kvm->arch.realm;
>>> + struct realm_rec *rec = &vcpu->arch.rec;
>>> + unsigned long base = rec->run->exit.ripas_base;
>>> + unsigned long top = rec->run->exit.ripas_top;
>>> + unsigned long ripas = rec->run->exit.ripas_value;
>>> +
>>> + if (!kvm_realm_is_private_address(realm, base) ||
>>> + !kvm_realm_is_private_address(realm, top - 1)) {
>>> + vcpu_err(vcpu, "Invalid RIPAS_CHANGE for %#lx - %#lx, ripas:
>>> %#lx\n",
>>> + base, top, ripas);
>>> + /* Set RMI_REJECT bit */
>>> + rec->run->enter.flags = REC_ENTER_FLAG_RIPAS_RESPONSE;
>>> + return -EINVAL;
>>> + }
>>> +
>>> + /* Exit to VMM, the actual RIPAS change is done on next entry */
>>> + kvm_prepare_memory_fault_exit(vcpu, base, top - base, false, false,
>>> + ripas == RMI_RAM);
>>> +
>>> + /*
>>> + * KVM_EXIT_MEMORY_FAULT requires an return code of -EFAULT, see the
>>> + * API documentation
>>> + */
>>> + return -EFAULT;
>>> +}
>>> +
>>> +static void update_arch_timer_irq_lines(struct kvm_vcpu *vcpu)
>>> +{
>>> + struct realm_rec *rec = &vcpu->arch.rec;
>>> +
>>> + __vcpu_assign_sys_reg(vcpu, CNTV_CTL_EL0, rec->run->exit.cntv_ctl);
>>> + __vcpu_assign_sys_reg(vcpu, CNTV_CVAL_EL0, rec->run-
>>>> exit.cntv_cval);
>>> + __vcpu_assign_sys_reg(vcpu, CNTP_CTL_EL0, rec->run->exit.cntp_ctl);
>>> + __vcpu_assign_sys_reg(vcpu, CNTP_CVAL_EL0, rec->run-
>>>> exit.cntp_cval);
>>> +
>>> + kvm_realm_timers_update(vcpu);
>>> +}
>>> +
>>> +/*
>>> + * Return > 0 to return to guest, < 0 on error, 0 (and set
>>> exit_reason) on
>>> + * proper exit to userspace.
>>> + */
>>> +int handle_rec_exit(struct kvm_vcpu *vcpu, int rec_run_ret)
>>> +{
>>> + struct realm_rec *rec = &vcpu->arch.rec;
>>> + u8 esr_ec = ESR_ELx_EC(rec->run->exit.esr);
>>> + unsigned long status, index;
>>> +
>>> + status = RMI_RETURN_STATUS(rec_run_ret);
>>> + index = RMI_RETURN_INDEX(rec_run_ret);
>>> +
>>> + /*
>>> + * If a PSCI_SYSTEM_OFF request raced with a vcpu executing, we
>>> might
>>> + * see the following status code and index indicating an attempt
>>> to run
>>> + * a REC when the RD state is SYSTEM_OFF. In this case, we just
>>> need to
>>> + * return to user space which can deal with the system event or
>>> will try
>>> + * to run the KVM VCPU again, at which point we will no longer
>>> attempt
>>> + * to enter the Realm because we will have a sleep request
>>> pending on
>>> + * the VCPU as a result of KVM's PSCI handling.
>>> + */
>>> + if (status == RMI_ERROR_REALM && index == 1) {
>>> + vcpu->run->exit_reason = KVM_EXIT_SHUTDOWN;
>>> + return 0;
>>> + }
>>> +
>>> + if (rec_run_ret)
>>> + return -ENXIO;
>>> +
>>> + vcpu->arch.fault.esr_el2 = rec->run->exit.esr;
>>
>> Even ESR_EL2 is only valid when the exit reason is RMI_EXIT_SYNC or
>> RMI_EXIT_SERROR.
>> Doing this unconditional copying is fine, as long as we don't consume
>> the esr_el2 in exit handling without consulting the exit reason, which
>> may not be available to the rest of the KVM. It may be safer to set it
>> to 0 ?
>
> For HPFAR_EL2 there is code in the kernel which hijacks the EL2_NS bit a
> 'valid' bit, hence we have to handle that one specially to record
> whether the value is valid or not.
>
> esr_el2/far_el2 may or may not be valid depending on the exit, but
> there's no 'valid' flag for the generic kernel code to look for - so
> that generic code either depends on the value (in which case 0 is just
> as invalid) or doesn't use it.
>
> My preference is to avoid trying to keep track of the exit reasons where
> such flags are valid and just provide the generic code with whatever the
> RMM provides. In any case the values are generally 'sanitised' by the
> RMM so they don't represent the real CPU registers.
>
>>> + vcpu->arch.fault.far_el2 = rec->run->exit.far;
>>> + /* HPFAR_EL2 is only valid for RMI_EXIT_SYNC */
>>> + vcpu->arch.fault.hpfar_el2 = 0;
>>> +
>>> + update_arch_timer_irq_lines(vcpu);
>>> +
>>> + /* Reset the emulation flags for the next run of the REC */
>>> + rec->run->enter.flags = 0;
>>> +
>>> + switch (rec->run->exit.exit_reason) {
>>> + case RMI_EXIT_SYNC:
>>> + /*
>>> + * HPFAR_EL2_NS is hijacked to indicate a valid HPFAR value,
>>> + * see __get_fault_info()
>>> + */
>>> + vcpu->arch.fault.hpfar_el2 = rec->run->exit.hpfar |
>>> HPFAR_EL2_NS;
>>> + return rec_exit_handlers[esr_ec](vcpu);
>>> + case RMI_EXIT_IRQ:
>>> + case RMI_EXIT_FIQ:
>>> + return 1;
>>> + case RMI_EXIT_PSCI:
>>> + return rec_exit_psci(vcpu);
>>> + case RMI_EXIT_RIPAS_CHANGE:
>>> + return rec_exit_ripas_change(vcpu);
>>
>> RMI_EXIT_SERROR is missing in the list above.
>
> Indeed, I think I need to read up on how that's meant to be handled.
>
>>> + }
>>> +
>>> + kvm_pr_unimpl("Unsupported exit reason: %u\n",
>>> + rec->run->exit.exit_reason);
>>
>>
>>
>>> + vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
>>> + return 0;
>>> +}
>>> diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
>>> index 6daf14c4b413..ee8aab098117 100644
>>> --- a/arch/arm64/kvm/rmi.c
>>> +++ b/arch/arm64/kvm/rmi.c
>>> @@ -394,6 +394,49 @@ static int realm_ensure_created(struct kvm *kvm)
>>> return -ENXIO;
>>> }
>>> +/*
>>> + * kvm_rec_pre_enter - Complete operations before entering a REC
>>> + *
>>> + * Some operations require work to be completed before entering a
>>> realm. That
>>> + * work may require memory allocation so cannot be done in the
>>> kvm_rec_enter()
>>> + * call.
>>> + *
>>> + * Return: 1 if we should enter the guest
>>> + * 0 if we should exit to userspace
>>> + * < 0 if we should exit to userspace, where the return value
>>> indicates
>>> + * an error
>>> + */
>>> +int kvm_rec_pre_enter(struct kvm_vcpu *vcpu)
>>> +{
>>> + struct realm_rec *rec = &vcpu->arch.rec;
>>> +
>>> + if (kvm_realm_state(vcpu->kvm) != REALM_STATE_ACTIVE)
>>> + return -EINVAL;
>>> +
>>> + switch (rec->run->exit.exit_reason) {
>>> + case RMI_EXIT_HOST_CALL:
>>> + case RMI_EXIT_PSCI:
>>> + for (int i = 0; i < REC_RUN_GPRS; i++)
>>> + rec->run->enter.gprs[i] = vcpu_get_reg(vcpu, i);
>>> + break;
>>> + }
>>> +
>>> + return 1;
>>> +}
>>> +
>>> +int noinstr kvm_rec_enter(struct kvm_vcpu *vcpu)
>>> +{
>>> + struct realm_rec *rec = &vcpu->arch.rec;
>>> + int ret;
>>> +
>>> + guest_state_enter_irqoff();
>>> + ret = rmi_rec_enter(virt_to_phys(rec->rec_page),
>>> + virt_to_phys(rec->run));
>>
>> In the normal VM case, we try to fixup some of the exits (e.g., GIC
>> CPUIF register accesses) which may be applicable to Realms. Do we
>> need such fixups here ? Given the cost of world switch, it is
>> debatable whether it matters or not.
>
> I'm not really sure what you are referring to here. Can you point me at
> the normal VM case? This function is the equivalent of
> kvm_arm_vcpu_enter_exit().
This happens via fixup_guest_exit() in either vhe/nvhe cases. The VGIC
registers are emulated in the fast path for normal VMs (when trapping is
enabled)
Cheers
Suzuki
>
> Thanks,
> Steve
>
>> Suzuki
>>> + guest_state_exit_irqoff();
>>> +
>>> + return ret;
>>> +}
>>> +
>>> static void free_rec_aux(struct page **aux_pages,
>>> unsigned int num_aux)
>>> {
>>
>
^ permalink raw reply
* Re: [PATCH v5 13/22] x86/virt/seamldr: Install a new TDX module
From: Chao Gao @ 2026-03-23 2:14 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: linux-kernel, linux-coco, kvm, binbin.wu, dan.j.williams,
dave.hansen, ira.weiny, kai.huang, nik.borisov, paulmck, pbonzini,
reinette.chatre, rick.p.edgecombe, sagis, seanjc, tony.lindgren,
vannapurve, vishal.l.verma, yilun.xu, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, x86, H. Peter Anvin
In-Reply-To: <abv6fT8Rvk01DGah@thinkstation>
On Thu, Mar 19, 2026 at 01:32:07PM +0000, Kiryl Shutsemau wrote:
>On Sun, Mar 15, 2026 at 06:58:33AM -0700, Chao Gao wrote:
>> @@ -225,6 +227,7 @@ static void ack_state(void)
>> static int do_seamldr_install_module(void *seamldr_params)
>> {
>> enum module_update_state newstate, curstate = MODULE_UPDATE_START;
>> + struct tdx_module_args args = {};
>> int cpu = smp_processor_id();
>> bool primary;
>> int ret = 0;
>> @@ -243,6 +246,10 @@ static int do_seamldr_install_module(void *seamldr_params)
>> if (primary)
>> ret = tdx_module_shutdown();
>> break;
>> + case MODULE_UPDATE_CPU_INSTALL:
>> + args.rcx = __pa(seamldr_params);
>> + ret = seamldr_call(P_SEAMLDR_INSTALL, &args);
>
>I think it should be in a helper alongside with the tdx_module_args.
Will do. Here is the updated version:
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index bb4aa6327eee..cd2aa2eabb65 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -19,6 +19,7 @@
/* P-SEAMLDR SEAMCALL leaf function */
#define P_SEAMLDR_INFO 0x8000000000000000
+#define P_SEAMLDR_INSTALL 0x8000000000000001
#define SEAMLDR_MAX_NR_MODULE_4KB_PAGES 496
#define SEAMLDR_MAX_NR_SIG_4KB_PAGES 4
@@ -72,6 +73,13 @@ int seamldr_get_info(struct seamldr_info *seamldr_info)
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
+static int seamldr_install(const struct seamldr_params *params)
+{
+ struct tdx_module_args args = { .rcx = __pa(params) };
+
+ return seamldr_call(P_SEAMLDR_INSTALL, &args);
+}
+
static void free_seamldr_params(struct seamldr_params *params)
{
free_page((unsigned long)params);
@@ -186,6 +194,7 @@ static struct seamldr_params *init_seamldr_params(const u8 *data, u32 size)
enum module_update_state {
MODULE_UPDATE_START,
MODULE_UPDATE_SHUTDOWN,
+ MODULE_UPDATE_CPU_INSTALL,
MODULE_UPDATE_DONE,
};
@@ -243,6 +252,9 @@ static int do_seamldr_install_module(void *seamldr_params)
if (primary)
ret = tdx_module_shutdown();
break;
+ case MODULE_UPDATE_CPU_INSTALL:
+ ret = seamldr_install(seamldr_params);
+ break;
default:
break;
}
^ permalink raw reply related
* Re: [PATCH v5 11/22] x86/virt/seamldr: Shut down the current TDX module
From: Chao Gao @ 2026-03-23 1:33 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: linux-kernel, linux-coco, kvm, binbin.wu, dan.j.williams,
dave.hansen, ira.weiny, kai.huang, nik.borisov, paulmck, pbonzini,
reinette.chatre, rick.p.edgecombe, sagis, seanjc, tony.lindgren,
vannapurve, vishal.l.verma, yilun.xu, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, x86, H. Peter Anvin
In-Reply-To: <ab0DpDNQ35/xD6hS@intel.com>
On Fri, Mar 20, 2026 at 04:22:10PM +0800, Chao Gao wrote:
>>> +static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff)
>>> +{
>>> + int ret = 0;
>>> + u64 val;
>>> +
>>> + if (!ret && !(ret = read_sys_metadata_field(0x8900000100000000, &val)))
>>
>>!ret check is redundant as well as the ret initialization above.
>
>Ok. Will remove them:
>
>static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff)
>{
> int ret;
> u64 val;
>
> if (!(ret = read_sys_metadata_field(0x8900000100000000, &val)))
> sysinfo_handoff->module_hv = val;
> return ret;
>}
While making this change, I'll also fix the checkpatch.pl error:
ERROR: do not use assignment in if condition
#23: FILE: arch/x86/virt/vmx/tdx/tdx_global_metadata.c:108:
+ if (!(ret = read_sys_metadata_field(0x8900000100000000, &val)))
Updated version:
static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff)
{
int ret;
u64 val;
ret = read_sys_metadata_field(0x8900000100000000, &val);
if (ret)
return ret;
sysinfo_handoff->module_hv = val;
return 0;
}
^ permalink raw reply
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