[PATCH 5/5] x86/paravirt: Trace contended_release on unlock
Dmitry Ilvokhin <[email protected]> Tue, 4 Aug 2026 07:15:45 +0000
| Newsgroups | gmane.comp.emulators.xen.devel,gmane.linux.kernel,gmane.linux.ports.mips,gmane.comp.emulators.kvm.devel,gmane.linux.kernel.cross-arch |
|---|---|
| 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