tty_ldsem.c (10702B)
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 49struct ldsem_waiter { 50 struct list_head list; 51 struct task_struct *task; 52}; 53 54/* 55 * Initialize an ldsem: 56 */ 57void __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 74static 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 104static 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 119static 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 */ 135static 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 143static 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 */ 155static struct ld_semaphore __sched * 156down_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 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 */ 229static struct ld_semaphore __sched * 230down_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 295static 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, _RET_IP_); 307 return 0; 308 } 309 } 310 lock_acquired(&sem->dep_map, _RET_IP_); 311 return 1; 312} 313 314static 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, _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 */ 337int __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 */ 346int 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 */ 363int __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 */ 372int 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 */ 389void ldsem_up_read(struct ld_semaphore *sem) 390{ 391 long count; 392 393 rwsem_release(&sem->dep_map, _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 */ 403void ldsem_up_write(struct ld_semaphore *sem) 404{ 405 long count; 406 407 rwsem_release(&sem->dep_map, _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 417int 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 423int 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