/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _LINUX_SIX_H #define _LINUX_SIX_H /* * Shared/intent/exclusive locks: sleepable read/write locks, much like rw * semaphores, except with a third intermediate state, intent. Basic operations * are: * * six_lock_read(&foo->lock); * six_unlock_read(&foo->lock); * * six_lock_intent(&foo->lock); * six_unlock_intent(&foo->lock); * * six_lock_write(&foo->lock); * six_unlock_write(&foo->lock); * * Intent locks block other intent locks, but do not block read locks, and you * must have an intent lock held before taking a write lock, like so: * * six_lock_intent(&foo->lock); * six_lock_write(&foo->lock); * six_unlock_write(&foo->lock); * six_unlock_intent(&foo->lock); * * Other operations: * * six_trylock_read() * six_trylock_intent() * six_trylock_write() * * six_lock_downgrade(): convert from intent to read * six_lock_tryupgrade(): attempt to convert from read to intent * * Locks also embed a sequence number, which is incremented when the lock is * locked or unlocked for write. The current sequence number can be grabbed * while a lock is held from lock->state.seq; then, if you drop the lock you can * use six_relock_(read|intent_write)(lock, seq) to attempt to retake the lock * iff it hasn't been locked for write in the meantime. * * There are also operations that take the lock type as a parameter, where the * type is one of SIX_LOCK_read, SIX_LOCK_intent, or SIX_LOCK_write: * * six_lock_type(lock, type) * six_unlock_type(lock, type) * six_relock(lock, type, seq) * six_trylock_type(lock, type) * six_trylock_convert(lock, from, to) * * A lock may be held multiple times by the same thread (for read or intent, * not write). However, the six locks code does _not_ implement the actual * recursive checks itself though - rather, if your code (e.g. btree iterator * code) knows that the current thread already has a lock held, and for the * correct type, six_lock_increment() may be used to bump up the counter for * that type - the only effect is that one more call to unlock will be required * before the lock is unlocked. */ #include #include #include #ifdef CONFIG_SIX_LOCK_SPIN_ON_OWNER #include #endif #define SIX_LOCK_SEPARATE_LOCKFNS union six_lock_state { struct { atomic64_t counter; }; struct { u64 v; }; struct { /* for waitlist_bitnr() */ unsigned long l; }; struct { unsigned read_lock:27; unsigned write_locking:1; unsigned intent_lock:1; unsigned waiters:3; /* * seq works much like in seqlocks: it's incremented every time * we lock and unlock for write. * * If it's odd write lock is held, even unlocked. * * Thus readers can unlock, and then lock again later iff it * hasn't been modified in the meantime. */ u32 seq; }; }; enum six_lock_type { SIX_LOCK_read, SIX_LOCK_intent, SIX_LOCK_write, }; struct six_lock { union six_lock_state state; unsigned intent_lock_recurse; struct task_struct *owner; #ifdef CONFIG_SIX_LOCK_SPIN_ON_OWNER struct optimistic_spin_queue osq; #endif unsigned __percpu *readers; raw_spinlock_t wait_lock; struct list_head wait_list[2]; #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif }; typedef int (*six_lock_should_sleep_fn)(struct six_lock *lock, void *); static __always_inline void __six_lock_init(struct six_lock *lock, const char *name, struct lock_class_key *key) { atomic64_set(&lock->state.counter, 0); raw_spin_lock_init(&lock->wait_lock); INIT_LIST_HEAD(&lock->wait_list[SIX_LOCK_read]); INIT_LIST_HEAD(&lock->wait_list[SIX_LOCK_intent]); #ifdef CONFIG_DEBUG_LOCK_ALLOC debug_check_no_locks_freed((void *) lock, sizeof(*lock)); lockdep_init_map(&lock->dep_map, name, key, 0); #endif } #define six_lock_init(lock) \ do { \ static struct lock_class_key __key; \ \ __six_lock_init((lock), #lock, &__key); \ } while (0) #define __SIX_VAL(field, _v) (((union six_lock_state) { .field = _v }).v) #define __SIX_LOCK(type) \ bool six_trylock_##type(struct six_lock *); \ bool six_relock_##type(struct six_lock *, u32); \ int six_lock_##type(struct six_lock *, six_lock_should_sleep_fn, void *);\ void six_unlock_##type(struct six_lock *); __SIX_LOCK(read) __SIX_LOCK(intent) __SIX_LOCK(write) #undef __SIX_LOCK #define SIX_LOCK_DISPATCH(type, fn, ...) \ switch (type) { \ case SIX_LOCK_read: \ return fn##_read(__VA_ARGS__); \ case SIX_LOCK_intent: \ return fn##_intent(__VA_ARGS__); \ case SIX_LOCK_write: \ return fn##_write(__VA_ARGS__); \ default: \ BUG(); \ } static inline bool six_trylock_type(struct six_lock *lock, enum six_lock_type type) { SIX_LOCK_DISPATCH(type, six_trylock, lock); } static inline bool six_relock_type(struct six_lock *lock, enum six_lock_type type, unsigned seq) { SIX_LOCK_DISPATCH(type, six_relock, lock, seq); } static inline int six_lock_type(struct six_lock *lock, enum six_lock_type type, six_lock_should_sleep_fn should_sleep_fn, void *p) { SIX_LOCK_DISPATCH(type, six_lock, lock, should_sleep_fn, p); } static inline void six_unlock_type(struct six_lock *lock, enum six_lock_type type) { SIX_LOCK_DISPATCH(type, six_unlock, lock); } void six_lock_downgrade(struct six_lock *); bool six_lock_tryupgrade(struct six_lock *); bool six_trylock_convert(struct six_lock *, enum six_lock_type, enum six_lock_type); void six_lock_increment(struct six_lock *, enum six_lock_type); void six_lock_wakeup_all(struct six_lock *); void six_lock_pcpu_free_rcu(struct six_lock *); void six_lock_pcpu_free(struct six_lock *); void six_lock_pcpu_alloc(struct six_lock *); struct six_lock_count { unsigned read; unsigned intent; }; struct six_lock_count six_lock_counts(struct six_lock *); #endif /* _LINUX_SIX_H */