Linux Confidential Computing Development
 help / color / mirror / Atom feed
* Re: [PATCH v2 20/21] x86/virt/tdx: Update tdx_sysinfo and check features post-update
From: Binbin Wu @ 2025-12-03  7:41 UTC (permalink / raw)
  To: Chao Gao
  Cc: linux-coco, linux-kernel, x86, reinette.chatre, ira.weiny,
	kai.huang, dan.j.williams, yilun.xu, sagis, vannapurve, paulmck,
	nik.borisov, Farrah Chen, Kirill A. Shutemov, Dave Hansen,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20251001025442.427697-21-chao.gao@intel.com>



On 10/1/2025 10:53 AM, Chao Gao wrote:

[...]
>  
> +/*
> + * Update tdx_sysinfo and check if any TDX module features changed after
> + * updates
> + */
> +int tdx_module_post_update(struct tdx_sys_info *info)
> +{
> +	struct tdx_sys_info_version *cur, *new;
> +	int ret;
> +
> +	/* Shouldn't fail as the update has succeeded */
> +	ret = get_tdx_sys_info(info);
> +	if (ret) {
> +		WARN_ONCE(1, "version retrieval failed after update, replace TDX Module\n");

Nit:
Could be if (WARN_ONCE(ret, "..."))

> +		return ret;
> +	}
> +
> +	guard(mutex)(&tdx_module_lock);
> +
> +	cur = &tdx_sysinfo.version;

Nit:
After update, the current TDX module is the new TDX module already, may be
better to use old instead of cur.

> +	new = &info->version;
> +	pr_info("version %u.%u.%02u -> %u.%u.%02u\n", cur->major_version,
> +						      cur->minor_version,
> +						      cur->update_version,
> +						      new->major_version,
> +						      new->minor_version,
> +						      new->update_version);
> +
> +	/*
> +	 * Blindly refreshing the entire tdx_sysinfo could disrupt running
> +	 * software, as it may subtly rely on the previous state unless
> +	 * proven otherwise.
> +	 *
> +	 * Only refresh version information (including handoff version)
> +	 * that does not affect functionality, and ignore all other
> +	 * changes.
> +	 */
> +	tdx_sysinfo.version	= info->version;
> +	tdx_sysinfo.handoff	= info->handoff;
> +
> +	if (!memcmp(&tdx_sysinfo, info, sizeof(*info)))
> +		return 0;
> +
> +	pr_info("TDX module features have changed after updates, but might not take effect.\n");
> +	pr_info("Please consider a potential BIOS update.\n");


BIOS update?
I guess it's "TDX module update via BIOS"?

Does it mean after a system reboot, the change done by TD preserving update will
be gone? If we want the TDX module upgrade to be permanent, it needs to replace
the TDX module binary the BIOS will load, right?

So the scenario of TD preserving update seems to be limited to security fixes?
(I guess the security fixes will take effect directly after TD preserving
update?)


> +	return 0;
> +}
> +
>  static bool is_pamt_page(unsigned long phys)
>  {
>  	struct tdmr_info_list *tdmr_list = &tdx_tdmr_list;
> diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
> index 983c01c6949a..ca76126880ee 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.h
> +++ b/arch/x86/virt/vmx/tdx/tdx.h
> @@ -3,6 +3,7 @@
>  #define _X86_VIRT_TDX_H
>  
>  #include <linux/bits.h>
> +#include <asm/tdx_global_metadata.h>
>  
>  /*
>   * This file contains both macros and data structures defined by the TDX
> @@ -124,5 +125,6 @@ int tdx_module_shutdown(void);
>  void tdx_module_set_error(void);
>  int tdx_cpu_enable(void);
>  int tdx_module_run_update(void);
> +int tdx_module_post_update(struct tdx_sys_info *info);
>  
>  #endif


^ permalink raw reply

* [PATCH v4] page_alloc: allow migration of smaller hugepages during contig_alloc
From: Gregory Price @ 2025-12-03  6:30 UTC (permalink / raw)
  To: linux-mm
  Cc: kernel-team, linux-kernel, akpm, vbabka, surenb, mhocko, jackmanb,
	hannes, ziy, kas, dave.hansen, rick.p.edgecombe, muchun.song,
	osalvador, david, x86, linux-coco, kvm, Wei Yang, David Rientjes,
	Joshua Hahn

We presently skip regions with hugepages entirely when trying to do
contiguous page allocation.  This will cause otherwise-movable
2MB HugeTLB pages to be considered unmovable, and will make 1GB
hugepages more difficult to allocate on systems utilizing both.

Instead, if hugepage migration is enabled, consider regions with
hugepages smaller than the target contiguous allocation request
as valid targets for allocation.

isolate_migrate_pages_block() has similar logic, and the hugetlb code
does a migratable check in folio_isolate_hugetlb() during isolation.
So the code servicing the subsequent allocaiton and migration already
supports this exact use case (it's just unreachable).

To test, allocate a bunch of 2MB HugeTLB pages (in this case 48GB)
and then attempt to allocate some 1G HugeTLB pages (in this case 4GB)
(Scale to your machine's memory capacity).

echo 24576 > .../hugepages-2048kB/nr_hugepages
echo 4 > .../hugepages-1048576kB/nr_hugepages

Prior to this patch, the 1GB page allocation can fail if no contiguous
1GB pages remain.  After this patch, the kernel will try to move 2MB
pages and successfully allocate the 1GB pages (assuming overall
sufficient memory is available).

folio_alloc_gigantic() is the primary user of alloc_contig_pages(),
other users are debug or init-time allocations and largely unaffected.
- ppc/memtrace is a debugfs interface
- x86/tdx memory allocation occurs once on module-init
- kfence/core happens once on module (late) init
- THP uses it in debug_vm_pgtable_alloc_huge_page at __init time

Suggested-by: David Hildenbrand <david@redhat.com>
Link: https://lore.kernel.org/linux-mm/6fe3562d-49b2-4975-aa86-e139c535ad00@redhat.com/
Signed-off-by: Gregory Price <gourry@gourry.net>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Wei Yang <richard.weiyang@gmail.com>
Reviewed-by: Oscar Salvador <osalvador@suse.de>
Acked-by: David Rientjes <rientjes@google.com>
Acked-by: David Hildenbrand <david@redhat.com>
Tested-by: Joshua Hahn <joshua.hahnjy@gmail.com>
---
 mm/page_alloc.c | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 95d8b812efd0..8ca3273f734a 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -7069,8 +7069,27 @@ static bool pfn_range_valid_contig(struct zone *z, unsigned long start_pfn,
 		if (PageReserved(page))
 			return false;
 
-		if (PageHuge(page))
-			return false;
+		/*
+		 * Only consider ranges containing hugepages if those pages are
+		 * smaller than the requested contiguous region.  e.g.:
+		 *     Move 2MB pages to free up a 1GB range.
+		 *     Don't move 1GB pages to free up a 2MB range.
+		 *
+		 * This makes contiguous allocation more reliable if multiple
+		 * hugepage sizes are used without causing needless movement.
+		 */
+		if (PageHuge(page)) {
+			unsigned int order;
+
+			if (!IS_ENABLED(CONFIG_ARCH_ENABLE_HUGEPAGE_MIGRATION))
+				return false;
+
+			page = compound_head(page);
+			order = compound_order(page);
+			if ((order >= MAX_FOLIO_ORDER) ||
+			    (nr_pages <= (1 << order)))
+				return false;
+		}
 	}
 	return true;
 }
-- 
2.52.0


^ permalink raw reply related

* Re: [PATCH v2 19/21] x86/virt/tdx: Establish contexts for the new TDX Module
From: Binbin Wu @ 2025-12-03  3:49 UTC (permalink / raw)
  To: Chao Gao
  Cc: linux-coco, linux-kernel, x86, reinette.chatre, ira.weiny,
	kai.huang, dan.j.williams, yilun.xu, sagis, vannapurve, paulmck,
	nik.borisov, Farrah Chen, Kirill A. Shutemov, Dave Hansen,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20251001025442.427697-20-chao.gao@intel.com>



On 10/1/2025 10:53 AM, Chao Gao wrote:
> After being installed, the new TDX Module shouldn't re-configure the
> global HKID, TDMRs or PAMTs. Instead, to preserve running TDs, it should
> import the handoff data from the old module to establish all necessary
> contexts.
> 
> Once the import is done, the TDX Module update is complete, and the new
> module is ready to handle requests from the VMM and guests.
> 
> Call the TDH.SYS.UPDATE SEAMCALL to import the handoff data from the old
> module.
> 
> Note that the location and the format of handoff data is defined by the
> TDX Module. The new module knows where to get the handoff data and how to
> parse it. The kernel doesn't need to provide its location, format etc.
> 
> Signed-off-by: Chao Gao <chao.gao@intel.com>
> Tested-by: Farrah Chen <farrah.chen@intel.com>
> ---
>  arch/x86/virt/vmx/tdx/seamldr.c |  5 +++++
>  arch/x86/virt/vmx/tdx/tdx.c     | 16 ++++++++++++++++
>  arch/x86/virt/vmx/tdx/tdx.h     |  2 ++
>  3 files changed, 23 insertions(+)
> 
> diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
> index 75bb650d8a16..a8ca6966beac 100644
> --- a/arch/x86/virt/vmx/tdx/seamldr.c
> +++ b/arch/x86/virt/vmx/tdx/seamldr.c
> @@ -236,6 +236,7 @@ enum tdp_state {
>  	TDP_SHUTDOWN,
>  	TDP_CPU_INSTALL,
>  	TDP_CPU_INIT,
> +	TDP_RUN_UPDATE,
>  	TDP_DONE,
>  };
>  
> @@ -310,6 +311,10 @@ static int do_seamldr_install_module(void *params)
>  			case TDP_CPU_INIT:
>  				ret = tdx_cpu_enable();
>  				break;
> +			case TDP_RUN_UPDATE:
> +				if (primary)
> +					ret = tdx_module_run_update();
> +				break;
>  			default:
>  				break;
>  			}
> diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
> index 280c2a9f3211..7613fd16a0ce 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.c
> +++ b/arch/x86/virt/vmx/tdx/tdx.c
> @@ -1225,6 +1225,22 @@ void tdx_module_set_error(void)
>  	tdx_module_status = TDX_MODULE_ERROR;
>  }
>  
> +int tdx_module_run_update(void)
> +{
> +	struct tdx_module_args args = {};
> +	int ret;> +
> +	ret = seamcall(TDH_SYS_UPDATE, &args);

Since it's a seamcall error, shouldn't it be u64?

> +	if (ret) {
> +		tdx_module_status = TDX_MODULE_ERROR;
> +		pr_info("module update failed: %d\n", ret);

pr_info -> pr_err?
Also, use 0x%016llx as the format.

> +		return ret;
> +	}
> +
> +	tdx_module_status = TDX_MODULE_INITIALIZED;
> +	return 0;
> +}
> +
>  static bool is_pamt_page(unsigned long phys)
>  {
>  	struct tdmr_info_list *tdmr_list = &tdx_tdmr_list;
> diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
> index b903e479e46a..983c01c6949a 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.h
> +++ b/arch/x86/virt/vmx/tdx/tdx.h
> @@ -47,6 +47,7 @@
>  #define TDH_VP_WR			43
>  #define TDH_SYS_CONFIG			45
>  #define TDH_SYS_SHUTDOWN		52
> +#define TDH_SYS_UPDATE		53
>  
>  /*
>   * SEAMCALL leaf:
> @@ -122,5 +123,6 @@ struct tdmr_info_list {
>  int tdx_module_shutdown(void);
>  void tdx_module_set_error(void);
>  int tdx_cpu_enable(void);
> +int tdx_module_run_update(void);
>  
>  #endif


^ permalink raw reply

* [PATCH] crypto/ccp: Fix CONFIG_PCI=n build
From: Dan Williams @ 2025-12-03  3:19 UTC (permalink / raw)
  To: linux-crypto
  Cc: linux-kernel, linux-coco, kernel test robot, Alexey Kardashevskiy,
	Tom Lendacky, John Allen

It turns out that the PCI driver for ccp is unconditionally built into the
kernel in the CONFIG_PCI=y case. This means that the new SEV-TIO support
needs an explicit dependency on PCI to avoid build errors when
CONFIG_CRYPTO_DEV_SP_PSP=y and CONFIG_PCI=n.

Reported-by: kernel test robot <lkp@intel.com>
Closes: http://lore.kernel.org/202512030743.6pVPA4sx-lkp@intel.com
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Cc: John Allen <john.allen@amd.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 drivers/crypto/ccp/Kconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/crypto/ccp/Kconfig b/drivers/crypto/ccp/Kconfig
index e2b127f0986b..f16a0f611317 100644
--- a/drivers/crypto/ccp/Kconfig
+++ b/drivers/crypto/ccp/Kconfig
@@ -39,7 +39,7 @@ config CRYPTO_DEV_SP_PSP
 	bool "Platform Security Processor (PSP) device"
 	default y
 	depends on CRYPTO_DEV_CCP_DD && X86_64 && AMD_IOMMU
-	select PCI_TSM
+	select PCI_TSM if PCI
 	help
 	 Provide support for the AMD Platform Security Processor (PSP).
 	 The PSP is a dedicated processor that provides support for key

base-commit: f7ae6d4ec6520a901787cbab273983e96d8516da
prerequisite-patch-id: 085ed7fc143cfcfd0418527cfad03db88d4b64ec
prerequisite-patch-id: c1d1a6d802b3b4bfffb9f45fc5ac6a9a1b5e361d
prerequisite-patch-id: 44c6ea6fb683418ae67ff3efdb0c07fda013e6b2
prerequisite-patch-id: 407daf59d54ecebcb7fefd22a5b5833e03c038e4
-- 
2.51.1


^ permalink raw reply related

* Re: [PATCH 3/3] KVM: guest_memfd: GUP source pages prior to populating guest memory
From: Yan Zhao @ 2025-12-03  2:46 UTC (permalink / raw)
  To: Michael Roth
  Cc: kvm, linux-coco, linux-mm, linux-kernel, thomas.lendacky,
	pbonzini, seanjc, vbabka, ashish.kalra, liam.merwick, david,
	vannapurve, ackerleytng, aik, ira.weiny
In-Reply-To: <20251201221355.wvsm6qxwxax46v4t@amd.com>

On Mon, Dec 01, 2025 at 04:13:55PM -0600, Michael Roth wrote:
> On Mon, Nov 24, 2025 at 05:31:46PM +0800, Yan Zhao wrote:
> > On Fri, Nov 21, 2025 at 07:01:44AM -0600, Michael Roth wrote:
> > > On Thu, Nov 20, 2025 at 05:11:48PM +0800, Yan Zhao wrote:
> > > > On Thu, Nov 13, 2025 at 05:07:59PM -0600, Michael Roth wrote:
> > > > > Currently the post-populate callbacks handle copying source pages into
> > > > > private GPA ranges backed by guest_memfd, where kvm_gmem_populate()
> > > > > acquires the filemap invalidate lock, then calls a post-populate
> > > > > callback which may issue a get_user_pages() on the source pages prior to
> > > > > copying them into the private GPA (e.g. TDX).
> > > > > 
> > > > > This will not be compatible with in-place conversion, where the
> > > > > userspace page fault path will attempt to acquire filemap invalidate
> > > > > lock while holding the mm->mmap_lock, leading to a potential ABBA
> > > > > deadlock[1].
> > > > > 
> > > > > Address this by hoisting the GUP above the filemap invalidate lock so
> > > > > that these page faults path can be taken early, prior to acquiring the
> > > > > filemap invalidate lock.
> > > > > 
> > > > > It's not currently clear whether this issue is reachable with the
> > > > > current implementation of guest_memfd, which doesn't support in-place
> > > > > conversion, however it does provide a consistent mechanism to provide
> > > > > stable source/target PFNs to callbacks rather than punting to
> > > > > vendor-specific code, which allows for more commonality across
> > > > > architectures, which may be worthwhile even without in-place conversion.
> > > > > 
> > > > > Suggested-by: Sean Christopherson <seanjc@google.com>
> > > > > Signed-off-by: Michael Roth <michael.roth@amd.com>
> > > > > ---
> > > > >  arch/x86/kvm/svm/sev.c   | 40 ++++++++++++++++++++++++++------------
> > > > >  arch/x86/kvm/vmx/tdx.c   | 21 +++++---------------
> > > > >  include/linux/kvm_host.h |  3 ++-
> > > > >  virt/kvm/guest_memfd.c   | 42 ++++++++++++++++++++++++++++++++++------
> > > > >  4 files changed, 71 insertions(+), 35 deletions(-)
> > > > > 
> > > > > diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
> > > > > index 0835c664fbfd..d0ac710697a2 100644
> > > > > --- a/arch/x86/kvm/svm/sev.c
> > > > > +++ b/arch/x86/kvm/svm/sev.c
> > > > > @@ -2260,7 +2260,8 @@ struct sev_gmem_populate_args {
> > > > >  };
> > > > >  
> > > > >  static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn_start, kvm_pfn_t pfn,
> > > > > -				  void __user *src, int order, void *opaque)
> > > > > +				  struct page **src_pages, loff_t src_offset,
> > > > > +				  int order, void *opaque)
> > > > >  {
> > > > >  	struct sev_gmem_populate_args *sev_populate_args = opaque;
> > > > >  	struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
> > > > > @@ -2268,7 +2269,7 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn_start, kvm_pfn_t pf
> > > > >  	int npages = (1 << order);
> > > > >  	gfn_t gfn;
> > > > >  
> > > > > -	if (WARN_ON_ONCE(sev_populate_args->type != KVM_SEV_SNP_PAGE_TYPE_ZERO && !src))
> > > > > +	if (WARN_ON_ONCE(sev_populate_args->type != KVM_SEV_SNP_PAGE_TYPE_ZERO && !src_pages))
> > > > >  		return -EINVAL;
> > > > >  
> > > > >  	for (gfn = gfn_start, i = 0; gfn < gfn_start + npages; gfn++, i++) {
> > > > > @@ -2284,14 +2285,21 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn_start, kvm_pfn_t pf
> > > > >  			goto err;
> > > > >  		}
> > > > >  
> > > > > -		if (src) {
> > > > > -			void *vaddr = kmap_local_pfn(pfn + i);
> > > > > +		if (src_pages) {
> > > > > +			void *src_vaddr = kmap_local_pfn(page_to_pfn(src_pages[i]));
> > > > > +			void *dst_vaddr = kmap_local_pfn(pfn + i);
> > > > >  
> > > > > -			if (copy_from_user(vaddr, src + i * PAGE_SIZE, PAGE_SIZE)) {
> > > > > -				ret = -EFAULT;
> > > > > -				goto err;
> > > > > +			memcpy(dst_vaddr, src_vaddr + src_offset, PAGE_SIZE - src_offset);
> > > > > +			kunmap_local(src_vaddr);
> > > > > +
> > > > > +			if (src_offset) {
> > > > > +				src_vaddr = kmap_local_pfn(page_to_pfn(src_pages[i + 1]));
> > > > > +
> > > > > +				memcpy(dst_vaddr + PAGE_SIZE - src_offset, src_vaddr, src_offset);
> > > > > +				kunmap_local(src_vaddr);
> > > > IIUC, src_offset is the src's offset from the first page. e.g.,
> > > > src could be 0x7fea82684100, with src_offset=0x100, while npages could be 512.
> > > > 
> > > > Then it looks like the two memcpy() calls here only work when npages == 1 ?
> > > 
> > > src_offset ends up being the offset into the pair of src pages that we
> > > are using to fully populate a single dest page with each iteration. So
> > > if we start at src_offset, read a page worth of data, then we are now at
> > > src_offset in the next src page and the loop continues that way even if
> > > npages > 1.
> > > 
> > > If src_offset is 0 we never have to bother with straddling 2 src pages so
> > > the 2nd memcpy is skipped on every iteration.
> > > 
> > > That's the intent at least. Is there a flaw in the code/reasoning that I
> > > missed?
> > Oh, I got you. SNP expects a single src_offset applies for each src page.
> > 
> > So if npages = 2, there're 4 memcpy() calls.
> > 
> > src:  |---------|---------|---------|  (VA contiguous)
> >           ^         ^         ^
> >           |         |         |
> > dst:      |---------|---------|   (PA contiguous)
> > 
> > 
> > I previously incorrectly thought kvm_gmem_populate() should pass in src_offset
> > as 0 for the 2nd src page.
> > 
> > Would you consider checking if params.uaddr is PAGE_ALIGNED() in
> > snp_launch_update() to simplify the design?
> 
> This was an option mentioned in the cover letter and during PUCK. I am
> not opposed if that's the direction we decide, but I also don't think
> it makes big difference since:
> 
>    int (*kvm_gmem_populate_cb)(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
>                                struct page **src_pages, loff_t src_offset,
>                                int order, void *opaque);
> 
> basically reduces to Sean's originally proposed:
> 
>    int (*kvm_gmem_populate_cb)(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
>                                struct page *src_pages, int order,
>                                void *opaque);

Hmm, the requirement of having each copy to dst_page account for src_offset
(which actually results in 2 copies) is quite confusing. I initially thought the
src_offset only applied to the first dst_page.

This will also cause kvm_gmem_populate() to allocate 1 more src_npages than
npages for dst pages.

> for any platform that enforces that the src is page-aligned, which
> doesn't seem like a huge technical burden, IMO, despite me initially
> thinking it would be gross when I brought this up during the PUCK call
> that preceeding this posting.
> > 
> > > > 
> > > > >  			}
> > > > > -			kunmap_local(vaddr);
> > > > > +
> > > > > +			kunmap_local(dst_vaddr);
> > > > >  		}
> > > > >  
> > > > >  		ret = rmp_make_private(pfn + i, gfn << PAGE_SHIFT, PG_LEVEL_4K,
> > > > > @@ -2331,12 +2339,20 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn_start, kvm_pfn_t pf
> > > > >  	if (!snp_page_reclaim(kvm, pfn + i) &&
> > > > >  	    sev_populate_args->type == KVM_SEV_SNP_PAGE_TYPE_CPUID &&
> > > > >  	    sev_populate_args->fw_error == SEV_RET_INVALID_PARAM) {
> > > > > -		void *vaddr = kmap_local_pfn(pfn + i);
> > > > > +		void *src_vaddr = kmap_local_pfn(page_to_pfn(src_pages[i]));
> > > > > +		void *dst_vaddr = kmap_local_pfn(pfn + i);
> > > > >  
> > > > > -		if (copy_to_user(src + i * PAGE_SIZE, vaddr, PAGE_SIZE))
> > > > > -			pr_debug("Failed to write CPUID page back to userspace\n");
> > > > > +		memcpy(src_vaddr + src_offset, dst_vaddr, PAGE_SIZE - src_offset);
> > > > > +		kunmap_local(src_vaddr);
> > > > > +
> > > > > +		if (src_offset) {
> > > > > +			src_vaddr = kmap_local_pfn(page_to_pfn(src_pages[i + 1]));
> > > > > +
> > > > > +			memcpy(src_vaddr, dst_vaddr + PAGE_SIZE - src_offset, src_offset);
> > > > > +			kunmap_local(src_vaddr);
> > > > > +		}
> > > > >  
> > > > > -		kunmap_local(vaddr);
> > > > > +		kunmap_local(dst_vaddr);
> > > > >  	}
> > > > >  
> > > > >  	/* pfn + i is hypervisor-owned now, so skip below cleanup for it. */
> > > > > diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
> > > > > index 57ed101a1181..dd5439ec1473 100644
> > > > > --- a/arch/x86/kvm/vmx/tdx.c
> > > > > +++ b/arch/x86/kvm/vmx/tdx.c
> > > > > @@ -3115,37 +3115,26 @@ struct tdx_gmem_post_populate_arg {
> > > > >  };
> > > > >  
> > > > >  static int tdx_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
> > > > > -				  void __user *src, int order, void *_arg)
> > > > > +				  struct page **src_pages, loff_t src_offset,
> > > > > +				  int order, void *_arg)
> > > > >  {
> > > > >  	struct tdx_gmem_post_populate_arg *arg = _arg;
> > > > >  	struct kvm_tdx *kvm_tdx = to_kvm_tdx(kvm);
> > > > >  	u64 err, entry, level_state;
> > > > >  	gpa_t gpa = gfn_to_gpa(gfn);
> > > > > -	struct page *src_page;
> > > > >  	int ret, i;
> > > > >  
> > > > >  	if (KVM_BUG_ON(kvm_tdx->page_add_src, kvm))
> > > > >  		return -EIO;
> > > > >  
> > > > > -	if (KVM_BUG_ON(!PAGE_ALIGNED(src), kvm))
> > > > > +	/* Source should be page-aligned, in which case src_offset will be 0. */
> > > > > +	if (KVM_BUG_ON(src_offset))
> > > > 	if (KVM_BUG_ON(src_offset, kvm))
> > > > 
> > > > >  		return -EINVAL;
> > > > >  
> > > > > -	/*
> > > > > -	 * Get the source page if it has been faulted in. Return failure if the
> > > > > -	 * source page has been swapped out or unmapped in primary memory.
> > > > > -	 */
> > > > > -	ret = get_user_pages_fast((unsigned long)src, 1, 0, &src_page);
> > > > > -	if (ret < 0)
> > > > > -		return ret;
> > > > > -	if (ret != 1)
> > > > > -		return -ENOMEM;
> > > > > -
> > > > > -	kvm_tdx->page_add_src = src_page;
> > > > > +	kvm_tdx->page_add_src = src_pages[i];
> > > > src_pages[0] ? i is not initialized. 
> > > 
> > > Sorry, I switched on TDX options for compile testing but I must have done a
> > > sloppy job confirming it actually built. I'll re-test push these and squash
> > > in the fixes in the github tree.
> > > 
> > > > 
> > > > Should there also be a KVM_BUG_ON(order > 0, kvm) ?
> > > 
> > > Seems reasonable, but I'm not sure this is the right patch. Maybe I
> > > could squash it into the preceeding documentation patch so as to not
> > > give the impression this patch changes those expectations in any way.
> > I don't think it should be documented as a user requirement.
> 
> I didn't necessarily mean in the documentation, but mainly some patch
> other than this. If we add that check here as part of this patch, we
> give the impression that the order expectations are changing as a result
> of the changes here, when in reality they are exactly the same as
> before.
> 
> If not the documentation patch here, then I don't think it really fits
> in this series at all and would be more of a standalone patch against
> kvm/next.
> 
> The change here:
> 
>  -	if (KVM_BUG_ON(!PAGE_ALIGNED(src), kvm))
>  +	/* Source should be page-aligned, in which case src_offset will be 0. */
>  +	if (KVM_BUG_ON(src_offset))
> 
> made sense as part of this patch, because now that we are passing struct
> page *src_pages, we can no longer infer alignment from 'src' field, and
> instead need to infer it from src_offset being 0.
> 
> > 
> > However, we need to comment out that this assertion is due to that
> > tdx_vcpu_init_mem_region() passes npages as 1 to kvm_gmem_populate().
> 
> You mean for the KVM_BUG_ON(order > 0, kvm) you're proposing to add?
> Again, if feels awkward to address this as part of this series since it
> is an existing/unchanged behavior and not really the intent of this
> patchset.
That's true. src_pages[0] just makes it more eye-catching.
What about just adding a comment for src_pages[0] instead of KVM_BUG_ON()?

> > > > >  	ret = kvm_tdp_mmu_map_private_pfn(arg->vcpu, gfn, pfn);
> > > > >  	kvm_tdx->page_add_src = NULL;
> > > > >  
> > > > > -	put_page(src_page);
> > > > > -
> > > > >  	if (ret || !(arg->flags & KVM_TDX_MEASURE_MEMORY_REGION))
> > > > >  		return ret;
> > > > >  
> > > > > diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
> > > > > index d93f75b05ae2..7e9d2403c61f 100644
> > > > > --- a/include/linux/kvm_host.h
> > > > > +++ b/include/linux/kvm_host.h
> > > > > @@ -2581,7 +2581,8 @@ int kvm_arch_gmem_prepare(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn, int max_ord
> > > > >   * Returns the number of pages that were populated.
> > > > >   */
> > > > >  typedef int (*kvm_gmem_populate_cb)(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
> > > > > -				    void __user *src, int order, void *opaque);
> > > > > +				    struct page **src_pages, loff_t src_offset,
> > > > > +				    int order, void *opaque);
> > > > >  
> > > > >  long kvm_gmem_populate(struct kvm *kvm, gfn_t gfn, void __user *src, long npages,
> > > > >  		       kvm_gmem_populate_cb post_populate, void *opaque);
> > > > > diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
> > > > > index 9160379df378..e9ac3fd4fd8f 100644
> > > > > --- a/virt/kvm/guest_memfd.c
> > > > > +++ b/virt/kvm/guest_memfd.c
> > > > > @@ -814,14 +814,17 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
> > > > >  EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_gmem_get_pfn);
> > > > >  
> > > > >  #ifdef CONFIG_HAVE_KVM_ARCH_GMEM_POPULATE
> > > > > +
> > > > > +#define GMEM_GUP_NPAGES (1UL << PMD_ORDER)
> > > > Limiting GMEM_GUP_NPAGES to PMD_ORDER may only work when the max_order of a huge
> > > > folio is 2MB. What if the max_order returned from  __kvm_gmem_get_pfn() is 1GB
> > > > when src_pages[] can only hold up to 512 pages?
> > > 
> > > This was necessarily chosen in prep for hugepages, but more about my
> > > unease at letting userspace GUP arbitrarilly large ranges. PMD_ORDER
> > > happens to align with 2MB hugepages while seeming like a reasonable
> > > batching value so that's why I chose it.
> > >
> > > Even with 1GB support, I wasn't really planning to increase it. SNP
> > > doesn't really make use of RMP sizes >2MB, and it sounds like TDX
> > > handles promotion in a completely different path. So atm I'm leaning
> > > toward just letting GMEM_GUP_NPAGES be the cap for the max page size we
> > > support for kvm_gmem_populate() path and not bothering to change it
> > > until a solid use-case arises.
> > The problem is that with hugetlb-based guest_memfd, the folio itself could be
> > of 1GB, though SNP and TDX can force mapping at only 4KB.
> 
> If TDX wants to unload handling of page-clearing to its per-page
> post-populate callback and tie that its shared/private tracking that's
> perfectly fine by me.
> 
> *How* TDX tells gmem it wants this different behavior is a topic for a
> follow-up patchset, Vishal suggested kernel-internal flags to
> kvm_gmem_create(), which seemed reasonable to me. In that case, uptodate
Not sure which flag you are referring to with "Vishal suggested kernel-internal
flags to kvm_gmem_create()".

However, my point is that when the backend folio is 1GB in size (leading to
max_order being PUD_ORDER), even if SNP only maps at 2MB to RMP, it may hit the
warning of "!IS_ALIGNED(gfn, 1 << max_order)".

For TDX, it's worse because it always passes npages as 1, so it will also hit
the warning of "(npages - i) < (1 << max_order)".

Given that this patch already considers huge pages for SNP, it feels half-baked
to leave the WARN_ON() for future handling.
    WARN_ON(!IS_ALIGNED(gfn, 1 << max_order) ||
            (npages - i) < (1 << max_order));

> flag would probably just default to set and punt to post-populate/prep
> hooks, because we absolutely *do not* want to have to re-introduce per-4K
> tracking of this type of state within gmem, since getting rid of that sort
> of tracking requirement within gmem is the entire motivation of this
> series. And since, within this series, the uptodate flag and
> prep-tracking both have the same 4K granularity, it seems unecessary to
> address this here.
> 
> If you were to send a patchset on top of this (or even independently) that
> introduces said kernel-internal gmem flag to offload uptodate-tracking to
> post-populate/prep hooks, and utilize it to optimize the current 4K-only
> TDX implementation by letting TDX module handle the initial
> page-clearing, then I think that change/discussion can progress without
> being blocked in any major way by this series.
> 
> But I don't think we need to flesh all that out here, so long as we are
> aware of this as a future change/requirement and have reasonable
> indication that it is compatible with this series.
> 
> > 
> > Then since max_order = folio_order(folio) (at least in the tree for [1]), 
> > WARN_ON() in kvm_gmem_populate() could still be hit:
> > 
> > folio = __kvm_gmem_get_pfn(file, slot, index, &pfn, &max_order);
> > WARN_ON(!IS_ALIGNED(gfn, 1 << max_order) ||
> >         (npages - i) < (1 << max_order));
> 
> Yes, in the SNP implementation of hugetlb I ended up removing this
> warning, and in that case I also ended up forcing kvm_gmem_populate() to
> be 4K-only:
> 
>   https://github.com/AMDESE/linux/blob/snp-hugetlb-v2-wip0/virt/kvm/guest_memfd.c#L2372

