1 /*
2 * Copyright (c) 2018 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28 #define ATOMIC_PRIVATE 1
29 #define LOCK_PRIVATE 1
30
31 #include <stdint.h>
32 #include <kern/thread.h>
33 #include <machine/atomic.h>
34 #include <kern/locks.h>
35 #include <kern/lock_stat.h>
36 #include <machine/machine_cpu.h>
37 #include <os/atomic_private.h>
38 #include <vm/pmap.h>
39
40 #if defined(__x86_64__)
41 #include <i386/mp.h>
42 extern uint64_t LockTimeOutTSC;
43 #define TICKET_LOCK_PANIC_TIMEOUT LockTimeOutTSC
44 #define lock_enable_preemption enable_preemption
45 #endif /* defined(__x86_64__) */
46
47 #if defined(__arm__) || defined(__arm64__)
48 extern uint64_t TLockTimeOut;
49 #define TICKET_LOCK_PANIC_TIMEOUT TLockTimeOut
50 #endif /* defined(__arm__) || defined(__arm64__) */
51
52
53 /*
54 * "Ticket": A FIFO spinlock with constant backoff
55 * cf. Algorithms for Scalable Synchronization on Shared-Memory Multiprocessors
56 * by Mellor-Crumney and Scott, 1991
57 */
58
59 /*
60 * TODO: proportional back-off based on desired-current ticket distance
61 * This has the potential to considerably reduce snoop traffic
62 * but must be tuned carefully
63 * TODO: Evaluate a bias towards the performant clusters on
64 * asymmetric efficient/performant multi-cluster systems, while
65 * retaining the starvation-free property. A small intra-cluster bias may
66 * be profitable for overall throughput
67 */
68
69 static_assert(sizeof(hw_lck_ticket_t) == 4);
70 static_assert(offsetof(hw_lck_ticket_t, tcurnext) == 2);
71 static_assert(offsetof(hw_lck_ticket_t, cticket) == 2);
72 static_assert(offsetof(hw_lck_ticket_t, nticket) == 3);
73 static_assert(HW_LCK_TICKET_LOCK_VALID_BIT ==
74 (8 * offsetof(hw_lck_ticket_t, lck_valid)));
75 static_assert(HW_LCK_TICKET_LOCK_INCREMENT ==
76 (1u << (8 * offsetof(hw_lck_ticket_t, nticket))));
77
78 /*
79 * Current ticket size limit--tickets can be trivially expanded
80 * to 16-bits if needed
81 */
82 static_assert(MAX_CPUS < 256);
83
84 #if DEVELOPMENT || DEBUG
85 __abortlike
86 static void
__hw_lck_invalid_panic(hw_lck_ticket_t * lck)87 __hw_lck_invalid_panic(hw_lck_ticket_t *lck)
88 {
89 if (lck->lck_type != LCK_TICKET_TYPE) {
90 panic("Invalid ticket lock %p", lck);
91 } else {
92 panic("Ticket lock destroyed %p", lck);
93 }
94 }
95 #endif /* DEVELOPMENT || DEBUG */
96
97 static inline void
hw_lck_ticket_verify(hw_lck_ticket_t * lck)98 hw_lck_ticket_verify(hw_lck_ticket_t *lck)
99 {
100 #if DEVELOPMENT || DEBUG
101 if (lck->lck_type != LCK_TICKET_TYPE) {
102 __hw_lck_invalid_panic(lck);
103 }
104 #else
105 (void)lck;
106 #endif /* DEVELOPMENT || DEBUG */
107 }
108
109 static inline void
lck_ticket_verify(lck_ticket_t * tlock)110 lck_ticket_verify(lck_ticket_t *tlock)
111 {
112 hw_lck_ticket_verify(&tlock->tu);
113 #if DEVELOPMENT || DEBUG
114 if (tlock->lck_tag == LCK_TICKET_TAG_DESTROYED) {
115 __hw_lck_invalid_panic(&tlock->tu);
116 }
117 #endif /* DEVELOPMENT || DEBUG */
118 }
119
120 void
hw_lck_ticket_init(hw_lck_ticket_t * lck,lck_grp_t * grp)121 hw_lck_ticket_init(hw_lck_ticket_t *lck, lck_grp_t *grp)
122 {
123 assert(((uintptr_t)lck & 3) == 0);
124 os_atomic_store(lck, ((hw_lck_ticket_t){
125 .lck_type = LCK_TICKET_TYPE,
126 .lck_valid = 1,
127 }), relaxed);
128
129 #if LOCK_STATS
130 if (grp) {
131 lck_grp_reference(grp, &grp->lck_grp_ticketcnt);
132 }
133 #endif /* LOCK_STATS */
134 }
135
136 void
hw_lck_ticket_init_locked(hw_lck_ticket_t * lck,lck_grp_t * grp)137 hw_lck_ticket_init_locked(hw_lck_ticket_t *lck, lck_grp_t *grp)
138 {
139 assert(((uintptr_t)lck & 3) == 0);
140
141 lock_disable_preemption_for_thread(current_thread());
142
143 os_atomic_store(lck, ((hw_lck_ticket_t){
144 .lck_type = LCK_TICKET_TYPE,
145 .lck_valid = 1,
146 .nticket = 1,
147 }), relaxed);
148
149 #if LOCK_STATS
150 if (grp) {
151 lck_grp_reference(grp, &grp->lck_grp_ticketcnt);
152 }
153 #endif /* LOCK_STATS */
154 }
155
156 void
lck_ticket_init(lck_ticket_t * tlock,__unused lck_grp_t * grp)157 lck_ticket_init(lck_ticket_t *tlock, __unused lck_grp_t *grp)
158 {
159 memset(tlock, 0, sizeof(*tlock));
160 hw_lck_ticket_init(&tlock->tu, grp);
161 }
162
163 static inline void
hw_lck_ticket_destroy_internal(hw_lck_ticket_t * lck,bool sync LCK_GRP_ARG (lck_grp_t * grp))164 hw_lck_ticket_destroy_internal(hw_lck_ticket_t *lck, bool sync
165 LCK_GRP_ARG(lck_grp_t *grp))
166 {
167 __assert_only hw_lck_ticket_t tmp;
168
169 tmp.lck_value = os_atomic_load(&lck->lck_value, relaxed);
170
171 if (__improbable(sync && !tmp.lck_valid && tmp.nticket != tmp.cticket)) {
172 /*
173 * If the lock has been invalidated and there are pending
174 * reservations, it means hw_lck_ticket_lock_allow_invalid()
175 * or hw_lck_ticket_reserve() are being used.
176 *
177 * Such caller do not guarantee the liveness of the object
178 * they try to lock, we need to flush their reservations
179 * before proceeding.
180 *
181 * Because the lock is FIFO, we go through a cycle of
182 * locking/unlocking which will have this effect, because
183 * the lock is now invalid, new calls to
184 * hw_lck_ticket_lock_allow_invalid() will fail before taking
185 * a reservation, and we can safely destroy the lock.
186 */
187 hw_lck_ticket_lock(lck, grp);
188 hw_lck_ticket_unlock(lck);
189 }
190
191 os_atomic_store(&lck->lck_value, 0U, relaxed);
192
193 #if LOCK_STATS
194 if (grp) {
195 lck_grp_deallocate(grp, &grp->lck_grp_ticketcnt);
196 }
197 #endif /* LOCK_STATS */
198 }
199
200 void
hw_lck_ticket_destroy(hw_lck_ticket_t * lck,lck_grp_t * grp)201 hw_lck_ticket_destroy(hw_lck_ticket_t *lck, lck_grp_t *grp)
202 {
203 hw_lck_ticket_verify(lck);
204 hw_lck_ticket_destroy_internal(lck, true LCK_GRP_ARG(grp));
205 }
206
207 void
lck_ticket_destroy(lck_ticket_t * tlock,__unused lck_grp_t * grp)208 lck_ticket_destroy(lck_ticket_t *tlock, __unused lck_grp_t *grp)
209 {
210 lck_ticket_verify(tlock);
211 assert(tlock->lck_owner == 0);
212 tlock->lck_tag = LCK_TICKET_TAG_DESTROYED;
213 hw_lck_ticket_destroy_internal(&tlock->tu, false LCK_GRP_ARG(grp));
214 }
215
216 bool
hw_lck_ticket_held(hw_lck_ticket_t * lck)217 hw_lck_ticket_held(hw_lck_ticket_t *lck)
218 {
219 hw_lck_ticket_t tmp;
220 tmp.tcurnext = os_atomic_load(&lck->tcurnext, relaxed);
221 return tmp.cticket != tmp.nticket;
222 }
223
224 bool
kdp_lck_ticket_is_acquired(lck_ticket_t * lck)225 kdp_lck_ticket_is_acquired(lck_ticket_t *lck)
226 {
227 if (not_in_kdp) {
228 panic("panic: ticket lock acquired check done outside of kernel debugger");
229 }
230 return hw_lck_ticket_held(&lck->tu);
231 }
232
233 static inline void
tlock_mark_owned(lck_ticket_t * tlock,thread_t cthread)234 tlock_mark_owned(lck_ticket_t *tlock, thread_t cthread)
235 {
236 /*
237 * There is a small pre-emption disabled window (also interrupts masked
238 * for the pset lock) between the acquisition of the lock and the
239 * population of the advisory 'owner' thread field
240 * On architectures with a DCAS (ARM v8.1 or x86), conceivably we could
241 * populate the next ticket and the thread atomically, with
242 * possible overhead, potential loss of micro-architectural fwd progress
243 * properties of an unconditional fetch-add, and a 16 byte alignment requirement.
244 */
245 assert(tlock->lck_owner == 0);
246 os_atomic_store(&tlock->lck_owner, (uintptr_t)cthread, relaxed);
247 }
248
249 __abortlike
250 static hw_lock_timeout_status_t
hw_lck_ticket_timeout_panic(void * _lock,uint64_t timeout,uint64_t start,uint64_t now,uint64_t interrupt_time)251 hw_lck_ticket_timeout_panic(void *_lock, uint64_t timeout, uint64_t start, uint64_t now, uint64_t interrupt_time)
252 {
253 #pragma unused(interrupt_time)
254
255 lck_spinlock_to_info_t lsti;
256 hw_lck_ticket_t *lck = _lock;
257 hw_lck_ticket_t tmp;
258
259 lsti = lck_spinlock_timeout_hit(lck, 0);
260 tmp.tcurnext = os_atomic_load(&lck->tcurnext, relaxed);
261
262 panic("Ticket spinlock[%p] timeout after %llu ticks; "
263 "cticket: 0x%x, nticket: 0x%x, waiting for 0x%x, "
264 #if INTERRUPT_MASKED_DEBUG
265 "interrupt time: %llu, "
266 #endif /* INTERRUPT_MASKED_DEBUG */
267 "start time: %llu, now: %llu, timeout: %llu",
268 lck, now - start, tmp.cticket, tmp.nticket, lsti->extra,
269 #if INTERRUPT_MASKED_DEBUG
270 interrupt_time,
271 #endif /* INTERRUPT_MASKED_DEBUG */
272 start, now, timeout);
273 }
274
275 __abortlike
276 static hw_lock_timeout_status_t
lck_ticket_timeout_panic(void * _lock,uint64_t timeout,uint64_t start,uint64_t now,uint64_t interrupt_time)277 lck_ticket_timeout_panic(void *_lock, uint64_t timeout, uint64_t start, uint64_t now, uint64_t interrupt_time)
278 {
279 #pragma unused(interrupt_time)
280 lck_spinlock_to_info_t lsti;
281 hw_lck_ticket_t *lck = _lock;
282 lck_ticket_t *tlock = __container_of(lck, lck_ticket_t, tu);
283 hw_lck_ticket_t tmp;
284
285 lsti = lck_spinlock_timeout_hit(lck, tlock->lck_owner);
286 tmp.tcurnext = os_atomic_load(&lck->tcurnext, relaxed);
287
288 panic("Ticket spinlock[%p] timeout after %llu ticks; "
289 "cticket: 0x%x, nticket: 0x%x, waiting for 0x%x, "
290 "current owner: %p (on CPU %d), "
291 #if DEBUG || DEVELOPMENT
292 "orig owner: %p, "
293 #endif /* DEBUG || DEVELOPMENT */
294 #if INTERRUPT_MASKED_DEBUG
295 "interrupt time: %llu, "
296 #endif /* INTERRUPT_MASKED_DEBUG */
297 "start time: %llu, now: %llu, timeout: %llu",
298 tlock, now - start, tmp.cticket, tmp.nticket, lsti->extra,
299 (void *)lsti->owner_thread_cur, lsti->owner_cpu,
300 #if DEBUG || DEVELOPMENT
301 (void *)lsti->owner_thread_orig,
302 #endif /* DEBUG || DEVELOPMENT */
303 #if INTERRUPT_MASKED_DEBUG
304 interrupt_time,
305 #endif /* INTERRUPT_MASKED_DEBUG */
306 start, now, timeout);
307 }
308
309 static inline void
hw_lck_ticket_unlock_internal(hw_lck_ticket_t * lck)310 hw_lck_ticket_unlock_internal(hw_lck_ticket_t *lck)
311 {
312 _Atomic uint8_t *ctp = (_Atomic uint8_t *)&lck->cticket;
313 uint8_t cticket;
314
315 /*
316 * Do not use os_atomic* here, we want non volatile atomics
317 * so that the compiler can codegen an `incb` on Intel.
318 */
319 cticket = atomic_load_explicit(ctp, memory_order_relaxed);
320 atomic_store_explicit(ctp, cticket + 1, memory_order_release);
321 #if __arm__
322 set_event();
323 #endif // __arm__
324 #if CONFIG_DTRACE
325 LOCKSTAT_RECORD(LS_LCK_TICKET_LOCK_RELEASE, lck);
326 #endif /* CONFIG_DTRACE */
327 lock_enable_preemption();
328 }
329
330 struct hw_lck_ticket_reserve_arg {
331 uint8_t mt;
332 bool validate;
333 };
334
335 /*
336 * On contention, poll for ownership
337 * Returns when the current ticket is observed equal to "mt"
338 */
339 __result_use_check
340 static hw_lock_status_t __attribute__((noinline))
hw_lck_ticket_contended(hw_lck_ticket_t * lck,thread_t cthread,struct hw_lck_ticket_reserve_arg arg,uint64_t timeout,hw_lock_timeout_handler_t handler LCK_GRP_ARG (lck_grp_t * grp))341 hw_lck_ticket_contended(hw_lck_ticket_t *lck, thread_t cthread, struct hw_lck_ticket_reserve_arg arg,
342 uint64_t timeout, hw_lock_timeout_handler_t handler LCK_GRP_ARG(lck_grp_t *grp))
343 {
344 #pragma unused(cthread)
345
346 uint64_t end = 0, start = 0, interrupts = 0;
347 bool has_timeout = true;
348
349 uint8_t cticket;
350 uint8_t mt = arg.mt;
351 #if INTERRUPT_MASKED_DEBUG
352 bool in_ppl = pmap_in_ppl();
353 bool interruptible = !in_ppl && ml_get_interrupts_enabled();
354 uint64_t start_interrupts = 0;
355 #endif /* INTERRUPT_MASKED_DEBUG */
356
357 #if CONFIG_DTRACE || LOCK_STATS
358 uint64_t begin = 0;
359 boolean_t stat_enabled = lck_grp_ticket_spin_enabled(lck LCK_GRP_ARG(grp));
360
361 if (__improbable(stat_enabled)) {
362 begin = mach_absolute_time();
363 }
364 #endif /* CONFIG_DTRACE || LOCK_STATS */
365
366 #if INTERRUPT_MASKED_DEBUG
367 timeout = hw_lock_compute_timeout(timeout, TICKET_LOCK_PANIC_TIMEOUT, in_ppl, interruptible);
368 #else
369 timeout = hw_lock_compute_timeout(timeout, TICKET_LOCK_PANIC_TIMEOUT);
370 #endif /* INTERRUPT_MASKED_DEBUG */
371 if (timeout == 0) {
372 has_timeout = false;
373 }
374
375 for (;;) {
376 for (int i = 0; i < LOCK_SNOOP_SPINS; i++) {
377 #if OS_ATOMIC_HAS_LLSC
378 cticket = os_atomic_load_exclusive(&lck->cticket, acquire);
379 if (__improbable(cticket != mt)) {
380 wait_for_event();
381 continue;
382 }
383 os_atomic_clear_exclusive();
384 #elif defined(__x86_64__)
385 __builtin_ia32_pause();
386 cticket = os_atomic_load(&lck->cticket, acquire);
387 if (__improbable(cticket != mt)) {
388 continue;
389 }
390 #else
391 #error unsupported architecture
392 #endif
393
394 /*
395 * We now have successfully acquired the lock
396 */
397
398 #if CONFIG_DTRACE || LOCK_STATS
399 if (__improbable(stat_enabled)) {
400 lck_grp_ticket_update_spin(lck LCK_GRP_ARG(grp),
401 mach_absolute_time() - begin);
402 }
403 lck_grp_ticket_update_miss(lck LCK_GRP_ARG(grp));
404 lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
405 #endif /* CONFIG_DTRACE || LOCK_STATS */
406 if (__improbable(arg.validate && !lck->lck_valid)) {
407 /*
408 * We got the lock, however the caller is
409 * hw_lck_ticket_lock_allow_invalid() and the
410 * lock has been invalidated while we were
411 * waiting for our turn.
412 *
413 * We need to unlock and pretend we failed.
414 */
415 hw_lck_ticket_unlock_internal(lck);
416 return HW_LOCK_INVALID;
417 }
418
419 return HW_LOCK_ACQUIRED;
420 }
421
422 if (has_timeout) {
423 uint64_t now = ml_get_timebase();
424 if (end == 0) {
425 #if INTERRUPT_MASKED_DEBUG
426 if (interruptible) {
427 start_interrupts = cthread->machine.int_time_mt;
428 }
429 #endif /* INTERRUPT_MASKED_DEBUG */
430 start = now;
431 end = now + timeout;
432 /* remember the droid we're looking for */
433 PERCPU_GET(lck_spinlock_to_info)->extra = mt;
434 } else if (now < end) {
435 /* keep spinning */
436 } else {
437 #if INTERRUPT_MASKED_DEBUG
438 if (interruptible) {
439 interrupts = cthread->machine.int_time_mt - start_interrupts;
440 }
441 #endif /* INTERRUPT_MASKED_DEBUG */
442 if (handler(lck, timeout, start, now, interrupts)) {
443 /* push the deadline */
444 end += timeout;
445 } else {
446 break;
447 }
448 }
449 }
450 }
451
452 #if CONFIG_DTRACE || LOCK_STATS
453 if (__improbable(stat_enabled)) {
454 lck_grp_ticket_update_spin(lck LCK_GRP_ARG(grp),
455 mach_absolute_time() - begin);
456 }
457 lck_grp_ticket_update_miss(lck LCK_GRP_ARG(grp));
458 #endif /* CONFIG_DTRACE || LOCK_STATS */
459 return HW_LOCK_CONTENDED;
460 }
461
462 static void __attribute__((noinline))
lck_ticket_contended(lck_ticket_t * tlock,uint8_t mt,thread_t cthread LCK_GRP_ARG (lck_grp_t * grp))463 lck_ticket_contended(lck_ticket_t *tlock, uint8_t mt, thread_t cthread
464 LCK_GRP_ARG(lck_grp_t *grp))
465 {
466 assertf(tlock->lck_owner != (uintptr_t) cthread,
467 "Recursive ticket lock, owner: %p, current thread: %p",
468 (void *) tlock->lck_owner, (void *) cthread);
469
470 struct hw_lck_ticket_reserve_arg arg = { .mt = mt };
471 lck_spinlock_timeout_set_orig_owner(tlock->lck_owner);
472 (void)hw_lck_ticket_contended(&tlock->tu, cthread, arg, 0,
473 lck_ticket_timeout_panic LCK_GRP_ARG(grp));
474 tlock_mark_owned(tlock, cthread);
475 }
476
477 static inline hw_lck_ticket_t
hw_lck_ticket_reserve_orig(hw_lck_ticket_t * lck,thread_t cthread __unused)478 hw_lck_ticket_reserve_orig(hw_lck_ticket_t *lck, thread_t cthread __unused)
479 {
480 hw_lck_ticket_t tmp;
481
482 lock_disable_preemption_for_thread(cthread);
483 /*
484 * Atomically load both the entier lock state, and increment the
485 * "nticket". Wrap of the ticket field is OK as long as the total
486 * number of contending CPUs is < maximum ticket
487 */
488 tmp.lck_value = os_atomic_add_orig(&lck->lck_value,
489 1U << (8 * offsetof(hw_lck_ticket_t, nticket)), acquire);
490
491 return tmp;
492 }
493
494 void
hw_lck_ticket_lock(hw_lck_ticket_t * lck,lck_grp_t * grp)495 hw_lck_ticket_lock(hw_lck_ticket_t *lck, lck_grp_t *grp)
496 {
497 thread_t cthread = current_thread();
498 hw_lck_ticket_t tmp;
499
500 hw_lck_ticket_verify(lck);
501 tmp = hw_lck_ticket_reserve_orig(lck, cthread);
502
503 if (__probable(tmp.cticket == tmp.nticket)) {
504 return lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
505 }
506
507 /* Contention? branch to out of line contended block */
508 struct hw_lck_ticket_reserve_arg arg = { .mt = tmp.nticket };
509 lck_spinlock_timeout_set_orig_owner(0);
510 (void)hw_lck_ticket_contended(lck, cthread, arg, 0,
511 hw_lck_ticket_timeout_panic LCK_GRP_ARG(grp));
512 }
513
514 hw_lock_status_t
hw_lck_ticket_lock_to(hw_lck_ticket_t * lck,uint64_t timeout,hw_lock_timeout_handler_t handler,lck_grp_t * grp)515 hw_lck_ticket_lock_to(hw_lck_ticket_t *lck, uint64_t timeout,
516 hw_lock_timeout_handler_t handler, lck_grp_t *grp)
517 {
518 thread_t cthread = current_thread();
519 hw_lck_ticket_t tmp;
520
521 hw_lck_ticket_verify(lck);
522 tmp = hw_lck_ticket_reserve_orig(lck, cthread);
523
524 if (__probable(tmp.cticket == tmp.nticket)) {
525 lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
526 return HW_LOCK_ACQUIRED;
527 }
528
529 /* Contention? branch to out of line contended block */
530 struct hw_lck_ticket_reserve_arg arg = { .mt = tmp.nticket };
531 lck_spinlock_timeout_set_orig_owner(0);
532 return hw_lck_ticket_contended(lck, cthread, arg, timeout,
533 handler LCK_GRP_ARG(grp));
534 }
535
536 void
lck_ticket_lock(lck_ticket_t * tlock,__unused lck_grp_t * grp)537 lck_ticket_lock(lck_ticket_t *tlock, __unused lck_grp_t *grp)
538 {
539 thread_t cthread = current_thread();
540 hw_lck_ticket_t tmp;
541
542 lck_ticket_verify(tlock);
543 tmp = hw_lck_ticket_reserve_orig(&tlock->tu, cthread);
544
545 if (__probable(tmp.cticket == tmp.nticket)) {
546 tlock_mark_owned(tlock, cthread);
547 return lck_grp_ticket_update_held(&tlock->tu LCK_GRP_ARG(grp));
548 }
549
550 /* Contention? branch to out of line contended block */
551 lck_ticket_contended(tlock, tmp.nticket, cthread LCK_GRP_ARG(grp));
552 }
553
554 bool
hw_lck_ticket_lock_try(hw_lck_ticket_t * lck,lck_grp_t * grp)555 hw_lck_ticket_lock_try(hw_lck_ticket_t *lck, lck_grp_t *grp)
556 {
557 hw_lck_ticket_t olck, nlck;
558
559 hw_lck_ticket_verify(lck);
560 lock_disable_preemption_for_thread(current_thread());
561
562 os_atomic_rmw_loop(&lck->tcurnext, olck.tcurnext, nlck.tcurnext, acquire, {
563 if (__improbable(olck.cticket != olck.nticket)) {
564 os_atomic_rmw_loop_give_up({
565 lock_enable_preemption();
566 return false;
567 });
568 }
569 nlck.cticket = olck.cticket;
570 nlck.nticket = olck.nticket + 1;
571 });
572
573 lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
574 return true;
575 }
576
577 bool
lck_ticket_lock_try(lck_ticket_t * tlock,__unused lck_grp_t * grp)578 lck_ticket_lock_try(lck_ticket_t *tlock, __unused lck_grp_t *grp)
579 {
580 thread_t cthread = current_thread();
581 hw_lck_ticket_t olck, nlck;
582
583 lck_ticket_verify(tlock);
584 lock_disable_preemption_for_thread(cthread);
585
586 os_atomic_rmw_loop(&tlock->tu.tcurnext, olck.tcurnext, nlck.tcurnext, acquire, {
587 if (__improbable(olck.cticket != olck.nticket)) {
588 os_atomic_rmw_loop_give_up({
589 lock_enable_preemption();
590 return false;
591 });
592 }
593 nlck.cticket = olck.cticket;
594 nlck.nticket = olck.nticket + 1;
595 });
596
597 tlock_mark_owned(tlock, cthread);
598 lck_grp_ticket_update_held(&tlock->tu LCK_GRP_ARG(grp));
599 return true;
600 }
601
602 /*
603 * Returns a "reserved" lock or a lock where `lck_valid` is 0.
604 *
605 * More or less equivalent to this:
606 *
607 * hw_lck_ticket_t
608 * hw_lck_ticket_lock_allow_invalid(hw_lck_ticket_t *lck)
609 * {
610 * hw_lck_ticket_t o, n;
611 *
612 * os_atomic_rmw_loop(lck, o, n, acquire, {
613 * if (__improbable(!o.lck_valid)) {
614 * os_atomic_rmw_loop_give_up({
615 * return (hw_lck_ticket_t){ 0 };
616 * });
617 * }
618 * n = o;
619 * n.nticket++;
620 * });
621 * return o;
622 * }
623 */
624 extern hw_lck_ticket_t
625 hw_lck_ticket_reserve_orig_allow_invalid(hw_lck_ticket_t *lck);
626
627 bool
hw_lck_ticket_reserve(hw_lck_ticket_t * lck,uint32_t * ticket,lck_grp_t * grp)628 hw_lck_ticket_reserve(hw_lck_ticket_t *lck, uint32_t *ticket, lck_grp_t *grp)
629 {
630 thread_t cthread = current_thread();
631 hw_lck_ticket_t tmp;
632
633 hw_lck_ticket_verify(lck);
634 tmp = hw_lck_ticket_reserve_orig(lck, cthread);
635 *ticket = tmp.lck_value;
636
637 if (__probable(tmp.cticket == tmp.nticket)) {
638 lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
639 return true;
640 }
641
642 return false;
643 }
644
645 hw_lock_status_t
hw_lck_ticket_reserve_allow_invalid(hw_lck_ticket_t * lck,uint32_t * ticket,lck_grp_t * grp)646 hw_lck_ticket_reserve_allow_invalid(hw_lck_ticket_t *lck, uint32_t *ticket, lck_grp_t *grp)
647 {
648 hw_lck_ticket_t tmp;
649
650 lock_disable_preemption_for_thread(current_thread());
651
652 tmp = hw_lck_ticket_reserve_orig_allow_invalid(lck);
653 *ticket = tmp.lck_value;
654
655 if (__improbable(!tmp.lck_valid)) {
656 lock_enable_preemption();
657 return HW_LOCK_INVALID;
658 }
659
660 if (__probable(tmp.cticket == tmp.nticket)) {
661 lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
662 return HW_LOCK_ACQUIRED;
663 }
664
665 return HW_LOCK_CONTENDED;
666 }
667
668 hw_lock_status_t
hw_lck_ticket_wait(hw_lck_ticket_t * lck,uint32_t ticket,uint64_t timeout,hw_lock_timeout_handler_t handler,lck_grp_t * grp)669 hw_lck_ticket_wait(hw_lck_ticket_t *lck, uint32_t ticket, uint64_t timeout,
670 hw_lock_timeout_handler_t handler, lck_grp_t *grp)
671 {
672 hw_lck_ticket_t tmp = { .lck_value = ticket };
673 struct hw_lck_ticket_reserve_arg arg = { .mt = tmp.nticket };
674 lck_spinlock_timeout_set_orig_owner(0);
675 return hw_lck_ticket_contended(lck, current_thread(), arg, timeout,
676 handler LCK_GRP_ARG(grp));
677 }
678
679 hw_lock_status_t
hw_lck_ticket_lock_allow_invalid(hw_lck_ticket_t * lck,uint64_t timeout,hw_lock_timeout_handler_t handler,lck_grp_t * grp)680 hw_lck_ticket_lock_allow_invalid(hw_lck_ticket_t *lck, uint64_t timeout,
681 hw_lock_timeout_handler_t handler, lck_grp_t *grp)
682 {
683 hw_lock_status_t st;
684 hw_lck_ticket_t tmp;
685
686 st = hw_lck_ticket_reserve_allow_invalid(lck, &tmp.lck_value, grp);
687
688 if (__improbable(st == HW_LOCK_CONTENDED)) {
689 /* Contention? branch to out of line contended block */
690 struct hw_lck_ticket_reserve_arg arg = {
691 .mt = tmp.nticket,
692 .validate = true,
693 };
694 lck_spinlock_timeout_set_orig_owner(0);
695 return hw_lck_ticket_contended(lck, current_thread(), arg, timeout,
696 handler LCK_GRP_ARG(grp));
697 }
698
699 return st;
700 }
701
702 void
hw_lck_ticket_invalidate(hw_lck_ticket_t * lck)703 hw_lck_ticket_invalidate(hw_lck_ticket_t *lck)
704 {
705 hw_lck_ticket_t tmp = { .lck_valid = 1 };
706
707 os_atomic_andnot(&lck->lck_value, tmp.lck_value, relaxed);
708 }
709
710 void
hw_lck_ticket_unlock(hw_lck_ticket_t * lck)711 hw_lck_ticket_unlock(hw_lck_ticket_t *lck)
712 {
713 hw_lck_ticket_verify(lck);
714 #if MACH_ASSERT
715 hw_lck_ticket_t tmp;
716 tmp.lck_value = os_atomic_load(&lck->lck_value, relaxed);
717 assertf(tmp.cticket != tmp.nticket,
718 "Ticket lock %p is not locked (0x%08x)", lck, tmp.lck_value);
719 #endif /* MACH_ASSERT */
720 hw_lck_ticket_unlock_internal(lck);
721 }
722
723 void
lck_ticket_unlock(lck_ticket_t * tlock)724 lck_ticket_unlock(lck_ticket_t *tlock)
725 {
726 lck_ticket_verify(tlock);
727
728 assertf(tlock->lck_owner == (uintptr_t)current_thread(),
729 "Ticket unlock non-owned, owner: %p", (void *) tlock->lck_owner);
730 os_atomic_store(&tlock->lck_owner, 0, relaxed);
731
732 hw_lck_ticket_unlock_internal(&tlock->tu);
733 }
734
735 void
lck_ticket_assert_owned(__assert_only lck_ticket_t * tlock)736 lck_ticket_assert_owned(__assert_only lck_ticket_t *tlock)
737 {
738 #if MACH_ASSERT
739 thread_t self, owner;
740
741 owner = (thread_t)os_atomic_load(&tlock->lck_owner, relaxed);
742 self = current_thread();
743 assertf(owner == self, "lck_ticket_assert_owned: owner %p, current: %p",
744 owner, self);
745 #endif /* MACH_ASSERT */
746 }
747