locking/ww_mutex: Support Clang's context analysis

Add support for Clang's context analysis for ww_mutex.

The programming model for ww_mutex is subtly more complex than other
locking primitives when using ww_acquire_ctx. Encoding the respective
pre-conditions for ww_mutex lock/unlock based on ww_acquire_ctx state
using Clang's context analysis makes incorrect use of the API harder.

Signed-off-by: Marco Elver <elver@google.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20251219154418.3592607-21-elver@google.com
This commit is contained in:
Marco Elver 2025-12-19 16:40:09 +01:00 committed by Peter Zijlstra
parent d3febf16de
commit 47907461e4
3 changed files with 87 additions and 7 deletions

View file

@ -80,7 +80,8 @@ Supported Kernel Primitives
Currently the following synchronization primitives are supported:
`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`, `local_lock_t`.
`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`, `local_lock_t`,
`ww_mutex`.
For context locks with an initialization function (e.g., `spin_lock_init()`),
calling this function before initializing any guarded members or globals

View file

@ -44,7 +44,7 @@ struct ww_class {
unsigned int is_wait_die;
};
struct ww_mutex {
context_lock_struct(ww_mutex) {
struct WW_MUTEX_BASE base;
struct ww_acquire_ctx *ctx;
#ifdef DEBUG_WW_MUTEXES
@ -52,7 +52,7 @@ struct ww_mutex {
#endif
};
struct ww_acquire_ctx {
context_lock_struct(ww_acquire_ctx) {
struct task_struct *task;
unsigned long stamp;
unsigned int acquired;
@ -107,6 +107,7 @@ struct ww_acquire_ctx {
*/
static inline void ww_mutex_init(struct ww_mutex *lock,
struct ww_class *ww_class)
__assumes_ctx_lock(lock)
{
ww_mutex_base_init(&lock->base, ww_class->mutex_name, &ww_class->mutex_key);
lock->ctx = NULL;
@ -141,6 +142,7 @@ static inline void ww_mutex_init(struct ww_mutex *lock,
*/
static inline void ww_acquire_init(struct ww_acquire_ctx *ctx,
struct ww_class *ww_class)
__acquires(ctx) __no_context_analysis
{
ctx->task = current;
ctx->stamp = atomic_long_inc_return_relaxed(&ww_class->stamp);
@ -179,6 +181,7 @@ static inline void ww_acquire_init(struct ww_acquire_ctx *ctx,
* data structures.
*/
static inline void ww_acquire_done(struct ww_acquire_ctx *ctx)
__releases(ctx) __acquires_shared(ctx) __no_context_analysis
{
#ifdef DEBUG_WW_MUTEXES
lockdep_assert_held(ctx);
@ -196,6 +199,7 @@ static inline void ww_acquire_done(struct ww_acquire_ctx *ctx)
* mutexes have been released with ww_mutex_unlock.
*/
static inline void ww_acquire_fini(struct ww_acquire_ctx *ctx)
__releases_shared(ctx) __no_context_analysis
{
#ifdef CONFIG_DEBUG_LOCK_ALLOC
mutex_release(&ctx->first_lock_dep_map, _THIS_IP_);
@ -245,7 +249,8 @@ static inline void ww_acquire_fini(struct ww_acquire_ctx *ctx)
*
* A mutex acquired with this function must be released with ww_mutex_unlock.
*/
extern int /* __must_check */ ww_mutex_lock(struct ww_mutex *lock, struct ww_acquire_ctx *ctx);
extern int /* __must_check */ ww_mutex_lock(struct ww_mutex *lock, struct ww_acquire_ctx *ctx)
__cond_acquires(0, lock) __must_hold(ctx);
/**
* ww_mutex_lock_interruptible - acquire the w/w mutex, interruptible
@ -278,7 +283,8 @@ extern int /* __must_check */ ww_mutex_lock(struct ww_mutex *lock, struct ww_acq
* A mutex acquired with this function must be released with ww_mutex_unlock.
*/
extern int __must_check ww_mutex_lock_interruptible(struct ww_mutex *lock,
struct ww_acquire_ctx *ctx);
struct ww_acquire_ctx *ctx)
__cond_acquires(0, lock) __must_hold(ctx);
/**
* ww_mutex_lock_slow - slowpath acquiring of the w/w mutex
@ -305,6 +311,7 @@ extern int __must_check ww_mutex_lock_interruptible(struct ww_mutex *lock,
*/
static inline void
ww_mutex_lock_slow(struct ww_mutex *lock, struct ww_acquire_ctx *ctx)
__acquires(lock) __must_hold(ctx) __no_context_analysis
{
int ret;
#ifdef DEBUG_WW_MUTEXES
@ -342,6 +349,7 @@ ww_mutex_lock_slow(struct ww_mutex *lock, struct ww_acquire_ctx *ctx)
static inline int __must_check
ww_mutex_lock_slow_interruptible(struct ww_mutex *lock,
struct ww_acquire_ctx *ctx)
__cond_acquires(0, lock) __must_hold(ctx)
{
#ifdef DEBUG_WW_MUTEXES
DEBUG_LOCKS_WARN_ON(!ctx->contending_lock);
@ -349,10 +357,11 @@ ww_mutex_lock_slow_interruptible(struct ww_mutex *lock,
return ww_mutex_lock_interruptible(lock, ctx);
}
extern void ww_mutex_unlock(struct ww_mutex *lock);
extern void ww_mutex_unlock(struct ww_mutex *lock) __releases(lock);
extern int __must_check ww_mutex_trylock(struct ww_mutex *lock,
struct ww_acquire_ctx *ctx);
struct ww_acquire_ctx *ctx)
__cond_acquires(true, lock) __must_hold(ctx);
/***
* ww_mutex_destroy - mark a w/w mutex unusable
@ -363,6 +372,7 @@ extern int __must_check ww_mutex_trylock(struct ww_mutex *lock,
* this function is called.
*/
static inline void ww_mutex_destroy(struct ww_mutex *lock)
__must_not_hold(lock)
{
#ifndef CONFIG_PREEMPT_RT
mutex_destroy(&lock->base);

View file

@ -14,6 +14,7 @@
#include <linux/seqlock.h>
#include <linux/spinlock.h>
#include <linux/srcu.h>
#include <linux/ww_mutex.h>
/*
* Test that helper macros work as expected.
@ -531,3 +532,71 @@ static void __used test_local_trylock(void)
local_unlock(&test_local_trylock_data.lock);
}
}
static DEFINE_WD_CLASS(ww_class);
struct test_ww_mutex_data {
struct ww_mutex mtx;
int counter __guarded_by(&mtx);
};
static void __used test_ww_mutex_init(struct test_ww_mutex_data *d)
{
ww_mutex_init(&d->mtx, &ww_class);
d->counter = 0;
}
static void __used test_ww_mutex_lock_noctx(struct test_ww_mutex_data *d)
{
if (!ww_mutex_lock(&d->mtx, NULL)) {
d->counter++;
ww_mutex_unlock(&d->mtx);
}
if (!ww_mutex_lock_interruptible(&d->mtx, NULL)) {
d->counter++;
ww_mutex_unlock(&d->mtx);
}
if (ww_mutex_trylock(&d->mtx, NULL)) {
d->counter++;
ww_mutex_unlock(&d->mtx);
}
ww_mutex_lock_slow(&d->mtx, NULL);
d->counter++;
ww_mutex_unlock(&d->mtx);
ww_mutex_destroy(&d->mtx);
}
static void __used test_ww_mutex_lock_ctx(struct test_ww_mutex_data *d)
{
struct ww_acquire_ctx ctx;
ww_acquire_init(&ctx, &ww_class);
if (!ww_mutex_lock(&d->mtx, &ctx)) {
d->counter++;
ww_mutex_unlock(&d->mtx);
}
if (!ww_mutex_lock_interruptible(&d->mtx, &ctx)) {
d->counter++;
ww_mutex_unlock(&d->mtx);
}
if (ww_mutex_trylock(&d->mtx, &ctx)) {
d->counter++;
ww_mutex_unlock(&d->mtx);
}
ww_mutex_lock_slow(&d->mtx, &ctx);
d->counter++;
ww_mutex_unlock(&d->mtx);
ww_acquire_done(&ctx);
ww_acquire_fini(&ctx);
ww_mutex_destroy(&d->mtx);
}