For 1G (aka HugeTLB) page, this fix is also needed, which was missed in [1] and
I pointed out to Ackerley at [2].

[1] https://github.com/googleprodkernel/linux-cc/tree/gmem-1g-page-support-rfc-v2
[2] https://lore.kernel.org/all/aFPGPVbzo92t565h@yzhao56-desk.sh.intel.com/

> but it makes a lot more sense to make those restrictions and changes in
> the context of hugepage support, rather than this series which is trying
> very hard to not do hugepage enablement, but simply keep what's partially
> there intact while reworking other things that have proven to be
> continued impediments to both in-place conversion and hugepage
> enablement.
Not sure how fixing the warning in this series could impede hugepage enabling :)

But if you prefer, I don't mind keeping it locally for longer.

> Also, there's talk now of enabling hugepages even without in-place
> conversion for hugetlbfs, and that will likely be the same path we
> follow for THP to remain in alignment. Rather than anticipating what all
> these changes will mean WRT hugepage implementation/requirements, I
> think it will be fruitful to remove some of the baggage that will
> complicate that process/discussion like this patchset attempts.
> 
> -Mike
> 
> > 
> > TDX is even easier to hit this warning because it always passes npages as 1.
> > 
> > [1] https://lore.kernel.org/all/cover.1747264138.git.ackerleytng@google.com
> > 
> >  
> > > > Increasing GMEM_GUP_NPAGES to (1UL << PUD_ORDER) is probabaly not a good idea.
> > > > 
> > > > Given both TDX/SNP map at 4KB granularity, why not just invoke post_populate()
> > > > per 4KB while removing the max_order from post_populate() parameters, as done
> > > > in Sean's sketch patch [1]?
> > > 
> > > That's an option too, but SNP can make use of 2MB pages in the
> > > post-populate callback so I don't want to shut the door on that option
> > > just yet if it's not too much of a pain to work in. Given the guest BIOS
> > > lives primarily in 1 or 2 of these 2MB regions the benefits might be
> > > worthwhile, and SNP doesn't have a post-post-populate promotion path
> > > like TDX (at least, not one that would help much for guest boot times)
> > I see.
> > 
> > So, what about below change?
> > 
> > --- a/virt/kvm/guest_memfd.c
> > +++ b/virt/kvm/guest_memfd.c
> > @@ -878,11 +878,10 @@ long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long
> >                 }
> > 
> >                 folio_unlock(folio);
> > -               WARN_ON(!IS_ALIGNED(gfn, 1 << max_order) ||
> > -                       (npages - i) < (1 << max_order));
> > 
> >                 ret = -EINVAL;
> > -               while (!kvm_range_has_memory_attributes(kvm, gfn, gfn + (1 << max_order),
> > +               while (!IS_ALIGNED(gfn, 1 << max_order) || (npages - i) < (1 << max_order) ||
> > +                      !kvm_range_has_memory_attributes(kvm, gfn, gfn + (1 << max_order),
> >                                                         KVM_MEMORY_ATTRIBUTE_PRIVATE,
> >                                                         KVM_MEMORY_ATTRIBUTE_PRIVATE)) {
> >                         if (!max_order)
> > 
> > 
> > 
> > > 
> > > > 
> > > > Then the WARN_ON() in kvm_gmem_populate() can be removed, which would be easily
> > > > triggered by TDX when max_order > 0 && npages == 1:
> > > > 
> > > >       WARN_ON(!IS_ALIGNED(gfn, 1 << max_order) ||
> > > >               (npages - i) < (1 << max_order));
> > > > 
> > > > 
> > > > [1] https://lore.kernel.org/all/aHEwT4X0RcfZzHlt@google.com/
> > > > 
> > > > >  long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long npages,
> > > > >  		       kvm_gmem_populate_cb post_populate, void *opaque)
> > > > >  {
> > > > >  	struct kvm_memory_slot *slot;
> > > > > -	void __user *p;
> > > > > -
> > > > > +	struct page **src_pages;
> > > > >  	int ret = 0, max_order;
> > > > > -	long i;
> > > > > +	loff_t src_offset = 0;
> > > > > +	long i, src_npages;
> > > > >  
> > > > >  	lockdep_assert_held(&kvm->slots_lock);
> > > > >  
> > > > > @@ -836,9 +839,28 @@ long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long
> > > > >  	if (!file)
> > > > >  		return -EFAULT;
> > > > >  
> > > > > +	npages = min_t(ulong, slot->npages - (start_gfn - slot->base_gfn), npages);
> > > > > +	npages = min_t(ulong, npages, GMEM_GUP_NPAGES);
> > > > > +
> > > > > +	if (src) {
> > > > > +		src_npages = IS_ALIGNED((unsigned long)src, PAGE_SIZE) ? npages : npages + 1;
> > > > > +
> > > > > +		src_pages = kmalloc_array(src_npages, sizeof(struct page *), GFP_KERNEL);
> > > > > +		if (!src_pages)
> > > > > +			return -ENOMEM;
> > > > > +
> > > > > +		ret = get_user_pages_fast((unsigned long)src, src_npages, 0, src_pages);
> > > > > +		if (ret < 0)
> > > > > +			return ret;
> > > > > +
> > > > > +		if (ret != src_npages)
> > > > > +			return -ENOMEM;
> > > > > +
> > > > > +		src_offset = (loff_t)(src - PTR_ALIGN_DOWN(src, PAGE_SIZE));
> > > > > +	}
> > > > > +
> > > > >  	filemap_invalidate_lock(file->f_mapping);
> > > > >  
> > > > > -	npages = min_t(ulong, slot->npages - (start_gfn - slot->base_gfn), npages);
> > > > >  	for (i = 0; i < npages; i += (1 << max_order)) {
> > > > >  		struct folio *folio;
> > > > >  		gfn_t gfn = start_gfn + i;
> > > > > @@ -869,8 +891,8 @@ long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long
> > > > >  			max_order--;
> > > > >  		}
> > > > >  
> > > > > -		p = src ? src + i * PAGE_SIZE : NULL;
> > > > > -		ret = post_populate(kvm, gfn, pfn, p, max_order, opaque);
> > > > > +		ret = post_populate(kvm, gfn, pfn, src ? &src_pages[i] : NULL,
> > > > > +				    src_offset, max_order, opaque);
> > > > Why src_offset is not 0 starting from the 2nd page?
> > > > 
> > > > >  		if (!ret)
> > > > >  			folio_mark_uptodate(folio);
> > > > >  
> > > > > @@ -882,6 +904,14 @@ long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long
> > > > >  
> > > > >  	filemap_invalidate_unlock(file->f_mapping);
> > > > >  
> > > > > +	if (src) {
> > > > > +		long j;
> > > > > +
> > > > > +		for (j = 0; j < src_npages; j++)
> > > > > +			put_page(src_pages[j]);
> > > > > +		kfree(src_pages);
> > > > > +	}
> > > > > +
> > > > >  	return ret && !i ? ret : i;
> > > > >  }
> > > > >  EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_gmem_populate);
> > > > > -- 
> > > > > 2.25.1
> > > > > 

^ permalink raw reply

* Re: [PATCH v2 14/21] x86/virt/seamldr: Shut down the current TDX module
From: Binbin Wu @ 2025-12-03  2:24 UTC (permalink / raw)
  To: Chao Gao
  Cc: linux-coco, linux-kernel, x86, reinette.chatre, ira.weiny,
	kai.huang, dan.j.williams, yilun.xu, sagis, vannapurve, paulmck,
	nik.borisov, Farrah Chen, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, H. Peter Anvin, Kirill A. Shutemov,
	Paolo Bonzini, Rick Edgecombe
In-Reply-To: <20251001025442.427697-15-chao.gao@intel.com>



On 10/1/2025 10:52 AM, Chao Gao wrote:
> TDX Module updates request shutting down the existing TDX module.
> During this shutdown, the module generates hand-off data, which captures
> the module's states essential for preserving running TDs. The new TDX
> Module can utilize this hand-off data to establish its states.
> 
> Invoke the TDH_SYS_SHUTDOWN SEAMCALL on one CPU to perform the shutdown.
> This SEAMCALL requires a hand-off module version. Use the module's own
> hand-off version, as it is the highest version the module can produce and
> is more likely to be compatible with new modules as new modules likely have
> higher hand-off version.

According to the TDX module base spec (348549006), each TDX module is built with
TDX Module Handoff Constants, including No-Downgrade Flag. If the current TDX
module is built with NO_DOWNGRADE=1, the hand-off module version must be the
current TDX module's HV.

This patch series doesn't seems to handle No-Downgrade Flag, IIUC it needs
to use the current TDX module's HV to avoid failures.

About "hand-off version" and "No-Downgrade Flag", I still have some questions.
Is it possible that two TDX module versions have the same hand-off version?
If the newer TDX module built with NO_DOWNGRADE=1, is it possible to downgrade
to the older TDX module when they are using the same hand-off version?

> 
> Generate changes to tdx_global_metadata.{hc} by following the
> instructions detailed in [1], after adding the following section to the
> tdx.py script:
> 
>     "handoff": [
>        "MODULE_HV",
>     ],
> 
> Manually add a check in get_tdx_sys_info_handoff() to guard reading the
> "module_hv" field with TDX Module update support as otherwise the field
> doesn't exist.
> 
> Signed-off-by: Chao Gao <chao.gao@intel.com>
> Tested-by: Farrah Chen <farrah.chen@intel.com>
> Link: https://lore.kernel.org/kvm/20250226181453.2311849-12-pbonzini@redhat.com/ # [1]

[...]

^ permalink raw reply

* Re: [PATCH v2 1/8] drivers/virt: Drop VIRT_DRIVERS build dependency
From: dan.j.williams @ 2025-12-03  1:51 UTC (permalink / raw)
  To: Nathan Chancellor, Dan Williams
  Cc: linux-pci, linux-coco, Jonathan.Cameron, kernel test robot
In-Reply-To: <20251202234435.GA1692217@ax162>

Nathan Chancellor wrote:
> Hi Dan,
> 
> On Wed, Nov 12, 2025 at 06:14:39PM -0800, Dan Williams wrote:
> > All of the objects in drivers/virt/ have their own configuration symbols to
> > gate compilation. I.e. nothing gets added to the kernel with
> > CONFIG_VIRT_DRIVERS=y in isolation.
> > 
> > Unconditionally descend into drivers/virt/ so that consumers do not need to
> > add an additional CONFIG_VIRT_DRIVERS dependency.
> > 
> > Fix warnings of the form:
> > 
> >     Kconfig warnings: (for reference only)
> >        WARNING: unmet direct dependencies detected for TSM
> >        Depends on [n]: VIRT_DRIVERS [=n]
> >        Selected by [y]:
> >        - PCI_TSM [=y] && PCI [=y]
> 
> I can still trigger this warning on next-20251202, which contains this
> change as commit 110c155e8a68 ("drivers/virt: Drop VIRT_DRIVERS build
> dependency").
> 
>   $ git merge-base --is-ancestor 110c155e8a684d8b2423a72cfde147903881f765 HEAD
>     echo $status
>   0
> 
>   $ make -skj"$(nproc)" ARCH=arm64 CROSS_COMPILE=aarch64-linux- clean defconfig
> 
>   $ scripts/config -e PCI_TSM
> 
>   $ make -skj"$(nproc)" ARCH=arm64 CROSS_COMPILE=aarch64-linux- olddefconfig
>   WARNING: unmet direct dependencies detected for TSM
>     Depends on [n]: VIRT_DRIVERS [=n]
>     Selected by [y]:
>     - PCI_TSM [=y] && PCI [=y]
> 
> I would think you would need something like this avoid the problem but I
> am not sure if it would be acceptable, hence just the report.

Oh, yes, thanks for that. It turns out I fixed the compilation problem
associated with this report [1].

   ld: drivers/pci/ide.o: in function `pci_ide_stream_release':
   ide.c:(.text+0xffb): undefined reference to `tsm_ide_stream_unregister'
   ld: drivers/pci/tsm.o: in function `connect_store':
   tsm.c:(.text+0x1d42): undefined reference to `find_tsm_dev'

I missed that the report I ended up quoting [2] was only about the
Kconfig problem not the build error.

[1]: http://lore.kernel.org/202511140712.NubhamPy-lkp@intel.com
[2]: http://lore.kernel.org/202511041832.ylcgIiqN-lkp@intel.com

> Cheers,
> Nathan
> 
> diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
> index d8c848cf09a6..52eb7e4ba71f 100644
> --- a/drivers/virt/Kconfig
> +++ b/drivers/virt/Kconfig
> @@ -47,6 +47,6 @@ source "drivers/virt/nitro_enclaves/Kconfig"
>  
>  source "drivers/virt/acrn/Kconfig"
>  
> -source "drivers/virt/coco/Kconfig"
> -
>  endif
> +
> +source "drivers/virt/coco/Kconfig"
> diff --git a/drivers/virt/coco/Kconfig b/drivers/virt/coco/Kconfig
> index bb0c6d6ddcc8..df1cfaf26c65 100644
> --- a/drivers/virt/coco/Kconfig
> +++ b/drivers/virt/coco/Kconfig
> @@ -3,6 +3,7 @@
>  # Confidential computing related collateral
>  #
>  
> +if VIRT_DRIVERS
>  source "drivers/virt/coco/efi_secret/Kconfig"
>  
>  source "drivers/virt/coco/pkvm-guest/Kconfig"
> @@ -14,6 +15,7 @@ source "drivers/virt/coco/tdx-guest/Kconfig"
>  source "drivers/virt/coco/arm-cca-guest/Kconfig"
>  
>  source "drivers/virt/coco/guest/Kconfig"
> +endif
>  
>  config TSM
>  	bool

Yes, I think that looks right to me as core infrastructure pieces are
not user selectable like the leaf drivers.

For now I will note the mixup in the pull request and would be happy to
take an incremental patch fixing this up.

^ permalink raw reply

* Re: [PATCH v5] virt: tdx-guest: Handle GetQuote request error code
From: Dave Hansen @ 2025-12-03  0:03 UTC (permalink / raw)
  To: Kuppuswamy Sathyanarayanan, Kirill A . Shutemov
  Cc: Rick Edgecombe, Dave Hansen, Dan Williams, x86, linux-kernel,
	linux-coco
In-Reply-To: <369b819c-5235-424d-a8c9-c7c45a9fd4db@linux.intel.com>

On 12/2/25 16:00, Kuppuswamy Sathyanarayanan wrote:
> The reason the kernel must parse the status field is that the failure
> code is only available in the header portion of the shared GPA buffer
> populated by the VMM. Userspace currently does not have access to this
> header since we only expose the Quote payload itself. Because userspace
> cannot directly interpret the VMM status codes, the kernel needs to parse
> them and return appropriate generic error codes.

That's kinda the key to this.

Users are poking at sysfs and expect (near) universal explicit errors.
Are they even doing this from shell scripts most of the time?

Also, please don't just keep tacking gunk onto the changelog. Start
cutting out the cruft, please.

^ permalink raw reply

* Re: [PATCH v5] virt: tdx-guest: Handle GetQuote request error code
From: Kuppuswamy Sathyanarayanan @ 2025-12-03  0:00 UTC (permalink / raw)
  To: Dave Hansen, Kirill A . Shutemov
  Cc: Rick Edgecombe, Dave Hansen, Dan Williams, x86, linux-kernel,
	linux-coco
In-Reply-To: <25044b05-7574-4b01-a8ea-3f7e7f32317b@intel.com>

Hi Dave,

Thanks for the review.

On 12/2/2025 2:46 PM, Dave Hansen wrote:
> On 12/2/25 14:22, Kuppuswamy Sathyanarayanan wrote:
>> The tdx-guest driver sends Quote requests to the quoting enclave via a
>> hypercall to obtain attestation evidence for the current TD state.
>> Quote generation can fail in two ways: a hypercall failure, or a Quote
>> failure that occurs after the VMM processes the request. The driver
>> currently handles only hypercall failures and timeout errors during
>> Quote processing. Update it to also handle other Quote failures
>> reported by the VMM (for more details, refer to GHCI spec, v1.5,
>> March 2023, sec titled "TDG.VP.VMCALL<GetQuote>).
> 
> I think you're talking about the "GetQuote Status Code" here, right?
> That would have been nice to mention. It wasn't exactly trivial to find
> because instead of saying what the format of a TDREPORT_STRUCT is, the
> docs just call it "format of shared GPA".

Yes, that's correct. I am referring to the GetQuote Status Code returned
by TDG.VP.VMCALL<GetQuote>, specifically the error codes that were not
previously checked (GET_QUOTE_ERROR and GET_QUOTE_SERVICE_UNAVAILABLE).

For clarity, I will update the commit description to explicitly refer to
status code table — Table 3-11: TDG.VP.VMCALL<GetQuote> – GetQuote Status
Code.

> 
>> This change does not break the existing ABI behavior. When a Quote
>> failure occurs, the VMM sets the Quote length to zero. Userspace
>> already interprets a zero-length Quote as a Quote generation failure.
>> Returning an explicit error in such cases makes the behavior more
>> consistent and simplifies error handling in userspace.
> I'm also not seeing a clear problem statement here. What is the end user
> visible effect of this "fix"? Why *should* the kernel be parsing this
> buffer? Why not not just leave the error handling to userspace?


The issue is that, prior to this patch, the kernel silently returned
success for certain Quote failure cases such as when the Quote service
is unavailable or when the VMM reports a processing error. In these
cases, the Quote buffer ends up being empty and userspace is expected
to infer failure indirectly by checking for a zero length Quote. This
behavior is ambiguous and has caused confusion in practice, as reported
in: https://github.com/confidential-containers/guest-components/issues/823

With this patch, all VMM reported Quote failures are explicitly
translated into kernel error returns. This makes failure detection
uniform and simplifies userspace error handling.

The reason the kernel must parse the status field is that the failure
code is only available in the header portion of the shared GPA buffer
populated by the VMM. Userspace currently does not have access to this
header since we only expose the Quote payload itself. Because userspace
cannot directly interpret the VMM status codes, the kernel needs to parse
them and return appropriate generic error codes.

> 
>> Fixes: f4738f56d1dc ("virt: tdx-guest: Add Quote generation support using TSM_REPORTS")
>> Reported-by: Xiaoyao Li <xiaoyao.li@intel.com>
>> Closes: https://lore.kernel.org/linux-coco/6bdf569c-684a-4459-af7c-4430691804eb@linux.intel.com/T/#u
>> Closes: https://github.com/confidential-containers/guest-components/issues/823
>> Reviewed-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
>> Reviewed-by: Xiaoyao Li <xiaoyao.li@intel.com>
>> Acked-by: Kai Huang <kai.huang@intel.com>
>> Reviewed-by: Dan Williams <dan.j.williams@intel.com>
>> Tested-by: Mikko Ylinen <mikko.ylinen@linux.intel.com>
>> Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
> 
> Please take a look at:
> 
>  https://docs.kernel.org/process/maintainer-tip.html#ordering-of-commit-tags
> 

Thanks for pointing this out. I will reorder the commit tags in the next revision.

> 

-- 
Sathyanarayanan Kuppuswamy
Linux Kernel Developer


^ permalink raw reply

* Subscribe
From: Balasubrawmanian, Vivaik @ 2025-12-02 23:52 UTC (permalink / raw)
  To: linux-coco



^ permalink raw reply

* Re: [PATCH v2 1/8] drivers/virt: Drop VIRT_DRIVERS build dependency
From: Nathan Chancellor @ 2025-12-02 23:44 UTC (permalink / raw)
  To: Dan Williams; +Cc: linux-pci, linux-coco, Jonathan.Cameron, kernel test robot
In-Reply-To: <20251113021446.436830-2-dan.j.williams@intel.com>

Hi Dan,

On Wed, Nov 12, 2025 at 06:14:39PM -0800, Dan Williams wrote:
> All of the objects in drivers/virt/ have their own configuration symbols to
> gate compilation. I.e. nothing gets added to the kernel with
> CONFIG_VIRT_DRIVERS=y in isolation.
> 
> Unconditionally descend into drivers/virt/ so that consumers do not need to
> add an additional CONFIG_VIRT_DRIVERS dependency.
> 
> Fix warnings of the form:
> 
>     Kconfig warnings: (for reference only)
>        WARNING: unmet direct dependencies detected for TSM
>        Depends on [n]: VIRT_DRIVERS [=n]
>        Selected by [y]:
>        - PCI_TSM [=y] && PCI [=y]

I can still trigger this warning on next-20251202, which contains this
change as commit 110c155e8a68 ("drivers/virt: Drop VIRT_DRIVERS build
dependency").

  $ git merge-base --is-ancestor 110c155e8a684d8b2423a72cfde147903881f765 HEAD
    echo $status
  0

  $ make -skj"$(nproc)" ARCH=arm64 CROSS_COMPILE=aarch64-linux- clean defconfig

  $ scripts/config -e PCI_TSM

  $ make -skj"$(nproc)" ARCH=arm64 CROSS_COMPILE=aarch64-linux- olddefconfig
  WARNING: unmet direct dependencies detected for TSM
    Depends on [n]: VIRT_DRIVERS [=n]
    Selected by [y]:
    - PCI_TSM [=y] && PCI [=y]

I would think you would need something like this avoid the problem but I
am not sure if it would be acceptable, hence just the report.

Cheers,
Nathan

diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
index d8c848cf09a6..52eb7e4ba71f 100644
--- a/drivers/virt/Kconfig
+++ b/drivers/virt/Kconfig
@@ -47,6 +47,6 @@ source "drivers/virt/nitro_enclaves/Kconfig"
 
 source "drivers/virt/acrn/Kconfig"
 
-source "drivers/virt/coco/Kconfig"
-
 endif
+
+source "drivers/virt/coco/Kconfig"
diff --git a/drivers/virt/coco/Kconfig b/drivers/virt/coco/Kconfig
index bb0c6d6ddcc8..df1cfaf26c65 100644
--- a/drivers/virt/coco/Kconfig
+++ b/drivers/virt/coco/Kconfig
@@ -3,6 +3,7 @@
 # Confidential computing related collateral
 #
 
+if VIRT_DRIVERS
 source "drivers/virt/coco/efi_secret/Kconfig"
 
 source "drivers/virt/coco/pkvm-guest/Kconfig"
@@ -14,6 +15,7 @@ source "drivers/virt/coco/tdx-guest/Kconfig"
 source "drivers/virt/coco/arm-cca-guest/Kconfig"
 
 source "drivers/virt/coco/guest/Kconfig"
+endif
 
 config TSM
 	bool

^ permalink raw reply related

* Re: [PATCH v5] virt: tdx-guest: Handle GetQuote request error code
From: Dave Hansen @ 2025-12-02 22:46 UTC (permalink / raw)
  To: Kuppuswamy Sathyanarayanan, Kirill A . Shutemov
  Cc: Rick Edgecombe, Dave Hansen, Dan Williams, x86, linux-kernel,
	linux-coco
In-Reply-To: <20251202222257.986825-1-sathyanarayanan.kuppuswamy@linux.intel.com>

On 12/2/25 14:22, Kuppuswamy Sathyanarayanan wrote:
> The tdx-guest driver sends Quote requests to the quoting enclave via a
> hypercall to obtain attestation evidence for the current TD state.
> Quote generation can fail in two ways: a hypercall failure, or a Quote
> failure that occurs after the VMM processes the request. The driver
> currently handles only hypercall failures and timeout errors during
> Quote processing. Update it to also handle other Quote failures
> reported by the VMM (for more details, refer to GHCI spec, v1.5,
> March 2023, sec titled "TDG.VP.VMCALL<GetQuote>).

I think you're talking about the "GetQuote Status Code" here, right?
That would have been nice to mention. It wasn't exactly trivial to find
because instead of saying what the format of a TDREPORT_STRUCT is, the
docs just call it "format of shared GPA".

> This change does not break the existing ABI behavior. When a Quote
> failure occurs, the VMM sets the Quote length to zero. Userspace
> already interprets a zero-length Quote as a Quote generation failure.
> Returning an explicit error in such cases makes the behavior more
> consistent and simplifies error handling in userspace.
I'm also not seeing a clear problem statement here. What is the end user
visible effect of this "fix"? Why *should* the kernel be parsing this
buffer? Why not not just leave the error handling to userspace?

> Fixes: f4738f56d1dc ("virt: tdx-guest: Add Quote generation support using TSM_REPORTS")
> Reported-by: Xiaoyao Li <xiaoyao.li@intel.com>
> Closes: https://lore.kernel.org/linux-coco/6bdf569c-684a-4459-af7c-4430691804eb@linux.intel.com/T/#u
> Closes: https://github.com/confidential-containers/guest-components/issues/823
> Reviewed-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
> Reviewed-by: Xiaoyao Li <xiaoyao.li@intel.com>
> Acked-by: Kai Huang <kai.huang@intel.com>
> Reviewed-by: Dan Williams <dan.j.williams@intel.com>
> Tested-by: Mikko Ylinen <mikko.ylinen@linux.intel.com>
> Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>

Please take a look at:

 https://docs.kernel.org/process/maintainer-tip.html#ordering-of-commit-tags



^ permalink raw reply

* Re: [PATCH kernel v3 4/4] crypto/ccp: Implement SEV-TIO PCIe IDE (phase1)
From: Kalra, Ashish @ 2025-12-02 22:39 UTC (permalink / raw)
  To: Alexey Kardashevskiy, Tom Lendacky, linux-kernel
  Cc: linux-crypto, John Allen, Herbert Xu, David S. Miller,
	Joerg Roedel, Suravee Suthikulpanit, Will Deacon, Robin Murphy,
	Borislav Petkov, Borislav Petkov (AMD), Dan Williams,
	Jason Gunthorpe, Jerry Snitselaar, Vasant Hegde, Gao Shiyuan,
	Sean Christopherson, Kim Phillips, Nikunj A Dadhania,
	Michael Roth, Paolo Bonzini, iommu, x86, linux-coco
In-Reply-To: <36929d3e-b56c-4e65-9ec1-42fc52fdd962@amd.com>



On 12/2/2025 4:30 PM, Alexey Kardashevskiy wrote:
> 
> 
> On 3/12/25 01:52, Tom Lendacky wrote:
>> On 12/1/25 20:44, Alexey Kardashevskiy wrote:
>>> Implement the SEV-TIO (Trusted I/O) firmware interface for PCIe TDISP
>>> (Trust Domain In-Socket Protocol). This enables secure communication
>>> between trusted domains and PCIe devices through the PSP (Platform
>>> Security Processor).
>>>
>>> The implementation includes:
>>> - Device Security Manager (DSM) operations for establishing secure links
>>> - SPDM (Security Protocol and Data Model) over DOE (Data Object Exchange)
>>> - IDE (Integrity Data Encryption) stream management for secure PCIe
>>>
>>> This module bridges the SEV firmware stack with the generic PCIe TSM
>>> framework.
>>>
>>> This is phase1 as described in Documentation/driver-api/pci/tsm.rst.
>>>
>>> On AMD SEV, the AMD PSP firmware acts as TSM (manages the security/trust).
>>> The CCP driver provides the interface to it and registers in the TSM
>>> subsystem.
>>>
>>> Detect the PSP support (reported via FEATURE_INFO + SNP_PLATFORM_STATUS)
>>> and enable SEV-TIO in the SNP_INIT_EX call if the hardware supports TIO.
>>>
>>> Implement SEV TIO PSP command wrappers in sev-dev-tio.c and store
>>> the data in the SEV-TIO-specific structs.
>>>
>>> Implement TSM hooks and IDE setup in sev-dev-tsm.c.
>>>
>>> Signed-off-by: Alexey Kardashevskiy <aik@amd.com>
>>
>> Just some minor comments below. After those are addressed:
>>
>> For the ccp related changes in the whole series:
>>
>> Acked-by: Tom Lendacky <thomas.lendacky@amd.com>
> 
> Dan did it right (thanks Dan!).
> 
> 
>>
>>> ---
>>> Changes:
>>> v2:
>>> * moved declarations from sev-dev-tio.h to sev-dev.h
>>> * removed include "sev-dev-tio.h" from sev-dev.c to fight errors when TSM is disabled
>>> * converted /** to /* as these are part of any external API and trigger unwanted kerneldoc warnings
>>> * got rid of ifdefs
>>> * "select PCI_TSM" moved under CRYPTO_DEV_SP_PSP
>>> * open coded SNP_SEV_TIO_SUPPORTED
>>> * renamed tio_present to tio_supp to match the flag name
>>> * merged "crypto: ccp: Enable SEV-TIO feature in the PSP when supported" to this one
>>> ---
>>>   drivers/crypto/ccp/Kconfig       |   1 +
>>>   drivers/crypto/ccp/Makefile      |   4 +
>>>   drivers/crypto/ccp/sev-dev-tio.h | 123 +++
>>>   drivers/crypto/ccp/sev-dev.h     |   9 +
>>>   include/linux/psp-sev.h          |  11 +-
>>>   drivers/crypto/ccp/sev-dev-tio.c | 864 ++++++++++++++++++++
>>>   drivers/crypto/ccp/sev-dev-tsm.c | 405 +++++++++
>>>   drivers/crypto/ccp/sev-dev.c     |  51 +-
>>>   8 files changed, 1465 insertions(+), 3 deletions(-)
>>>
>>
>>> diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
>>> index 9e0c16b36f9c..d6095d1467b3 100644
>>> --- a/drivers/crypto/ccp/sev-dev.c
>>> +++ b/drivers/crypto/ccp/sev-dev.c
>>> @@ -75,6 +75,10 @@ static bool psp_init_on_probe = true;
>>>   module_param(psp_init_on_probe, bool, 0444);
>>>   MODULE_PARM_DESC(psp_init_on_probe, "  if true, the PSP will be initialized on module init. Else the PSP will be initialized on the first command requiring it");
>>>   +static bool sev_tio_enabled = IS_ENABLED(CONFIG_PCI_TSM);
>>> +module_param_named(tio, sev_tio_enabled, bool, 0444);
>>> +MODULE_PARM_DESC(tio, "Enables TIO in SNP_INIT_EX");
>>
>> Hmmm... I thought you said you wanted to hide the module parameter if
>> CONFIG_PCI_TSM isn't enabled. Either way, it's fine.
> 
> I did but you did not and I do not care that much :)
> 
>>
>>> +
>>>   MODULE_FIRMWARE("amd/amd_sev_fam17h_model0xh.sbin"); /* 1st gen EPYC */
>>>   MODULE_FIRMWARE("amd/amd_sev_fam17h_model3xh.sbin"); /* 2nd gen EPYC */
>>>   MODULE_FIRMWARE("amd/amd_sev_fam19h_model0xh.sbin"); /* 3rd gen EPYC */
>>> @@ -251,7 +255,7 @@ static int sev_cmd_buffer_len(int cmd)
>>>       case SEV_CMD_SNP_COMMIT:        return sizeof(struct sev_data_snp_commit);
>>>       case SEV_CMD_SNP_FEATURE_INFO:        return sizeof(struct sev_data_snp_feature_info);
>>>       case SEV_CMD_SNP_VLEK_LOAD:        return sizeof(struct sev_user_data_snp_vlek_load);
>>> -    default:                return 0;
>>> +    default:                return sev_tio_cmd_buffer_len(cmd);
>>>       }
>>>         return 0;
>>> @@ -1434,6 +1438,19 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>>>           data.init_rmp = 1;
>>>           data.list_paddr_en = 1;
>>>           data.list_paddr = __psp_pa(snp_range_list);
>>> +
>>> +        bool tio_supp = !!(sev->snp_feat_info_0.ebx & SNP_SEV_TIO_SUPPORTED);
>>
>> Please put the variable definition at the top of the "if" block instead
>> of in the middle of the code.
>>> +
>>> +        data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
>>
>> Don't you still want to take CONFIG_PCI_TSM into account?
>>
>>     data.tio_en = IS_ENABLED(CONFIG_PCI_TSM) && tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
>>
>> or
>>     if (IS_ENABLED(CONFIG_PCI_TSM)
>>         data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
>>
>> But if you change back to #ifdef the module parameter, then you won't
>> need the IS_ENABLED() check here because sev_tio_enabled will be set
>> based on CONFIG_PCI_TSM and will be false and not changeable if
>> CONFIG_PCI_TSM is not y.
> 
> 
> Ah true. I thought sev_tio_enabled=IS_ENABLED(CONFIG_PCI_TSM) does it but missed that sev_tio_enabled is exported as a parameter so not a constant at compile time.
> 
> 
>>> +
>>> +        /*
>>> +         * When psp_init_on_probe is disabled, the userspace calling
>>> +         * SEV ioctl can inadvertently shut down SNP and SEV-TIO causing
>>> +         * unexpected state loss.
>>> +         */
>>
>> After this is merged, lets see if sev_move_to_init_state() can be
>> cleaned up to avoid this situation.
> 
> Do we want to keep psp_init_on_probe, why? Thanks,
> 

I think we still need to support "psp_init_on_probe" to support deferred SEV initialization and continue supporting SEV INIT_EX.

Thanks,
Ashish

> 
>>
>> Thanks,
>> Tom
>>
>>> +        if (data.tio_en && !psp_init_on_probe)
>>> +            dev_warn(sev->dev, "SEV-TIO as incompatible with psp_init_on_probe=0\n");
>>> +
>>>           cmd = SEV_CMD_SNP_INIT_EX;
>>>       } else {
>>>           cmd = SEV_CMD_SNP_INIT;
>>> @@ -1471,7 +1488,8 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>>>         snp_hv_fixed_pages_state_update(sev, HV_FIXED);
>>>       sev->snp_initialized = true;
>>> -    dev_dbg(sev->dev, "SEV-SNP firmware initialized\n");
>>> +    dev_dbg(sev->dev, "SEV-SNP firmware initialized, SEV-TIO is %s\n",
>>> +        data.tio_en ? "enabled" : "disabled");
>>>         dev_info(sev->dev, "SEV-SNP API:%d.%d build:%d\n", sev->api_major,
>>>            sev->api_minor, sev->build);
>>> @@ -1479,6 +1497,23 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>>>       atomic_notifier_chain_register(&panic_notifier_list,
>>>                          &snp_panic_notifier);
>>>   +    if (data.tio_en) {
>>> +        /*
>>> +         * This executes with the sev_cmd_mutex held so down the stack
>>> +         * snp_reclaim_pages(locked=false) might be needed (which is extremely
>>> +         * unlikely) but will cause a deadlock.
>>> +         * Instead of exporting __snp_alloc_firmware_pages(), allocate a page
>>> +         * for this one call here.
>>> +         */
>>> +        void *tio_status = page_address(__snp_alloc_firmware_pages(
>>> +            GFP_KERNEL_ACCOUNT | __GFP_ZERO, 0, true));
>>> +
>>> +        if (tio_status) {
>>> +            sev_tsm_init_locked(sev, tio_status);
>>> +            __snp_free_firmware_pages(virt_to_page(tio_status), 0, true);
>>> +        }
>>> +    }
>>> +
>>>       sev_es_tmr_size = SNP_TMR_SIZE;
>>>         return 0;
>>> @@ -2758,8 +2793,20 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic)
>>>     static void sev_firmware_shutdown(struct sev_device *sev)
>>>   {
>>> +    /*
>>> +     * Calling without sev_cmd_mutex held as TSM will likely try disconnecting
>>> +     * IDE and this ends up calling sev_do_cmd() which locks sev_cmd_mutex.
>>> +     */
>>> +    if (sev->tio_status)
>>> +        sev_tsm_uninit(sev);
>>> +
>>>       mutex_lock(&sev_cmd_mutex);
>>> +
>>>       __sev_firmware_shutdown(sev, false);
>>> +
>>> +    kfree(sev->tio_status);
>>> +    sev->tio_status = NULL;
>>> +
>>>       mutex_unlock(&sev_cmd_mutex);
>>>   }
>>>   
>>
> 

^ permalink raw reply

* Re: [PATCH kernel v3 4/4] crypto/ccp: Implement SEV-TIO PCIe IDE (phase1)
From: Alexey Kardashevskiy @ 2025-12-02 22:30 UTC (permalink / raw)
  To: Tom Lendacky, linux-kernel
  Cc: linux-crypto, John Allen, Herbert Xu, David S. Miller,
	Ashish Kalra, Joerg Roedel, Suravee Suthikulpanit, Will Deacon,
	Robin Murphy, Borislav Petkov, Borislav Petkov (AMD),
	Dan Williams, Jason Gunthorpe, Jerry Snitselaar, Vasant Hegde,
	Gao Shiyuan, Sean Christopherson, Kim Phillips, Nikunj A Dadhania,
	Michael Roth, Paolo Bonzini, iommu, x86, linux-coco
In-Reply-To: <b6d45b8e-3eeb-4b96-b781-e0ad28861a2c@amd.com>



On 3/12/25 01:52, Tom Lendacky wrote:
> On 12/1/25 20:44, Alexey Kardashevskiy wrote:
>> Implement the SEV-TIO (Trusted I/O) firmware interface for PCIe TDISP
>> (Trust Domain In-Socket Protocol). This enables secure communication
>> between trusted domains and PCIe devices through the PSP (Platform
>> Security Processor).
>>
>> The implementation includes:
>> - Device Security Manager (DSM) operations for establishing secure links
>> - SPDM (Security Protocol and Data Model) over DOE (Data Object Exchange)
>> - IDE (Integrity Data Encryption) stream management for secure PCIe
>>
>> This module bridges the SEV firmware stack with the generic PCIe TSM
>> framework.
>>
>> This is phase1 as described in Documentation/driver-api/pci/tsm.rst.
>>
>> On AMD SEV, the AMD PSP firmware acts as TSM (manages the security/trust).
>> The CCP driver provides the interface to it and registers in the TSM
>> subsystem.
>>
>> Detect the PSP support (reported via FEATURE_INFO + SNP_PLATFORM_STATUS)
>> and enable SEV-TIO in the SNP_INIT_EX call if the hardware supports TIO.
>>
>> Implement SEV TIO PSP command wrappers in sev-dev-tio.c and store
>> the data in the SEV-TIO-specific structs.
>>
>> Implement TSM hooks and IDE setup in sev-dev-tsm.c.
>>
>> Signed-off-by: Alexey Kardashevskiy <aik@amd.com>
> 
> Just some minor comments below. After those are addressed:
> 
> For the ccp related changes in the whole series:
> 
> Acked-by: Tom Lendacky <thomas.lendacky@amd.com>

Dan did it right (thanks Dan!).


> 
>> ---
>> Changes:
>> v2:
>> * moved declarations from sev-dev-tio.h to sev-dev.h
>> * removed include "sev-dev-tio.h" from sev-dev.c to fight errors when TSM is disabled
>> * converted /** to /* as these are part of any external API and trigger unwanted kerneldoc warnings
>> * got rid of ifdefs
>> * "select PCI_TSM" moved under CRYPTO_DEV_SP_PSP
>> * open coded SNP_SEV_TIO_SUPPORTED
>> * renamed tio_present to tio_supp to match the flag name
>> * merged "crypto: ccp: Enable SEV-TIO feature in the PSP when supported" to this one
>> ---
>>   drivers/crypto/ccp/Kconfig       |   1 +
>>   drivers/crypto/ccp/Makefile      |   4 +
>>   drivers/crypto/ccp/sev-dev-tio.h | 123 +++
>>   drivers/crypto/ccp/sev-dev.h     |   9 +
>>   include/linux/psp-sev.h          |  11 +-
>>   drivers/crypto/ccp/sev-dev-tio.c | 864 ++++++++++++++++++++
>>   drivers/crypto/ccp/sev-dev-tsm.c | 405 +++++++++
>>   drivers/crypto/ccp/sev-dev.c     |  51 +-
>>   8 files changed, 1465 insertions(+), 3 deletions(-)
>>
> 
>> diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
>> index 9e0c16b36f9c..d6095d1467b3 100644
>> --- a/drivers/crypto/ccp/sev-dev.c
>> +++ b/drivers/crypto/ccp/sev-dev.c
>> @@ -75,6 +75,10 @@ static bool psp_init_on_probe = true;
>>   module_param(psp_init_on_probe, bool, 0444);
>>   MODULE_PARM_DESC(psp_init_on_probe, "  if true, the PSP will be initialized on module init. Else the PSP will be initialized on the first command requiring it");
>>   
>> +static bool sev_tio_enabled = IS_ENABLED(CONFIG_PCI_TSM);
>> +module_param_named(tio, sev_tio_enabled, bool, 0444);
>> +MODULE_PARM_DESC(tio, "Enables TIO in SNP_INIT_EX");
> 
> Hmmm... I thought you said you wanted to hide the module parameter if
> CONFIG_PCI_TSM isn't enabled. Either way, it's fine.

I did but you did not and I do not care that much :)

> 
>> +
>>   MODULE_FIRMWARE("amd/amd_sev_fam17h_model0xh.sbin"); /* 1st gen EPYC */
>>   MODULE_FIRMWARE("amd/amd_sev_fam17h_model3xh.sbin"); /* 2nd gen EPYC */
>>   MODULE_FIRMWARE("amd/amd_sev_fam19h_model0xh.sbin"); /* 3rd gen EPYC */
>> @@ -251,7 +255,7 @@ static int sev_cmd_buffer_len(int cmd)
>>   	case SEV_CMD_SNP_COMMIT:		return sizeof(struct sev_data_snp_commit);
>>   	case SEV_CMD_SNP_FEATURE_INFO:		return sizeof(struct sev_data_snp_feature_info);
>>   	case SEV_CMD_SNP_VLEK_LOAD:		return sizeof(struct sev_user_data_snp_vlek_load);
>> -	default:				return 0;
>> +	default:				return sev_tio_cmd_buffer_len(cmd);
>>   	}
>>   
>>   	return 0;
>> @@ -1434,6 +1438,19 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>>   		data.init_rmp = 1;
>>   		data.list_paddr_en = 1;
>>   		data.list_paddr = __psp_pa(snp_range_list);
>> +
>> +		bool tio_supp = !!(sev->snp_feat_info_0.ebx & SNP_SEV_TIO_SUPPORTED);
> 
> Please put the variable definition at the top of the "if" block instead
> of in the middle of the code.
>> +
>> +		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
> 
> Don't you still want to take CONFIG_PCI_TSM into account?
> 
> 	data.tio_en = IS_ENABLED(CONFIG_PCI_TSM) && tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
> 
> or
> 	if (IS_ENABLED(CONFIG_PCI_TSM)
> 		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
> 
> But if you change back to #ifdef the module parameter, then you won't
> need the IS_ENABLED() check here because sev_tio_enabled will be set
> based on CONFIG_PCI_TSM and will be false and not changeable if
> CONFIG_PCI_TSM is not y.


Ah true. I thought sev_tio_enabled=IS_ENABLED(CONFIG_PCI_TSM) does it but missed that sev_tio_enabled is exported as a parameter so not a constant at compile time.


>> +
>> +		/*
>> +		 * When psp_init_on_probe is disabled, the userspace calling
>> +		 * SEV ioctl can inadvertently shut down SNP and SEV-TIO causing
>> +		 * unexpected state loss.
>> +		 */
> 
> After this is merged, lets see if sev_move_to_init_state() can be
> cleaned up to avoid this situation.

Do we want to keep psp_init_on_probe, why? Thanks,


> 
> Thanks,
> Tom
> 
>> +		if (data.tio_en && !psp_init_on_probe)
>> +			dev_warn(sev->dev, "SEV-TIO as incompatible with psp_init_on_probe=0\n");
>> +
>>   		cmd = SEV_CMD_SNP_INIT_EX;
>>   	} else {
>>   		cmd = SEV_CMD_SNP_INIT;
>> @@ -1471,7 +1488,8 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>>   
>>   	snp_hv_fixed_pages_state_update(sev, HV_FIXED);
>>   	sev->snp_initialized = true;
>> -	dev_dbg(sev->dev, "SEV-SNP firmware initialized\n");
>> +	dev_dbg(sev->dev, "SEV-SNP firmware initialized, SEV-TIO is %s\n",
>> +		data.tio_en ? "enabled" : "disabled");
>>   
>>   	dev_info(sev->dev, "SEV-SNP API:%d.%d build:%d\n", sev->api_major,
>>   		 sev->api_minor, sev->build);
>> @@ -1479,6 +1497,23 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>>   	atomic_notifier_chain_register(&panic_notifier_list,
>>   				       &snp_panic_notifier);
>>   
>> +	if (data.tio_en) {
>> +		/*
>> +		 * This executes with the sev_cmd_mutex held so down the stack
>> +		 * snp_reclaim_pages(locked=false) might be needed (which is extremely
>> +		 * unlikely) but will cause a deadlock.
>> +		 * Instead of exporting __snp_alloc_firmware_pages(), allocate a page
>> +		 * for this one call here.
>> +		 */
>> +		void *tio_status = page_address(__snp_alloc_firmware_pages(
>> +			GFP_KERNEL_ACCOUNT | __GFP_ZERO, 0, true));
>> +
>> +		if (tio_status) {
>> +			sev_tsm_init_locked(sev, tio_status);
>> +			__snp_free_firmware_pages(virt_to_page(tio_status), 0, true);
>> +		}
>> +	}
>> +
>>   	sev_es_tmr_size = SNP_TMR_SIZE;
>>   
>>   	return 0;
>> @@ -2758,8 +2793,20 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic)
>>   
>>   static void sev_firmware_shutdown(struct sev_device *sev)
>>   {
>> +	/*
>> +	 * Calling without sev_cmd_mutex held as TSM will likely try disconnecting
>> +	 * IDE and this ends up calling sev_do_cmd() which locks sev_cmd_mutex.
>> +	 */
>> +	if (sev->tio_status)
>> +		sev_tsm_uninit(sev);
>> +
>>   	mutex_lock(&sev_cmd_mutex);
>> +
>>   	__sev_firmware_shutdown(sev, false);
>> +
>> +	kfree(sev->tio_status);
>> +	sev->tio_status = NULL;
>> +
>>   	mutex_unlock(&sev_cmd_mutex);
>>   }
>>   
> 

-- 
Alexey


^ permalink raw reply

* Re: [PATCH kernel v3 4/4] crypto/ccp: Implement SEV-TIO PCIe IDE (phase1)
From: Alexey Kardashevskiy @ 2025-12-02 22:26 UTC (permalink / raw)
  To: dan.j.williams, Tom Lendacky, linux-kernel
  Cc: linux-crypto, John Allen, Herbert Xu, David S. Miller,
	Ashish Kalra, Joerg Roedel, Suravee Suthikulpanit, Will Deacon,
	Robin Murphy, Borislav Petkov, Borislav Petkov (AMD),
	Jason Gunthorpe, Jerry Snitselaar, Vasant Hegde, Gao Shiyuan,
	Sean Christopherson, Kim Phillips, Nikunj A Dadhania,
	Michael Roth, Paolo Bonzini, iommu, x86, linux-coco
In-Reply-To: <692f506bb80c9_261c11004@dwillia2-mobl4.notmuch>



On 3/12/25 07:47, dan.j.williams@intel.com wrote:
> Tom Lendacky wrote:
>> On 12/1/25 20:44, Alexey Kardashevskiy wrote:
>>> Implement the SEV-TIO (Trusted I/O) firmware interface for PCIe TDISP
>>> (Trust Domain In-Socket Protocol). This enables secure communication
>>> between trusted domains and PCIe devices through the PSP (Platform
>>> Security Processor).
>>>
>>> The implementation includes:
>>> - Device Security Manager (DSM) operations for establishing secure links
>>> - SPDM (Security Protocol and Data Model) over DOE (Data Object Exchange)
>>> - IDE (Integrity Data Encryption) stream management for secure PCIe
>>>
>>> This module bridges the SEV firmware stack with the generic PCIe TSM
>>> framework.
>>>
>>> This is phase1 as described in Documentation/driver-api/pci/tsm.rst.
>>>
>>> On AMD SEV, the AMD PSP firmware acts as TSM (manages the security/trust).
>>> The CCP driver provides the interface to it and registers in the TSM
>>> subsystem.
>>>
>>> Detect the PSP support (reported via FEATURE_INFO + SNP_PLATFORM_STATUS)
>>> and enable SEV-TIO in the SNP_INIT_EX call if the hardware supports TIO.
>>>
>>> Implement SEV TIO PSP command wrappers in sev-dev-tio.c and store
>>> the data in the SEV-TIO-specific structs.
>>>
>>> Implement TSM hooks and IDE setup in sev-dev-tsm.c.
>>>
>>> Signed-off-by: Alexey Kardashevskiy <aik@amd.com>
>>
>> Just some minor comments below. After those are addressed:
>>
>> For the ccp related changes in the whole series:
>>
>> Acked-by: Tom Lendacky <thomas.lendacky@amd.com>
> 
> Thanks, Tom. Given Alexey is likely sleeping and this needs to start
> soaking in linux-next today to have a chance, I will take a stab at
> these fixups.
> 
>>> Changes:
>>> v2:
>>> * moved declarations from sev-dev-tio.h to sev-dev.h
>>> * removed include "sev-dev-tio.h" from sev-dev.c to fight errors when TSM is disabled
>>> * converted /** to /* as these are part of any external API and trigger unwanted kerneldoc warnings
>>> * got rid of ifdefs
>>> * "select PCI_TSM" moved under CRYPTO_DEV_SP_PSP
>>> * open coded SNP_SEV_TIO_SUPPORTED
>>> * renamed tio_present to tio_supp to match the flag name
>>> * merged "crypto: ccp: Enable SEV-TIO feature in the PSP when supported" to this one
>>> ---
>>>   drivers/crypto/ccp/Kconfig       |   1 +
>>>   drivers/crypto/ccp/Makefile      |   4 +
>>>   drivers/crypto/ccp/sev-dev-tio.h | 123 +++
>>>   drivers/crypto/ccp/sev-dev.h     |   9 +
>>>   include/linux/psp-sev.h          |  11 +-
>>>   drivers/crypto/ccp/sev-dev-tio.c | 864 ++++++++++++++++++++
>>>   drivers/crypto/ccp/sev-dev-tsm.c | 405 +++++++++
>>>   drivers/crypto/ccp/sev-dev.c     |  51 +-
>>>   8 files changed, 1465 insertions(+), 3 deletions(-)
>>>
>>
>>> diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
>>> index 9e0c16b36f9c..d6095d1467b3 100644
>>> --- a/drivers/crypto/ccp/sev-dev.c
>>> +++ b/drivers/crypto/ccp/sev-dev.c
>>> @@ -75,6 +75,10 @@ static bool psp_init_on_probe = true;
>>>   module_param(psp_init_on_probe, bool, 0444);
>>>   MODULE_PARM_DESC(psp_init_on_probe, "  if true, the PSP will be initialized on module init. Else the PSP will be initialized on the first command requiring it");
>>>   
>>> +static bool sev_tio_enabled = IS_ENABLED(CONFIG_PCI_TSM);
>>> +module_param_named(tio, sev_tio_enabled, bool, 0444);
>>> +MODULE_PARM_DESC(tio, "Enables TIO in SNP_INIT_EX");
>>
>> Hmmm... I thought you said you wanted to hide the module parameter if
>> CONFIG_PCI_TSM isn't enabled. Either way, it's fine.
> 
> I think it makes sense to hide options that have no effect.
> 
>>
>>> +
>>>   MODULE_FIRMWARE("amd/amd_sev_fam17h_model0xh.sbin"); /* 1st gen EPYC */
>>>   MODULE_FIRMWARE("amd/amd_sev_fam17h_model3xh.sbin"); /* 2nd gen EPYC */
>>>   MODULE_FIRMWARE("amd/amd_sev_fam19h_model0xh.sbin"); /* 3rd gen EPYC */
>>> @@ -251,7 +255,7 @@ static int sev_cmd_buffer_len(int cmd)
>>>   	case SEV_CMD_SNP_COMMIT:		return sizeof(struct sev_data_snp_commit);
>>>   	case SEV_CMD_SNP_FEATURE_INFO:		return sizeof(struct sev_data_snp_feature_info);
>>>   	case SEV_CMD_SNP_VLEK_LOAD:		return sizeof(struct sev_user_data_snp_vlek_load);
>>> -	default:				return 0;
>>> +	default:				return sev_tio_cmd_buffer_len(cmd);
>>>   	}
>>>   
>>>   	return 0;
>>> @@ -1434,6 +1438,19 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>>>   		data.init_rmp = 1;
>>>   		data.list_paddr_en = 1;
>>>   		data.list_paddr = __psp_pa(snp_range_list);
>>> +
>>> +		bool tio_supp = !!(sev->snp_feat_info_0.ebx & SNP_SEV_TIO_SUPPORTED);
>>
>> Please put the variable definition at the top of the "if" block instead
>> of in the middle of the code.
>>> +
>>> +		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
>>
>> Don't you still want to take CONFIG_PCI_TSM into account?
>>
>> 	data.tio_en = IS_ENABLED(CONFIG_PCI_TSM) && tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
>>
>> or
>> 	if (IS_ENABLED(CONFIG_PCI_TSM)
>> 		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
>>
>> But if you change back to #ifdef the module parameter, then you won't
>> need the IS_ENABLED() check here because sev_tio_enabled will be set
>> based on CONFIG_PCI_TSM and will be false and not changeable if
>> CONFIG_PCI_TSM is not y.
> 
> Yeah, I like that side effect.



g'morning! thanks for fixing that up, looks good to me.

Reviewed-by: Alexey Kardashevskiy <aik@amd.com>




> 
> -- >8 --
>  From 0f4017581b5f2f6defb0e8a05dab2727f3a197b0 Mon Sep 17 00:00:00 2001
> From: Alexey Kardashevskiy <aik@amd.com>
> Date: Tue, 2 Dec 2025 13:44:49 +1100
> Subject: [PATCH v4] crypto/ccp: Implement SEV-TIO PCIe IDE (phase1)
> 
> Implement the SEV-TIO (Trusted I/O) firmware interface for PCIe TDISP
> (Trust Domain In-Socket Protocol). This enables secure communication
> between trusted domains and PCIe devices through the PSP (Platform
> Security Processor).
> 
> The implementation includes:
> - Device Security Manager (DSM) operations for establishing secure links
> - SPDM (Security Protocol and Data Model) over DOE (Data Object Exchange)
> - IDE (Integrity Data Encryption) stream management for secure PCIe
> 
> This module bridges the SEV firmware stack with the generic PCIe TSM
> framework.
> 
> This is phase1 as described in Documentation/driver-api/pci/tsm.rst.
> 
> On AMD SEV, the AMD PSP firmware acts as TSM (manages the security/trust).
> The CCP driver provides the interface to it and registers in the TSM
> subsystem.
> 
> Detect the PSP support (reported via FEATURE_INFO + SNP_PLATFORM_STATUS)
> and enable SEV-TIO in the SNP_INIT_EX call if the hardware supports TIO.
> 
> Implement SEV TIO PSP command wrappers in sev-dev-tio.c and store
> the data in the SEV-TIO-specific structs.
> 
> Implement TSM hooks and IDE setup in sev-dev-tsm.c.
> 
> Signed-off-by: Alexey Kardashevskiy <aik@amd.com>
> Acked-by: Tom Lendacky <thomas.lendacky@amd.com>
> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
> ---
> Changes since v3:
> * Hide the sev_tio_enabled module option when CONFIG_PCI_TSM is
>    disabled, and make the @sev_tio_enabled variable a 'const bool false'
>    value in that case to preclude the need for more
>    IS_ENABLED(CONFIG_PCI_TSM) checks (Tom).
> * Move @tio_supp declaration to top of scope (Tom).
> 
>   drivers/crypto/ccp/Kconfig       |   1 +
>   drivers/crypto/ccp/Makefile      |   4 +
>   drivers/crypto/ccp/sev-dev-tio.h | 123 +++++
>   drivers/crypto/ccp/sev-dev.h     |   9 +
>   include/linux/psp-sev.h          |  11 +-
>   drivers/crypto/ccp/sev-dev-tio.c | 864 +++++++++++++++++++++++++++++++
>   drivers/crypto/ccp/sev-dev-tsm.c | 405 +++++++++++++++
>   drivers/crypto/ccp/sev-dev.c     |  55 +-
>   8 files changed, 1469 insertions(+), 3 deletions(-)
>   create mode 100644 drivers/crypto/ccp/sev-dev-tio.h
>   create mode 100644 drivers/crypto/ccp/sev-dev-tio.c
>   create mode 100644 drivers/crypto/ccp/sev-dev-tsm.c
> 
> diff --git a/drivers/crypto/ccp/Kconfig b/drivers/crypto/ccp/Kconfig
> index f394e45e11ab..e2b127f0986b 100644
> --- a/drivers/crypto/ccp/Kconfig
> +++ b/drivers/crypto/ccp/Kconfig
> @@ -39,6 +39,7 @@ config CRYPTO_DEV_SP_PSP
>   	bool "Platform Security Processor (PSP) device"
>   	default y
>   	depends on CRYPTO_DEV_CCP_DD && X86_64 && AMD_IOMMU
> +	select PCI_TSM
>   	help
>   	 Provide support for the AMD Platform Security Processor (PSP).
>   	 The PSP is a dedicated processor that provides support for key
> diff --git a/drivers/crypto/ccp/Makefile b/drivers/crypto/ccp/Makefile
> index a9626b30044a..0424e08561ef 100644
> --- a/drivers/crypto/ccp/Makefile
> +++ b/drivers/crypto/ccp/Makefile
> @@ -16,6 +16,10 @@ ccp-$(CONFIG_CRYPTO_DEV_SP_PSP) += psp-dev.o \
>                                      hsti.o \
>                                      sfs.o
>   
> +ifeq ($(CONFIG_PCI_TSM),y)
> +ccp-$(CONFIG_CRYPTO_DEV_SP_PSP) += sev-dev-tsm.o sev-dev-tio.o
> +endif
> +
>   obj-$(CONFIG_CRYPTO_DEV_CCP_CRYPTO) += ccp-crypto.o
>   ccp-crypto-objs := ccp-crypto-main.o \
>   		   ccp-crypto-aes.o \
> diff --git a/drivers/crypto/ccp/sev-dev-tio.h b/drivers/crypto/ccp/sev-dev-tio.h
> new file mode 100644
> index 000000000000..67512b3dbc53
> --- /dev/null
> +++ b/drivers/crypto/ccp/sev-dev-tio.h
> @@ -0,0 +1,123 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __PSP_SEV_TIO_H__
> +#define __PSP_SEV_TIO_H__
> +
> +#include <linux/pci-tsm.h>
> +#include <linux/pci-ide.h>
> +#include <linux/tsm.h>
> +#include <uapi/linux/psp-sev.h>
> +
> +struct sla_addr_t {
> +	union {
> +		u64 sla;
> +		struct {
> +			u64 page_type	:1,
> +			    page_size	:1,
> +			    reserved1	:10,
> +			    pfn		:40,
> +			    reserved2	:12;
> +		};
> +	};
> +} __packed;
> +
> +#define SEV_TIO_MAX_COMMAND_LENGTH	128
> +
> +/* SPDM control structure for DOE */
> +struct tsm_spdm {
> +	unsigned long req_len;
> +	void *req;
> +	unsigned long rsp_len;
> +	void *rsp;
> +};
> +
> +/* Describes TIO device */
> +struct tsm_dsm_tio {
> +	u8 cert_slot;
> +	struct sla_addr_t dev_ctx;
> +	struct sla_addr_t req;
> +	struct sla_addr_t resp;
> +	struct sla_addr_t scratch;
> +	struct sla_addr_t output;
> +	size_t output_len;
> +	size_t scratch_len;
> +	struct tsm_spdm spdm;
> +	struct sla_buffer_hdr *reqbuf; /* vmap'ed @req for DOE */
> +	struct sla_buffer_hdr *respbuf; /* vmap'ed @resp for DOE */
> +
> +	int cmd;
> +	int psp_ret;
> +	u8 cmd_data[SEV_TIO_MAX_COMMAND_LENGTH];
> +	void *data_pg; /* Data page for DEV_STATUS/TDI_STATUS/TDI_INFO/ASID_FENCE */
> +
> +#define TIO_IDE_MAX_TC	8
> +	struct pci_ide *ide[TIO_IDE_MAX_TC];
> +};
> +
> +/* Describes TSM structure for PF0 pointed by pci_dev->tsm */
> +struct tio_dsm {
> +	struct pci_tsm_pf0 tsm;
> +	struct tsm_dsm_tio data;
> +	struct sev_device *sev;
> +};
> +
> +/* Data object IDs */
> +#define SPDM_DOBJ_ID_NONE		0
> +#define SPDM_DOBJ_ID_REQ		1
> +#define SPDM_DOBJ_ID_RESP		2
> +
> +struct spdm_dobj_hdr {
> +	u32 id;     /* Data object type identifier */
> +	u32 length; /* Length of the data object, INCLUDING THIS HEADER */
> +	struct { /* Version of the data object structure */
> +		u8 minor;
> +		u8 major;
> +	} version;
> +} __packed;
> +
> +/**
> + * struct sev_tio_status - TIO_STATUS command's info_paddr buffer
> + *
> + * @length: Length of this structure in bytes
> + * @tio_en: Indicates that SNP_INIT_EX initialized the RMP for SEV-TIO
> + * @tio_init_done: Indicates TIO_INIT has been invoked
> + * @spdm_req_size_min: Minimum SPDM request buffer size in bytes
> + * @spdm_req_size_max: Maximum SPDM request buffer size in bytes
> + * @spdm_scratch_size_min: Minimum SPDM scratch buffer size in bytes
> + * @spdm_scratch_size_max: Maximum SPDM scratch buffer size in bytes
> + * @spdm_out_size_min: Minimum SPDM output buffer size in bytes
> + * @spdm_out_size_max: Maximum for the SPDM output buffer size in bytes
> + * @spdm_rsp_size_min: Minimum SPDM response buffer size in bytes
> + * @spdm_rsp_size_max: Maximum SPDM response buffer size in bytes
> + * @devctx_size: Size of a device context buffer in bytes
> + * @tdictx_size: Size of a TDI context buffer in bytes
> + * @tio_crypto_alg: TIO crypto algorithms supported
> + */
> +struct sev_tio_status {
> +	u32 length;
> +	u32 tio_en	  :1,
> +	    tio_init_done :1,
> +	    reserved	  :30;
> +	u32 spdm_req_size_min;
> +	u32 spdm_req_size_max;
> +	u32 spdm_scratch_size_min;
> +	u32 spdm_scratch_size_max;
> +	u32 spdm_out_size_min;
> +	u32 spdm_out_size_max;
> +	u32 spdm_rsp_size_min;
> +	u32 spdm_rsp_size_max;
> +	u32 devctx_size;
> +	u32 tdictx_size;
> +	u32 tio_crypto_alg;
> +	u8 reserved2[12];
> +} __packed;
> +
> +int sev_tio_init_locked(void *tio_status_page);
> +int sev_tio_continue(struct tsm_dsm_tio *dev_data);
> +
> +int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id, u16 root_port_id,
> +		       u8 segment_id);
> +int sev_tio_dev_connect(struct tsm_dsm_tio *dev_data, u8 tc_mask, u8 ids[8], u8 cert_slot);
> +int sev_tio_dev_disconnect(struct tsm_dsm_tio *dev_data, bool force);
> +int sev_tio_dev_reclaim(struct tsm_dsm_tio *dev_data);
> +
> +#endif	/* __PSP_SEV_TIO_H__ */
> diff --git a/drivers/crypto/ccp/sev-dev.h b/drivers/crypto/ccp/sev-dev.h
> index b9029506383f..b1cd556bbbf6 100644
> --- a/drivers/crypto/ccp/sev-dev.h
> +++ b/drivers/crypto/ccp/sev-dev.h
> @@ -34,6 +34,8 @@ struct sev_misc_dev {
>   	struct miscdevice misc;
>   };
>   
> +struct sev_tio_status;
> +
>   struct sev_device {
>   	struct device *dev;
>   	struct psp_device *psp;
> @@ -61,6 +63,9 @@ struct sev_device {
>   
>   	struct sev_user_data_snp_status snp_plat_status;
>   	struct snp_feature_info snp_feat_info_0;
> +
> +	struct tsm_dev *tsmdev;
> +	struct sev_tio_status *tio_status;
>   };
>   
>   int sev_dev_init(struct psp_device *psp);
> @@ -74,4 +79,8 @@ void sev_pci_exit(void);
>   struct page *snp_alloc_hv_fixed_pages(unsigned int num_2mb_pages);
>   void snp_free_hv_fixed_pages(struct page *page);
>   
> +void sev_tsm_init_locked(struct sev_device *sev, void *tio_status_page);
> +void sev_tsm_uninit(struct sev_device *sev);
> +int sev_tio_cmd_buffer_len(int cmd);
> +
>   #endif /* __SEV_DEV_H */
> diff --git a/include/linux/psp-sev.h b/include/linux/psp-sev.h
> index 34a25209f909..cce864dbf281 100644
> --- a/include/linux/psp-sev.h
> +++ b/include/linux/psp-sev.h
> @@ -109,6 +109,13 @@ enum sev_cmd {
>   	SEV_CMD_SNP_VLEK_LOAD		= 0x0CD,
>   	SEV_CMD_SNP_FEATURE_INFO	= 0x0CE,
>   
> +	/* SEV-TIO commands */
> +	SEV_CMD_TIO_STATUS		= 0x0D0,
> +	SEV_CMD_TIO_INIT		= 0x0D1,
> +	SEV_CMD_TIO_DEV_CREATE		= 0x0D2,
> +	SEV_CMD_TIO_DEV_RECLAIM		= 0x0D3,
> +	SEV_CMD_TIO_DEV_CONNECT		= 0x0D4,
> +	SEV_CMD_TIO_DEV_DISCONNECT	= 0x0D5,
>   	SEV_CMD_MAX,
>   };
>   
> @@ -750,7 +757,8 @@ struct sev_data_snp_init_ex {
>   	u32 list_paddr_en:1;
>   	u32 rapl_dis:1;
>   	u32 ciphertext_hiding_en:1;
> -	u32 rsvd:28;
> +	u32 tio_en:1;
> +	u32 rsvd:27;
>   	u32 rsvd1;
>   	u64 list_paddr;
>   	u16 max_snp_asid;
> @@ -850,6 +858,7 @@ struct snp_feature_info {
>   } __packed;
>   
>   #define SNP_CIPHER_TEXT_HIDING_SUPPORTED	BIT(3)
> +#define SNP_SEV_TIO_SUPPORTED			BIT(1) /* EBX */
>   
>   #ifdef CONFIG_CRYPTO_DEV_SP_PSP
>   
> diff --git a/drivers/crypto/ccp/sev-dev-tio.c b/drivers/crypto/ccp/sev-dev-tio.c
> new file mode 100644
> index 000000000000..9a98f98c20a7
> --- /dev/null
> +++ b/drivers/crypto/ccp/sev-dev-tio.c
> @@ -0,0 +1,864 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +// Interface to PSP for CCP/SEV-TIO/SNP-VM
> +
> +#include <linux/pci.h>
> +#include <linux/tsm.h>
> +#include <linux/psp.h>
> +#include <linux/vmalloc.h>
> +#include <linux/bitfield.h>
> +#include <linux/pci-doe.h>
> +#include <asm/sev-common.h>
> +#include <asm/sev.h>
> +#include <asm/page.h>
> +#include "sev-dev.h"
> +#include "sev-dev-tio.h"
> +
> +#define to_tio_status(dev_data)	\
> +		(container_of((dev_data), struct tio_dsm, data)->sev->tio_status)
> +
> +#define SLA_PAGE_TYPE_DATA	0
> +#define SLA_PAGE_TYPE_SCATTER	1
> +#define SLA_PAGE_SIZE_4K	0
> +#define SLA_PAGE_SIZE_2M	1
> +#define SLA_SZ(s)		((s).page_size == SLA_PAGE_SIZE_2M ? SZ_2M : SZ_4K)
> +#define SLA_SCATTER_LEN(s)	(SLA_SZ(s) / sizeof(struct sla_addr_t))
> +#define SLA_EOL			((struct sla_addr_t) { .pfn = ((1UL << 40) - 1) })
> +#define SLA_NULL		((struct sla_addr_t) { 0 })
> +#define IS_SLA_NULL(s)		((s).sla == SLA_NULL.sla)
> +#define IS_SLA_EOL(s)		((s).sla == SLA_EOL.sla)
> +
> +static phys_addr_t sla_to_pa(struct sla_addr_t sla)
> +{
> +	u64 pfn = sla.pfn;
> +	u64 pa = pfn << PAGE_SHIFT;
> +
> +	return pa;
> +}
> +
> +static void *sla_to_va(struct sla_addr_t sla)
> +{
> +	void *va = __va(__sme_clr(sla_to_pa(sla)));
> +
> +	return va;
> +}
> +
> +#define sla_to_pfn(sla)		(__pa(sla_to_va(sla)) >> PAGE_SHIFT)
> +#define sla_to_page(sla)	virt_to_page(sla_to_va(sla))
> +
> +static struct sla_addr_t make_sla(struct page *pg, bool stp)
> +{
> +	u64 pa = __sme_set(page_to_phys(pg));
> +	struct sla_addr_t ret = {
> +		.pfn = pa >> PAGE_SHIFT,
> +		.page_size = SLA_PAGE_SIZE_4K, /* Do not do SLA_PAGE_SIZE_2M ATM */
> +		.page_type = stp ? SLA_PAGE_TYPE_SCATTER : SLA_PAGE_TYPE_DATA
> +	};
> +
> +	return ret;
> +}
> +
> +/* the BUFFER Structure */
> +#define SLA_BUFFER_FLAG_ENCRYPTION	BIT(0)
> +
> +/*
> + * struct sla_buffer_hdr - Scatter list address buffer header
> + *
> + * @capacity_sz: Total capacity of the buffer in bytes
> + * @payload_sz: Size of buffer payload in bytes, must be multiple of 32B
> + * @flags: Buffer flags (SLA_BUFFER_FLAG_ENCRYPTION: buffer is encrypted)
> + * @iv: Initialization vector used for encryption
> + * @authtag: Authentication tag for encrypted buffer
> + */
> +struct sla_buffer_hdr {
> +	u32 capacity_sz;
> +	u32 payload_sz; /* The size of BUFFER_PAYLOAD in bytes. Must be multiple of 32B */
> +	u32 flags;
> +	u8 reserved1[4];
> +	u8 iv[16];	/* IV used for the encryption of this buffer */
> +	u8 authtag[16]; /* Authentication tag for this buffer */
> +	u8 reserved2[16];
> +} __packed;
> +
> +enum spdm_data_type_t {
> +	DOBJ_DATA_TYPE_SPDM = 0x1,
> +	DOBJ_DATA_TYPE_SECURE_SPDM = 0x2,
> +};
> +
> +struct spdm_dobj_hdr_req {
> +	struct spdm_dobj_hdr hdr; /* hdr.id == SPDM_DOBJ_ID_REQ */
> +	u8 data_type; /* spdm_data_type_t */
> +	u8 reserved2[5];
> +} __packed;
> +
> +struct spdm_dobj_hdr_resp {
> +	struct spdm_dobj_hdr hdr; /* hdr.id == SPDM_DOBJ_ID_RESP */
> +	u8 data_type; /* spdm_data_type_t */
> +	u8 reserved2[5];
> +} __packed;
> +
> +/* Defined in sev-dev-tio.h so sev-dev-tsm.c can read types of blobs */
> +struct spdm_dobj_hdr_cert;
> +struct spdm_dobj_hdr_meas;
> +struct spdm_dobj_hdr_report;
> +
> +/* Used in all SPDM-aware TIO commands */
> +struct spdm_ctrl {
> +	struct sla_addr_t req;
> +	struct sla_addr_t resp;
> +	struct sla_addr_t scratch;
> +	struct sla_addr_t output;
> +} __packed;
> +
> +static size_t sla_dobj_id_to_size(u8 id)
> +{
> +	size_t n;
> +
> +	BUILD_BUG_ON(sizeof(struct spdm_dobj_hdr_resp) != 0x10);
> +	switch (id) {
> +	case SPDM_DOBJ_ID_REQ:
> +		n = sizeof(struct spdm_dobj_hdr_req);
> +		break;
> +	case SPDM_DOBJ_ID_RESP:
> +		n = sizeof(struct spdm_dobj_hdr_resp);
> +		break;
> +	default:
> +		WARN_ON(1);
> +		n = 0;
> +		break;
> +	}
> +
> +	return n;
> +}
> +
> +#define SPDM_DOBJ_HDR_SIZE(hdr)		sla_dobj_id_to_size((hdr)->id)
> +#define SPDM_DOBJ_DATA(hdr)		((u8 *)(hdr) + SPDM_DOBJ_HDR_SIZE(hdr))
> +#define SPDM_DOBJ_LEN(hdr)		((hdr)->length - SPDM_DOBJ_HDR_SIZE(hdr))
> +
> +#define sla_to_dobj_resp_hdr(buf)	((struct spdm_dobj_hdr_resp *) \
> +					sla_to_dobj_hdr_check((buf), SPDM_DOBJ_ID_RESP))
> +#define sla_to_dobj_req_hdr(buf)	((struct spdm_dobj_hdr_req *) \
> +					sla_to_dobj_hdr_check((buf), SPDM_DOBJ_ID_REQ))
> +
> +static struct spdm_dobj_hdr *sla_to_dobj_hdr(struct sla_buffer_hdr *buf)
> +{
> +	if (!buf)
> +		return NULL;
> +
> +	return (struct spdm_dobj_hdr *) &buf[1];
> +}
> +
> +static struct spdm_dobj_hdr *sla_to_dobj_hdr_check(struct sla_buffer_hdr *buf, u32 check_dobjid)
> +{
> +	struct spdm_dobj_hdr *hdr = sla_to_dobj_hdr(buf);
> +
> +	if (WARN_ON_ONCE(!hdr))
> +		return NULL;
> +
> +	if (hdr->id != check_dobjid) {
> +		pr_err("! ERROR: expected %d, found %d\n", check_dobjid, hdr->id);
> +		return NULL;
> +	}
> +
> +	return hdr;
> +}
> +
> +static void *sla_to_data(struct sla_buffer_hdr *buf, u32 dobjid)
> +{
> +	struct spdm_dobj_hdr *hdr = sla_to_dobj_hdr(buf);
> +
> +	if (WARN_ON_ONCE(dobjid != SPDM_DOBJ_ID_REQ && dobjid != SPDM_DOBJ_ID_RESP))
> +		return NULL;
> +
> +	if (!hdr)
> +		return NULL;
> +
> +	return (u8 *) hdr + sla_dobj_id_to_size(dobjid);
> +}
> +
> +/*
> + * struct sev_data_tio_status - SEV_CMD_TIO_STATUS command
> + *
> + * @length: Length of this command buffer in bytes
> + * @status_paddr: System physical address of the TIO_STATUS structure
> + */
> +struct sev_data_tio_status {
> +	u32 length;
> +	u8 reserved[4];
> +	u64 status_paddr;
> +} __packed;
> +
> +/* TIO_INIT */
> +struct sev_data_tio_init {
> +	u32 length;
> +	u8 reserved[12];
> +} __packed;
> +
> +/*
> + * struct sev_data_tio_dev_create - TIO_DEV_CREATE command
> + *
> + * @length: Length in bytes of this command buffer
> + * @dev_ctx_sla: Scatter list address pointing to a buffer to be used as a device context buffer
> + * @device_id: PCIe Routing Identifier of the device to connect to
> + * @root_port_id: PCIe Routing Identifier of the root port of the device
> + * @segment_id: PCIe Segment Identifier of the device to connect to
> + */
> +struct sev_data_tio_dev_create {
> +	u32 length;
> +	u8 reserved1[4];
> +	struct sla_addr_t dev_ctx_sla;
> +	u16 device_id;
> +	u16 root_port_id;
> +	u8 segment_id;
> +	u8 reserved2[11];
> +} __packed;
> +
> +/*
> + * struct sev_data_tio_dev_connect - TIO_DEV_CONNECT command
> + *
> + * @length: Length in bytes of this command buffer
> + * @spdm_ctrl: SPDM control structure defined in Section 5.1
> + * @dev_ctx_sla: Scatter list address of the device context buffer
> + * @tc_mask: Bitmask of the traffic classes to initialize for SEV-TIO usage.
> + *           Setting the kth bit of the TC_MASK to 1 indicates that the traffic
> + *           class k will be initialized
> + * @cert_slot: Slot number of the certificate requested for constructing the SPDM session
> + * @ide_stream_id: IDE stream IDs to be associated with this device.
> + *                 Valid only if corresponding bit in TC_MASK is set
> + */
> +struct sev_data_tio_dev_connect {
> +	u32 length;
> +	u8 reserved1[4];
> +	struct spdm_ctrl spdm_ctrl;
> +	u8 reserved2[8];
> +	struct sla_addr_t dev_ctx_sla;
> +	u8 tc_mask;
> +	u8 cert_slot;
> +	u8 reserved3[6];
> +	u8 ide_stream_id[8];
> +	u8 reserved4[8];
> +} __packed;
> +
> +/*
> + * struct sev_data_tio_dev_disconnect - TIO_DEV_DISCONNECT command
> + *
> + * @length: Length in bytes of this command buffer
> + * @flags: Command flags (TIO_DEV_DISCONNECT_FLAG_FORCE: force disconnect)
> + * @spdm_ctrl: SPDM control structure defined in Section 5.1
> + * @dev_ctx_sla: Scatter list address of the device context buffer
> + */
> +#define TIO_DEV_DISCONNECT_FLAG_FORCE	BIT(0)
> +
> +struct sev_data_tio_dev_disconnect {
> +	u32 length;
> +	u32 flags;
> +	struct spdm_ctrl spdm_ctrl;
> +	struct sla_addr_t dev_ctx_sla;
> +} __packed;
> +
> +/*
> + * struct sev_data_tio_dev_meas - TIO_DEV_MEASUREMENTS command
> + *
> + * @length: Length in bytes of this command buffer
> + * @flags: Command flags (TIO_DEV_MEAS_FLAG_RAW_BITSTREAM: request raw measurements)
> + * @spdm_ctrl: SPDM control structure defined in Section 5.1
> + * @dev_ctx_sla: Scatter list address of the device context buffer
> + * @meas_nonce: Nonce for measurement freshness verification
> + */
> +#define TIO_DEV_MEAS_FLAG_RAW_BITSTREAM	BIT(0)
> +
> +struct sev_data_tio_dev_meas {
> +	u32 length;
> +	u32 flags;
> +	struct spdm_ctrl spdm_ctrl;
> +	struct sla_addr_t dev_ctx_sla;
> +	u8 meas_nonce[32];
> +} __packed;
> +
> +/*
> + * struct sev_data_tio_dev_certs - TIO_DEV_CERTIFICATES command
> + *
> + * @length: Length in bytes of this command buffer
> + * @spdm_ctrl: SPDM control structure defined in Section 5.1
> + * @dev_ctx_sla: Scatter list address of the device context buffer
> + */
> +struct sev_data_tio_dev_certs {
> +	u32 length;
> +	u8 reserved[4];
> +	struct spdm_ctrl spdm_ctrl;
> +	struct sla_addr_t dev_ctx_sla;
> +} __packed;
> +
> +/*
> + * struct sev_data_tio_dev_reclaim - TIO_DEV_RECLAIM command
> + *
> + * @length: Length in bytes of this command buffer
> + * @dev_ctx_sla: Scatter list address of the device context buffer
> + *
> + * This command reclaims resources associated with a device context.
> + */
> +struct sev_data_tio_dev_reclaim {
> +	u32 length;
> +	u8 reserved[4];
> +	struct sla_addr_t dev_ctx_sla;
> +} __packed;
> +
> +static struct sla_buffer_hdr *sla_buffer_map(struct sla_addr_t sla)
> +{
> +	struct sla_buffer_hdr *buf;
> +
> +	BUILD_BUG_ON(sizeof(struct sla_buffer_hdr) != 0x40);
> +	if (IS_SLA_NULL(sla))
> +		return NULL;
> +
> +	if (sla.page_type == SLA_PAGE_TYPE_SCATTER) {
> +		struct sla_addr_t *scatter = sla_to_va(sla);
> +		unsigned int i, npages = 0;
> +
> +		for (i = 0; i < SLA_SCATTER_LEN(sla); ++i) {
> +			if (WARN_ON_ONCE(SLA_SZ(scatter[i]) > SZ_4K))
> +				return NULL;
> +
> +			if (WARN_ON_ONCE(scatter[i].page_type == SLA_PAGE_TYPE_SCATTER))
> +				return NULL;
> +
> +			if (IS_SLA_EOL(scatter[i])) {
> +				npages = i;
> +				break;
> +			}
> +		}
> +		if (WARN_ON_ONCE(!npages))
> +			return NULL;
> +
> +		struct page **pp = kmalloc_array(npages, sizeof(pp[0]), GFP_KERNEL);
> +
> +		if (!pp)
> +			return NULL;
> +
> +		for (i = 0; i < npages; ++i)
> +			pp[i] = sla_to_page(scatter[i]);
> +
> +		buf = vm_map_ram(pp, npages, 0);
> +		kfree(pp);
> +	} else {
> +		struct page *pg = sla_to_page(sla);
> +
> +		buf = vm_map_ram(&pg, 1, 0);
> +	}
> +
> +	return buf;
> +}
> +
> +static void sla_buffer_unmap(struct sla_addr_t sla, struct sla_buffer_hdr *buf)
> +{
> +	if (!buf)
> +		return;
> +
> +	if (sla.page_type == SLA_PAGE_TYPE_SCATTER) {
> +		struct sla_addr_t *scatter = sla_to_va(sla);
> +		unsigned int i, npages = 0;
> +
> +		for (i = 0; i < SLA_SCATTER_LEN(sla); ++i) {
> +			if (IS_SLA_EOL(scatter[i])) {
> +				npages = i;
> +				break;
> +			}
> +		}
> +		if (!npages)
> +			return;
> +
> +		vm_unmap_ram(buf, npages);
> +	} else {
> +		vm_unmap_ram(buf, 1);
> +	}
> +}
> +
> +static void dobj_response_init(struct sla_buffer_hdr *buf)
> +{
> +	struct spdm_dobj_hdr *dobj = sla_to_dobj_hdr(buf);
> +
> +	dobj->id = SPDM_DOBJ_ID_RESP;
> +	dobj->version.major = 0x1;
> +	dobj->version.minor = 0;
> +	dobj->length = 0;
> +	buf->payload_sz = sla_dobj_id_to_size(dobj->id) + dobj->length;
> +}
> +
> +static void sla_free(struct sla_addr_t sla, size_t len, bool firmware_state)
> +{
> +	unsigned int npages = PAGE_ALIGN(len) >> PAGE_SHIFT;
> +	struct sla_addr_t *scatter = NULL;
> +	int ret = 0, i;
> +
> +	if (IS_SLA_NULL(sla))
> +		return;
> +
> +	if (firmware_state) {
> +		if (sla.page_type == SLA_PAGE_TYPE_SCATTER) {
> +			scatter = sla_to_va(sla);
> +
> +			for (i = 0; i < npages; ++i) {
> +				if (IS_SLA_EOL(scatter[i]))
> +					break;
> +
> +				ret = snp_reclaim_pages(sla_to_pa(scatter[i]), 1, false);
> +				if (ret)
> +					break;
> +			}
> +		} else {
> +			ret = snp_reclaim_pages(sla_to_pa(sla), 1, false);
> +		}
> +	}
> +
> +	if (WARN_ON(ret))
> +		return;
> +
> +	if (scatter) {
> +		for (i = 0; i < npages; ++i) {
> +			if (IS_SLA_EOL(scatter[i]))
> +				break;
> +			free_page((unsigned long)sla_to_va(scatter[i]));
> +		}
> +	}
> +
> +	free_page((unsigned long)sla_to_va(sla));
> +}
> +
> +static struct sla_addr_t sla_alloc(size_t len, bool firmware_state)
> +{
> +	unsigned long i, npages = PAGE_ALIGN(len) >> PAGE_SHIFT;
> +	struct sla_addr_t *scatter = NULL;
> +	struct sla_addr_t ret = SLA_NULL;
> +	struct sla_buffer_hdr *buf;
> +	struct page *pg;
> +
> +	if (npages == 0)
> +		return ret;
> +
> +	if (WARN_ON_ONCE(npages > ((PAGE_SIZE / sizeof(struct sla_addr_t)) + 1)))
> +		return ret;
> +
> +	BUILD_BUG_ON(PAGE_SIZE < SZ_4K);
> +
> +	if (npages > 1) {
> +		pg = alloc_page(GFP_KERNEL | __GFP_ZERO);
> +		if (!pg)
> +			return SLA_NULL;
> +
> +		ret = make_sla(pg, true);
> +		scatter = page_to_virt(pg);
> +		for (i = 0; i < npages; ++i) {
> +			pg = alloc_page(GFP_KERNEL | __GFP_ZERO);
> +			if (!pg)
> +				goto no_reclaim_exit;
> +
> +			scatter[i] = make_sla(pg, false);
> +		}
> +		scatter[i] = SLA_EOL;
> +	} else {
> +		pg = alloc_page(GFP_KERNEL | __GFP_ZERO);
> +		if (!pg)
> +			return SLA_NULL;
> +
> +		ret = make_sla(pg, false);
> +	}
> +
> +	buf = sla_buffer_map(ret);
> +	if (!buf)
> +		goto no_reclaim_exit;
> +
> +	buf->capacity_sz = (npages << PAGE_SHIFT);
> +	sla_buffer_unmap(ret, buf);
> +
> +	if (firmware_state) {
> +		if (scatter) {
> +			for (i = 0; i < npages; ++i) {
> +				if (rmp_make_private(sla_to_pfn(scatter[i]), 0,
> +						     PG_LEVEL_4K, 0, true))
> +					goto free_exit;
> +			}
> +		} else {
> +			if (rmp_make_private(sla_to_pfn(ret), 0, PG_LEVEL_4K, 0, true))
> +				goto no_reclaim_exit;
> +		}
> +	}
> +
> +	return ret;
> +
> +no_reclaim_exit:
> +	firmware_state = false;
> +free_exit:
> +	sla_free(ret, len, firmware_state);
> +	return SLA_NULL;
> +}
> +
> +/* Expands a buffer, only firmware owned buffers allowed for now */
> +static int sla_expand(struct sla_addr_t *sla, size_t *len)
> +{
> +	struct sla_buffer_hdr *oldbuf = sla_buffer_map(*sla), *newbuf;
> +	struct sla_addr_t oldsla = *sla, newsla;
> +	size_t oldlen = *len, newlen;
> +
> +	if (!oldbuf)
> +		return -EFAULT;
> +
> +	newlen = oldbuf->capacity_sz;
> +	if (oldbuf->capacity_sz == oldlen) {
> +		/* This buffer does not require expansion, must be another buffer */
> +		sla_buffer_unmap(oldsla, oldbuf);
> +		return 1;
> +	}
> +
> +	pr_notice("Expanding BUFFER from %ld to %ld bytes\n", oldlen, newlen);
> +
> +	newsla = sla_alloc(newlen, true);
> +	if (IS_SLA_NULL(newsla))
> +		return -ENOMEM;
> +
> +	newbuf = sla_buffer_map(newsla);
> +	if (!newbuf) {
> +		sla_free(newsla, newlen, true);
> +		return -EFAULT;
> +	}
> +
> +	memcpy(newbuf, oldbuf, oldlen);
> +
> +	sla_buffer_unmap(newsla, newbuf);
> +	sla_free(oldsla, oldlen, true);
> +	*sla = newsla;
> +	*len = newlen;
> +
> +	return 0;
> +}
> +
> +static int sev_tio_do_cmd(int cmd, void *data, size_t data_len, int *psp_ret,
> +			  struct tsm_dsm_tio *dev_data)
> +{
> +	int rc;
> +
> +	*psp_ret = 0;
> +	rc = sev_do_cmd(cmd, data, psp_ret);
> +
> +	if (WARN_ON(!rc && *psp_ret == SEV_RET_SPDM_REQUEST))
> +		return -EIO;
> +
> +	if (rc == 0 && *psp_ret == SEV_RET_EXPAND_BUFFER_LENGTH_REQUEST) {
> +		int rc1, rc2;
> +
> +		rc1 = sla_expand(&dev_data->output, &dev_data->output_len);
> +		if (rc1 < 0)
> +			return rc1;
> +
> +		rc2 = sla_expand(&dev_data->scratch, &dev_data->scratch_len);
> +		if (rc2 < 0)
> +			return rc2;
> +
> +		if (!rc1 && !rc2)
> +			/* Neither buffer requires expansion, this is wrong */
> +			return -EFAULT;
> +
> +		*psp_ret = 0;
> +		rc = sev_do_cmd(cmd, data, psp_ret);
> +	}
> +
> +	if ((rc == 0 || rc == -EIO) && *psp_ret == SEV_RET_SPDM_REQUEST) {
> +		struct spdm_dobj_hdr_resp *resp_hdr;
> +		struct spdm_dobj_hdr_req *req_hdr;
> +		struct sev_tio_status *tio_status = to_tio_status(dev_data);
> +		size_t resp_len = tio_status->spdm_req_size_max -
> +			(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) + sizeof(struct sla_buffer_hdr));
> +
> +		if (!dev_data->cmd) {
> +			if (WARN_ON_ONCE(!data_len || (data_len != *(u32 *) data)))
> +				return -EINVAL;
> +			if (WARN_ON(data_len > sizeof(dev_data->cmd_data)))
> +				return -EFAULT;
> +			memcpy(dev_data->cmd_data, data, data_len);
> +			memset(&dev_data->cmd_data[data_len], 0xFF,
> +			       sizeof(dev_data->cmd_data) - data_len);
> +			dev_data->cmd = cmd;
> +		}
> +
> +		req_hdr = sla_to_dobj_req_hdr(dev_data->reqbuf);
> +		resp_hdr = sla_to_dobj_resp_hdr(dev_data->respbuf);
> +		switch (req_hdr->data_type) {
> +		case DOBJ_DATA_TYPE_SPDM:
> +			rc = PCI_DOE_FEATURE_CMA;
> +			break;
> +		case DOBJ_DATA_TYPE_SECURE_SPDM:
> +			rc = PCI_DOE_FEATURE_SSESSION;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		resp_hdr->data_type = req_hdr->data_type;
> +		dev_data->spdm.req_len = req_hdr->hdr.length -
> +			sla_dobj_id_to_size(SPDM_DOBJ_ID_REQ);
> +		dev_data->spdm.rsp_len = resp_len;
> +	} else if (dev_data && dev_data->cmd) {
> +		/* For either error or success just stop the bouncing */
> +		memset(dev_data->cmd_data, 0, sizeof(dev_data->cmd_data));
> +		dev_data->cmd = 0;
> +	}
> +
> +	return rc;
> +}
> +
> +int sev_tio_continue(struct tsm_dsm_tio *dev_data)
> +{
> +	struct spdm_dobj_hdr_resp *resp_hdr;
> +	int ret;
> +
> +	if (!dev_data || !dev_data->cmd)
> +		return -EINVAL;
> +
> +	resp_hdr = sla_to_dobj_resp_hdr(dev_data->respbuf);
> +	resp_hdr->hdr.length = ALIGN(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) +
> +				     dev_data->spdm.rsp_len, 32);
> +	dev_data->respbuf->payload_sz = resp_hdr->hdr.length;
> +
> +	ret = sev_tio_do_cmd(dev_data->cmd, dev_data->cmd_data, 0,
> +			     &dev_data->psp_ret, dev_data);
> +	if (ret)
> +		return ret;
> +
> +	if (dev_data->psp_ret != SEV_RET_SUCCESS)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static void spdm_ctrl_init(struct spdm_ctrl *ctrl, struct tsm_dsm_tio *dev_data)
> +{
> +	ctrl->req = dev_data->req;
> +	ctrl->resp = dev_data->resp;
> +	ctrl->scratch = dev_data->scratch;
> +	ctrl->output = dev_data->output;
> +}
> +
> +static void spdm_ctrl_free(struct tsm_dsm_tio *dev_data)
> +{
> +	struct sev_tio_status *tio_status = to_tio_status(dev_data);
> +	size_t len = tio_status->spdm_req_size_max -
> +		(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) +
> +		 sizeof(struct sla_buffer_hdr));
> +	struct tsm_spdm *spdm = &dev_data->spdm;
> +
> +	sla_buffer_unmap(dev_data->resp, dev_data->respbuf);
> +	sla_buffer_unmap(dev_data->req, dev_data->reqbuf);
> +	spdm->rsp = NULL;
> +	spdm->req = NULL;
> +	sla_free(dev_data->req, len, true);
> +	sla_free(dev_data->resp, len, false);
> +	sla_free(dev_data->scratch, tio_status->spdm_scratch_size_max, true);
> +
> +	dev_data->req.sla = 0;
> +	dev_data->resp.sla = 0;
> +	dev_data->scratch.sla = 0;
> +	dev_data->respbuf = NULL;
> +	dev_data->reqbuf = NULL;
> +	sla_free(dev_data->output, tio_status->spdm_out_size_max, true);
> +}
> +
> +static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
> +{
> +	struct sev_tio_status *tio_status = to_tio_status(dev_data);
> +	struct tsm_spdm *spdm = &dev_data->spdm;
> +	int ret;
> +
> +	dev_data->req = sla_alloc(tio_status->spdm_req_size_max, true);
> +	dev_data->resp = sla_alloc(tio_status->spdm_req_size_max, false);
> +	dev_data->scratch_len = tio_status->spdm_scratch_size_max;
> +	dev_data->scratch = sla_alloc(dev_data->scratch_len, true);
> +	dev_data->output_len = tio_status->spdm_out_size_max;
> +	dev_data->output = sla_alloc(dev_data->output_len, true);
> +
> +	if (IS_SLA_NULL(dev_data->req) || IS_SLA_NULL(dev_data->resp) ||
> +	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->dev_ctx)) {
> +		ret = -ENOMEM;
> +		goto free_spdm_exit;
> +	}
> +
> +	dev_data->reqbuf = sla_buffer_map(dev_data->req);
> +	dev_data->respbuf = sla_buffer_map(dev_data->resp);
> +	if (!dev_data->reqbuf || !dev_data->respbuf) {
> +		ret = -EFAULT;
> +		goto free_spdm_exit;
> +	}
> +
> +	spdm->req = sla_to_data(dev_data->reqbuf, SPDM_DOBJ_ID_REQ);
> +	spdm->rsp = sla_to_data(dev_data->respbuf, SPDM_DOBJ_ID_RESP);
> +	if (!spdm->req || !spdm->rsp) {
> +		ret = -EFAULT;
> +		goto free_spdm_exit;
> +	}
> +
> +	dobj_response_init(dev_data->respbuf);
> +
> +	return 0;
> +
> +free_spdm_exit:
> +	spdm_ctrl_free(dev_data);
> +	return ret;
> +}
> +
> +int sev_tio_init_locked(void *tio_status_page)
> +{
> +	struct sev_tio_status *tio_status = tio_status_page;
> +	struct sev_data_tio_status data_status = {
> +		.length = sizeof(data_status),
> +	};
> +	int ret, psp_ret;
> +
> +	data_status.status_paddr = __psp_pa(tio_status_page);
> +	ret = __sev_do_cmd_locked(SEV_CMD_TIO_STATUS, &data_status, &psp_ret);
> +	if (ret)
> +		return ret;
> +
> +	if (tio_status->length < offsetofend(struct sev_tio_status, tdictx_size) ||
> +	    tio_status->reserved)
> +		return -EFAULT;
> +
> +	if (!tio_status->tio_en && !tio_status->tio_init_done)
> +		return -ENOENT;
> +
> +	if (tio_status->tio_init_done)
> +		return -EBUSY;
> +
> +	struct sev_data_tio_init ti = { .length = sizeof(ti) };
> +
> +	ret = __sev_do_cmd_locked(SEV_CMD_TIO_INIT, &ti, &psp_ret);
> +	if (ret)
> +		return ret;
> +
> +	ret = __sev_do_cmd_locked(SEV_CMD_TIO_STATUS, &data_status, &psp_ret);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id,
> +		       u16 root_port_id, u8 segment_id)
> +{
> +	struct sev_tio_status *tio_status = to_tio_status(dev_data);
> +	struct sev_data_tio_dev_create create = {
> +		.length = sizeof(create),
> +		.device_id = device_id,
> +		.root_port_id = root_port_id,
> +		.segment_id = segment_id,
> +	};
> +	void *data_pg;
> +	int ret;
> +
> +	dev_data->dev_ctx = sla_alloc(tio_status->devctx_size, true);
> +	if (IS_SLA_NULL(dev_data->dev_ctx))
> +		return -ENOMEM;
> +
> +	data_pg = snp_alloc_firmware_page(GFP_KERNEL_ACCOUNT);
> +	if (!data_pg) {
> +		ret = -ENOMEM;
> +		goto free_ctx_exit;
> +	}
> +
> +	create.dev_ctx_sla = dev_data->dev_ctx;
> +	ret = sev_do_cmd(SEV_CMD_TIO_DEV_CREATE, &create, &dev_data->psp_ret);
> +	if (ret)
> +		goto free_data_pg_exit;
> +
> +	dev_data->data_pg = data_pg;
> +
> +	return 0;
> +
> +free_data_pg_exit:
> +	snp_free_firmware_page(data_pg);
> +free_ctx_exit:
> +	sla_free(create.dev_ctx_sla, tio_status->devctx_size, true);
> +	return ret;
> +}
> +
> +int sev_tio_dev_reclaim(struct tsm_dsm_tio *dev_data)
> +{
> +	struct sev_tio_status *tio_status = to_tio_status(dev_data);
> +	struct sev_data_tio_dev_reclaim r = {
> +		.length = sizeof(r),
> +		.dev_ctx_sla = dev_data->dev_ctx,
> +	};
> +	int ret;
> +
> +	if (dev_data->data_pg) {
> +		snp_free_firmware_page(dev_data->data_pg);
> +		dev_data->data_pg = NULL;
> +	}
> +
> +	if (IS_SLA_NULL(dev_data->dev_ctx))
> +		return 0;
> +
> +	ret = sev_do_cmd(SEV_CMD_TIO_DEV_RECLAIM, &r, &dev_data->psp_ret);
> +
> +	sla_free(dev_data->dev_ctx, tio_status->devctx_size, true);
> +	dev_data->dev_ctx = SLA_NULL;
> +
> +	spdm_ctrl_free(dev_data);
> +
> +	return ret;
> +}
> +
> +int sev_tio_dev_connect(struct tsm_dsm_tio *dev_data, u8 tc_mask, u8 ids[8], u8 cert_slot)
> +{
> +	struct sev_data_tio_dev_connect connect = {
> +		.length = sizeof(connect),
> +		.tc_mask = tc_mask,
> +		.cert_slot = cert_slot,
> +		.dev_ctx_sla = dev_data->dev_ctx,
> +		.ide_stream_id = {
> +			ids[0], ids[1], ids[2], ids[3],
> +			ids[4], ids[5], ids[6], ids[7]
> +		},
> +	};
> +	int ret;
> +
> +	if (WARN_ON(IS_SLA_NULL(dev_data->dev_ctx)))
> +		return -EFAULT;
> +	if (!(tc_mask & 1))
> +		return -EINVAL;
> +
> +	ret = spdm_ctrl_alloc(dev_data);
> +	if (ret)
> +		return ret;
> +
> +	spdm_ctrl_init(&connect.spdm_ctrl, dev_data);
> +
> +	return sev_tio_do_cmd(SEV_CMD_TIO_DEV_CONNECT, &connect, sizeof(connect),
> +			      &dev_data->psp_ret, dev_data);
> +}
> +
> +int sev_tio_dev_disconnect(struct tsm_dsm_tio *dev_data, bool force)
> +{
> +	struct sev_data_tio_dev_disconnect dc = {
> +		.length = sizeof(dc),
> +		.dev_ctx_sla = dev_data->dev_ctx,
> +		.flags = force ? TIO_DEV_DISCONNECT_FLAG_FORCE : 0,
> +	};
> +
> +	if (WARN_ON_ONCE(IS_SLA_NULL(dev_data->dev_ctx)))
> +		return -EFAULT;
> +
> +	spdm_ctrl_init(&dc.spdm_ctrl, dev_data);
> +
> +	return sev_tio_do_cmd(SEV_CMD_TIO_DEV_DISCONNECT, &dc, sizeof(dc),
> +			      &dev_data->psp_ret, dev_data);
> +}
> +
> +int sev_tio_cmd_buffer_len(int cmd)
> +{
> +	switch (cmd) {
> +	case SEV_CMD_TIO_STATUS:		return sizeof(struct sev_data_tio_status);
> +	case SEV_CMD_TIO_INIT:			return sizeof(struct sev_data_tio_init);
> +	case SEV_CMD_TIO_DEV_CREATE:		return sizeof(struct sev_data_tio_dev_create);
> +	case SEV_CMD_TIO_DEV_RECLAIM:		return sizeof(struct sev_data_tio_dev_reclaim);
> +	case SEV_CMD_TIO_DEV_CONNECT:		return sizeof(struct sev_data_tio_dev_connect);
> +	case SEV_CMD_TIO_DEV_DISCONNECT:	return sizeof(struct sev_data_tio_dev_disconnect);
> +	default:				return 0;
> +	}
> +}
> diff --git a/drivers/crypto/ccp/sev-dev-tsm.c b/drivers/crypto/ccp/sev-dev-tsm.c
> new file mode 100644
> index 000000000000..ea29cd5d0ff9
> --- /dev/null
> +++ b/drivers/crypto/ccp/sev-dev-tsm.c
> @@ -0,0 +1,405 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +// Interface to CCP/SEV-TIO for generic PCIe TDISP module
> +
> +#include <linux/pci.h>
> +#include <linux/device.h>
> +#include <linux/tsm.h>
> +#include <linux/iommu.h>
> +#include <linux/pci-doe.h>
> +#include <linux/bitfield.h>
> +#include <linux/module.h>
> +
> +#include <asm/sev-common.h>
> +#include <asm/sev.h>
> +
> +#include "psp-dev.h"
> +#include "sev-dev.h"
> +#include "sev-dev-tio.h"
> +
> +MODULE_IMPORT_NS("PCI_IDE");
> +
> +#define TIO_DEFAULT_NR_IDE_STREAMS	1
> +
> +static uint nr_ide_streams = TIO_DEFAULT_NR_IDE_STREAMS;
> +module_param_named(ide_nr, nr_ide_streams, uint, 0644);
> +MODULE_PARM_DESC(ide_nr, "Set the maximum number of IDE streams per PHB");
> +
> +#define dev_to_sp(dev)		((struct sp_device *)dev_get_drvdata(dev))
> +#define dev_to_psp(dev)		((struct psp_device *)(dev_to_sp(dev)->psp_data))
> +#define dev_to_sev(dev)		((struct sev_device *)(dev_to_psp(dev)->sev_data))
> +#define tsm_dev_to_sev(tsmdev)	dev_to_sev((tsmdev)->dev.parent)
> +
> +#define pdev_to_tio_dsm(pdev)	(container_of((pdev)->tsm, struct tio_dsm, tsm.base_tsm))
> +
> +static int sev_tio_spdm_cmd(struct tio_dsm *dsm, int ret)
> +{
> +	struct tsm_dsm_tio *dev_data = &dsm->data;
> +	struct tsm_spdm *spdm = &dev_data->spdm;
> +
> +	/* Check the main command handler response before entering the loop */
> +	if (ret == 0 && dev_data->psp_ret != SEV_RET_SUCCESS)
> +		return -EINVAL;
> +
> +	if (ret <= 0)
> +		return ret;
> +
> +	/* ret > 0 means "SPDM requested" */
> +	while (ret == PCI_DOE_FEATURE_CMA || ret == PCI_DOE_FEATURE_SSESSION) {
> +		ret = pci_doe(dsm->tsm.doe_mb, PCI_VENDOR_ID_PCI_SIG, ret,
> +			      spdm->req, spdm->req_len, spdm->rsp, spdm->rsp_len);
> +		if (ret < 0)
> +			break;
> +
> +		WARN_ON_ONCE(ret == 0); /* The response should never be empty */
> +		spdm->rsp_len = ret;
> +		ret = sev_tio_continue(dev_data);
> +	}
> +
> +	return ret;
> +}
> +
> +static int stream_enable(struct pci_ide *ide)
> +{
> +	struct pci_dev *rp = pcie_find_root_port(ide->pdev);
> +	int ret;
> +
> +	ret = pci_ide_stream_enable(rp, ide);
> +	if (ret)
> +		return ret;
> +
> +	ret = pci_ide_stream_enable(ide->pdev, ide);
> +	if (ret)
> +		pci_ide_stream_disable(rp, ide);
> +
> +	return ret;
> +}
> +
> +static int streams_enable(struct pci_ide **ide)
> +{
> +	int ret = 0;
> +
> +	for (int i = 0; i < TIO_IDE_MAX_TC; ++i) {
> +		if (ide[i]) {
> +			ret = stream_enable(ide[i]);
> +			if (ret)
> +				break;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static void stream_disable(struct pci_ide *ide)
> +{
> +	pci_ide_stream_disable(ide->pdev, ide);
> +	pci_ide_stream_disable(pcie_find_root_port(ide->pdev), ide);
> +}
> +
> +static void streams_disable(struct pci_ide **ide)
> +{
> +	for (int i = 0; i < TIO_IDE_MAX_TC; ++i)
> +		if (ide[i])
> +			stream_disable(ide[i]);
> +}
> +
> +static void stream_setup(struct pci_ide *ide)
> +{
> +	struct pci_dev *rp = pcie_find_root_port(ide->pdev);
> +
> +	ide->partner[PCI_IDE_EP].rid_start = 0;
> +	ide->partner[PCI_IDE_EP].rid_end = 0xffff;
> +	ide->partner[PCI_IDE_RP].rid_start = 0;
> +	ide->partner[PCI_IDE_RP].rid_end = 0xffff;
> +
> +	ide->pdev->ide_cfg = 0;
> +	ide->pdev->ide_tee_limit = 1;
> +	rp->ide_cfg = 1;
> +	rp->ide_tee_limit = 0;
> +
> +	pci_warn(ide->pdev, "Forcing CFG/TEE for %s", pci_name(rp));
> +	pci_ide_stream_setup(ide->pdev, ide);
> +	pci_ide_stream_setup(rp, ide);
> +}
> +
> +static u8 streams_setup(struct pci_ide **ide, u8 *ids)
> +{
> +	bool def = false;
> +	u8 tc_mask = 0;
> +	int i;
> +
> +	for (i = 0; i < TIO_IDE_MAX_TC; ++i) {
> +		if (!ide[i]) {
> +			ids[i] = 0xFF;
> +			continue;
> +		}
> +
> +		tc_mask |= BIT(i);
> +		ids[i] = ide[i]->stream_id;
> +
> +		if (!def) {
> +			struct pci_ide_partner *settings;
> +
> +			settings = pci_ide_to_settings(ide[i]->pdev, ide[i]);
> +			settings->default_stream = 1;
> +			def = true;
> +		}
> +
> +		stream_setup(ide[i]);
> +	}
> +
> +	return tc_mask;
> +}
> +
> +static int streams_register(struct pci_ide **ide)
> +{
> +	int ret = 0, i;
> +
> +	for (i = 0; i < TIO_IDE_MAX_TC; ++i) {
> +		if (ide[i]) {
> +			ret = pci_ide_stream_register(ide[i]);
> +			if (ret)
> +				break;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static void streams_unregister(struct pci_ide **ide)
> +{
> +	for (int i = 0; i < TIO_IDE_MAX_TC; ++i)
> +		if (ide[i])
> +			pci_ide_stream_unregister(ide[i]);
> +}
> +
> +static void stream_teardown(struct pci_ide *ide)
> +{
> +	pci_ide_stream_teardown(ide->pdev, ide);
> +	pci_ide_stream_teardown(pcie_find_root_port(ide->pdev), ide);
> +}
> +
> +static void streams_teardown(struct pci_ide **ide)
> +{
> +	for (int i = 0; i < TIO_IDE_MAX_TC; ++i) {
> +		if (ide[i]) {
> +			stream_teardown(ide[i]);
> +			pci_ide_stream_free(ide[i]);
> +			ide[i] = NULL;
> +		}
> +	}
> +}
> +
> +static int stream_alloc(struct pci_dev *pdev, struct pci_ide **ide,
> +			unsigned int tc)
> +{
> +	struct pci_dev *rp = pcie_find_root_port(pdev);
> +	struct pci_ide *ide1;
> +
> +	if (ide[tc]) {
> +		pci_err(pdev, "Stream for class=%d already registered", tc);
> +		return -EBUSY;
> +	}
> +
> +	/* FIXME: find a better way */
> +	if (nr_ide_streams != TIO_DEFAULT_NR_IDE_STREAMS)
> +		pci_notice(pdev, "Enable non-default %d streams", nr_ide_streams);
> +	pci_ide_set_nr_streams(to_pci_host_bridge(rp->bus->bridge), nr_ide_streams);
> +
> +	ide1 = pci_ide_stream_alloc(pdev);
> +	if (!ide1)
> +		return -EFAULT;
> +
> +	/* Blindly assign streamid=0 to TC=0, and so on */
> +	ide1->stream_id = tc;
> +
> +	ide[tc] = ide1;
> +
> +	return 0;
> +}
> +
> +static struct pci_tsm *tio_pf0_probe(struct pci_dev *pdev, struct sev_device *sev)
> +{
> +	struct tio_dsm *dsm __free(kfree) = kzalloc(sizeof(*dsm), GFP_KERNEL);
> +	int rc;
> +
> +	if (!dsm)
> +		return NULL;
> +
> +	rc = pci_tsm_pf0_constructor(pdev, &dsm->tsm, sev->tsmdev);
> +	if (rc)
> +		return NULL;
> +
> +	pci_dbg(pdev, "TSM enabled\n");
> +	dsm->sev = sev;
> +	return &no_free_ptr(dsm)->tsm.base_tsm;
> +}
> +
> +static struct pci_tsm *dsm_probe(struct tsm_dev *tsmdev, struct pci_dev *pdev)
> +{
> +	struct sev_device *sev = tsm_dev_to_sev(tsmdev);
> +
> +	if (is_pci_tsm_pf0(pdev))
> +		return tio_pf0_probe(pdev, sev);
> +	return 0;
> +}
> +
> +static void dsm_remove(struct pci_tsm *tsm)
> +{
> +	struct pci_dev *pdev = tsm->pdev;
> +
> +	pci_dbg(pdev, "TSM disabled\n");
> +
> +	if (is_pci_tsm_pf0(pdev)) {
> +		struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm);
> +
> +		pci_tsm_pf0_destructor(&dsm->tsm);
> +		kfree(dsm);
> +	}
> +}
> +
> +static int dsm_create(struct tio_dsm *dsm)
> +{
> +	struct pci_dev *pdev = dsm->tsm.base_tsm.pdev;
> +	u8 segment_id = pdev->bus ? pci_domain_nr(pdev->bus) : 0;
> +	struct pci_dev *rootport = pcie_find_root_port(pdev);
> +	u16 device_id = pci_dev_id(pdev);
> +	u16 root_port_id;
> +	u32 lnkcap = 0;
> +
> +	if (pci_read_config_dword(rootport, pci_pcie_cap(rootport) + PCI_EXP_LNKCAP,
> +				  &lnkcap))
> +		return -ENODEV;
> +
> +	root_port_id = FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap);
> +
> +	return sev_tio_dev_create(&dsm->data, device_id, root_port_id, segment_id);
> +}
> +
> +static int dsm_connect(struct pci_dev *pdev)
> +{
> +	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
> +	struct tsm_dsm_tio *dev_data = &dsm->data;
> +	u8 ids[TIO_IDE_MAX_TC];
> +	u8 tc_mask;
> +	int ret;
> +
> +	if (pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
> +				 PCI_DOE_FEATURE_SSESSION) != dsm->tsm.doe_mb) {
> +		pci_err(pdev, "CMA DOE MB must support SSESSION\n");
> +		return -EFAULT;
> +	}
> +
> +	ret = stream_alloc(pdev, dev_data->ide, 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = dsm_create(dsm);
> +	if (ret)
> +		goto ide_free_exit;
> +
> +	tc_mask = streams_setup(dev_data->ide, ids);
> +
> +	ret = sev_tio_dev_connect(dev_data, tc_mask, ids, dev_data->cert_slot);
> +	ret = sev_tio_spdm_cmd(dsm, ret);
> +	if (ret)
> +		goto free_exit;
> +
> +	streams_enable(dev_data->ide);
> +
> +	ret = streams_register(dev_data->ide);
> +	if (ret)
> +		goto free_exit;
> +
> +	return 0;
> +
> +free_exit:
> +	sev_tio_dev_reclaim(dev_data);
> +
> +	streams_disable(dev_data->ide);
> +ide_free_exit:
> +
> +	streams_teardown(dev_data->ide);
> +
> +	return ret;
> +}
> +
> +static void dsm_disconnect(struct pci_dev *pdev)
> +{
> +	bool force = SYSTEM_HALT <= system_state && system_state <= SYSTEM_RESTART;
> +	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
> +	struct tsm_dsm_tio *dev_data = &dsm->data;
> +	int ret;
> +
> +	ret = sev_tio_dev_disconnect(dev_data, force);
> +	ret = sev_tio_spdm_cmd(dsm, ret);
> +	if (ret && !force) {
> +		ret = sev_tio_dev_disconnect(dev_data, true);
> +		sev_tio_spdm_cmd(dsm, ret);
> +	}
> +
> +	sev_tio_dev_reclaim(dev_data);
> +
> +	streams_disable(dev_data->ide);
> +	streams_unregister(dev_data->ide);
> +	streams_teardown(dev_data->ide);
> +}
> +
> +static struct pci_tsm_ops sev_tsm_ops = {
> +	.probe = dsm_probe,
> +	.remove = dsm_remove,
> +	.connect = dsm_connect,
> +	.disconnect = dsm_disconnect,
> +};
> +
> +void sev_tsm_init_locked(struct sev_device *sev, void *tio_status_page)
> +{
> +	struct sev_tio_status *t = kzalloc(sizeof(*t), GFP_KERNEL);
> +	struct tsm_dev *tsmdev;
> +	int ret;
> +
> +	WARN_ON(sev->tio_status);
> +
> +	if (!t)
> +		return;
> +
> +	ret = sev_tio_init_locked(tio_status_page);
> +	if (ret) {
> +		pr_warn("SEV-TIO STATUS failed with %d\n", ret);
> +		goto error_exit;
> +	}
> +
> +	tsmdev = tsm_register(sev->dev, &sev_tsm_ops);
> +	if (IS_ERR(tsmdev))
> +		goto error_exit;
> +
> +	memcpy(t, tio_status_page, sizeof(*t));
> +
> +	pr_notice("SEV-TIO status: EN=%d INIT_DONE=%d rq=%d..%d rs=%d..%d "
> +		  "scr=%d..%d out=%d..%d dev=%d tdi=%d algos=%x\n",
> +		  t->tio_en, t->tio_init_done,
> +		  t->spdm_req_size_min, t->spdm_req_size_max,
> +		  t->spdm_rsp_size_min, t->spdm_rsp_size_max,
> +		  t->spdm_scratch_size_min, t->spdm_scratch_size_max,
> +		  t->spdm_out_size_min, t->spdm_out_size_max,
> +		  t->devctx_size, t->tdictx_size,
> +		  t->tio_crypto_alg);
> +
> +	sev->tsmdev = tsmdev;
> +	sev->tio_status = t;
> +
> +	return;
> +
> +error_exit:
> +	kfree(t);
> +	pr_err("Failed to enable SEV-TIO: ret=%d en=%d initdone=%d SEV=%d\n",
> +	       ret, t->tio_en, t->tio_init_done, boot_cpu_has(X86_FEATURE_SEV));
> +}
> +
> +void sev_tsm_uninit(struct sev_device *sev)
> +{
> +	if (sev->tsmdev)
> +		tsm_unregister(sev->tsmdev);
> +
> +	sev->tsmdev = NULL;
> +}
> diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
> index 9e0c16b36f9c..8a74a08553a5 100644
> --- a/drivers/crypto/ccp/sev-dev.c
> +++ b/drivers/crypto/ccp/sev-dev.c
> @@ -75,6 +75,14 @@ static bool psp_init_on_probe = true;
>   module_param(psp_init_on_probe, bool, 0444);
>   MODULE_PARM_DESC(psp_init_on_probe, "  if true, the PSP will be initialized on module init. Else the PSP will be initialized on the first command requiring it");
>   
> +#if IS_ENABLED(CONFIG_PCI_TSM)
> +static bool sev_tio_enabled = true;
> +module_param_named(tio, sev_tio_enabled, bool, 0444);
> +MODULE_PARM_DESC(tio, "Enables TIO in SNP_INIT_EX");
> +#else
> +static const bool sev_tio_enabled = false;
> +#endif
> +
>   MODULE_FIRMWARE("amd/amd_sev_fam17h_model0xh.sbin"); /* 1st gen EPYC */
>   MODULE_FIRMWARE("amd/amd_sev_fam17h_model3xh.sbin"); /* 2nd gen EPYC */
>   MODULE_FIRMWARE("amd/amd_sev_fam19h_model0xh.sbin"); /* 3rd gen EPYC */
> @@ -251,7 +259,7 @@ static int sev_cmd_buffer_len(int cmd)
>   	case SEV_CMD_SNP_COMMIT:		return sizeof(struct sev_data_snp_commit);
>   	case SEV_CMD_SNP_FEATURE_INFO:		return sizeof(struct sev_data_snp_feature_info);
>   	case SEV_CMD_SNP_VLEK_LOAD:		return sizeof(struct sev_user_data_snp_vlek_load);
> -	default:				return 0;
> +	default:				return sev_tio_cmd_buffer_len(cmd);
>   	}
>   
>   	return 0;
> @@ -1394,6 +1402,8 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>   	 *
>   	 */
>   	if (sev_version_greater_or_equal(SNP_MIN_API_MAJOR, 52)) {
> +		bool tio_supp = !!(sev->snp_feat_info_0.ebx & SNP_SEV_TIO_SUPPORTED);
> +
>   		/*
>   		 * Firmware checks that the pages containing the ranges enumerated
>   		 * in the RANGES structure are either in the default page state or in the
> @@ -1434,6 +1444,17 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>   		data.init_rmp = 1;
>   		data.list_paddr_en = 1;
>   		data.list_paddr = __psp_pa(snp_range_list);
> +
> +		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
> +
> +		/*
> +		 * When psp_init_on_probe is disabled, the userspace calling
> +		 * SEV ioctl can inadvertently shut down SNP and SEV-TIO causing
> +		 * unexpected state loss.
> +		 */
> +		if (data.tio_en && !psp_init_on_probe)
> +			dev_warn(sev->dev, "SEV-TIO as incompatible with psp_init_on_probe=0\n");
> +
>   		cmd = SEV_CMD_SNP_INIT_EX;
>   	} else {
>   		cmd = SEV_CMD_SNP_INIT;
> @@ -1471,7 +1492,8 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>   
>   	snp_hv_fixed_pages_state_update(sev, HV_FIXED);
>   	sev->snp_initialized = true;
> -	dev_dbg(sev->dev, "SEV-SNP firmware initialized\n");
> +	dev_dbg(sev->dev, "SEV-SNP firmware initialized, SEV-TIO is %s\n",
> +		data.tio_en ? "enabled" : "disabled");
>   
>   	dev_info(sev->dev, "SEV-SNP API:%d.%d build:%d\n", sev->api_major,
>   		 sev->api_minor, sev->build);
> @@ -1479,6 +1501,23 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>   	atomic_notifier_chain_register(&panic_notifier_list,
>   				       &snp_panic_notifier);
>   
> +	if (data.tio_en) {
> +		/*
> +		 * This executes with the sev_cmd_mutex held so down the stack
> +		 * snp_reclaim_pages(locked=false) might be needed (which is extremely
> +		 * unlikely) but will cause a deadlock.
> +		 * Instead of exporting __snp_alloc_firmware_pages(), allocate a page
> +		 * for this one call here.
> +		 */
> +		void *tio_status = page_address(__snp_alloc_firmware_pages(
> +			GFP_KERNEL_ACCOUNT | __GFP_ZERO, 0, true));
> +
> +		if (tio_status) {
> +			sev_tsm_init_locked(sev, tio_status);
> +			__snp_free_firmware_pages(virt_to_page(tio_status), 0, true);
> +		}
> +	}
> +
>   	sev_es_tmr_size = SNP_TMR_SIZE;
>   
>   	return 0;
> @@ -2758,8 +2797,20 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic)
>   
>   static void sev_firmware_shutdown(struct sev_device *sev)
>   {
> +	/*
> +	 * Calling without sev_cmd_mutex held as TSM will likely try disconnecting
> +	 * IDE and this ends up calling sev_do_cmd() which locks sev_cmd_mutex.
> +	 */
> +	if (sev->tio_status)
> +		sev_tsm_uninit(sev);
> +
>   	mutex_lock(&sev_cmd_mutex);
> +
>   	__sev_firmware_shutdown(sev, false);
> +
> +	kfree(sev->tio_status);
> +	sev->tio_status = NULL;
> +
>   	mutex_unlock(&sev_cmd_mutex);
>   }
>   

-- 
Alexey


^ permalink raw reply

* [PATCH v5] virt: tdx-guest: Handle GetQuote request error code
From: Kuppuswamy Sathyanarayanan @ 2025-12-02 22:22 UTC (permalink / raw)
  To: Kirill A . Shutemov
  Cc: Rick Edgecombe, Dave Hansen, Dan Williams, x86, linux-kernel,
	linux-coco

The tdx-guest driver sends Quote requests to the quoting enclave via a
hypercall to obtain attestation evidence for the current TD state.
Quote generation can fail in two ways: a hypercall failure, or a Quote
failure that occurs after the VMM processes the request. The driver
currently handles only hypercall failures and timeout errors during
Quote processing. Update it to also handle other Quote failures
reported by the VMM (for more details, refer to GHCI spec, v1.5,
March 2023, sec titled "TDG.VP.VMCALL<GetQuote>).

This change does not break the existing ABI behavior. When a Quote
failure occurs, the VMM sets the Quote length to zero. Userspace
already interprets a zero-length Quote as a Quote generation failure.
Returning an explicit error in such cases makes the behavior more
consistent and simplifies error handling in userspace.

Fixes: f4738f56d1dc ("virt: tdx-guest: Add Quote generation support using TSM_REPORTS")
Reported-by: Xiaoyao Li <xiaoyao.li@intel.com>
Closes: https://lore.kernel.org/linux-coco/6bdf569c-684a-4459-af7c-4430691804eb@linux.intel.com/T/#u
Closes: https://github.com/confidential-containers/guest-components/issues/823
Reviewed-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Reviewed-by: Xiaoyao Li <xiaoyao.li@intel.com>
Acked-by: Kai Huang <kai.huang@intel.com>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Tested-by: Mikko Ylinen <mikko.ylinen@linux.intel.com>
Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
---

Changes since v4:
 * Rebased on top of v6.18-rc1
 * Added Tested-by tag from Mikko.
 * Added more details in commit log to clarify no user impact and also
   link to a related github issue.
 * Added error message for the failed  case.

Changes since v3:
 * Rebased on top of v6.9-rc1
 * Added Dan's Reviewed-by tag.

Changes since v2:
 * Updated the commit log (Dan)
 * Removed pr_err message.

Changes since v1:
 * Updated the commit log (Kirill)

 drivers/virt/coco/tdx-guest/tdx-guest.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c
index 4e239ec960c9..4e55958184d2 100644
--- a/drivers/virt/coco/tdx-guest/tdx-guest.c
+++ b/drivers/virt/coco/tdx-guest/tdx-guest.c
@@ -304,6 +304,11 @@ static int tdx_report_new_locked(struct tsm_report *report, void *data)
 		return ret;
 	}
 
+	if (quote_buf->status != GET_QUOTE_SUCCESS) {
+		pr_err("GetQuote request failed, status:%llx\n", quote_buf->status);
+		return -EIO;
+	}
+
 	buf = kvmemdup(quote_buf->data, quote_buf->out_len, GFP_KERNEL);
 	if (!buf)
 		return -ENOMEM;
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH kernel v3 4/4] crypto/ccp: Implement SEV-TIO PCIe IDE (phase1)
From: dan.j.williams @ 2025-12-02 20:47 UTC (permalink / raw)
  To: Tom Lendacky, Alexey Kardashevskiy, linux-kernel
  Cc: linux-crypto, John Allen, Herbert Xu, David S. Miller,
	Ashish Kalra, Joerg Roedel, Suravee Suthikulpanit, Will Deacon,
	Robin Murphy, Borislav Petkov, Borislav Petkov (AMD),
	Dan Williams, Jason Gunthorpe, Jerry Snitselaar, Vasant Hegde,
	Gao Shiyuan, Sean Christopherson, Kim Phillips, Nikunj A Dadhania,
	Michael Roth, Paolo Bonzini, iommu, x86, linux-coco
In-Reply-To: <b6d45b8e-3eeb-4b96-b781-e0ad28861a2c@amd.com>

Tom Lendacky wrote:
> On 12/1/25 20:44, Alexey Kardashevskiy wrote:
> > Implement the SEV-TIO (Trusted I/O) firmware interface for PCIe TDISP
> > (Trust Domain In-Socket Protocol). This enables secure communication
> > between trusted domains and PCIe devices through the PSP (Platform
> > Security Processor).
> > 
> > The implementation includes:
> > - Device Security Manager (DSM) operations for establishing secure links
> > - SPDM (Security Protocol and Data Model) over DOE (Data Object Exchange)
> > - IDE (Integrity Data Encryption) stream management for secure PCIe
> > 
> > This module bridges the SEV firmware stack with the generic PCIe TSM
> > framework.
> > 
> > This is phase1 as described in Documentation/driver-api/pci/tsm.rst.
> > 
> > On AMD SEV, the AMD PSP firmware acts as TSM (manages the security/trust).
> > The CCP driver provides the interface to it and registers in the TSM
> > subsystem.
> > 
> > Detect the PSP support (reported via FEATURE_INFO + SNP_PLATFORM_STATUS)
> > and enable SEV-TIO in the SNP_INIT_EX call if the hardware supports TIO.
> > 
> > Implement SEV TIO PSP command wrappers in sev-dev-tio.c and store
> > the data in the SEV-TIO-specific structs.
> > 
> > Implement TSM hooks and IDE setup in sev-dev-tsm.c.
> > 
> > Signed-off-by: Alexey Kardashevskiy <aik@amd.com>
> 
> Just some minor comments below. After those are addressed:
> 
> For the ccp related changes in the whole series:
> 
> Acked-by: Tom Lendacky <thomas.lendacky@amd.com>

Thanks, Tom. Given Alexey is likely sleeping and this needs to start
soaking in linux-next today to have a chance, I will take a stab at
these fixups.

> > Changes:
> > v2:
> > * moved declarations from sev-dev-tio.h to sev-dev.h
> > * removed include "sev-dev-tio.h" from sev-dev.c to fight errors when TSM is disabled
> > * converted /** to /* as these are part of any external API and trigger unwanted kerneldoc warnings
> > * got rid of ifdefs
> > * "select PCI_TSM" moved under CRYPTO_DEV_SP_PSP
> > * open coded SNP_SEV_TIO_SUPPORTED
> > * renamed tio_present to tio_supp to match the flag name
> > * merged "crypto: ccp: Enable SEV-TIO feature in the PSP when supported" to this one
> > ---
> >  drivers/crypto/ccp/Kconfig       |   1 +
> >  drivers/crypto/ccp/Makefile      |   4 +
> >  drivers/crypto/ccp/sev-dev-tio.h | 123 +++
> >  drivers/crypto/ccp/sev-dev.h     |   9 +
> >  include/linux/psp-sev.h          |  11 +-
> >  drivers/crypto/ccp/sev-dev-tio.c | 864 ++++++++++++++++++++
> >  drivers/crypto/ccp/sev-dev-tsm.c | 405 +++++++++
> >  drivers/crypto/ccp/sev-dev.c     |  51 +-
> >  8 files changed, 1465 insertions(+), 3 deletions(-)
> > 
> 
> > diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
> > index 9e0c16b36f9c..d6095d1467b3 100644
> > --- a/drivers/crypto/ccp/sev-dev.c
> > +++ b/drivers/crypto/ccp/sev-dev.c
> > @@ -75,6 +75,10 @@ static bool psp_init_on_probe = true;
> >  module_param(psp_init_on_probe, bool, 0444);
> >  MODULE_PARM_DESC(psp_init_on_probe, "  if true, the PSP will be initialized on module init. Else the PSP will be initialized on the first command requiring it");
> >  
> > +static bool sev_tio_enabled = IS_ENABLED(CONFIG_PCI_TSM);
> > +module_param_named(tio, sev_tio_enabled, bool, 0444);
> > +MODULE_PARM_DESC(tio, "Enables TIO in SNP_INIT_EX");
> 
> Hmmm... I thought you said you wanted to hide the module parameter if
> CONFIG_PCI_TSM isn't enabled. Either way, it's fine.

I think it makes sense to hide options that have no effect.

> 
> > +
> >  MODULE_FIRMWARE("amd/amd_sev_fam17h_model0xh.sbin"); /* 1st gen EPYC */
> >  MODULE_FIRMWARE("amd/amd_sev_fam17h_model3xh.sbin"); /* 2nd gen EPYC */
> >  MODULE_FIRMWARE("amd/amd_sev_fam19h_model0xh.sbin"); /* 3rd gen EPYC */
> > @@ -251,7 +255,7 @@ static int sev_cmd_buffer_len(int cmd)
> >  	case SEV_CMD_SNP_COMMIT:		return sizeof(struct sev_data_snp_commit);
> >  	case SEV_CMD_SNP_FEATURE_INFO:		return sizeof(struct sev_data_snp_feature_info);
> >  	case SEV_CMD_SNP_VLEK_LOAD:		return sizeof(struct sev_user_data_snp_vlek_load);
> > -	default:				return 0;
> > +	default:				return sev_tio_cmd_buffer_len(cmd);
> >  	}
> >  
> >  	return 0;
> > @@ -1434,6 +1438,19 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
> >  		data.init_rmp = 1;
> >  		data.list_paddr_en = 1;
> >  		data.list_paddr = __psp_pa(snp_range_list);
> > +
> > +		bool tio_supp = !!(sev->snp_feat_info_0.ebx & SNP_SEV_TIO_SUPPORTED);
> 
> Please put the variable definition at the top of the "if" block instead
> of in the middle of the code.
> > +
> > +		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
> 
> Don't you still want to take CONFIG_PCI_TSM into account?
> 
> 	data.tio_en = IS_ENABLED(CONFIG_PCI_TSM) && tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
> 
> or
> 	if (IS_ENABLED(CONFIG_PCI_TSM)
> 		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
> 
> But if you change back to #ifdef the module parameter, then you won't
> need the IS_ENABLED() check here because sev_tio_enabled will be set
> based on CONFIG_PCI_TSM and will be false and not changeable if
> CONFIG_PCI_TSM is not y.

Yeah, I like that side effect.

-- >8 --
From 0f4017581b5f2f6defb0e8a05dab2727f3a197b0 Mon Sep 17 00:00:00 2001
From: Alexey Kardashevskiy <aik@amd.com>
Date: Tue, 2 Dec 2025 13:44:49 +1100
Subject: [PATCH v4] crypto/ccp: Implement SEV-TIO PCIe IDE (phase1)

Implement the SEV-TIO (Trusted I/O) firmware interface for PCIe TDISP
(Trust Domain In-Socket Protocol). This enables secure communication
between trusted domains and PCIe devices through the PSP (Platform
Security Processor).

The implementation includes:
- Device Security Manager (DSM) operations for establishing secure links
- SPDM (Security Protocol and Data Model) over DOE (Data Object Exchange)
- IDE (Integrity Data Encryption) stream management for secure PCIe

This module bridges the SEV firmware stack with the generic PCIe TSM
framework.

This is phase1 as described in Documentation/driver-api/pci/tsm.rst.

On AMD SEV, the AMD PSP firmware acts as TSM (manages the security/trust).
The CCP driver provides the interface to it and registers in the TSM
subsystem.

Detect the PSP support (reported via FEATURE_INFO + SNP_PLATFORM_STATUS)
and enable SEV-TIO in the SNP_INIT_EX call if the hardware supports TIO.

Implement SEV TIO PSP command wrappers in sev-dev-tio.c and store
the data in the SEV-TIO-specific structs.

Implement TSM hooks and IDE setup in sev-dev-tsm.c.

Signed-off-by: Alexey Kardashevskiy <aik@amd.com>
Acked-by: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
Changes since v3:
* Hide the sev_tio_enabled module option when CONFIG_PCI_TSM is
  disabled, and make the @sev_tio_enabled variable a 'const bool false'
  value in that case to preclude the need for more
  IS_ENABLED(CONFIG_PCI_TSM) checks (Tom).
* Move @tio_supp declaration to top of scope (Tom).

 drivers/crypto/ccp/Kconfig       |   1 +
 drivers/crypto/ccp/Makefile      |   4 +
 drivers/crypto/ccp/sev-dev-tio.h | 123 +++++
 drivers/crypto/ccp/sev-dev.h     |   9 +
 include/linux/psp-sev.h          |  11 +-
 drivers/crypto/ccp/sev-dev-tio.c | 864 +++++++++++++++++++++++++++++++
 drivers/crypto/ccp/sev-dev-tsm.c | 405 +++++++++++++++
 drivers/crypto/ccp/sev-dev.c     |  55 +-
 8 files changed, 1469 insertions(+), 3 deletions(-)
 create mode 100644 drivers/crypto/ccp/sev-dev-tio.h
 create mode 100644 drivers/crypto/ccp/sev-dev-tio.c
 create mode 100644 drivers/crypto/ccp/sev-dev-tsm.c

diff --git a/drivers/crypto/ccp/Kconfig b/drivers/crypto/ccp/Kconfig
index f394e45e11ab..e2b127f0986b 100644
--- a/drivers/crypto/ccp/Kconfig
+++ b/drivers/crypto/ccp/Kconfig
@@ -39,6 +39,7 @@ config CRYPTO_DEV_SP_PSP
 	bool "Platform Security Processor (PSP) device"
 	default y
 	depends on CRYPTO_DEV_CCP_DD && X86_64 && AMD_IOMMU
+	select PCI_TSM
 	help
 	 Provide support for the AMD Platform Security Processor (PSP).
 	 The PSP is a dedicated processor that provides support for key
diff --git a/drivers/crypto/ccp/Makefile b/drivers/crypto/ccp/Makefile
index a9626b30044a..0424e08561ef 100644
--- a/drivers/crypto/ccp/Makefile
+++ b/drivers/crypto/ccp/Makefile
@@ -16,6 +16,10 @@ ccp-$(CONFIG_CRYPTO_DEV_SP_PSP) += psp-dev.o \
                                    hsti.o \
                                    sfs.o
 
+ifeq ($(CONFIG_PCI_TSM),y)
+ccp-$(CONFIG_CRYPTO_DEV_SP_PSP) += sev-dev-tsm.o sev-dev-tio.o
+endif
+
 obj-$(CONFIG_CRYPTO_DEV_CCP_CRYPTO) += ccp-crypto.o
 ccp-crypto-objs := ccp-crypto-main.o \
 		   ccp-crypto-aes.o \
diff --git a/drivers/crypto/ccp/sev-dev-tio.h b/drivers/crypto/ccp/sev-dev-tio.h
new file mode 100644
index 000000000000..67512b3dbc53
--- /dev/null
+++ b/drivers/crypto/ccp/sev-dev-tio.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __PSP_SEV_TIO_H__
+#define __PSP_SEV_TIO_H__
+
+#include <linux/pci-tsm.h>
+#include <linux/pci-ide.h>
+#include <linux/tsm.h>
+#include <uapi/linux/psp-sev.h>
+
+struct sla_addr_t {
+	union {
+		u64 sla;
+		struct {
+			u64 page_type	:1,
+			    page_size	:1,
+			    reserved1	:10,
+			    pfn		:40,
+			    reserved2	:12;
+		};
+	};
+} __packed;
+
+#define SEV_TIO_MAX_COMMAND_LENGTH	128
+
+/* SPDM control structure for DOE */
+struct tsm_spdm {
+	unsigned long req_len;
+	void *req;
+	unsigned long rsp_len;
+	void *rsp;
+};
+
+/* Describes TIO device */
+struct tsm_dsm_tio {
+	u8 cert_slot;
+	struct sla_addr_t dev_ctx;
+	struct sla_addr_t req;
+	struct sla_addr_t resp;
+	struct sla_addr_t scratch;
+	struct sla_addr_t output;
+	size_t output_len;
+	size_t scratch_len;
+	struct tsm_spdm spdm;
+	struct sla_buffer_hdr *reqbuf; /* vmap'ed @req for DOE */
+	struct sla_buffer_hdr *respbuf; /* vmap'ed @resp for DOE */
+
+	int cmd;
+	int psp_ret;
+	u8 cmd_data[SEV_TIO_MAX_COMMAND_LENGTH];
+	void *data_pg; /* Data page for DEV_STATUS/TDI_STATUS/TDI_INFO/ASID_FENCE */
+
+#define TIO_IDE_MAX_TC	8
+	struct pci_ide *ide[TIO_IDE_MAX_TC];
+};
+
+/* Describes TSM structure for PF0 pointed by pci_dev->tsm */
+struct tio_dsm {
+	struct pci_tsm_pf0 tsm;
+	struct tsm_dsm_tio data;
+	struct sev_device *sev;
+};
+
+/* Data object IDs */
+#define SPDM_DOBJ_ID_NONE		0
+#define SPDM_DOBJ_ID_REQ		1
+#define SPDM_DOBJ_ID_RESP		2
+
+struct spdm_dobj_hdr {
+	u32 id;     /* Data object type identifier */
+	u32 length; /* Length of the data object, INCLUDING THIS HEADER */
+	struct { /* Version of the data object structure */
+		u8 minor;
+		u8 major;
+	} version;
+} __packed;
+
+/**
+ * struct sev_tio_status - TIO_STATUS command's info_paddr buffer
+ *
+ * @length: Length of this structure in bytes
+ * @tio_en: Indicates that SNP_INIT_EX initialized the RMP for SEV-TIO
+ * @tio_init_done: Indicates TIO_INIT has been invoked
+ * @spdm_req_size_min: Minimum SPDM request buffer size in bytes
+ * @spdm_req_size_max: Maximum SPDM request buffer size in bytes
+ * @spdm_scratch_size_min: Minimum SPDM scratch buffer size in bytes
+ * @spdm_scratch_size_max: Maximum SPDM scratch buffer size in bytes
+ * @spdm_out_size_min: Minimum SPDM output buffer size in bytes
+ * @spdm_out_size_max: Maximum for the SPDM output buffer size in bytes
+ * @spdm_rsp_size_min: Minimum SPDM response buffer size in bytes
+ * @spdm_rsp_size_max: Maximum SPDM response buffer size in bytes
+ * @devctx_size: Size of a device context buffer in bytes
+ * @tdictx_size: Size of a TDI context buffer in bytes
+ * @tio_crypto_alg: TIO crypto algorithms supported
+ */
+struct sev_tio_status {
+	u32 length;
+	u32 tio_en	  :1,
+	    tio_init_done :1,
+	    reserved	  :30;
+	u32 spdm_req_size_min;
+	u32 spdm_req_size_max;
+	u32 spdm_scratch_size_min;
+	u32 spdm_scratch_size_max;
+	u32 spdm_out_size_min;
+	u32 spdm_out_size_max;
+	u32 spdm_rsp_size_min;
+	u32 spdm_rsp_size_max;
+	u32 devctx_size;
+	u32 tdictx_size;
+	u32 tio_crypto_alg;
+	u8 reserved2[12];
+} __packed;
+
+int sev_tio_init_locked(void *tio_status_page);
+int sev_tio_continue(struct tsm_dsm_tio *dev_data);
+
+int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id, u16 root_port_id,
+		       u8 segment_id);
+int sev_tio_dev_connect(struct tsm_dsm_tio *dev_data, u8 tc_mask, u8 ids[8], u8 cert_slot);
+int sev_tio_dev_disconnect(struct tsm_dsm_tio *dev_data, bool force);
+int sev_tio_dev_reclaim(struct tsm_dsm_tio *dev_data);
+
+#endif	/* __PSP_SEV_TIO_H__ */
diff --git a/drivers/crypto/ccp/sev-dev.h b/drivers/crypto/ccp/sev-dev.h
index b9029506383f..b1cd556bbbf6 100644
--- a/drivers/crypto/ccp/sev-dev.h
+++ b/drivers/crypto/ccp/sev-dev.h
@@ -34,6 +34,8 @@ struct sev_misc_dev {
 	struct miscdevice misc;
 };
 
+struct sev_tio_status;
+
 struct sev_device {
 	struct device *dev;
 	struct psp_device *psp;
@@ -61,6 +63,9 @@ struct sev_device {
 
 	struct sev_user_data_snp_status snp_plat_status;
 	struct snp_feature_info snp_feat_info_0;
+
+	struct tsm_dev *tsmdev;
+	struct sev_tio_status *tio_status;
 };
 
 int sev_dev_init(struct psp_device *psp);
@@ -74,4 +79,8 @@ void sev_pci_exit(void);
 struct page *snp_alloc_hv_fixed_pages(unsigned int num_2mb_pages);
 void snp_free_hv_fixed_pages(struct page *page);
 
+void sev_tsm_init_locked(struct sev_device *sev, void *tio_status_page);
+void sev_tsm_uninit(struct sev_device *sev);
+int sev_tio_cmd_buffer_len(int cmd);
+
 #endif /* __SEV_DEV_H */
diff --git a/include/linux/psp-sev.h b/include/linux/psp-sev.h
index 34a25209f909..cce864dbf281 100644
--- a/include/linux/psp-sev.h
+++ b/include/linux/psp-sev.h
@@ -109,6 +109,13 @@ enum sev_cmd {
 	SEV_CMD_SNP_VLEK_LOAD		= 0x0CD,
 	SEV_CMD_SNP_FEATURE_INFO	= 0x0CE,
 
+	/* SEV-TIO commands */
+	SEV_CMD_TIO_STATUS		= 0x0D0,
+	SEV_CMD_TIO_INIT		= 0x0D1,
+	SEV_CMD_TIO_DEV_CREATE		= 0x0D2,
+	SEV_CMD_TIO_DEV_RECLAIM		= 0x0D3,
+	SEV_CMD_TIO_DEV_CONNECT		= 0x0D4,
+	SEV_CMD_TIO_DEV_DISCONNECT	= 0x0D5,
 	SEV_CMD_MAX,
 };
 
@@ -750,7 +757,8 @@ struct sev_data_snp_init_ex {
 	u32 list_paddr_en:1;
 	u32 rapl_dis:1;
 	u32 ciphertext_hiding_en:1;
-	u32 rsvd:28;
+	u32 tio_en:1;
+	u32 rsvd:27;
 	u32 rsvd1;
 	u64 list_paddr;
 	u16 max_snp_asid;
@@ -850,6 +858,7 @@ struct snp_feature_info {
 } __packed;
 
 #define SNP_CIPHER_TEXT_HIDING_SUPPORTED	BIT(3)
+#define SNP_SEV_TIO_SUPPORTED			BIT(1) /* EBX */
 
 #ifdef CONFIG_CRYPTO_DEV_SP_PSP
 
diff --git a/drivers/crypto/ccp/sev-dev-tio.c b/drivers/crypto/ccp/sev-dev-tio.c
new file mode 100644
index 000000000000..9a98f98c20a7
--- /dev/null
+++ b/drivers/crypto/ccp/sev-dev-tio.c
@@ -0,0 +1,864 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+// Interface to PSP for CCP/SEV-TIO/SNP-VM
+
+#include <linux/pci.h>
+#include <linux/tsm.h>
+#include <linux/psp.h>
+#include <linux/vmalloc.h>
+#include <linux/bitfield.h>
+#include <linux/pci-doe.h>
+#include <asm/sev-common.h>
+#include <asm/sev.h>
+#include <asm/page.h>
+#include "sev-dev.h"
+#include "sev-dev-tio.h"
+
+#define to_tio_status(dev_data)	\
+		(container_of((dev_data), struct tio_dsm, data)->sev->tio_status)
+
+#define SLA_PAGE_TYPE_DATA	0
+#define SLA_PAGE_TYPE_SCATTER	1
+#define SLA_PAGE_SIZE_4K	0
+#define SLA_PAGE_SIZE_2M	1
+#define SLA_SZ(s)		((s).page_size == SLA_PAGE_SIZE_2M ? SZ_2M : SZ_4K)
+#define SLA_SCATTER_LEN(s)	(SLA_SZ(s) / sizeof(struct sla_addr_t))
+#define SLA_EOL			((struct sla_addr_t) { .pfn = ((1UL << 40) - 1) })
+#define SLA_NULL		((struct sla_addr_t) { 0 })
+#define IS_SLA_NULL(s)		((s).sla == SLA_NULL.sla)
+#define IS_SLA_EOL(s)		((s).sla == SLA_EOL.sla)
+
+static phys_addr_t sla_to_pa(struct sla_addr_t sla)
+{
+	u64 pfn = sla.pfn;
+	u64 pa = pfn << PAGE_SHIFT;
+
+	return pa;
+}
+
+static void *sla_to_va(struct sla_addr_t sla)
+{
+	void *va = __va(__sme_clr(sla_to_pa(sla)));
+
+	return va;
+}
+
+#define sla_to_pfn(sla)		(__pa(sla_to_va(sla)) >> PAGE_SHIFT)
+#define sla_to_page(sla)	virt_to_page(sla_to_va(sla))
+
+static struct sla_addr_t make_sla(struct page *pg, bool stp)
+{
+	u64 pa = __sme_set(page_to_phys(pg));
+	struct sla_addr_t ret = {
+		.pfn = pa >> PAGE_SHIFT,
+		.page_size = SLA_PAGE_SIZE_4K, /* Do not do SLA_PAGE_SIZE_2M ATM */
+		.page_type = stp ? SLA_PAGE_TYPE_SCATTER : SLA_PAGE_TYPE_DATA
+	};
+
+	return ret;
+}
+
+/* the BUFFER Structure */
+#define SLA_BUFFER_FLAG_ENCRYPTION	BIT(0)
+
+/*
+ * struct sla_buffer_hdr - Scatter list address buffer header
+ *
+ * @capacity_sz: Total capacity of the buffer in bytes
+ * @payload_sz: Size of buffer payload in bytes, must be multiple of 32B
+ * @flags: Buffer flags (SLA_BUFFER_FLAG_ENCRYPTION: buffer is encrypted)
+ * @iv: Initialization vector used for encryption
+ * @authtag: Authentication tag for encrypted buffer
+ */
+struct sla_buffer_hdr {
+	u32 capacity_sz;
+	u32 payload_sz; /* The size of BUFFER_PAYLOAD in bytes. Must be multiple of 32B */
+	u32 flags;
+	u8 reserved1[4];
+	u8 iv[16];	/* IV used for the encryption of this buffer */
+	u8 authtag[16]; /* Authentication tag for this buffer */
+	u8 reserved2[16];
+} __packed;
+
+enum spdm_data_type_t {
+	DOBJ_DATA_TYPE_SPDM = 0x1,
+	DOBJ_DATA_TYPE_SECURE_SPDM = 0x2,
+};
+
+struct spdm_dobj_hdr_req {
+	struct spdm_dobj_hdr hdr; /* hdr.id == SPDM_DOBJ_ID_REQ */
+	u8 data_type; /* spdm_data_type_t */
+	u8 reserved2[5];
+} __packed;
+
+struct spdm_dobj_hdr_resp {
+	struct spdm_dobj_hdr hdr; /* hdr.id == SPDM_DOBJ_ID_RESP */
+	u8 data_type; /* spdm_data_type_t */
+	u8 reserved2[5];
+} __packed;
+
+/* Defined in sev-dev-tio.h so sev-dev-tsm.c can read types of blobs */
+struct spdm_dobj_hdr_cert;
+struct spdm_dobj_hdr_meas;
+struct spdm_dobj_hdr_report;
+
+/* Used in all SPDM-aware TIO commands */
+struct spdm_ctrl {
+	struct sla_addr_t req;
+	struct sla_addr_t resp;
+	struct sla_addr_t scratch;
+	struct sla_addr_t output;
+} __packed;
+
+static size_t sla_dobj_id_to_size(u8 id)
+{
+	size_t n;
+
+	BUILD_BUG_ON(sizeof(struct spdm_dobj_hdr_resp) != 0x10);
+	switch (id) {
+	case SPDM_DOBJ_ID_REQ:
+		n = sizeof(struct spdm_dobj_hdr_req);
+		break;
+	case SPDM_DOBJ_ID_RESP:
+		n = sizeof(struct spdm_dobj_hdr_resp);
+		break;
+	default:
+		WARN_ON(1);
+		n = 0;
+		break;
+	}
+
+	return n;
+}
+
+#define SPDM_DOBJ_HDR_SIZE(hdr)		sla_dobj_id_to_size((hdr)->id)
+#define SPDM_DOBJ_DATA(hdr)		((u8 *)(hdr) + SPDM_DOBJ_HDR_SIZE(hdr))
+#define SPDM_DOBJ_LEN(hdr)		((hdr)->length - SPDM_DOBJ_HDR_SIZE(hdr))
+
+#define sla_to_dobj_resp_hdr(buf)	((struct spdm_dobj_hdr_resp *) \
+					sla_to_dobj_hdr_check((buf), SPDM_DOBJ_ID_RESP))
+#define sla_to_dobj_req_hdr(buf)	((struct spdm_dobj_hdr_req *) \
+					sla_to_dobj_hdr_check((buf), SPDM_DOBJ_ID_REQ))
+
+static struct spdm_dobj_hdr *sla_to_dobj_hdr(struct sla_buffer_hdr *buf)
+{
+	if (!buf)
+		return NULL;
+
+	return (struct spdm_dobj_hdr *) &buf[1];
+}
+
+static struct spdm_dobj_hdr *sla_to_dobj_hdr_check(struct sla_buffer_hdr *buf, u32 check_dobjid)
+{
+	struct spdm_dobj_hdr *hdr = sla_to_dobj_hdr(buf);
+
+	if (WARN_ON_ONCE(!hdr))
+		return NULL;
+
+	if (hdr->id != check_dobjid) {
+		pr_err("! ERROR: expected %d, found %d\n", check_dobjid, hdr->id);
+		return NULL;
+	}
+
+	return hdr;
+}
+
+static void *sla_to_data(struct sla_buffer_hdr *buf, u32 dobjid)
+{
+	struct spdm_dobj_hdr *hdr = sla_to_dobj_hdr(buf);
+
+	if (WARN_ON_ONCE(dobjid != SPDM_DOBJ_ID_REQ && dobjid != SPDM_DOBJ_ID_RESP))
+		return NULL;
+
+	if (!hdr)
+		return NULL;
+
+	return (u8 *) hdr + sla_dobj_id_to_size(dobjid);
+}
+
+/*
+ * struct sev_data_tio_status - SEV_CMD_TIO_STATUS command
+ *
+ * @length: Length of this command buffer in bytes
+ * @status_paddr: System physical address of the TIO_STATUS structure
+ */
+struct sev_data_tio_status {
+	u32 length;
+	u8 reserved[4];
+	u64 status_paddr;
+} __packed;
+
+/* TIO_INIT */
+struct sev_data_tio_init {
+	u32 length;
+	u8 reserved[12];
+} __packed;
+
+/*
+ * struct sev_data_tio_dev_create - TIO_DEV_CREATE command
+ *
+ * @length: Length in bytes of this command buffer
+ * @dev_ctx_sla: Scatter list address pointing to a buffer to be used as a device context buffer
+ * @device_id: PCIe Routing Identifier of the device to connect to
+ * @root_port_id: PCIe Routing Identifier of the root port of the device
+ * @segment_id: PCIe Segment Identifier of the device to connect to
+ */
+struct sev_data_tio_dev_create {
+	u32 length;
+	u8 reserved1[4];
+	struct sla_addr_t dev_ctx_sla;
+	u16 device_id;
+	u16 root_port_id;
+	u8 segment_id;
+	u8 reserved2[11];
+} __packed;
+
+/*
+ * struct sev_data_tio_dev_connect - TIO_DEV_CONNECT command
+ *
+ * @length: Length in bytes of this command buffer
+ * @spdm_ctrl: SPDM control structure defined in Section 5.1
+ * @dev_ctx_sla: Scatter list address of the device context buffer
+ * @tc_mask: Bitmask of the traffic classes to initialize for SEV-TIO usage.
+ *           Setting the kth bit of the TC_MASK to 1 indicates that the traffic
+ *           class k will be initialized
+ * @cert_slot: Slot number of the certificate requested for constructing the SPDM session
+ * @ide_stream_id: IDE stream IDs to be associated with this device.
+ *                 Valid only if corresponding bit in TC_MASK is set
+ */
+struct sev_data_tio_dev_connect {
+	u32 length;
+	u8 reserved1[4];
+	struct spdm_ctrl spdm_ctrl;
+	u8 reserved2[8];
+	struct sla_addr_t dev_ctx_sla;
+	u8 tc_mask;
+	u8 cert_slot;
+	u8 reserved3[6];
+	u8 ide_stream_id[8];
+	u8 reserved4[8];
+} __packed;
+
+/*
+ * struct sev_data_tio_dev_disconnect - TIO_DEV_DISCONNECT command
+ *
+ * @length: Length in bytes of this command buffer
+ * @flags: Command flags (TIO_DEV_DISCONNECT_FLAG_FORCE: force disconnect)
+ * @spdm_ctrl: SPDM control structure defined in Section 5.1
+ * @dev_ctx_sla: Scatter list address of the device context buffer
+ */
+#define TIO_DEV_DISCONNECT_FLAG_FORCE	BIT(0)
+
+struct sev_data_tio_dev_disconnect {
+	u32 length;
+	u32 flags;
+	struct spdm_ctrl spdm_ctrl;
+	struct sla_addr_t dev_ctx_sla;
+} __packed;
+
+/*
+ * struct sev_data_tio_dev_meas - TIO_DEV_MEASUREMENTS command
+ *
+ * @length: Length in bytes of this command buffer
+ * @flags: Command flags (TIO_DEV_MEAS_FLAG_RAW_BITSTREAM: request raw measurements)
+ * @spdm_ctrl: SPDM control structure defined in Section 5.1
+ * @dev_ctx_sla: Scatter list address of the device context buffer
+ * @meas_nonce: Nonce for measurement freshness verification
+ */
+#define TIO_DEV_MEAS_FLAG_RAW_BITSTREAM	BIT(0)
+
+struct sev_data_tio_dev_meas {
+	u32 length;
+	u32 flags;
+	struct spdm_ctrl spdm_ctrl;
+	struct sla_addr_t dev_ctx_sla;
+	u8 meas_nonce[32];
+} __packed;
+
+/*
+ * struct sev_data_tio_dev_certs - TIO_DEV_CERTIFICATES command
+ *
+ * @length: Length in bytes of this command buffer
+ * @spdm_ctrl: SPDM control structure defined in Section 5.1
+ * @dev_ctx_sla: Scatter list address of the device context buffer
+ */
+struct sev_data_tio_dev_certs {
+	u32 length;
+	u8 reserved[4];
+	struct spdm_ctrl spdm_ctrl;
+	struct sla_addr_t dev_ctx_sla;
+} __packed;
+
+/*
+ * struct sev_data_tio_dev_reclaim - TIO_DEV_RECLAIM command
+ *
+ * @length: Length in bytes of this command buffer
+ * @dev_ctx_sla: Scatter list address of the device context buffer
+ *
+ * This command reclaims resources associated with a device context.
+ */
+struct sev_data_tio_dev_reclaim {
+	u32 length;
+	u8 reserved[4];
+	struct sla_addr_t dev_ctx_sla;
+} __packed;
+
+static struct sla_buffer_hdr *sla_buffer_map(struct sla_addr_t sla)
+{
+	struct sla_buffer_hdr *buf;
+
+	BUILD_BUG_ON(sizeof(struct sla_buffer_hdr) != 0x40);
+	if (IS_SLA_NULL(sla))
+		return NULL;
+
+	if (sla.page_type == SLA_PAGE_TYPE_SCATTER) {
+		struct sla_addr_t *scatter = sla_to_va(sla);
+		unsigned int i, npages = 0;
+
+		for (i = 0; i < SLA_SCATTER_LEN(sla); ++i) {
+			if (WARN_ON_ONCE(SLA_SZ(scatter[i]) > SZ_4K))
+				return NULL;
+
+			if (WARN_ON_ONCE(scatter[i].page_type == SLA_PAGE_TYPE_SCATTER))
+				return NULL;
+
+			if (IS_SLA_EOL(scatter[i])) {
+				npages = i;
+				break;
+			}
+		}
+		if (WARN_ON_ONCE(!npages))
+			return NULL;
+
+		struct page **pp = kmalloc_array(npages, sizeof(pp[0]), GFP_KERNEL);
+
+		if (!pp)
+			return NULL;
+
+		for (i = 0; i < npages; ++i)
+			pp[i] = sla_to_page(scatter[i]);
+
+		buf = vm_map_ram(pp, npages, 0);
+		kfree(pp);
+	} else {
+		struct page *pg = sla_to_page(sla);
+
+		buf = vm_map_ram(&pg, 1, 0);
+	}
+
+	return buf;
+}
+
+static void sla_buffer_unmap(struct sla_addr_t sla, struct sla_buffer_hdr *buf)
+{
+	if (!buf)
+		return;
+
+	if (sla.page_type == SLA_PAGE_TYPE_SCATTER) {
+		struct sla_addr_t *scatter = sla_to_va(sla);
+		unsigned int i, npages = 0;
+
+		for (i = 0; i < SLA_SCATTER_LEN(sla); ++i) {
+			if (IS_SLA_EOL(scatter[i])) {
+				npages = i;
+				break;
+			}
+		}
+		if (!npages)
+			return;
+
+		vm_unmap_ram(buf, npages);
+	} else {
+		vm_unmap_ram(buf, 1);
+	}
+}
+
+static void dobj_response_init(struct sla_buffer_hdr *buf)
+{
+	struct spdm_dobj_hdr *dobj = sla_to_dobj_hdr(buf);
+
+	dobj->id = SPDM_DOBJ_ID_RESP;
+	dobj->version.major = 0x1;
+	dobj->version.minor = 0;
+	dobj->length = 0;
+	buf->payload_sz = sla_dobj_id_to_size(dobj->id) + dobj->length;
+}
+
+static void sla_free(struct sla_addr_t sla, size_t len, bool firmware_state)
+{
+	unsigned int npages = PAGE_ALIGN(len) >> PAGE_SHIFT;
+	struct sla_addr_t *scatter = NULL;
+	int ret = 0, i;
+
+	if (IS_SLA_NULL(sla))
+		return;
+
+	if (firmware_state) {
+		if (sla.page_type == SLA_PAGE_TYPE_SCATTER) {
+			scatter = sla_to_va(sla);
+
+			for (i = 0; i < npages; ++i) {
+				if (IS_SLA_EOL(scatter[i]))
+					break;
+
+				ret = snp_reclaim_pages(sla_to_pa(scatter[i]), 1, false);
+				if (ret)
+					break;
+			}
+		} else {
+			ret = snp_reclaim_pages(sla_to_pa(sla), 1, false);
+		}
+	}
+
+	if (WARN_ON(ret))
+		return;
+
+	if (scatter) {
+		for (i = 0; i < npages; ++i) {
+			if (IS_SLA_EOL(scatter[i]))
+				break;
+			free_page((unsigned long)sla_to_va(scatter[i]));
+		}
+	}
+
+	free_page((unsigned long)sla_to_va(sla));
+}
+
+static struct sla_addr_t sla_alloc(size_t len, bool firmware_state)
+{
+	unsigned long i, npages = PAGE_ALIGN(len) >> PAGE_SHIFT;
+	struct sla_addr_t *scatter = NULL;
+	struct sla_addr_t ret = SLA_NULL;
+	struct sla_buffer_hdr *buf;
+	struct page *pg;
+
+	if (npages == 0)
+		return ret;
+
+	if (WARN_ON_ONCE(npages > ((PAGE_SIZE / sizeof(struct sla_addr_t)) + 1)))
+		return ret;
+
+	BUILD_BUG_ON(PAGE_SIZE < SZ_4K);
+
+	if (npages > 1) {
+		pg = alloc_page(GFP_KERNEL | __GFP_ZERO);
+		if (!pg)
+			return SLA_NULL;
+
+		ret = make_sla(pg, true);
+		scatter = page_to_virt(pg);
+		for (i = 0; i < npages; ++i) {
+			pg = alloc_page(GFP_KERNEL | __GFP_ZERO);
+			if (!pg)
+				goto no_reclaim_exit;
+
+			scatter[i] = make_sla(pg, false);
+		}
+		scatter[i] = SLA_EOL;
+	} else {
+		pg = alloc_page(GFP_KERNEL | __GFP_ZERO);
+		if (!pg)
+			return SLA_NULL;
+
+		ret = make_sla(pg, false);
+	}
+
+	buf = sla_buffer_map(ret);
+	if (!buf)
+		goto no_reclaim_exit;
+
+	buf->capacity_sz = (npages << PAGE_SHIFT);
+	sla_buffer_unmap(ret, buf);
+
+	if (firmware_state) {
+		if (scatter) {
+			for (i = 0; i < npages; ++i) {
+				if (rmp_make_private(sla_to_pfn(scatter[i]), 0,
+						     PG_LEVEL_4K, 0, true))
+					goto free_exit;
+			}
+		} else {
+			if (rmp_make_private(sla_to_pfn(ret), 0, PG_LEVEL_4K, 0, true))
+				goto no_reclaim_exit;
+		}
+	}
+
+	return ret;
+
+no_reclaim_exit:
+	firmware_state = false;
+free_exit:
+	sla_free(ret, len, firmware_state);
+	return SLA_NULL;
+}
+
+/* Expands a buffer, only firmware owned buffers allowed for now */
+static int sla_expand(struct sla_addr_t *sla, size_t *len)
+{
+	struct sla_buffer_hdr *oldbuf = sla_buffer_map(*sla), *newbuf;
+	struct sla_addr_t oldsla = *sla, newsla;
+	size_t oldlen = *len, newlen;
+
+	if (!oldbuf)
+		return -EFAULT;
+
+	newlen = oldbuf->capacity_sz;
+	if (oldbuf->capacity_sz == oldlen) {
+		/* This buffer does not require expansion, must be another buffer */
+		sla_buffer_unmap(oldsla, oldbuf);
+		return 1;
+	}
+
+	pr_notice("Expanding BUFFER from %ld to %ld bytes\n", oldlen, newlen);
+
+	newsla = sla_alloc(newlen, true);
+	if (IS_SLA_NULL(newsla))
+		return -ENOMEM;
+
+	newbuf = sla_buffer_map(newsla);
+	if (!newbuf) {
+		sla_free(newsla, newlen, true);
+		return -EFAULT;
+	}
+
+	memcpy(newbuf, oldbuf, oldlen);
+
+	sla_buffer_unmap(newsla, newbuf);
+	sla_free(oldsla, oldlen, true);
+	*sla = newsla;
+	*len = newlen;
+
+	return 0;
+}
+
+static int sev_tio_do_cmd(int cmd, void *data, size_t data_len, int *psp_ret,
+			  struct tsm_dsm_tio *dev_data)
+{
+	int rc;
+
+	*psp_ret = 0;
+	rc = sev_do_cmd(cmd, data, psp_ret);
+
+	if (WARN_ON(!rc && *psp_ret == SEV_RET_SPDM_REQUEST))
+		return -EIO;
+
+	if (rc == 0 && *psp_ret == SEV_RET_EXPAND_BUFFER_LENGTH_REQUEST) {
+		int rc1, rc2;
+
+		rc1 = sla_expand(&dev_data->output, &dev_data->output_len);
+		if (rc1 < 0)
+			return rc1;
+
+		rc2 = sla_expand(&dev_data->scratch, &dev_data->scratch_len);
+		if (rc2 < 0)
+			return rc2;
+
+		if (!rc1 && !rc2)
+			/* Neither buffer requires expansion, this is wrong */
+			return -EFAULT;
+
+		*psp_ret = 0;
+		rc = sev_do_cmd(cmd, data, psp_ret);
+	}
+
+	if ((rc == 0 || rc == -EIO) && *psp_ret == SEV_RET_SPDM_REQUEST) {
+		struct spdm_dobj_hdr_resp *resp_hdr;
+		struct spdm_dobj_hdr_req *req_hdr;
+		struct sev_tio_status *tio_status = to_tio_status(dev_data);
+		size_t resp_len = tio_status->spdm_req_size_max -
+			(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) + sizeof(struct sla_buffer_hdr));
+
+		if (!dev_data->cmd) {
+			if (WARN_ON_ONCE(!data_len || (data_len != *(u32 *) data)))
+				return -EINVAL;
+			if (WARN_ON(data_len > sizeof(dev_data->cmd_data)))
+				return -EFAULT;
+			memcpy(dev_data->cmd_data, data, data_len);
+			memset(&dev_data->cmd_data[data_len], 0xFF,
+			       sizeof(dev_data->cmd_data) - data_len);
+			dev_data->cmd = cmd;
+		}
+
+		req_hdr = sla_to_dobj_req_hdr(dev_data->reqbuf);
+		resp_hdr = sla_to_dobj_resp_hdr(dev_data->respbuf);
+		switch (req_hdr->data_type) {
+		case DOBJ_DATA_TYPE_SPDM:
+			rc = PCI_DOE_FEATURE_CMA;
+			break;
+		case DOBJ_DATA_TYPE_SECURE_SPDM:
+			rc = PCI_DOE_FEATURE_SSESSION;
+			break;
+		default:
+			return -EINVAL;
+		}
+		resp_hdr->data_type = req_hdr->data_type;
+		dev_data->spdm.req_len = req_hdr->hdr.length -
+			sla_dobj_id_to_size(SPDM_DOBJ_ID_REQ);
+		dev_data->spdm.rsp_len = resp_len;
+	} else if (dev_data && dev_data->cmd) {
+		/* For either error or success just stop the bouncing */
+		memset(dev_data->cmd_data, 0, sizeof(dev_data->cmd_data));
+		dev_data->cmd = 0;
+	}
+
+	return rc;
+}
+
+int sev_tio_continue(struct tsm_dsm_tio *dev_data)
+{
+	struct spdm_dobj_hdr_resp *resp_hdr;
+	int ret;
+
+	if (!dev_data || !dev_data->cmd)
+		return -EINVAL;
+
+	resp_hdr = sla_to_dobj_resp_hdr(dev_data->respbuf);
+	resp_hdr->hdr.length = ALIGN(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) +
+				     dev_data->spdm.rsp_len, 32);
+	dev_data->respbuf->payload_sz = resp_hdr->hdr.length;
+
+	ret = sev_tio_do_cmd(dev_data->cmd, dev_data->cmd_data, 0,
+			     &dev_data->psp_ret, dev_data);
+	if (ret)
+		return ret;
+
+	if (dev_data->psp_ret != SEV_RET_SUCCESS)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void spdm_ctrl_init(struct spdm_ctrl *ctrl, struct tsm_dsm_tio *dev_data)
+{
+	ctrl->req = dev_data->req;
+	ctrl->resp = dev_data->resp;
+	ctrl->scratch = dev_data->scratch;
+	ctrl->output = dev_data->output;
+}
+
+static void spdm_ctrl_free(struct tsm_dsm_tio *dev_data)
+{
+	struct sev_tio_status *tio_status = to_tio_status(dev_data);
+	size_t len = tio_status->spdm_req_size_max -
+		(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) +
+		 sizeof(struct sla_buffer_hdr));
+	struct tsm_spdm *spdm = &dev_data->spdm;
+
+	sla_buffer_unmap(dev_data->resp, dev_data->respbuf);
+	sla_buffer_unmap(dev_data->req, dev_data->reqbuf);
+	spdm->rsp = NULL;
+	spdm->req = NULL;
+	sla_free(dev_data->req, len, true);
+	sla_free(dev_data->resp, len, false);
+	sla_free(dev_data->scratch, tio_status->spdm_scratch_size_max, true);
+
+	dev_data->req.sla = 0;
+	dev_data->resp.sla = 0;
+	dev_data->scratch.sla = 0;
+	dev_data->respbuf = NULL;
+	dev_data->reqbuf = NULL;
+	sla_free(dev_data->output, tio_status->spdm_out_size_max, true);
+}
+
+static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
+{
+	struct sev_tio_status *tio_status = to_tio_status(dev_data);
+	struct tsm_spdm *spdm = &dev_data->spdm;
+	int ret;
+
+	dev_data->req = sla_alloc(tio_status->spdm_req_size_max, true);
+	dev_data->resp = sla_alloc(tio_status->spdm_req_size_max, false);
+	dev_data->scratch_len = tio_status->spdm_scratch_size_max;
+	dev_data->scratch = sla_alloc(dev_data->scratch_len, true);
+	dev_data->output_len = tio_status->spdm_out_size_max;
+	dev_data->output = sla_alloc(dev_data->output_len, true);
+
+	if (IS_SLA_NULL(dev_data->req) || IS_SLA_NULL(dev_data->resp) ||
+	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->dev_ctx)) {
+		ret = -ENOMEM;
+		goto free_spdm_exit;
+	}
+
+	dev_data->reqbuf = sla_buffer_map(dev_data->req);
+	dev_data->respbuf = sla_buffer_map(dev_data->resp);
+	if (!dev_data->reqbuf || !dev_data->respbuf) {
+		ret = -EFAULT;
+		goto free_spdm_exit;
+	}
+
+	spdm->req = sla_to_data(dev_data->reqbuf, SPDM_DOBJ_ID_REQ);
+	spdm->rsp = sla_to_data(dev_data->respbuf, SPDM_DOBJ_ID_RESP);
+	if (!spdm->req || !spdm->rsp) {
+		ret = -EFAULT;
+		goto free_spdm_exit;
+	}
+
+	dobj_response_init(dev_data->respbuf);
+
+	return 0;
+
+free_spdm_exit:
+	spdm_ctrl_free(dev_data);
+	return ret;
+}
+
+int sev_tio_init_locked(void *tio_status_page)
+{
+	struct sev_tio_status *tio_status = tio_status_page;
+	struct sev_data_tio_status data_status = {
+		.length = sizeof(data_status),
+	};
+	int ret, psp_ret;
+
+	data_status.status_paddr = __psp_pa(tio_status_page);
+	ret = __sev_do_cmd_locked(SEV_CMD_TIO_STATUS, &data_status, &psp_ret);
+	if (ret)
+		return ret;
+
+	if (tio_status->length < offsetofend(struct sev_tio_status, tdictx_size) ||
+	    tio_status->reserved)
+		return -EFAULT;
+
+	if (!tio_status->tio_en && !tio_status->tio_init_done)
+		return -ENOENT;
+
+	if (tio_status->tio_init_done)
+		return -EBUSY;
+
+	struct sev_data_tio_init ti = { .length = sizeof(ti) };
+
+	ret = __sev_do_cmd_locked(SEV_CMD_TIO_INIT, &ti, &psp_ret);
+	if (ret)
+		return ret;
+
+	ret = __sev_do_cmd_locked(SEV_CMD_TIO_STATUS, &data_status, &psp_ret);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id,
+		       u16 root_port_id, u8 segment_id)
+{
+	struct sev_tio_status *tio_status = to_tio_status(dev_data);
+	struct sev_data_tio_dev_create create = {
+		.length = sizeof(create),
+		.device_id = device_id,
+		.root_port_id = root_port_id,
+		.segment_id = segment_id,
+	};
+	void *data_pg;
+	int ret;
+
+	dev_data->dev_ctx = sla_alloc(tio_status->devctx_size, true);
+	if (IS_SLA_NULL(dev_data->dev_ctx))
+		return -ENOMEM;
+
+	data_pg = snp_alloc_firmware_page(GFP_KERNEL_ACCOUNT);
+	if (!data_pg) {
+		ret = -ENOMEM;
+		goto free_ctx_exit;
+	}
+
+	create.dev_ctx_sla = dev_data->dev_ctx;
+	ret = sev_do_cmd(SEV_CMD_TIO_DEV_CREATE, &create, &dev_data->psp_ret);
+	if (ret)
+		goto free_data_pg_exit;
+
+	dev_data->data_pg = data_pg;
+
+	return 0;
+
+free_data_pg_exit:
+	snp_free_firmware_page(data_pg);
+free_ctx_exit:
+	sla_free(create.dev_ctx_sla, tio_status->devctx_size, true);
+	return ret;
+}
+
+int sev_tio_dev_reclaim(struct tsm_dsm_tio *dev_data)
+{
+	struct sev_tio_status *tio_status = to_tio_status(dev_data);
+	struct sev_data_tio_dev_reclaim r = {
+		.length = sizeof(r),
+		.dev_ctx_sla = dev_data->dev_ctx,
+	};
+	int ret;
+
+	if (dev_data->data_pg) {
+		snp_free_firmware_page(dev_data->data_pg);
+		dev_data->data_pg = NULL;
+	}
+
+	if (IS_SLA_NULL(dev_data->dev_ctx))
+		return 0;
+
+	ret = sev_do_cmd(SEV_CMD_TIO_DEV_RECLAIM, &r, &dev_data->psp_ret);
+
+	sla_free(dev_data->dev_ctx, tio_status->devctx_size, true);
+	dev_data->dev_ctx = SLA_NULL;
+
+	spdm_ctrl_free(dev_data);
+
+	return ret;
+}
+
+int sev_tio_dev_connect(struct tsm_dsm_tio *dev_data, u8 tc_mask, u8 ids[8], u8 cert_slot)
+{
+	struct sev_data_tio_dev_connect connect = {
+		.length = sizeof(connect),
+		.tc_mask = tc_mask,
+		.cert_slot = cert_slot,
+		.dev_ctx_sla = dev_data->dev_ctx,
+		.ide_stream_id = {
+			ids[0], ids[1], ids[2], ids[3],
+			ids[4], ids[5], ids[6], ids[7]
+		},
+	};
+	int ret;
+
+	if (WARN_ON(IS_SLA_NULL(dev_data->dev_ctx)))
+		return -EFAULT;
+	if (!(tc_mask & 1))
+		return -EINVAL;
+
+	ret = spdm_ctrl_alloc(dev_data);
+	if (ret)
+		return ret;
+
+	spdm_ctrl_init(&connect.spdm_ctrl, dev_data);
+
+	return sev_tio_do_cmd(SEV_CMD_TIO_DEV_CONNECT, &connect, sizeof(connect),
+			      &dev_data->psp_ret, dev_data);
+}
+
+int sev_tio_dev_disconnect(struct tsm_dsm_tio *dev_data, bool force)
+{
+	struct sev_data_tio_dev_disconnect dc = {
+		.length = sizeof(dc),
+		.dev_ctx_sla = dev_data->dev_ctx,
+		.flags = force ? TIO_DEV_DISCONNECT_FLAG_FORCE : 0,
+	};
+
+	if (WARN_ON_ONCE(IS_SLA_NULL(dev_data->dev_ctx)))
+		return -EFAULT;
+
+	spdm_ctrl_init(&dc.spdm_ctrl, dev_data);
+
+	return sev_tio_do_cmd(SEV_CMD_TIO_DEV_DISCONNECT, &dc, sizeof(dc),
+			      &dev_data->psp_ret, dev_data);
+}
+
+int sev_tio_cmd_buffer_len(int cmd)
+{
+	switch (cmd) {
+	case SEV_CMD_TIO_STATUS:		return sizeof(struct sev_data_tio_status);
+	case SEV_CMD_TIO_INIT:			return sizeof(struct sev_data_tio_init);
+	case SEV_CMD_TIO_DEV_CREATE:		return sizeof(struct sev_data_tio_dev_create);
+	case SEV_CMD_TIO_DEV_RECLAIM:		return sizeof(struct sev_data_tio_dev_reclaim);
+	case SEV_CMD_TIO_DEV_CONNECT:		return sizeof(struct sev_data_tio_dev_connect);
+	case SEV_CMD_TIO_DEV_DISCONNECT:	return sizeof(struct sev_data_tio_dev_disconnect);
+	default:				return 0;
+	}
+}
diff --git a/drivers/crypto/ccp/sev-dev-tsm.c b/drivers/crypto/ccp/sev-dev-tsm.c
new file mode 100644
index 000000000000..ea29cd5d0ff9
--- /dev/null
+++ b/drivers/crypto/ccp/sev-dev-tsm.c
@@ -0,0 +1,405 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+// Interface to CCP/SEV-TIO for generic PCIe TDISP module
+
+#include <linux/pci.h>
+#include <linux/device.h>
+#include <linux/tsm.h>
+#include <linux/iommu.h>
+#include <linux/pci-doe.h>
+#include <linux/bitfield.h>
+#include <linux/module.h>
+
+#include <asm/sev-common.h>
+#include <asm/sev.h>
+
+#include "psp-dev.h"
+#include "sev-dev.h"
+#include "sev-dev-tio.h"
+
+MODULE_IMPORT_NS("PCI_IDE");
+
+#define TIO_DEFAULT_NR_IDE_STREAMS	1
+
+static uint nr_ide_streams = TIO_DEFAULT_NR_IDE_STREAMS;
+module_param_named(ide_nr, nr_ide_streams, uint, 0644);
+MODULE_PARM_DESC(ide_nr, "Set the maximum number of IDE streams per PHB");
+
+#define dev_to_sp(dev)		((struct sp_device *)dev_get_drvdata(dev))
+#define dev_to_psp(dev)		((struct psp_device *)(dev_to_sp(dev)->psp_data))
+#define dev_to_sev(dev)		((struct sev_device *)(dev_to_psp(dev)->sev_data))
+#define tsm_dev_to_sev(tsmdev)	dev_to_sev((tsmdev)->dev.parent)
+
+#define pdev_to_tio_dsm(pdev)	(container_of((pdev)->tsm, struct tio_dsm, tsm.base_tsm))
+
+static int sev_tio_spdm_cmd(struct tio_dsm *dsm, int ret)
+{
+	struct tsm_dsm_tio *dev_data = &dsm->data;
+	struct tsm_spdm *spdm = &dev_data->spdm;
+
+	/* Check the main command handler response before entering the loop */
+	if (ret == 0 && dev_data->psp_ret != SEV_RET_SUCCESS)
+		return -EINVAL;
+
+	if (ret <= 0)
+		return ret;
+
+	/* ret > 0 means "SPDM requested" */
+	while (ret == PCI_DOE_FEATURE_CMA || ret == PCI_DOE_FEATURE_SSESSION) {
+		ret = pci_doe(dsm->tsm.doe_mb, PCI_VENDOR_ID_PCI_SIG, ret,
+			      spdm->req, spdm->req_len, spdm->rsp, spdm->rsp_len);
+		if (ret < 0)
+			break;
+
+		WARN_ON_ONCE(ret == 0); /* The response should never be empty */
+		spdm->rsp_len = ret;
+		ret = sev_tio_continue(dev_data);
+	}
+
+	return ret;
+}
+
+static int stream_enable(struct pci_ide *ide)
+{
+	struct pci_dev *rp = pcie_find_root_port(ide->pdev);
+	int ret;
+
+	ret = pci_ide_stream_enable(rp, ide);
+	if (ret)
+		return ret;
+
+	ret = pci_ide_stream_enable(ide->pdev, ide);
+	if (ret)
+		pci_ide_stream_disable(rp, ide);
+
+	return ret;
+}
+
+static int streams_enable(struct pci_ide **ide)
+{
+	int ret = 0;
+
+	for (int i = 0; i < TIO_IDE_MAX_TC; ++i) {
+		if (ide[i]) {
+			ret = stream_enable(ide[i]);
+			if (ret)
+				break;
+		}
+	}
+
+	return ret;
+}
+
+static void stream_disable(struct pci_ide *ide)
+{
+	pci_ide_stream_disable(ide->pdev, ide);
+	pci_ide_stream_disable(pcie_find_root_port(ide->pdev), ide);
+}
+
+static void streams_disable(struct pci_ide **ide)
+{
+	for (int i = 0; i < TIO_IDE_MAX_TC; ++i)
+		if (ide[i])
+			stream_disable(ide[i]);
+}
+
+static void stream_setup(struct pci_ide *ide)
+{
+	struct pci_dev *rp = pcie_find_root_port(ide->pdev);
+
+	ide->partner[PCI_IDE_EP].rid_start = 0;
+	ide->partner[PCI_IDE_EP].rid_end = 0xffff;
+	ide->partner[PCI_IDE_RP].rid_start = 0;
+	ide->partner[PCI_IDE_RP].rid_end = 0xffff;
+
+	ide->pdev->ide_cfg = 0;
+	ide->pdev->ide_tee_limit = 1;
+	rp->ide_cfg = 1;
+	rp->ide_tee_limit = 0;
+
+	pci_warn(ide->pdev, "Forcing CFG/TEE for %s", pci_name(rp));
+	pci_ide_stream_setup(ide->pdev, ide);
+	pci_ide_stream_setup(rp, ide);
+}
+
+static u8 streams_setup(struct pci_ide **ide, u8 *ids)
+{
+	bool def = false;
+	u8 tc_mask = 0;
+	int i;
+
+	for (i = 0; i < TIO_IDE_MAX_TC; ++i) {
+		if (!ide[i]) {
+			ids[i] = 0xFF;
+			continue;
+		}
+
+		tc_mask |= BIT(i);
+		ids[i] = ide[i]->stream_id;
+
+		if (!def) {
+			struct pci_ide_partner *settings;
+
+			settings = pci_ide_to_settings(ide[i]->pdev, ide[i]);
+			settings->default_stream = 1;
+			def = true;
+		}
+
+		stream_setup(ide[i]);
+	}
+
+	return tc_mask;
+}
+
+static int streams_register(struct pci_ide **ide)
+{
+	int ret = 0, i;
+
+	for (i = 0; i < TIO_IDE_MAX_TC; ++i) {
+		if (ide[i]) {
+			ret = pci_ide_stream_register(ide[i]);
+			if (ret)
+				break;
+		}
+	}
+
+	return ret;
+}
+
+static void streams_unregister(struct pci_ide **ide)
+{
+	for (int i = 0; i < TIO_IDE_MAX_TC; ++i)
+		if (ide[i])
+			pci_ide_stream_unregister(ide[i]);
+}
+
+static void stream_teardown(struct pci_ide *ide)
+{
+	pci_ide_stream_teardown(ide->pdev, ide);
+	pci_ide_stream_teardown(pcie_find_root_port(ide->pdev), ide);
+}
+
+static void streams_teardown(struct pci_ide **ide)
+{
+	for (int i = 0; i < TIO_IDE_MAX_TC; ++i) {
+		if (ide[i]) {
+			stream_teardown(ide[i]);
+			pci_ide_stream_free(ide[i]);
+			ide[i] = NULL;
+		}
+	}
+}
+
+static int stream_alloc(struct pci_dev *pdev, struct pci_ide **ide,
+			unsigned int tc)
+{
+	struct pci_dev *rp = pcie_find_root_port(pdev);
+	struct pci_ide *ide1;
+
+	if (ide[tc]) {
+		pci_err(pdev, "Stream for class=%d already registered", tc);
+		return -EBUSY;
+	}
+
+	/* FIXME: find a better way */
+	if (nr_ide_streams != TIO_DEFAULT_NR_IDE_STREAMS)
+		pci_notice(pdev, "Enable non-default %d streams", nr_ide_streams);
+	pci_ide_set_nr_streams(to_pci_host_bridge(rp->bus->bridge), nr_ide_streams);
+
+	ide1 = pci_ide_stream_alloc(pdev);
+	if (!ide1)
+		return -EFAULT;
+
+	/* Blindly assign streamid=0 to TC=0, and so on */
+	ide1->stream_id = tc;
+
+	ide[tc] = ide1;
+
+	return 0;
+}
+
+static struct pci_tsm *tio_pf0_probe(struct pci_dev *pdev, struct sev_device *sev)
+{
+	struct tio_dsm *dsm __free(kfree) = kzalloc(sizeof(*dsm), GFP_KERNEL);
+	int rc;
+
+	if (!dsm)
+		return NULL;
+
+	rc = pci_tsm_pf0_constructor(pdev, &dsm->tsm, sev->tsmdev);
+	if (rc)
+		return NULL;
+
+	pci_dbg(pdev, "TSM enabled\n");
+	dsm->sev = sev;
+	return &no_free_ptr(dsm)->tsm.base_tsm;
+}
+
+static struct pci_tsm *dsm_probe(struct tsm_dev *tsmdev, struct pci_dev *pdev)
+{
+	struct sev_device *sev = tsm_dev_to_sev(tsmdev);
+
+	if (is_pci_tsm_pf0(pdev))
+		return tio_pf0_probe(pdev, sev);
+	return 0;
+}
+
+static void dsm_remove(struct pci_tsm *tsm)
+{
+	struct pci_dev *pdev = tsm->pdev;
+
+	pci_dbg(pdev, "TSM disabled\n");
+
+	if (is_pci_tsm_pf0(pdev)) {
+		struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm);
+
+		pci_tsm_pf0_destructor(&dsm->tsm);
+		kfree(dsm);
+	}
+}
+
+static int dsm_create(struct tio_dsm *dsm)
+{
+	struct pci_dev *pdev = dsm->tsm.base_tsm.pdev;
+	u8 segment_id = pdev->bus ? pci_domain_nr(pdev->bus) : 0;
+	struct pci_dev *rootport = pcie_find_root_port(pdev);
+	u16 device_id = pci_dev_id(pdev);
+	u16 root_port_id;
+	u32 lnkcap = 0;
+
+	if (pci_read_config_dword(rootport, pci_pcie_cap(rootport) + PCI_EXP_LNKCAP,
+				  &lnkcap))
+		return -ENODEV;
+
+	root_port_id = FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap);
+
+	return sev_tio_dev_create(&dsm->data, device_id, root_port_id, segment_id);
+}
+
+static int dsm_connect(struct pci_dev *pdev)
+{
+	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
+	struct tsm_dsm_tio *dev_data = &dsm->data;
+	u8 ids[TIO_IDE_MAX_TC];
+	u8 tc_mask;
+	int ret;
+
+	if (pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
+				 PCI_DOE_FEATURE_SSESSION) != dsm->tsm.doe_mb) {
+		pci_err(pdev, "CMA DOE MB must support SSESSION\n");
+		return -EFAULT;
+	}
+
+	ret = stream_alloc(pdev, dev_data->ide, 0);
+	if (ret)
+		return ret;
+
+	ret = dsm_create(dsm);
+	if (ret)
+		goto ide_free_exit;
+
+	tc_mask = streams_setup(dev_data->ide, ids);
+
+	ret = sev_tio_dev_connect(dev_data, tc_mask, ids, dev_data->cert_slot);
+	ret = sev_tio_spdm_cmd(dsm, ret);
+	if (ret)
+		goto free_exit;
+
+	streams_enable(dev_data->ide);
+
+	ret = streams_register(dev_data->ide);
+	if (ret)
+		goto free_exit;
+
+	return 0;
+
+free_exit:
+	sev_tio_dev_reclaim(dev_data);
+
+	streams_disable(dev_data->ide);
+ide_free_exit:
+
+	streams_teardown(dev_data->ide);
+
+	return ret;
+}
+
+static void dsm_disconnect(struct pci_dev *pdev)
+{
+	bool force = SYSTEM_HALT <= system_state && system_state <= SYSTEM_RESTART;
+	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
+	struct tsm_dsm_tio *dev_data = &dsm->data;
+	int ret;
+
+	ret = sev_tio_dev_disconnect(dev_data, force);
+	ret = sev_tio_spdm_cmd(dsm, ret);
+	if (ret && !force) {
+		ret = sev_tio_dev_disconnect(dev_data, true);
+		sev_tio_spdm_cmd(dsm, ret);
+	}
+
+	sev_tio_dev_reclaim(dev_data);
+
+	streams_disable(dev_data->ide);
+	streams_unregister(dev_data->ide);
+	streams_teardown(dev_data->ide);
+}
+
+static struct pci_tsm_ops sev_tsm_ops = {
+	.probe = dsm_probe,
+	.remove = dsm_remove,
+	.connect = dsm_connect,
+	.disconnect = dsm_disconnect,
+};
+
+void sev_tsm_init_locked(struct sev_device *sev, void *tio_status_page)
+{
+	struct sev_tio_status *t = kzalloc(sizeof(*t), GFP_KERNEL);
+	struct tsm_dev *tsmdev;
+	int ret;
+
+	WARN_ON(sev->tio_status);
+
+	if (!t)
+		return;
+
+	ret = sev_tio_init_locked(tio_status_page);
+	if (ret) {
+		pr_warn("SEV-TIO STATUS failed with %d\n", ret);
+		goto error_exit;
+	}
+
+	tsmdev = tsm_register(sev->dev, &sev_tsm_ops);
+	if (IS_ERR(tsmdev))
+		goto error_exit;
+
+	memcpy(t, tio_status_page, sizeof(*t));
+
+	pr_notice("SEV-TIO status: EN=%d INIT_DONE=%d rq=%d..%d rs=%d..%d "
+		  "scr=%d..%d out=%d..%d dev=%d tdi=%d algos=%x\n",
+		  t->tio_en, t->tio_init_done,
+		  t->spdm_req_size_min, t->spdm_req_size_max,
+		  t->spdm_rsp_size_min, t->spdm_rsp_size_max,
+		  t->spdm_scratch_size_min, t->spdm_scratch_size_max,
+		  t->spdm_out_size_min, t->spdm_out_size_max,
+		  t->devctx_size, t->tdictx_size,
+		  t->tio_crypto_alg);
+
+	sev->tsmdev = tsmdev;
+	sev->tio_status = t;
+
+	return;
+
+error_exit:
+	kfree(t);
+	pr_err("Failed to enable SEV-TIO: ret=%d en=%d initdone=%d SEV=%d\n",
+	       ret, t->tio_en, t->tio_init_done, boot_cpu_has(X86_FEATURE_SEV));
+}
+
+void sev_tsm_uninit(struct sev_device *sev)
+{
+	if (sev->tsmdev)
+		tsm_unregister(sev->tsmdev);
+
+	sev->tsmdev = NULL;
+}
diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
index 9e0c16b36f9c..8a74a08553a5 100644
--- a/drivers/crypto/ccp/sev-dev.c
+++ b/drivers/crypto/ccp/sev-dev.c
@@ -75,6 +75,14 @@ static bool psp_init_on_probe = true;
 module_param(psp_init_on_probe, bool, 0444);
 MODULE_PARM_DESC(psp_init_on_probe, "  if true, the PSP will be initialized on module init. Else the PSP will be initialized on the first command requiring it");
 
