mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Rick Edgecombe <rick.p.edgecombe@intel.com>
To: bp@alien8.de, chao.gao@intel.com, dave.hansen@intel.com,
	isaku.yamahata@intel.com, kai.huang@intel.com, kas@kernel.org,
	kvm@vger.kernel.org, linux-coco@lists.linux.dev,
	linux-kernel@vger.kernel.org, mingo@redhat.com,
	pbonzini@redhat.com, seanjc@google.com, tglx@linutronix.de,
	vannapurve@google.com, x86@kernel.org, yan.y.zhao@intel.com,
	xiaoyao.li@intel.com, binbin.wu@intel.com
Cc: rick.p.edgecombe@intel.com,
	"Kirill A. Shutemov" <kirill.shutemov@linux.intel.com>
Subject: [PATCH v4 08/16] x86/virt/tdx: Optimize tdx_alloc/free_page() helpers
Date: Thu, 20 Nov 2025 16:51:17 -0800	[thread overview]
Message-ID: <20251121005125.417831-9-rick.p.edgecombe@intel.com> (raw)
In-Reply-To: <20251121005125.417831-1-rick.p.edgecombe@intel.com>

From: "Kirill A. Shutemov" <kirill.shutemov@linux.intel.com>

Optimize the PAMT alloc/free helpers to avoid taking the global lock when
possible.

The recently introduced PAMT alloc/free helpers maintain a refcount to
keep track of when it is ok to reclaim and free a 4KB PAMT page. This
refcount is protected by a global lock in order to guarantee that races
don’t result in the PAMT getting freed while another caller requests it
be mapped. But a global lock is a bit heavyweight, especially since the
refcounts can be (already are) updated atomically.

A simple approach would be to increment/decrement the refcount outside of
the lock before actually adjusting the PAMT, and only adjust the PAMT if
the refcount transitions from/to 0. This would correctly allocate and free
the PAMT page without getting out of sync. But there it leaves a race
where a simultaneous caller could see the refcount already incremented and
return before it is actually mapped.

So treat the refcount 0->1 case as a special case. On add, if the refcount
is zero *don’t* increment the refcount outside the lock (to 1). Always
take the lock in that case and only set the refcount to 1 after the PAMT
is actually added. This way simultaneous adders, when PAMT is not
installed yet, will take the slow lock path.

On the 1->0 case, it is ok to return from tdx_pamt_put() when the DPAMT is
not actually freed yet, so the basic approach works. Just decrement the
refcount before  taking the lock. Only do the lock and removal of the PAMT
when the refcount goes to zero.

There is an asymmetry between tdx_pamt_get() and tdx_pamt_put() in that
tdx_pamt_put() goes 1->0 outside the lock, but tdx_pamt_get() does 0-1
inside the lock. Because of this, there is a special race where
tdx_pamt_put() could decrement the refcount to zero before the PAMT is
actually removed, and tdx_pamt_get() could try to do a PAMT.ADD when the
page is already mapped. Luckily the TDX module will tell return a special
error that tells us we hit this case. So handle it specially by looking
for the error code.

The optimization is a little special, so make the code extra commented
and verbose.

Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
[Clean up code, update log]
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
---
v4:
 - Use atomic_set() in the HPA_RANGE_NOT_FREE case (Kiryl)
 - Log, comment typos (Binbin)
 - Move PAMT page allocation after refcount check in tdx_pamt_get() to
   avoid an alloc/free in the common path.

v3:
 - Split out optimization from “x86/virt/tdx: Add tdx_alloc/free_page() helpers”
 - Remove edge case handling that I could not find a reason for
 - Write log
---
 arch/x86/include/asm/shared/tdx_errno.h |  2 +
 arch/x86/virt/vmx/tdx/tdx.c             | 68 ++++++++++++++++++-------
 2 files changed, 53 insertions(+), 17 deletions(-)

diff --git a/arch/x86/include/asm/shared/tdx_errno.h b/arch/x86/include/asm/shared/tdx_errno.h
index e302aed31b50..acf7197527da 100644
--- a/arch/x86/include/asm/shared/tdx_errno.h
+++ b/arch/x86/include/asm/shared/tdx_errno.h
@@ -21,6 +21,7 @@
 #define TDX_PREVIOUS_TLB_EPOCH_BUSY		0x8000020100000000ULL
 #define TDX_RND_NO_ENTROPY			0x8000020300000000ULL
 #define TDX_PAGE_METADATA_INCORRECT		0xC000030000000000ULL
+#define TDX_HPA_RANGE_NOT_FREE			0xC000030400000000ULL
 #define TDX_VCPU_NOT_ASSOCIATED			0x8000070200000000ULL
 #define TDX_KEY_GENERATION_FAILED		0x8000080000000000ULL
 #define TDX_KEY_STATE_INCORRECT			0xC000081100000000ULL
