[PATCH 5/5] x86/paravirt: Trace contended_release on unlock

Dmitry Ilvokhin <[email protected]>
Newsgroups org.xenproject.lists.xen-devel,dev.linux.lists.virtualization,org.kernel.vger.kvm,org.kernel.vger.linux-arch,org.kernel.vger.linux-hyperv,org.kernel.vger.linux-kernel,org.kernel.vger.linux-mips,org.kernel.vger.linux-trace-kernel
Message-ID <17fa67f9fa4cf93f1150725e89f5f916e41a9b6f.1785778551.git.d@ilvokhin.com>
On PARAVIRT_SPINLOCKS=y kernels queued_spin_unlock() is dispatched
through a static_call(). Those PARAVIRT_SPINLOCKS=y kernels are quite
popular. Gating contended_release behind a static branch would leave a
NOP on the unlock hot path even, when the tracepoint is disabled.

Since the static_call() is already present, swap its target to a traced
unlock, when the tracepoint is enabled instead. When contended_release
tracepoint is disabled the target is the plain unlock (an inline store
on native x86_64), so the unlock path is unchanged and the tracepoint is
truly zero-cost.

Provide two traced variants, native_queued_spin_unlock_traced() and
pv_queued_spin_unlock_traced(), so each tail-calls its own base unlock
directly rather than recursing through the now-traced static_call().

Teach pv_is_native_spin_unlock() that the traced native variant still
counts as native.

Only PARAVIRT_SPINLOCKS=y is affected. PARAVIRT_SPINLOCKS=n keeps the
generic static-branch path.

Suggested-by: Peter Zijlstra <[email protected]>
Signed-off-by: Dmitry Ilvokhin <[email protected]>
---
 arch/x86/include/asm/paravirt-spinlock.h |  2 +
 arch/x86/kernel/paravirt-spinlocks.c     | 53 +++++++++++++++++++++++-
 2 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/arch/x86/include/asm/paravirt-spinlock.h b/arch/x86/include/asm/paravirt-spinlock.h
index ff735830de4a..302bc2ba3a75 100644
--- a/arch/x86/include/asm/paravirt-spinlock.h
+++ b/arch/x86/include/asm/paravirt-spinlock.h
@@ -99,6 +99,8 @@ bool __raw_callee_save___native_vcpu_is_preempted(long cpu);
 
 void __init native_pv_lock_init(void);
 __visible void __native_queued_spin_unlock(struct qspinlock *lock);
+__visible void native_queued_spin_unlock_traced(struct qspinlock *lock);
+__visible void pv_queued_spin_unlock_traced(struct qspinlock *lock);
 bool pv_is_native_spin_unlock(void);
 __visible bool __native_vcpu_is_preempted(long cpu);
 bool pv_is_native_vcpu_is_preempted(void);
diff --git a/arch/x86/kernel/paravirt-spinlocks.c b/arch/x86/kernel/paravirt-spinlocks.c
index ddc19dc28ba1..ca12b3655307 100644
--- a/arch/x86/kernel/paravirt-spinlocks.c
+++ b/arch/x86/kernel/paravirt-spinlocks.c
@@ -7,6 +7,7 @@
 #include <linux/spinlock.h>
 #include <linux/export.h>
 #include <linux/jump_label.h>
+#include <trace/events/lock.h>
 
 DEFINE_STATIC_KEY_FALSE(virt_spin_lock_key);
 
@@ -30,10 +31,58 @@ EXPORT_STATIC_CALL_TRAMP(queued_spin_lock_slowpath);
 DEFINE_STATIC_CALL(queued_spin_unlock, __raw_callee_save___native_queued_spin_unlock);
 EXPORT_STATIC_CALL_TRAMP(queued_spin_unlock);
 
+/*
+ * Traced unlock variants, swapped in via static_call while the
+ * contended_release tracepoint is enabled. Two of them, so each tail calls its
+ * own base directly.
+ */
+__visible void native_queued_spin_unlock_traced(struct qspinlock *lock)
+{
+	if (queued_spin_is_contended(lock))
+		trace_call__contended_release(lock);
+	native_queued_spin_unlock(lock);
+}
+PV_CALLEE_SAVE_REGS_THUNK(native_queued_spin_unlock_traced);
+
+__visible void pv_queued_spin_unlock_traced(struct qspinlock *lock)
+{
+	if (queued_spin_is_contended(lock))
+		trace_call__contended_release(lock);
+	__raw_callee_save___pv_queued_spin_unlock(lock);
+}
+PV_CALLEE_SAVE_REGS_THUNK(pv_queued_spin_unlock_traced);
+
 bool pv_is_native_spin_unlock(void)
 {
-	return static_call_query(queued_spin_unlock) ==
-		__raw_callee_save___native_queued_spin_unlock;
+	void *unlock = static_call_query(queued_spin_unlock);
+
+	return unlock == __raw_callee_save___native_queued_spin_unlock ||
+	       unlock == __raw_callee_save_native_queued_spin_unlock_traced;
+}
+
+int arch_contended_release_trace_reg(void)
+{
+	void *cur = static_call_query(queued_spin_unlock);
+
+	if (cur == __raw_callee_save___native_queued_spin_unlock)
+		static_call_update(queued_spin_unlock,
+				   __raw_callee_save_native_queued_spin_unlock_traced);
+	else if (cur == __raw_callee_save___pv_queued_spin_unlock)
+		static_call_update(queued_spin_unlock,
+				   __raw_callee_save_pv_queued_spin_unlock_traced);
+	return 0;
+}
+
+void arch_contended_release_trace_unreg(void)
+{
+	void *cur = static_call_query(queued_spin_unlock);
+
+	if (cur == __raw_callee_save_native_queued_spin_unlock_traced)
+		static_call_update(queued_spin_unlock,
+				   __raw_callee_save___native_queued_spin_unlock);
+	else if (cur == __raw_callee_save_pv_queued_spin_unlock_traced)
+		static_call_update(queued_spin_unlock,
+				   __raw_callee_save___pv_queued_spin_unlock);
 }
 
 __visible bool __native_vcpu_is_preempted(long cpu)
-- 
2.53.0-Meta
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.