summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Ilvokhin <d@ilvokhin.com>2026-07-27 09:47:45 -0700
committerDmitry Ilvokhin <d@ilvokhin.com>2026-07-29 06:07:29 -0700
commitb65e9e77eb73462a2c122c8d53bad1e2d55f8122 (patch)
tree96bb44824befb27db380abe66eb5e4a1381ac55f
parent7feb6b7578860a23d3fdba5d2ad79cefc0ba79a3 (diff)
downloadlinux-contended-release-qspinlock.tar.gz
linux-contended-release-qspinlock.tar.bz2
linux-contended-release-qspinlock.zip
x86/paravirt: Trace contended_release on unlockcontended-release-qspinlock
On PARAVIRT_SPINLOCKS=y kernels queued_spin_unlock() is dispatched through a static_call(), and those kernels are quite common. Gating contended_release behind a static branch there would leave a NOP on the unlock path even while the tracepoint is disabled. Since the static_call() is already present, swap its target to a traced unlock while the tracepoint is enabled instead. When it is disabled the target is the plain unlock (an inline store on native), so the unlock path is unchanged and the tracepoint is truly zero-cost. Implement the arch_contended_release_trace_reg() and arch_contended_release_trace_unreg() hooks to update the queued_spin_unlock static_call() on the tracepoint's enable and disable transitions. 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. Signed-off-by: Dmitry Ilvokhin <d@ilvokhin.com>
-rw-r--r--arch/x86/include/asm/paravirt-spinlock.h2
-rw-r--r--arch/x86/kernel/paravirt-spinlocks.c53
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)