+#if IS_ENABLED(CONFIG_PCI_TSM)
+static bool sev_tio_enabled = true;
+module_param_named(tio, sev_tio_enabled, bool, 0444);
+MODULE_PARM_DESC(tio, "Enables TIO in SNP_INIT_EX");
+#else
+static const bool sev_tio_enabled = false;
+#endif
+
 MODULE_FIRMWARE("amd/amd_sev_fam17h_model0xh.sbin"); /* 1st gen EPYC */
 MODULE_FIRMWARE("amd/amd_sev_fam17h_model3xh.sbin"); /* 2nd gen EPYC */
 MODULE_FIRMWARE("amd/amd_sev_fam19h_model0xh.sbin"); /* 3rd gen EPYC */
@@ -251,7 +259,7 @@ static int sev_cmd_buffer_len(int cmd)
 	case SEV_CMD_SNP_COMMIT:		return sizeof(struct sev_data_snp_commit);
 	case SEV_CMD_SNP_FEATURE_INFO:		return sizeof(struct sev_data_snp_feature_info);
 	case SEV_CMD_SNP_VLEK_LOAD:		return sizeof(struct sev_user_data_snp_vlek_load);
-	default:				return 0;
+	default:				return sev_tio_cmd_buffer_len(cmd);
 	}
 
 	return 0;
