root/drivers/tty/tty_ldsem.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. __init_ldsem
  2. __ldsem_wake_readers
  3. writer_trylock
  4. __ldsem_wake_writer
  5. __ldsem_wake
  6. ldsem_wake
  7. down_read_failed
  8. down_write_failed
  9. __ldsem_down_read_nested
  10. __ldsem_down_write_nested
  11. ldsem_down_read
  12. ldsem_down_read_trylock
  13. ldsem_down_write
  14. ldsem_down_write_trylock
  15. ldsem_up_read
  16. ldsem_up_write
  17. ldsem_down_read_nested
  18. ldsem_down_write_nested

   1 // SPDX-License-Identifier: GPL-2.0
   2 /*
   3  * Ldisc rw semaphore
   4  *
   5  * The ldisc semaphore is semantically a rw_semaphore but which enforces
   6  * an alternate policy, namely:
   7  *   1) Supports lock wait timeouts
   8  *   2) Write waiter has priority
   9  *   3) Downgrading is not supported
  10  *
  11  * Implementation notes:
  12  *   1) Upper half of semaphore count is a wait count (differs from rwsem
  13  *      in that rwsem normalizes the upper half to the wait bias)
  14  *   2) Lacks overflow checking
  15  *
  16  * The generic counting was copied and modified from include/asm-generic/rwsem.h
  17  * by Paul Mackerras <paulus@samba.org>.
  18  *
  19  * The scheduling policy was copied and modified from lib/rwsem.c
  20  * Written by David Howells (dhowells@redhat.com).
  21  *
  22  * This implementation incorporates the write lock stealing work of
  23  * Michel Lespinasse <walken@google.com>.
  24  *
  25  * Copyright (C) 2013 Peter Hurley <peter@hurleysoftware.com>
  26  */
  27 
  28 #include <linux/list.h>
  29 #include <linux/spinlock.h>
  30 #include <linux/atomic.h>
  31 #include <linux/tty.h>
  32 #include <linux/sched.h>
  33 #include <linux/sched/debug.h>
  34 #include <linux/sched/task.h>
  35 
  36 
  37 #if BITS_PER_LONG == 64
  38 # define LDSEM_ACTIVE_MASK      0xffffffffL
  39 #else
  40 # define LDSEM_ACTIVE_MASK      0x0000ffffL
  41 #endif
  42 
  43 #define LDSEM_UNLOCKED          0L
  44 #define LDSEM_ACTIVE_BIAS       1L
  45 #define LDSEM_WAIT_BIAS         (-LDSEM_ACTIVE_MASK-1)
  46 #define LDSEM_READ_BIAS         LDSEM_ACTIVE_BIAS
  47 #define LDSEM_WRITE_BIAS        (LDSEM_WAIT_BIAS + LDSEM_ACTIVE_BIAS)
  48 
  49 struct ldsem_waiter {
  50         struct list_head list;
  51         struct task_struct *task;
  52 };
  53 
  54 /*
  55  * Initialize an ldsem:
  56  */
  57 void __init_ldsem(struct ld_semaphore *sem, const char *name,
  58                   struct lock_class_key *key)
  59 {
  60 #ifdef CONFIG_DEBUG_LOCK_ALLOC
  61         /*
  62          * Make sure we are not reinitializing a held semaphore:
  63          */
  64         debug_check_no_locks_freed((void *)sem, sizeof(*sem));
  65         lockdep_init_map(&sem->dep_map, name, key, 0);
  66 #endif
  67         atomic_long_set(&sem->count, LDSEM_UNLOCKED);
  68         sem->wait_readers = 0;
  69         raw_spin_lock_init(&sem->wait_lock);
  70         INIT_LIST_HEAD(&sem->read_wait);
  71         INIT_LIST_HEAD(&sem->write_wait);
  72 }
  73 
  74 static void __ldsem_wake_readers(struct ld_semaphore *sem)
  75 {
  76         struct ldsem_waiter *waiter, *next;
  77         struct task_struct *tsk;
  78         long adjust, count;
  79 
  80         /*
  81          * Try to grant read locks to all readers on the read wait list.
  82          * Note the 'active part' of the count is incremented by
  83          * the number of readers before waking any processes up.
  84          */
  85         adjust = sem->wait_readers * (LDSEM_ACTIVE_BIAS - LDSEM_WAIT_BIAS);
  86         count = atomic_long_add_return(adjust, &sem->count);
  87         do {
  88                 if (count > 0)
  89                         break;
  90                 if (atomic_long_try_cmpxchg(&sem->count, &count, count - adjust))
  91                         return;
  92         } while (1);
  93 
  94         list_for_each_entry_safe(waiter, next, &sem->read_wait, list) {
  95                 tsk = waiter->task;
  96                 smp_store_release(&waiter->task, NULL);
  97                 wake_up_process(tsk);
  98                 put_task_struct(tsk);
  99         }
 100         INIT_LIST_HEAD(&sem->read_wait);
 101         sem->wait_readers = 0;
 102 }
 103 
 104 static inline int writer_trylock(struct ld_semaphore *sem)
 105 {
 106         /*
 107          * Only wake this writer if the active part of the count can be
 108          * transitioned from 0 -> 1
 109          */
 110         long count = atomic_long_add_return(LDSEM_ACTIVE_BIAS, &sem->count);
 111         do {
 112                 if ((count & LDSEM_ACTIVE_MASK) == LDSEM_ACTIVE_BIAS)
 113                         return 1;
 114                 if (atomic_long_try_cmpxchg(&sem->count, &count, count - LDSEM_ACTIVE_BIAS))
 115                         return 0;
 116         } while (1);
 117 }
 118 
 119 static void __ldsem_wake_writer(struct ld_semaphore *sem)
 120 {
 121         struct ldsem_waiter *waiter;
 122 
 123         waiter = list_entry(sem->write_wait.next, struct ldsem_waiter, list);
 124         wake_up_process(waiter->task);
 125 }
 126 
 127 /*
 128  * handle the lock release when processes blocked on it that can now run
 129  * - if we come here from up_xxxx(), then:
 130  *   - the 'active part' of count (&0x0000ffff) reached 0 (but may have changed)
 131  *   - the 'waiting part' of count (&0xffff0000) is -ve (and will still be so)
 132  * - the spinlock must be held by the caller
 133  * - woken process blocks are discarded from the list after having task zeroed
 134  */
 135 static void __ldsem_wake(struct ld_semaphore *sem)
 136 {
 137         if (!list_empty(&sem->write_wait))
 138                 __ldsem_wake_writer(sem);
 139         else if (!list_empty(&sem->read_wait))
 140                 __ldsem_wake_readers(sem);
 141 }
 142 
 143 static void ldsem_wake(struct ld_semaphore *sem)
 144 {
 145         unsigned long flags;
 146 
 147         raw_spin_lock_irqsave(&sem->wait_lock, flags);
 148         __ldsem_wake(sem);
 149         raw_spin_unlock_irqrestore(&sem->wait_lock, flags);
 150 }
 151 
 152 /*
 153  * wait for the read lock to be granted
 154  */
 155 static struct ld_semaphore __sched *
 156 down_read_failed(struct ld_semaphore *sem, long count, long timeout)
 157 {
 158         struct ldsem_waiter waiter;
 159         long adjust = -LDSEM_ACTIVE_BIAS + LDSEM_WAIT_BIAS;
 160 
 161         /* set up my own style of waitqueue */
 162         raw_spin_lock_irq(&sem->wait_lock);
 163 
 164         /*
 165          * Try to reverse the lock attempt but if the count has changed
 166          * so that reversing fails, check if there are are no waiters,
 167          * and early-out if not
 168          */
 169         do {
 170                 if (atomic_long_try_cmpxchg(&sem->count, &count, count + adjust)) {
 171                         count += adjust;
 172                         break;
 173                 }
 174                 if (count > 0) {
 175                         raw_spin_unlock_irq(&sem->wait_lock);
 176                         return sem;
 177                 }
 178         } while (1);
 179 
 180         list_add_tail(&waiter.list, &sem->read_wait);
 181         sem->wait_readers++;
 182 
 183         waiter.task = current;
 184         get_task_struct(current);
 185 
 186         /* if there are no active locks, wake the new lock owner(s) */
 187         if ((count & LDSEM_ACTIVE_MASK) == 0)
 188                 __ldsem_wake(sem);
 189 
 190         raw_spin_unlock_irq(&sem->wait_lock);
 191 
 192         /* wait to be given the lock */
 193         for (;;) {
 194                 set_current_state(TASK_UNINTERRUPTIBLE);
 195 
 196                 if (!smp_load_acquire(&waiter.task))
 197                         break;
 198                 if (!timeout)
 199                         break;
 200                 timeout = schedule_timeout(timeout);
 201         }
 202 
 203         __set_current_state(TASK_RUNNING);
 204 
 205         if (!timeout) {
 206                 /*
 207                  * Lock timed out but check if this task was just
 208                  * granted lock ownership - if so, pretend there
 209                  * was no timeout; otherwise, cleanup lock wait.
 210                  */
 211                 raw_spin_lock_irq(&sem->wait_lock);
 212                 if (waiter.task) {
 213                         atomic_long_add_return(-LDSEM_WAIT_BIAS, &sem->count);
 214                         sem->wait_readers--;
 215                         list_del(&waiter.list);
 216                         raw_spin_unlock_irq(&sem->wait_lock);
 217                         put_task_struct(waiter.task);
 218                         return NULL;
 219                 }
 220                 raw_spin_unlock_irq(&sem->wait_lock);
 221         }
 222 
 223         return sem;
 224 }
 225 
 226 /*
 227  * wait for the write lock to be granted
 228  */
 229 static struct ld_semaphore __sched *
 230 down_write_failed(struct ld_semaphore *sem, long count, long timeout)
 231 {
 232         struct ldsem_waiter waiter;
 233         long adjust = -LDSEM_ACTIVE_BIAS;
 234         int locked = 0;
 235 
 236         /* set up my own style of waitqueue */
 237         raw_spin_lock_irq(&sem->wait_lock);
 238 
 239         /*
 240          * Try to reverse the lock attempt but if the count has changed
 241          * so that reversing fails, check if the lock is now owned,
 242          * and early-out if so.
 243          */
 244         do {
 245                 if (atomic_long_try_cmpxchg(&sem->count, &count, count + adjust))
 246                         break;
 247                 if ((count & LDSEM_ACTIVE_MASK) == LDSEM_ACTIVE_BIAS) {
 248                         raw_spin_unlock_irq(&sem->wait_lock);
 249                         return sem;
 250                 }
 251         } while (1);
 252 
 253         list_add_tail(&waiter.list, &sem->write_wait);
 254 
 255         waiter.task = current;
 256 
 257         set_current_state(TASK_UNINTERRUPTIBLE);
 258         for (;;) {
 259                 if (!timeout)
 260                         break;
 261                 raw_spin_unlock_irq(&sem->wait_lock);
 262                 timeout = schedule_timeout(timeout);
 263                 raw_spin_lock_irq(&sem->wait_lock);
 264                 set_current_state(TASK_UNINTERRUPTIBLE);
 265                 locked = writer_trylock(sem);
 266                 if (locked)
 267                         break;
 268         }
 269 
 270         if (!locked)
 271                 atomic_long_add_return(-LDSEM_WAIT_BIAS, &sem->count);
 272         list_del(&waiter.list);
 273 
 274         /*
 275          * In case of timeout, wake up every reader who gave the right of way
 276          * to writer. Prevent separation readers into two groups:
 277          * one that helds semaphore and another that sleeps.
 278          * (in case of no contention with a writer)
 279          */
 280         if (!locked && list_empty(&sem->write_wait))
 281                 __ldsem_wake_readers(sem);
 282 
 283         raw_spin_unlock_irq(&sem->wait_lock);
 284 
 285         __set_current_state(TASK_RUNNING);
 286 
 287         /* lock wait may have timed out */
 288         if (!locked)
 289                 return NULL;
 290         return sem;
 291 }
 292 
 293 
 294 
 295 static int __ldsem_down_read_nested(struct ld_semaphore *sem,
 296                                            int subclass, long timeout)
 297 {
 298         long count;
 299 
 300         rwsem_acquire_read(&sem->dep_map, subclass, 0, _RET_IP_);
 301 
 302         count = atomic_long_add_return(LDSEM_READ_BIAS, &sem->count);
 303         if (count <= 0) {
 304                 lock_contended(&sem->dep_map, _RET_IP_);
 305                 if (!down_read_failed(sem, count, timeout)) {
 306                         rwsem_release(&sem->dep_map, 1, _RET_IP_);
 307                         return 0;
 308                 }
 309         }
 310         lock_acquired(&sem->dep_map, _RET_IP_);
 311         return 1;
 312 }
 313 
 314 static int __ldsem_down_write_nested(struct ld_semaphore *sem,
 315                                             int subclass, long timeout)
 316 {
 317         long count;
 318 
 319         rwsem_acquire(&sem->dep_map, subclass, 0, _RET_IP_);
 320 
 321         count = atomic_long_add_return(LDSEM_WRITE_BIAS, &sem->count);
 322         if ((count & LDSEM_ACTIVE_MASK) != LDSEM_ACTIVE_BIAS) {
 323                 lock_contended(&sem->dep_map, _RET_IP_);
 324                 if (!down_write_failed(sem, count, timeout)) {
 325                         rwsem_release(&sem->dep_map, 1, _RET_IP_);
 326                         return 0;
 327                 }
 328         }
 329         lock_acquired(&sem->dep_map, _RET_IP_);
 330         return 1;
 331 }
 332 
 333 
 334 /*
 335  * lock for reading -- returns 1 if successful, 0 if timed out
 336  */
 337 int __sched ldsem_down_read(struct ld_semaphore *sem, long timeout)
 338 {
 339         might_sleep();
 340         return __ldsem_down_read_nested(sem, 0, timeout);
 341 }
 342 
 343 /*
 344  * trylock for reading -- returns 1 if successful, 0 if contention
 345  */
 346 int ldsem_down_read_trylock(struct ld_semaphore *sem)
 347 {
 348         long count = atomic_long_read(&sem->count);
 349 
 350         while (count >= 0) {
 351                 if (atomic_long_try_cmpxchg(&sem->count, &count, count + LDSEM_READ_BIAS)) {
 352                         rwsem_acquire_read(&sem->dep_map, 0, 1, _RET_IP_);
 353                         lock_acquired(&sem->dep_map, _RET_IP_);
 354                         return 1;
 355                 }
 356         }
 357         return 0;
 358 }
 359 
 360 /*
 361  * lock for writing -- returns 1 if successful, 0 if timed out
 362  */
 363 int __sched ldsem_down_write(struct ld_semaphore *sem, long timeout)
 364 {
 365         might_sleep();
 366         return __ldsem_down_write_nested(sem, 0, timeout);
 367 }
 368 
 369 /*
 370  * trylock for writing -- returns 1 if successful, 0 if contention
 371  */
 372 int ldsem_down_write_trylock(struct ld_semaphore *sem)
 373 {
 374         long count = atomic_long_read(&sem->count);
 375 
 376         while ((count & LDSEM_ACTIVE_MASK) == 0) {
 377                 if (atomic_long_try_cmpxchg(&sem->count, &count, count + LDSEM_WRITE_BIAS)) {
 378                         rwsem_acquire(&sem->dep_map, 0, 1, _RET_IP_);
 379                         lock_acquired(&sem->dep_map, _RET_IP_);
 380                         return 1;
 381                 }
 382         }
 383         return 0;
 384 }
 385 
 386 /*
 387  * release a read lock
 388  */
 389 void ldsem_up_read(struct ld_semaphore *sem)
 390 {
 391         long count;
 392 
 393         rwsem_release(&sem->dep_map, 1, _RET_IP_);
 394 
 395         count = atomic_long_add_return(-LDSEM_READ_BIAS, &sem->count);
 396         if (count < 0 && (count & LDSEM_ACTIVE_MASK) == 0)
 397                 ldsem_wake(sem);
 398 }
 399 
 400 /*
 401  * release a write lock
 402  */
 403 void ldsem_up_write(struct ld_semaphore *sem)
 404 {
 405         long count;
 406 
 407         rwsem_release(&sem->dep_map, 1, _RET_IP_);
 408 
 409         count = atomic_long_add_return(-LDSEM_WRITE_BIAS, &sem->count);
 410         if (count < 0)
 411                 ldsem_wake(sem);
 412 }
 413 
 414 
 415 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 416 
 417 int ldsem_down_read_nested(struct ld_semaphore *sem, int subclass, long timeout)
 418 {
 419         might_sleep();
 420         return __ldsem_down_read_nested(sem, subclass, timeout);
 421 }
 422 
 423 int ldsem_down_write_nested(struct ld_semaphore *sem, int subclass,
 424                             long timeout)
 425 {
 426         might_sleep();
 427         return __ldsem_down_write_nested(sem, subclass, timeout);
 428 }
 429 
 430 #endif

/* [<][>][^][v][top][bottom][index][help] */