xref: /xnu-8020.101.4/osfmk/kern/lock_ticket.c (revision e7776783b89a353188416a9a346c6cdb4928faad)
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