@@ -1394,6 +1402,8 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
 	 *
 	 */
 	if (sev_version_greater_or_equal(SNP_MIN_API_MAJOR, 52)) {
+		bool tio_supp = !!(sev->snp_feat_info_0.ebx & SNP_SEV_TIO_SUPPORTED);
+
 		/*
 		 * Firmware checks that the pages containing the ranges enumerated
 		 * in the RANGES structure are either in the default page state or in the
@@ -1434,6 +1444,17 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
 		data.init_rmp = 1;
 		data.list_paddr_en = 1;
 		data.list_paddr = __psp_pa(snp_range_list);
+
+		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();
+
+		/*
+		 * When psp_init_on_probe is disabled, the userspace calling
+		 * SEV ioctl can inadvertently shut down SNP and SEV-TIO causing
+		 * unexpected state loss.
+		 */
+		if (data.tio_en && !psp_init_on_probe)
+			dev_warn(sev->dev, "SEV-TIO as incompatible with psp_init_on_probe=0\n");
+
 		cmd = SEV_CMD_SNP_INIT_EX;
 	} else {
 		cmd = SEV_CMD_SNP_INIT;
@@ -1471,7 +1492,8 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
 
 	snp_hv_fixed_pages_state_update(sev, HV_FIXED);
 	sev->snp_initialized = true;
-	dev_dbg(sev->dev, "SEV-SNP firmware initialized\n");
+	dev_dbg(sev->dev, "SEV-SNP firmware initialized, SEV-TIO is %s\n",
+		data.tio_en ? "enabled" : "disabled");
 
 	dev_info(sev->dev, "SEV-SNP API:%d.%d build:%d\n", sev->api_major,
 		 sev->api_minor, sev->build);
@@ -1479,6 +1501,23 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
 	atomic_notifier_chain_register(&panic_notifier_list,
 				       &snp_panic_notifier);
 
+	if (data.tio_en) {
+		/*
+		 * This executes with the sev_cmd_mutex held so down the stack
+		 * snp_reclaim_pages(locked=false) might be needed (which is extremely
+		 * unlikely) but will cause a deadlock.
+		 * Instead of exporting __snp_alloc_firmware_pages(), allocate a page
+		 * for this one call here.
+		 */
+		void *tio_status = page_address(__snp_alloc_firmware_pages(
+			GFP_KERNEL_ACCOUNT | __GFP_ZERO, 0, true));
+
+		if (tio_status) {
+			sev_tsm_init_locked(sev, tio_status);
+			__snp_free_firmware_pages(virt_to_page(tio_status), 0, true);
+		}
+	}
+
 	sev_es_tmr_size = SNP_TMR_SIZE;
 
 	return 0;
@@ -2758,8 +2797,20 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic)
 
 static void sev_firmware_shutdown(struct sev_device *sev)
 {
+	/*
+	 * Calling without sev_cmd_mutex held as TSM will likely try disconnecting
+	 * IDE and this ends up calling sev_do_cmd() which locks sev_cmd_mutex.
+	 */
+	if (sev->tio_status)
+		sev_tsm_uninit(sev);
+
 	mutex_lock(&sev_cmd_mutex);
+
 	__sev_firmware_shutdown(sev, false);
+
+	kfree(sev->tio_status);
+	sev->tio_status = NULL;
+
 	mutex_unlock(&sev_cmd_mutex);
 }
 