@@ -94,6 +95,7 @@ DEFINE_TDX_ERRNO_HELPER(TDX_SUCCESS);
 DEFINE_TDX_ERRNO_HELPER(TDX_RND_NO_ENTROPY);
 DEFINE_TDX_ERRNO_HELPER(TDX_OPERAND_INVALID);
 DEFINE_TDX_ERRNO_HELPER(TDX_OPERAND_BUSY);
+DEFINE_TDX_ERRNO_HELPER(TDX_HPA_RANGE_NOT_FREE);
 DEFINE_TDX_ERRNO_HELPER(TDX_VCPU_NOT_ASSOCIATED);
 DEFINE_TDX_ERRNO_HELPER(TDX_FLUSHVP_NOT_DONE);
 DEFINE_TDX_ERRNO_HELPER(TDX_SW_ERROR);
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 745b308785d6..39e2e448c8ba 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -2139,21 +2139,28 @@ int tdx_pamt_get(struct page *page)
 	u64 pamt_pa_array[MAX_TDX_ARG_SIZE(rdx)];
 	atomic_t *pamt_refcount;
 	u64 tdx_status;
-	int ret;
+	int ret = 0;
 
 	if (!tdx_supports_dynamic_pamt(&tdx_sysinfo))
 		return 0;
 
+	pamt_refcount = tdx_find_pamt_refcount(page_to_pfn(page));
+
+	/*
+	 * If the pamt page is already added (i.e. refcount >= 1),
+	 * then just increment the refcount.
+	 */
+	if (atomic_inc_not_zero(pamt_refcount))
+		return 0;
+
 	ret = alloc_pamt_array(pamt_pa_array);
 	if (ret)
 		goto out_free;
 
-	pamt_refcount = tdx_find_pamt_refcount(page_to_pfn(page));
-
 	scoped_guard(spinlock, &pamt_lock) {
 		/*
-		 * If the pamt page is already added (i.e. refcount >= 1),
-		 * then just increment the refcount.
+		 * Lost race to other tdx_pamt_add(). Other task has already allocated
+		 * PAMT memory for the HPA.
 		 */
 		if (atomic_read(pamt_refcount)) {
 			atomic_inc(pamt_refcount);
@@ -2163,12 +2170,29 @@ int tdx_pamt_get(struct page *page)
 		/* Try to add the pamt page and take the refcount 0->1. */
 
 		tdx_status = tdh_phymem_pamt_add(page, pamt_pa_array);
-		if (!IS_TDX_SUCCESS(tdx_status)) {
+		if (IS_TDX_SUCCESS(tdx_status)) {
+			/*
+			 * The refcount is zero, and this locked path is the only way to
+			 * increase it from 0-1. If the PAMT.ADD was successful, set it
+			 * to 1 (obviously).
+			 */
+			atomic_set(pamt_refcount, 1);
+		} else if (IS_TDX_HPA_RANGE_NOT_FREE(tdx_status)) {
+			/*
+			 * Less obviously, another CPU's call to tdx_pamt_put() could have
+			 * decremented the refcount before entering its lock section.
+			 * In this case, the PAMT is not actually removed yet. Luckily
+			 * TDX module tells about this case, so increment the refcount
+			 * 0-1, so tdx_pamt_put() skips its pending PAMT.REMOVE.
+			 *
+			 * The call didn't need the pages though, so free them.
+			 */
+			atomic_set(pamt_refcount, 1);
+			goto out_free;
+		} else {
 			pr_err("TDH_PHYMEM_PAMT_ADD failed: %#llx\n", tdx_status);
 			goto out_free;
 		}
-
-		atomic_inc(pamt_refcount);
 	}
 
 	return ret;
@@ -2197,20 +2221,32 @@ void tdx_pamt_put(struct page *page)
 
 	pamt_refcount = tdx_find_pamt_refcount(page_to_pfn(page));
 
+	/*
+	 * If the there are more than 1 references on the pamt page,
+	 * don't remove it yet. Just decrement the refcount.
+	 *
+	 * Unlike the paired call in tdx_pamt_get(), decrement the refcount
+	 * outside the lock even if it's the special 0<->1 transition. See
+	 * special logic around HPA_RANGE_NOT_FREE in tdx_pamt_get().
+	 */
+	if (!atomic_dec_and_test(pamt_refcount))
+		return;
+
 	scoped_guard(spinlock, &pamt_lock) {
-		/*
-		 * If the there are more than 1 references on the pamt page,
-		 * don't remove it yet. Just decrement the refcount.
-		 */
-		if (atomic_read(pamt_refcount) > 1) {
-			atomic_dec(pamt_refcount);
+		/* Lost race with tdx_pamt_get(). */
+		if (atomic_read(pamt_refcount))
 			return;
-		}
 
 		/* Try to remove the pamt page and take the refcount 1->0. */
 
 		tdx_status = tdh_phymem_pamt_remove(page, pamt_pa_array);
 		if (!IS_TDX_SUCCESS(tdx_status)) {
+			/*
+			 * Since the refcount was optimistically decremented above
+			 * outside the lock, revert it if there is a failure.
+			 */
+			atomic_inc(pamt_refcount);
+
 			pr_err("TDH_PHYMEM_PAMT_REMOVE failed: %#llx\n", tdx_status);
 
 			/*
@@ -2219,8 +2255,6 @@ void tdx_pamt_put(struct page *page)
 			 */
 			return;
 		}
-
-		atomic_dec(pamt_refcount);
 	}
 
 	/*
-- 
2.51.2


  parent reply	other threads:[~2025-11-21  0:51 UTC|newest]

Thread overview: 106+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-21  0:51 [PATCH v4 00/16] TDX: Enable Dynamic PAMT Rick Edgecombe
2025-11-21  0:51 ` [PATCH v4 01/16] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h> Rick Edgecombe
2025-11-25 22:30   ` Huang, Kai
2025-11-25 22:44     ` Huang, Kai
2025-11-26 23:15       ` Edgecombe, Rick P
2025-11-26 23:14     ` Edgecombe, Rick P
2025-11-21  0:51 ` [PATCH v4 02/16] x86/tdx: Add helpers to check return status codes Rick Edgecombe
2025-11-24  8:56   ` Binbin Wu
2025-11-24 19:31     ` Edgecombe, Rick P
2025-11-25 23:07   ` Huang, Kai
2025-11-26 23:26     ` Edgecombe, Rick P
2025-11-21  0:51 ` [PATCH v4 03/16] x86/virt/tdx: Simplify tdmr_get_pamt_sz() Rick Edgecombe
2025-11-24  9:26   ` Binbin Wu
2025-11-24 19:47     ` Edgecombe, Rick P
2025-11-25  1:27       ` Binbin Wu
2025-11-21  0:51 ` [PATCH v4 04/16] x86/virt/tdx: Allocate page bitmap for Dynamic PAMT Rick Edgecombe
2025-11-25  1:50   ` Binbin Wu
2025-11-26 17:56     ` Edgecombe, Rick P
2025-12-24  9:10   ` Xu Yilun
2026-01-05 22:06     ` Edgecombe, Rick P
2026-01-06  4:01       ` Xu Yilun
2026-01-06 17:00         ` Edgecombe, Rick P
2026-01-07  6:01           ` Xu Yilun
2026-01-07 14:41             ` Edgecombe, Rick P
2026-01-08 12:53               ` Xu Yilun
2026-01-08 16:52                 ` Edgecombe, Rick P
2026-01-09  2:18                   ` Xu Yilun
2026-01-09 16:05                     ` Edgecombe, Rick P
2026-01-12  0:24                       ` Xu Yilun
2025-11-21  0:51 ` [PATCH v4 05/16] x86/virt/tdx: Allocate reference counters for PAMT memory Rick Edgecombe
2025-11-21  0:51 ` [PATCH v4 06/16] x86/virt/tdx: Improve PAMT refcounts allocation for sparse memory Rick Edgecombe
2025-11-25  3:15   ` Binbin Wu
2025-11-26 20:47     ` Edgecombe, Rick P
2025-11-27 15:57       ` Kiryl Shutsemau
2025-12-01 22:14         ` Edgecombe, Rick P
2025-11-26 14:45   ` Nikolay Borisov
2025-11-26 20:47     ` Edgecombe, Rick P
2025-11-27  7:36       ` Nikolay Borisov
2025-12-11  0:07         ` Edgecombe, Rick P
2025-11-27 16:04       ` Kiryl Shutsemau
2025-11-21  0:51 ` [PATCH v4 07/16] x86/virt/tdx: Add tdx_alloc/free_page() helpers Rick Edgecombe
2025-11-25  8:09   ` Binbin Wu
2025-11-26 22:28     ` Edgecombe, Rick P
2026-01-29  1:19       ` Sean Christopherson
2026-01-29 17:18         ` Edgecombe, Rick P
2026-01-29 19:09           ` Sean Christopherson
2026-01-29 19:12             ` Edgecombe, Rick P
2025-11-26  1:21   ` Huang, Kai
2025-11-26 22:28     ` Edgecombe, Rick P
2025-11-27 12:29   ` Nikolay Borisov
2025-12-01 22:31     ` Edgecombe, Rick P
2025-11-27 16:11   ` Nikolay Borisov
2025-12-01 22:39     ` Edgecombe, Rick P
2025-12-02  7:38       ` Nikolay Borisov
2025-12-02 20:02         ` Edgecombe, Rick P
2025-12-03 13:46           ` Kiryl Shutsemau
2025-12-03 13:48             ` Nikolay Borisov
2025-12-03 15:41               ` Dave Hansen
2025-12-03 18:15                 ` Edgecombe, Rick P
2025-12-03 18:21                   ` Dave Hansen
2025-12-03 19:59                     ` Edgecombe, Rick P
2025-12-03 20:13                       ` Dave Hansen
2025-12-03 21:39                         ` Edgecombe, Rick P
2025-12-03 21:40                           ` Dave Hansen
2025-12-08  9:15   ` Yan Zhao
2025-12-08 20:27     ` Edgecombe, Rick P
2026-01-16 23:17   ` Sean Christopherson
2026-01-16 23:25     ` Edgecombe, Rick P
2026-01-16 23:40     ` Dave Hansen
2025-11-21  0:51 ` Rick Edgecombe [this message]
2025-11-21  0:51 ` [PATCH v4 09/16] KVM: TDX: Allocate PAMT memory for TD control structures Rick Edgecombe
2025-11-25  9:11   ` Binbin Wu
2025-11-21  0:51 ` [PATCH v4 10/16] KVM: TDX: Allocate PAMT memory for vCPU " Rick Edgecombe
2025-11-25  9:14   ` Binbin Wu
2025-11-21  0:51 ` [PATCH v4 11/16] KVM: TDX: Add x86 ops for external spt cache Rick Edgecombe
2026-01-17  0:53   ` Sean Christopherson
2026-01-19  2:31     ` Yan Zhao
2026-01-20  8:42       ` Huang, Kai
2026-01-20  9:18         ` Yan Zhao
2026-01-20 10:00           ` Huang, Kai
2026-01-20 19:53     ` Edgecombe, Rick P
2026-01-21 22:12       ` Sean Christopherson
2026-01-21 22:34         ` Edgecombe, Rick P
2025-11-21  0:51 ` [PATCH v4 12/16] x86/virt/tdx: Add helpers to allow for pre-allocating pages Rick Edgecombe
2025-11-26  3:40   ` Binbin Wu
2025-11-26  5:21     ` Binbin Wu
2025-11-26 22:33     ` Edgecombe, Rick P
2025-11-27  2:38       ` Binbin Wu
2026-01-20  7:10         ` Huang, Kai
2026-01-20  7:46           ` Yan Zhao
2026-01-20  8:01             ` Huang, Kai
2026-01-17  1:02   ` Sean Christopherson
2026-01-21  0:52   ` Sean Christopherson
2026-01-21  0:58     ` Edgecombe, Rick P
2025-11-21  0:51 ` [PATCH v4 13/16] KVM: TDX: Handle PAMT allocation in fault path Rick Edgecombe
2025-11-26  5:56   ` Binbin Wu
2025-11-26 22:33     ` Edgecombe, Rick P
2025-11-21  0:51 ` [PATCH v4 14/16] KVM: TDX: Reclaim PAMT memory Rick Edgecombe
2025-11-26  8:53   ` Binbin Wu
2025-11-26 22:58     ` Edgecombe, Rick P
2025-11-21  0:51 ` [PATCH v4 15/16] x86/virt/tdx: Enable Dynamic PAMT Rick Edgecombe
2025-11-21  0:51 ` [PATCH v4 16/16] Documentation/x86: Add documentation for TDX's " Rick Edgecombe
2025-11-26 10:33   ` Binbin Wu
2025-11-26 20:05     ` Edgecombe, Rick P
2025-11-24 20:18 ` [PATCH v4 00/16] TDX: Enable " Sagi Shahar
2025-11-25 20:19 ` Vishal Annapurve

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251121005125.417831-9-rick.p.edgecombe@intel.com \
    --to=rick.p.edgecombe@intel.com \
    --cc=binbin.wu@intel.com \
    --cc=bp@alien8.de \
    --cc=chao.gao@intel.com \
    --cc=dave.hansen@intel.com \
    --cc=isaku.yamahata@intel.com \
    --cc=kai.huang@intel.com \
    --cc=kas@kernel.org \
    --cc=kirill.shutemov@linux.intel.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-coco@lists.linux.dev \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mingo@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=seanjc@google.com \
    --cc=tglx@linutronix.de \
    --cc=vannapurve@google.com \
    --cc=x86@kernel.org \
    --cc=xiaoyao.li@intel.com \
    --cc=yan.y.zhao@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome