@@ -1050,6 +1050,10 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
{
union tdx_exit_reason exit_reason = to_tdx(vcpu)->exit_reason;
+ /* See the comment of tdh_sept_seamcall(). */
+ if (unlikely(exit_reason.full == (TDX_OPERAND_BUSY | TDX_OPERAND_ID_SEPT)))
+ return 1;
+
if (unlikely(exit_reason.non_recoverable || exit_reason.error)) {
if (exit_reason.basic == EXIT_REASON_TRIPLE_FAULT)
return tdx_handle_triple_fault(vcpu);
@@ -18,7 +18,35 @@
void pr_tdx_error(u64 op, u64 error_code, const struct tdx_module_output *out);
-#define TDX_ERROR_SEPT_BUSY (TDX_OPERAND_BUSY | TDX_OPERAND_ID_SEPT)
+/*
+ * TDX module acquires its internal lock for resources. It doesn't spin to get
+ * locks because of its restrictions of allowed execution time. Instead, it
+ * returns TDX_OPERAND_BUSY with an operand id.
+ *
+ * Multiple VCPUs can operate on SEPT. Also with zero-step attack mitigation,
+ * TDH.VP.ENTER may rarely acquire SEPT lock and release it when zero-step
+ * attack is suspected. It results in TDX_OPERAND_BUSY | TDX_OPERAND_ID_SEPT
+ * with TDH.MEM.* operation. Note: TDH.MEM.TRACK is an exception.
+ *
+ * Because TDP MMU uses read lock for scalability, spin lock around SEAMCALL
+ * spoils TDP MMU effort. Retry several times with the assumption that SEPT
+ * lock contention is rare. But don't loop forever to avoid lockup. Let TDP
+ * MMU retry.
+ */
+#define TDX_ERROR_SEPT_BUSY (TDX_OPERAND_BUSY | TDX_OPERAND_ID_SEPT)
+
+static inline u64 seamcall_sept(u64 op, u64 rcx, u64 rdx, u64 r8, u64 r9,
+ struct tdx_module_output *out)
+{
+#define SEAMCALL_RETRY_MAX 16
+ int retry = SEAMCALL_RETRY_MAX;
+ u64 ret;
+
+ do {
+ ret = __seamcall(op, rcx, rdx, r8, r9, out);
+ } while (ret == TDX_ERROR_SEPT_BUSY && retry-- > 0);
+ return ret;
+}
static inline u64 tdh_mng_addcx(hpa_t tdr, hpa_t addr)
{
@@ -30,14 +58,14 @@ static inline u64 tdh_mem_page_add(hpa_t tdr, gpa_t gpa, hpa_t hpa, hpa_t source
struct tdx_module_output *out)
{
clflush_cache_range(__va(hpa), PAGE_SIZE);
- return __seamcall(TDH_MEM_PAGE_ADD, gpa, tdr, hpa, source, out);
+ return seamcall_sept(TDH_MEM_PAGE_ADD, gpa, tdr, hpa, source, out);
}
static inline u64 tdh_mem_sept_add(hpa_t tdr, gpa_t gpa, int level, hpa_t page,
struct tdx_module_output *out)
{
clflush_cache_range(__va(page), PAGE_SIZE);
- return __seamcall(TDH_MEM_SEPT_ADD, gpa | level, tdr, page, 0, out);
+ return seamcall_sept(TDH_MEM_SEPT_ADD, gpa | level, tdr, page, 0, out);
}
static inline u64 tdh_mem_sept_remove(hpa_t tdr, gpa_t gpa, int level,
@@ -63,13 +91,13 @@ static inline u64 tdh_mem_page_aug(hpa_t tdr, gpa_t gpa, hpa_t hpa,
struct tdx_module_output *out)
{
clflush_cache_range(__va(hpa), PAGE_SIZE);
- return __seamcall(TDH_MEM_PAGE_AUG, gpa, tdr, hpa, 0, out);
+ return seamcall_sept(TDH_MEM_PAGE_AUG, gpa, tdr, hpa, 0, out);
}
static inline u64 tdh_mem_range_block(hpa_t tdr, gpa_t gpa, int level,
struct tdx_module_output *out)
{
- return __seamcall(TDH_MEM_RANGE_BLOCK, gpa | level, tdr, 0, 0, out);
+ return seamcall_sept(TDH_MEM_RANGE_BLOCK, gpa | level, tdr, 0, 0, out);
}
static inline u64 tdh_mng_key_config(hpa_t tdr)
@@ -151,7 +179,7 @@ static inline u64 tdh_phymem_page_reclaim(hpa_t page,
static inline u64 tdh_mem_page_remove(hpa_t tdr, gpa_t gpa, int level,
struct tdx_module_output *out)
{
- return __seamcall(TDH_MEM_PAGE_REMOVE, gpa | level, tdr, 0, 0, out);
+ return seamcall_sept(TDH_MEM_PAGE_REMOVE, gpa | level, tdr, 0, 0, out);
}
static inline u64 tdh_sys_lp_shutdown(void)
@@ -167,7 +195,7 @@ static inline u64 tdh_mem_track(hpa_t tdr)
static inline u64 tdh_mem_range_unblock(hpa_t tdr, gpa_t gpa, int level,
struct tdx_module_output *out)
{
- return __seamcall(TDH_MEM_RANGE_UNBLOCK, gpa | level, tdr, 0, 0, out);
+ return seamcall_sept(TDH_MEM_RANGE_UNBLOCK, gpa | level, tdr, 0, 0, out);
}
static inline u64 tdh_phymem_cache_wb(bool resume)