-- 
2.51.1

^ permalink raw reply related

* Re: [PATCH v4 07/16] x86/virt/tdx: Add tdx_alloc/free_page() helpers
From: Edgecombe, Rick P @ 2025-12-02 20:02 UTC (permalink / raw)
  To: kvm@vger.kernel.org, linux-coco@lists.linux.dev, Huang, Kai,
	Li, Xiaoyao, Hansen, Dave, Zhao, Yan Y, Wu, Binbin,
	kas@kernel.org, seanjc@google.com, mingo@redhat.com,
	pbonzini@redhat.com, tglx@linutronix.de, Yamahata, Isaku,
	nik.borisov@suse.com, linux-kernel@vger.kernel.org,
	Annapurve, Vishal, Gao, Chao, bp@alien8.de, x86@kernel.org
In-Reply-To: <f080efe3-6bf4-4631-9018-2dbf546c25fb@suse.com>

On Tue, 2025-12-02 at 09:38 +0200, Nikolay Borisov wrote:
> > Yea, it could be simpler if it was always guaranteed to be 2 pages. But it
> > was
> > my understanding that it would not be a fixed size. Can you point to what
> > docs
> > makes you think that?
> 
> Looking at the PHYMEM.PAMT.ADD ABI spec the pages being added are always 
> put into pair in rdx/r8. So e.g. looking into tdh_phymem_pamt_add rcx is 
> set to a 2mb page, and subsequently we have the memcpy which simply sets 
> the rdx/r8 input argument registers, no ? Or am I misunderstanding the 
> code?

Hmm, you are totally right. The docs specify the size of the 4k entries, but
doesn't specify that Dynamic PAMT is supposed to provide larger sizes in the
other registers. A reasonable reading could assume 2 pages always, and the usage
of the other registers seems like an assumption.

Kirill, any history here?

Otherwise, we could turn tdx_dpamt_entry_pages() into a define and shrink the
stack allocated buffers. I'll see how much removing the loops helps.

^ permalink raw reply

* Re: [PATCH kernel v3 4/4] crypto/ccp: Implement SEV-TIO PCIe IDE (phase1)
From: Tom Lendacky @ 2025-12-02 14:52 UTC (permalink / raw)
  To: Alexey Kardashevskiy, linux-kernel
  Cc: linux-crypto, John Allen, Herbert Xu, David S. Miller,
	Ashish Kalra, Joerg Roedel, Suravee Suthikulpanit, Will Deacon,
	Robin Murphy, Borislav Petkov, Borislav Petkov (AMD),
	Dan Williams, Jason Gunthorpe, Jerry Snitselaar, Vasant Hegde,
	Gao Shiyuan, Sean Christopherson, Kim Phillips, Nikunj A Dadhania,
	Michael Roth, Paolo Bonzini, iommu, x86, linux-coco
In-Reply-To: <20251202024449.542361-5-aik@amd.com>

On 12/1/25 20:44, Alexey Kardashevskiy wrote:
> Implement the SEV-TIO (Trusted I/O) firmware interface for PCIe TDISP
> (Trust Domain In-Socket Protocol). This enables secure communication
> between trusted domains and PCIe devices through the PSP (Platform
> Security Processor).
> 
> The implementation includes:
> - Device Security Manager (DSM) operations for establishing secure links
> - SPDM (Security Protocol and Data Model) over DOE (Data Object Exchange)
> - IDE (Integrity Data Encryption) stream management for secure PCIe
> 
> This module bridges the SEV firmware stack with the generic PCIe TSM
> framework.
> 
> This is phase1 as described in Documentation/driver-api/pci/tsm.rst.
> 
> On AMD SEV, the AMD PSP firmware acts as TSM (manages the security/trust).
> The CCP driver provides the interface to it and registers in the TSM
> subsystem.
> 
> Detect the PSP support (reported via FEATURE_INFO + SNP_PLATFORM_STATUS)
> and enable SEV-TIO in the SNP_INIT_EX call if the hardware supports TIO.
> 
> Implement SEV TIO PSP command wrappers in sev-dev-tio.c and store
> the data in the SEV-TIO-specific structs.
> 
> Implement TSM hooks and IDE setup in sev-dev-tsm.c.
> 
> Signed-off-by: Alexey Kardashevskiy <aik@amd.com>

Just some minor comments below. After those are addressed:

For the ccp related changes in the whole series:

Acked-by: Tom Lendacky <thomas.lendacky@amd.com>

> ---
> Changes:
> v2:
> * moved declarations from sev-dev-tio.h to sev-dev.h
> * removed include "sev-dev-tio.h" from sev-dev.c to fight errors when TSM is disabled
> * converted /** to /* as these are part of any external API and trigger unwanted kerneldoc warnings
> * got rid of ifdefs
> * "select PCI_TSM" moved under CRYPTO_DEV_SP_PSP
> * open coded SNP_SEV_TIO_SUPPORTED
> * renamed tio_present to tio_supp to match the flag name
> * merged "crypto: ccp: Enable SEV-TIO feature in the PSP when supported" to this one
> ---
>  drivers/crypto/ccp/Kconfig       |   1 +
>  drivers/crypto/ccp/Makefile      |   4 +
>  drivers/crypto/ccp/sev-dev-tio.h | 123 +++
>  drivers/crypto/ccp/sev-dev.h     |   9 +
>  include/linux/psp-sev.h          |  11 +-
>  drivers/crypto/ccp/sev-dev-tio.c | 864 ++++++++++++++++++++
>  drivers/crypto/ccp/sev-dev-tsm.c | 405 +++++++++
>  drivers/crypto/ccp/sev-dev.c     |  51 +-
>  8 files changed, 1465 insertions(+), 3 deletions(-)
> 

> diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
> index 9e0c16b36f9c..d6095d1467b3 100644
> --- a/drivers/crypto/ccp/sev-dev.c
> +++ b/drivers/crypto/ccp/sev-dev.c
> @@ -75,6 +75,10 @@ static bool psp_init_on_probe = true;
>  module_param(psp_init_on_probe, bool, 0444);
>  MODULE_PARM_DESC(psp_init_on_probe, "  if true, the PSP will be initialized on module init. Else the PSP will be initialized on the first command requiring it");
>  
> +static bool sev_tio_enabled = IS_ENABLED(CONFIG_PCI_TSM);
> +module_param_named(tio, sev_tio_enabled, bool, 0444);
> +MODULE_PARM_DESC(tio, "Enables TIO in SNP_INIT_EX");

Hmmm... I thought you said you wanted to hide the module parameter if
CONFIG_PCI_TSM isn't enabled. Either way, it's fine.

> +
>  MODULE_FIRMWARE("amd/amd_sev_fam17h_model0xh.sbin"); /* 1st gen EPYC */
>  MODULE_FIRMWARE("amd/amd_sev_fam17h_model3xh.sbin"); /* 2nd gen EPYC */
>  MODULE_FIRMWARE("amd/amd_sev_fam19h_model0xh.sbin"); /* 3rd gen EPYC */
> @@ -251,7 +255,7 @@ static int sev_cmd_buffer_len(int cmd)
>  	case SEV_CMD_SNP_COMMIT:		return sizeof(struct sev_data_snp_commit);
>  	case SEV_CMD_SNP_FEATURE_INFO:		return sizeof(struct sev_data_snp_feature_info);
>  	case SEV_CMD_SNP_VLEK_LOAD:		return sizeof(struct sev_user_data_snp_vlek_load);
> -	default:				return 0;
> +	default:				return sev_tio_cmd_buffer_len(cmd);
>  	}
>  
>  	return 0;
> @@ -1434,6 +1438,19 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>  		data.init_rmp = 1;
>  		data.list_paddr_en = 1;
>  		data.list_paddr = __psp_pa(snp_range_list);
> +
> +		bool tio_supp = !!(sev->snp_feat_info_0.ebx & SNP_SEV_TIO_SUPPORTED);

Please put the variable definition at the top of the "if" block instead
of in the middle of the code.
> +
> +		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();

Don't you still want to take CONFIG_PCI_TSM into account?

	data.tio_en = IS_ENABLED(CONFIG_PCI_TSM) && tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();

or
	if (IS_ENABLED(CONFIG_PCI_TSM)
		data.tio_en = tio_supp && sev_tio_enabled && amd_iommu_sev_tio_supported();

But if you change back to #ifdef the module parameter, then you won't
need the IS_ENABLED() check here because sev_tio_enabled will be set
based on CONFIG_PCI_TSM and will be false and not changeable if
CONFIG_PCI_TSM is not y.
> +
> +		/*
> +		 * When psp_init_on_probe is disabled, the userspace calling
> +		 * SEV ioctl can inadvertently shut down SNP and SEV-TIO causing
> +		 * unexpected state loss.
> +		 */

After this is merged, lets see if sev_move_to_init_state() can be
cleaned up to avoid this situation.

Thanks,
Tom

> +		if (data.tio_en && !psp_init_on_probe)
> +			dev_warn(sev->dev, "SEV-TIO as incompatible with psp_init_on_probe=0\n");
> +
>  		cmd = SEV_CMD_SNP_INIT_EX;
>  	} else {
>  		cmd = SEV_CMD_SNP_INIT;
> @@ -1471,7 +1488,8 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>  
>  	snp_hv_fixed_pages_state_update(sev, HV_FIXED);
>  	sev->snp_initialized = true;
> -	dev_dbg(sev->dev, "SEV-SNP firmware initialized\n");
> +	dev_dbg(sev->dev, "SEV-SNP firmware initialized, SEV-TIO is %s\n",
> +		data.tio_en ? "enabled" : "disabled");
>  
>  	dev_info(sev->dev, "SEV-SNP API:%d.%d build:%d\n", sev->api_major,
>  		 sev->api_minor, sev->build);
> @@ -1479,6 +1497,23 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid)
>  	atomic_notifier_chain_register(&panic_notifier_list,
>  				       &snp_panic_notifier);
>  
> +	if (data.tio_en) {
> +		/*
> +		 * This executes with the sev_cmd_mutex held so down the stack
> +		 * snp_reclaim_pages(locked=false) might be needed (which is extremely
> +		 * unlikely) but will cause a deadlock.
> +		 * Instead of exporting __snp_alloc_firmware_pages(), allocate a page
> +		 * for this one call here.
> +		 */
> +		void *tio_status = page_address(__snp_alloc_firmware_pages(
> +			GFP_KERNEL_ACCOUNT | __GFP_ZERO, 0, true));
> +
> +		if (tio_status) {
> +			sev_tsm_init_locked(sev, tio_status);
> +			__snp_free_firmware_pages(virt_to_page(tio_status), 0, true);
> +		}
> +	}
> +
>  	sev_es_tmr_size = SNP_TMR_SIZE;
>  
>  	return 0;
> @@ -2758,8 +2793,20 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic)
>  
>  static void sev_firmware_shutdown(struct sev_device *sev)
>  {
> +	/*
> +	 * Calling without sev_cmd_mutex held as TSM will likely try disconnecting
> +	 * IDE and this ends up calling sev_do_cmd() which locks sev_cmd_mutex.
> +	 */
> +	if (sev->tio_status)
> +		sev_tsm_uninit(sev);
> +
>  	mutex_lock(&sev_cmd_mutex);
> +
>  	__sev_firmware_shutdown(sev, false);
> +
> +	kfree(sev->tio_status);
> +	sev->tio_status = NULL;
> +
>  	mutex_unlock(&sev_cmd_mutex);
>  }
>  


^ permalink raw reply

* [Invitation] bi-weekly guest_memfd upstream call on 2025-12-04
From: David Hildenbrand (Red Hat) @ 2025-12-02 10:22 UTC (permalink / raw)
  To: linux-coco@lists.linux.dev, linux-mm@kvack.org, KVM

Hi,

Our next guest_memfd upstream call is scheduled for Thursday,
2025-12-04 at 8:00 - 9:00am (GMT-08:00) Pacific Time - Vancouver.

We'll be using the following Google meet:
http://meet.google.com/wxp-wtju-jzw

The meeting notes can be found at [1], where we also link recordings and
collect current guest_memfd upstream proposals. If you want an google
calendar invitation that also covers all future meetings, just write me
a mail.

In this meeting, we'll have Ackerley continue giving us an overview of
work-in-progress HugeTLB support and discuss whatever comes up.

To put something to discuss onto the agenda, reply to this mail or add
them to the "Topics/questions for next meeting(s)" section in the
meeting notes as a comment.


This might be the last meeting this year: I will be traveling for LPC on 
December 11 and December 18. Then, Christmas is already around the 
corner and we'll skip the one on December 25. So we'll probably have our 
next meeting then on January 8.

[1]
https://docs.google.com/document/d/1M6766BzdY1Lhk7LiR5IqVR8B8mG3cr-cxTxOrAosPOk/edit?usp=sharing

-- 
Cheers

David



^ permalink raw reply

* Re: [PATCH 1/3] KVM: guest_memfd: Remove preparation tracking
From: Yan Zhao @ 2025-12-02  9:17 UTC (permalink / raw)
  To: Michael Roth
  Cc: kvm, linux-coco, linux-mm, linux-kernel, thomas.lendacky,
	pbonzini, seanjc, vbabka, ashish.kalra, liam.merwick, david,
	vannapurve, ackerleytng, aik, ira.weiny
In-Reply-To: <20251201234447.5x62vhlg3d5mmtpw@amd.com>

On Mon, Dec 01, 2025 at 05:44:47PM -0600, Michael Roth wrote:
> On Tue, Nov 25, 2025 at 11:13:25AM +0800, Yan Zhao wrote:
> > On Fri, Nov 21, 2025 at 06:43:14AM -0600, Michael Roth wrote:
> > > On Thu, Nov 20, 2025 at 05:12:55PM +0800, Yan Zhao wrote:
> > > > On Thu, Nov 13, 2025 at 05:07:57PM -0600, Michael Roth wrote:
> > > > > @@ -797,19 +782,25 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
> > > > >  {
> > > > >  	pgoff_t index = kvm_gmem_get_index(slot, gfn);
> > > > >  	struct folio *folio;
> > > > > -	bool is_prepared = false;
> > > > >  	int r = 0;
> > > > >  
> > > > >  	CLASS(gmem_get_file, file)(slot);
> > > > >  	if (!file)
> > > > >  		return -EFAULT;
> > > > >  
> > > > > -	folio = __kvm_gmem_get_pfn(file, slot, index, pfn, &is_prepared, max_order);
> > > > > +	folio = __kvm_gmem_get_pfn(file, slot, index, pfn, max_order);
> > > > >  	if (IS_ERR(folio))
> > > > >  		return PTR_ERR(folio);
> > > > >  
> > > > > -	if (!is_prepared)
> > > > > -		r = kvm_gmem_prepare_folio(kvm, slot, gfn, folio);
> > > > > +	if (!folio_test_uptodate(folio)) {
> > > > > +		unsigned long i, nr_pages = folio_nr_pages(folio);
> > > > > +
> > > > > +		for (i = 0; i < nr_pages; i++)
> > > > > +			clear_highpage(folio_page(folio, i));
> > > > > +		folio_mark_uptodate(folio);
> > > > Here, the entire folio is cleared only when the folio is not marked uptodate.
> > > > Then, please check my questions at the bottom
> > > 
> > > Yes, in this patch at least where I tried to mirror the current logic. I
> > > would not be surprised if we need to rework things for inplace/hugepage
> > > support though, but decoupling 'preparation' from the uptodate flag is
> > > the main goal here.
> > Could you elaborate a little why the decoupling is needed if it's not for
> > hugepage?
> 
> For instance, for in-place conversion:
> 
>   1. initial allocation: clear, set uptodate, fault in as private
>   2. private->shared: call invalidate hook, fault in as shared
>   3. shared->private: call prep hook, fault in as private
> 
> Here, 2/3 need to track where the current state is shared/private in
> order to make appropriate architecture-specific changes (e.g. RMP table
> updates). But we want to allow for non-destructive in-place conversion,
> where a page is 'uptodate', but not in the desired shared/private state.
> So 'uptodate' becomes a separate piece of state, which is still
> reasonable for gmem to track in the current 4K-only implementation, and
> provides for a reasonable approach to upstreaming in-place conversion,
> which isn't far off for either SNP or TDX.
To me, "1. initial allocation: clear, set uptodate" is more appropriate to
be done in kvm_gmem_get_folio(), instead of in kvm_gmem_get_pfn().

With it, below looks reasonable to me.
> For hugepages, we'll have other things to consider, but those things are
> probably still somewhat far off, and so we shouldn't block steps toward
> in-place conversion based on uncertainty around hugepages. I think it's
> gotten enough attention at least that we know it *can* work, e.g. even
> if we take the inefficient/easy route of zero'ing the whole folio on
> initial access, setting it uptodate, and never doing anything with 
> uptodate again, it's still a usable implementation.

<...>
> > > Assuming this patch goes upstream in some form, we will now have the
> > > following major differences versus previous code:
> > > 
> > >   1) uptodate flag only tracks whether a folio has been cleared
> > >   2) gmem always calls kvm_arch_gmem_prepare() via kvm_gmem_get_pfn() and
> > >      the architecture can handle it's own tracking at whatever granularity
> > >      it likes.
> > 2) looks good to me.
> > 
> > > My hope is that 1) can similarly be done in such a way that gmem does not
> > > need to track things at sub-hugepage granularity and necessitate the need
> > > for some new data structure/state/flag to track sub-page status.
> > I actually don't understand what uptodate flag helps gmem to track.
> > Why can't clear_highpage() be done inside arch specific code? TDX doesn't need
> > this clearing after all.
> 
> It could. E.g. via the kernel-internal gmem flag that I mentioned in my
> earlier reply, or some alternative. 
> 
> In the context of this series, uptodate flag continues to instruct
> kvm_gmem_get_pfn() that it doesn't not need to re-clear pages, because
> a prior kvm_gmem_get_pfn() or kvm_gmem_populate() already initialized
> the folio, and it is no longer tied to any notion of
> preparedness-tracking.
> 
> What use uptodate will have in the context of hugepages: I'm not sure.
> For non-in-place conversion, it's tempting to just let it continue to be
> per-folio and require clearing the whole folio on initial access, but
> it's not efficient. It may make sense to farm it out to
> post-populate/prep hooks instead, as you're suggesting for TDX.
> 
> But then, for in-place conversion, you have to deal with pages initially
> faulted in as shared. They might be split prior to initial access as a
> private page, where we can't assume TDX will have scrubbed things. So in
> that case it might still make sense to rely on it.
> 
> Definitely things that require some more thought. But having it inextricably
> tied to preparedness just makes preparation tracking similarly more
> complicated as it pulls it back into gmem when that does not seem to be
> the direction any architectures other SNP have/want to go.
> 
> > 
> > > My understanding based on prior discussion in guest_memfd calls was that
> > > it would be okay to go ahead and clear the entire folio at initial allocation
> > > time, and basically never mess with it again. It was also my understanding
> > That's where I don't follow in this patch.
> > I don't see where the entire folio A is cleared if it's only partially mapped by
> > kvm_gmem_populate(). kvm_gmem_get_pfn() won't clear folio A either due to
> > kvm_gmem_populate() has set the uptodate flag.
> > 
> > > that for TDX it might even be optimal to completely skip clearing the folio
> > > if it is getting mapped into SecureEPT as a hugepage since the TDX module
> > > would handle that, but that maybe conversely after private->shared there
> > > would be some need to reclear... I'll try to find that discussion and
> > > refresh. Vishal I believe suggested some flags to provide more control over
> > > this behavior.
> > > 
> > > > 
> > > > It's possible (at least for TDX) that a huge folio is only partially populated
> > > > by kvm_gmem_populate(). Then kvm_gmem_get_pfn() faults in another part of the
> > > > huge folio. For example, in TDX, GFN 0x81f belongs to the init memory region,
> > > > while GFN 0x820 is faulted after TD is running. However, these two GFNs can
> > > > belong to the same folio of order 9.
> > > 
> > > Would the above scheme of clearing the entire folio up front and not re-clearing
> > > at fault time work for this case?
> > This case doesn't affect TDX, because TDX clearing private pages internally in
> > SEAM APIs. So, as long as kvm_gmem_get_pfn() does not invoke clear_highpage()
> > after making a folio private, it works fine for TDX.
> > 
> > I was just trying to understand why SNP needs the clearing of entire folio in
> > kvm_gmem_get_pfn() while I don't see how the entire folio is cleared when it's
> > partially mapped in kvm_gmem_populate().
> > Also, I'm wondering if it would be better if SNP could move the clearing of
> > folio into something like kvm_arch_gmem_clear(), just as kvm_arch_gmem_prepare()
> > which is always invoked by kvm_gmem_get_pfn() and the architecture can handle
> > it's own tracking at whatever granularity.
> 
> Possibly, but I touched elsewhere on where in-place conversion might
> trip up this approach. At least decoupling them allows for the prep side
> of things to be moved to architecture-specific tracking. We can deal
> with uptodate separately I think.
> 
> -Mike
> 
> > 
> >  
> > > > Note: the current code should not impact TDX. I'm just asking out of curiosity:)
> > > > 
> > > > [1] https://lore.kernel.org/all/aQ3uj4BZL6uFQzrD@yzhao56-desk.sh.intel.com/
> > > > 
> > > >  

^ permalink raw reply

* Re: [PATCH 1/3] KVM: guest_memfd: Remove preparation tracking
From: Yan Zhao @ 2025-12-02  9:16 UTC (permalink / raw)
  To: Vishal Annapurve
  Cc: Michael Roth, kvm, linux-coco, linux-mm, linux-kernel,
	thomas.lendacky, pbonzini, seanjc, vbabka, ashish.kalra,
	liam.merwick, david, ackerleytng, aik, ira.weiny
In-Reply-To: <CAGtprH8kVtyMiZeF+40hSpkY=O_HD0K+1Gy10rPdi8-mNLr8Yg@mail.gmail.com>

On Mon, Dec 01, 2025 at 11:33:18AM -0800, Vishal Annapurve wrote:
> On Sun, Nov 30, 2025 at 6:53 PM Yan Zhao <yan.y.zhao@intel.com> wrote:
> >
> > On Sun, Nov 30, 2025 at 05:35:41PM -0800, Vishal Annapurve wrote:
> > > On Mon, Nov 24, 2025 at 7:15 PM Yan Zhao <yan.y.zhao@intel.com> wrote:
> > > > > > > @@ -889,7 +872,7 @@ long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long
> > > > > > >           p = src ? src + i * PAGE_SIZE : NULL;
> > > > > > >           ret = post_populate(kvm, gfn, pfn, p, max_order, opaque);
> > > > > > >           if (!ret)
> > > > > > > -                 kvm_gmem_mark_prepared(folio);
> > > > > > > +                 folio_mark_uptodate(folio);
> > > > > > As also asked in [1], why is the entire folio marked as uptodate here? Why does
> > > > > > kvm_gmem_get_pfn() clear all pages of a huge folio when the folio isn't marked
> > > > > > uptodate?
> > > > >
> > > > > Quoting your example from[1] for more context:
> > > > >
> > > > > > I also have a question about this patch:
> > > > > >
> > > > > > Suppose there's a 2MB huge folio A, where
> > > > > > A1 and A2 are 4KB pages belonging to folio A.
> > > > > >
> > > > > > (1) kvm_gmem_populate() invokes __kvm_gmem_get_pfn() and gets folio A.
> > > > > >     It adds page A1 and invokes folio_mark_uptodate() on folio A.
> > > > >
> > > > > In SNP hugepage patchset you responded to, it would only mark A1 as
> > > > You mean code in
> > > > https://github.com/amdese/linux/commits/snp-inplace-conversion-rfc1 ?
> > > >
> > > > > prepared/cleared. There was 4K-granularity tracking added to handle this.
> > > > I don't find the code that marks only A1 as "prepared/cleared".
> > > > Instead, I just found folio_mark_uptodate() is invoked by kvm_gmem_populate()
> > > > to mark the entire folio A as uptodate.
> > > >
> > > > However, according to your statement below that "uptodate flag only tracks
> > > > whether a folio has been cleared", I don't follow why and where the entire folio
> > > > A would be cleared if kvm_gmem_populate() only adds page A1.
> > >
> > > I think kvm_gmem_populate() is currently only used by SNP and TDX
> > > logic, I don't see an issue with marking the complete folio as
> > > uptodate even if its partially updated by kvm_gmem_populate() paths as
> > > the private memory will eventually get initialized anyways.
> > Still using the above example,
> > If only page A1 is passed to sev_gmem_post_populate(), will SNP initialize the
> > entire folio A?
> > - if yes, could you kindly point me to the code that does this? .
> > - if sev_gmem_post_populate() only initializes page A1, after marking the
> >   complete folio A as uptodate in kvm_gmem_populate(), later faulting in page A2
> >   in kvm_gmem_get_pfn() will not clear page A2 by invoking clear_highpage(),
> >   since the entire folio A is uptodate. I don't understand why this is OK.
> >   Or what's the purpose of invoking clear_highpage() on other folios?
> 
> I think sev_gmem_post_populate() only initializes the ranges marked
> for snp_launch_update(). Since the current code lacks a hugepage
> provider, the kvm_gmem_populate() doesn't need to explicitly clear
> anything for 4K backings during kvm_gmem_populate().
> 
> I see your point. Once a hugepage provider lands, kvm_gmem_populate()
> can first invoke clear_highpage() or an equivalent API on a complete
> huge folio before calling the architecture-specific post-populate hook
> to keep the implementation consistent.
Maybe clear_highpage() in kvm_gmem_get_folio()?

When in-place copy in kvm_gmem_populate() comes, kvm_gmem_get_folio() can be
invoked first for shared memory, so clear_highpage() there is before userspace
writes to shared memory. No clear_highpage() is required when kvm_gmem_populate()
invokes __kvm_gmem_get_pfn() to get the folio again.

> Subsequently, we need to figure out a way to avoid this clearing for
> SNP/TDX/CCA private faults.
> 
> >
> > Thanks
> > Yan

^ permalink raw reply

* Re: [PATCH v4 07/16] x86/virt/tdx: Add tdx_alloc/free_page() helpers
From: Nikolay Borisov @ 2025-12-02  7:38 UTC (permalink / raw)
  To: Edgecombe, Rick P, kvm@vger.kernel.org,
	linux-coco@lists.linux.dev, Huang, Kai, Li, Xiaoyao, Hansen, Dave,
	Zhao, Yan Y, Wu, Binbin, kas@kernel.org, seanjc@google.com,
	mingo@redhat.com, linux-kernel@vger.kernel.org,
	tglx@linutronix.de, Yamahata, Isaku, pbonzini@redhat.com,
	Annapurve, Vishal, Gao, Chao, bp@alien8.de, x86@kernel.org
In-Reply-To: <730de4be289ed7e3550d40170ea7d67e5d37458f.camel@intel.com>



On 2.12.25 г. 0:39 ч., Edgecombe, Rick P wrote:
> On Thu, 2025-11-27 at 18:11 +0200, Nikolay Borisov wrote:
>>> +/* Number PAMT pages to be provided to TDX module per 2M region of PA */
>>> +static int tdx_dpamt_entry_pages(void)
>>> +{
>>> +	if (!tdx_supports_dynamic_pamt(&tdx_sysinfo))
>>> +		return 0;
>>> +
>>> +	return tdx_sysinfo.tdmr.pamt_4k_entry_size * PTRS_PER_PTE /
>>> PAGE_SIZE;
>>> +}
>>
>> Isn't this guaranteed to return 2 always as per the ABI? Can't the
>> allocation of the 2 pages be moved closer to where it's used - in
>> tdh_phymem_pamt_add which will simplify things a bit?
> 
> Yea, it could be simpler if it was always guaranteed to be 2 pages. But it was
> my understanding that it would not be a fixed size. Can you point to what docs
> makes you think that?

Looking at the PHYMEM.PAMT.ADD ABI spec the pages being added are always 
put into pair in rdx/r8. So e.g. looking into tdh_phymem_pamt_add rcx is 
set to a 2mb page, and subsequently we have the memcpy which simply sets 
the rdx/r8 input argument registers, no ? Or am I misunderstanding the 
code?
> 
> Another option would be to ask TDX folks to make it fixed, and then require an
> opt-in for it to be expanded later if needed. I would have to check on them on
> the reasoning for it being dynamic sized. I'm not sure if it is *that*
> complicated at this point though. Once there is more than one, the loops becomes
> tempting. And if we loop over 2 we could easily loop over n.


^ permalink raw reply

* Re: [PATCH v2 04/21] x86/virt/tdx: Prepare to support P-SEAMLDR SEAMCALLs
From: Chao Gao @ 2025-12-02  7:23 UTC (permalink / raw)
  To: Binbin Wu
  Cc: linux-coco, linux-kernel, x86, reinette.chatre, ira.weiny,
	kai.huang, dan.j.williams, yilun.xu, sagis, vannapurve, paulmck,
	nik.borisov, Farrah Chen, Kirill A. Shutemov, Dave Hansen,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <526f295a-326c-4999-8078-53fb758759ef@linux.intel.com>

>> +static inline bool is_seamldr_call(u64 fn)
>> +{
>> +	return fn & SEAMLDR_SEAMCALL_MASK;
>> +}
>> +
>> +static inline bool sc_need_retry(u64 fn, u64 error_code)
>> +{
>> +	if (is_seamldr_call(fn))
>
>Comparing to TDX module seamcall, seamldr seamcall should be much less.
>Maybe unlikely()?

Makes sense.

>
>> +		return error_code == SEAMLDR_RND_NO_ENTROPY;
>> +	else
>> +		return error_code == TDX_RND_NO_ENTROPY;
>> +}
>> +
>>   static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
>>   			   struct tdx_module_args *args)
>>   {
>> @@ -22,7 +35,7 @@ static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
>>   	do {
>>   		ret = func(fn, args);
>> -	} while (ret == TDX_RND_NO_ENTROPY && --retry);
>> +	} while (sc_need_retry(fn, ret) && --retry);
>>   	return ret;
>>   }
>> @@ -48,6 +61,17 @@ static inline void seamcall_err_ret(u64 fn, u64 err,
>>   			args->r9, args->r10, args->r11);
>>   }
>> +static inline void seamldr_err(u64 fn, u64 err, struct tdx_module_args *args)
>> +{
>> +	/*
>> +	 * Get the actual leaf number. No need to print the bit used to
>> +	 * differentiate between P-SEAMLDR and TDX module as the "P-SEAMLDR"
>> +	 * string in the error message already provides that information.
>> +	 */
>> +	fn &= ~SEAMLDR_SEAMCALL_MASK;
>> +	pr_err("P-SEAMLDR (%lld) failed: 0x%016llx\n", fn, err);
>
>%lld -> %llu ?
>
>And 0x% -> %# to align with seamcall_err().

Sure. Will Do.

^ permalink raw reply

* Re: [PATCH v2 08/21] coco/tdx-host: Implement FW_UPLOAD sysfs ABI for TDX Module updates
From: Chao Gao @ 2025-12-02  7:20 UTC (permalink / raw)
  To: Binbin Wu
  Cc: linux-coco, linux-kernel, x86, reinette.chatre, ira.weiny,
	kai.huang, dan.j.williams, yilun.xu, sagis, vannapurve, paulmck,
	nik.borisov, Farrah Chen, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, H. Peter Anvin, Kirill A. Shutemov
In-Reply-To: <41cdd3d5-c45b-4991-ace9-bef7cf9ed197@linux.intel.com>

On Mon, Nov 24, 2025 at 03:49:34PM +0800, Binbin Wu wrote:
>
>
>On 10/1/2025 10:52 AM, Chao Gao wrote:
>[...]
>> +static enum fw_upload_err tdx_fw_prepare(struct fw_upload *fwl,
>> +					 const u8 *data, u32 size)
>> +{
>> +	struct tdx_fw_upload_status *status = fwl->dd_handle;
>> +
>> +	if (status->cancel_request) {
>> +		status->cancel_request = false;
>> +		return FW_UPLOAD_ERR_CANCELED;
>> +	}
>> +
>> +	return FW_UPLOAD_ERR_NONE;
>> +}
>> +
>> +static enum fw_upload_err tdx_fw_write(struct fw_upload *fwl, const u8 *data,
>> +				       u32 offset, u32 size, u32 *written)
>> +{
>> +	struct tdx_fw_upload_status *status = fwl->dd_handle;
>> +
>> +	if (status->cancel_request) {
>> +		status->cancel_request = false;
>> +		return FW_UPLOAD_ERR_CANCELED;
>> +	}
>
>Since the execution of the work is not protected by the mutex, if userspace
>requests cancellation after this point, after the TDX module update finished,
>it seems that the cancel value is left over and it could impact the next update?

Yes, I think this is a bug. A few other drivers just clear "cancel_request" in
the "prepare" phase, e.g., pd692x0_fw_prepare(), mpfs_auto_update_prepare(),
m10bmc_sec_prepare(). I will follow that approach.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox