xref: /xnu-12377.61.12/osfmk/kern/lock_ticket.c (revision 4d495c6e23c53686cf65f45067f79024cf5dcee8)
1 /*
2  * Copyright (c) 2021 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 
29 #define LOCK_PRIVATE 1
30 #include <stdint.h>
31 #include <kern/thread.h>
32 #include <kern/locks_internal.h>
33 #include <kern/locks.h>
34 #include <kern/lock_stat.h>
35 #include <machine/machine_cpu.h>
36 #include <vm/pmap.h>
37 #include <san/kasan.h>
38 
39 /*
40  * "Ticket": A FIFO spinlock with constant backoff
41  * cf. Algorithms for Scalable Synchronization on Shared-Memory Multiprocessors
42  * by Mellor-Crumney and Scott, 1991
43  *
44  * Note: 'cticket' is 'now_serving', 'nticket' is 'next_ticket'
45  */
46 
47 /*
48  * TODO: proportional back-off based on desired-current ticket distance
49  * This has the potential to considerably reduce snoop traffic
50  * but must be tuned carefully
51  * TODO: Evaluate a bias towards the performant clusters on
52  * asymmetric efficient/performant multi-cluster systems, while
53  * retaining the starvation-free property. A small intra-cluster bias may
54  * be profitable for overall throughput
55  */
56 
57 #if defined(__x86_64__)
58 #include <i386/mp.h>
59 extern uint64_t LockTimeOutTSC;
60 #define TICKET_LOCK_PANIC_TIMEOUT LockTimeOutTSC
61 #endif /* defined(__x86_64__) */
62 
63 #if defined(__arm64__)
64 extern uint64_t TLockTimeOut;
65 #define TICKET_LOCK_PANIC_TIMEOUT TLockTimeOut
66 #endif /* defined(__arm64__) */
67 
68 #if CONFIG_PV_TICKET
69 
70 /*
71  * Tunable that controls how many pause/wfe loops
72  * to execute before checking for timeouts and
73  * issuing a "wait" hypercall.
74  */
75 #define DEFAULT_TICKET_LOOPS (LOCK_SNOOP_SPINS)
76 uint32_t ticket_lock_spins = DEFAULT_TICKET_LOOPS;
77 #define TICKET_LOCK_SNOOP_LOOPS ticket_lock_spins
78 
79 #else /* CONFIG_PV_TICKET */
80 
81 /*
82  * How many pause/wfe loops to execute before
83  * checking for timeouts.
84  */
85 #define TICKET_LOCK_SNOOP_LOOPS LOCK_SNOOP_SPINS
86 
87 #endif /* CONFIG_PV_TICKET */
88 
89 /*
90  * Current ticket size limit--tickets can be trivially expanded
91  * to 16-bits if needed
92  */
93 static_assert(MAX_CPUS < (256 / HW_LCK_TICKET_LOCK_INCREMENT));
94 static_assert(sizeof(hw_lck_ticket_t) == 4);
95 static_assert(offsetof(hw_lck_ticket_t, tcurnext) == 2);
96 static_assert(offsetof(hw_lck_ticket_t, cticket) == 2);
97 static_assert(offsetof(hw_lck_ticket_t, nticket) == 3);
98 static_assert(HW_LCK_TICKET_LOCK_INC_WORD ==
99     (HW_LCK_TICKET_LOCK_INCREMENT << (8 * offsetof(hw_lck_ticket_t, nticket))));
100 #if 0 /* the expression below is sadly not constant, thank you for nothing C */
101 static_assert((1u << HW_LCK_TICKET_LOCK_VALID_BIT) ==
102     ((hw_lck_ticket_t){ .lck_valid = 1 }).lck_value);
103 #endif
104 
105 __header_always_inline int
equal_tickets(uint8_t t0,uint8_t t1)106 equal_tickets(uint8_t t0, uint8_t t1)
107 {
108 	return !((t0 ^ t1) & ~HW_LCK_TICKET_LOCK_PVWAITFLAG);
109 }
110 
111 __header_always_inline uint8_t
ticket_count(uint8_t t)112 ticket_count(uint8_t t)
113 {
114 	return t & ~HW_LCK_TICKET_LOCK_PVWAITFLAG;
115 }
116 
117 __abortlike
118 static void
__hw_lck_ticket_invalid_panic(hw_lck_ticket_t * lck)119 __hw_lck_ticket_invalid_panic(hw_lck_ticket_t *lck)
120 {
121 	panic("Invalid HW ticket lock %p <0x%08x>", lck, lck->lck_value);
122 }
123 
124 __abortlike
125 static void
__lck_ticket_invalid_panic(lck_ticket_t * lck)126 __lck_ticket_invalid_panic(lck_ticket_t *lck)
127 {
128 	panic("Invalid ticket lock %p <0x%08x 0x%08x 0x%08x 0x%08x>",
129 	    lck, *(uint32_t *)lck, lck->lck_ticket_owner,
130 	    lck->tu.lck_value, lck->lck_ticket_padding);
131 }
132 
133 __abortlike
134 static void
__lck_ticket_owned_panic(const lck_ticket_t * lck)135 __lck_ticket_owned_panic(const lck_ticket_t *lck)
136 {
137 	thread_t self = current_thread();
138 
139 	panic("Ticket lock %p is unexpectedly owned by thread %p", lck, self);
140 }
141 
142 __abortlike
143 static void
__lck_ticket_not_owned_panic(const lck_ticket_t * lck)144 __lck_ticket_not_owned_panic(const lck_ticket_t *lck)
145 {
146 	thread_t self = current_thread();
147 
148 	panic("Ticket lock %p is unexpectedly not owned by thread %p", lck, self);
149 }
150 
151 static inline void
hw_lck_ticket_verify(hw_lck_ticket_t * lck)152 hw_lck_ticket_verify(hw_lck_ticket_t *lck)
153 {
154 	if (lck->lck_type != LCK_TYPE_TICKET) {
155 		__hw_lck_ticket_invalid_panic(lck);
156 	}
157 }
158 
159 static inline void
lck_ticket_verify(lck_ticket_t * tlock)160 lck_ticket_verify(lck_ticket_t *tlock)
161 {
162 	if (tlock->lck_ticket_type != LCK_TYPE_TICKET) {
163 		__lck_ticket_invalid_panic(tlock);
164 	}
165 }
166 
167 #if DEVELOPMENT || DEBUG
168 #define HW_LCK_TICKET_VERIFY(lck)      hw_lck_ticket_verify(lck)
169 #define LCK_TICKET_VERIFY(lck)         lck_ticket_verify(lck)
170 #define LCK_TICKET_UNLOCK_VERIFY(l)    ({ \
171 	if ((l)->lck_ticket_owner != current_thread()->ctid) {  \
172 	        __lck_ticket_not_owned_panic(l);                \
173 	}                                                       \
174 })
175 #else
176 #define HW_LCK_TICKET_VERIFY(lck)      ((void)0)
177 #define LCK_TICKET_VERIFY(lck)         ((void)0)
178 #define LCK_TICKET_UNLOCK_VERIFY(l)    ((void)0)
179 #endif /* DEVELOPMENT || DEBUG */
180 
181 MARK_AS_HIBERNATE_TEXT void
hw_lck_ticket_init(hw_lck_ticket_t * lck,lck_grp_t * grp)182 hw_lck_ticket_init(hw_lck_ticket_t *lck, lck_grp_t *grp)
183 {
184 	assert(((uintptr_t)lck & 3) == 0);
185 	os_atomic_store(lck, ((hw_lck_ticket_t){
186 		.lck_type = LCK_TYPE_TICKET,
187 #if CONFIG_PV_TICKET
188 		.lck_is_pv = has_lock_pv,
189 #endif /* CONFIG_PV_TICKET */
190 		.lck_valid = 1,
191 	}), relaxed);
192 
193 #if LCK_GRP_USE_ARG
194 	if (grp) {
195 		lck_grp_reference(grp, &grp->lck_grp_ticketcnt);
196 	}
197 #endif /* LCK_GRP_USE_ARG */
198 }
199 
200 void
hw_lck_ticket_init_locked(hw_lck_ticket_t * lck,lck_grp_t * grp)201 hw_lck_ticket_init_locked(hw_lck_ticket_t *lck, lck_grp_t *grp)
202 {
203 	assert(((uintptr_t)lck & 3) == 0);
204 
205 	lock_disable_preemption_for_thread(current_thread());
206 
207 	os_atomic_store(lck, ((hw_lck_ticket_t){
208 		.lck_type = LCK_TYPE_TICKET,
209 #if CONFIG_PV_TICKET
210 		.lck_is_pv = has_lock_pv,
211 #endif /* CONFIG_PV_TICKET */
212 		.lck_valid = 1,
213 		.nticket = HW_LCK_TICKET_LOCK_INCREMENT,
214 	}), relaxed);
215 
216 #if LCK_GRP_USE_ARG
217 	if (grp) {
218 		lck_grp_reference(grp, &grp->lck_grp_ticketcnt);
219 	}
220 #endif /* LCK_GRP_USE_ARG */
221 }
222 
223 MARK_AS_HIBERNATE_TEXT void
lck_ticket_init(lck_ticket_t * tlock,__unused lck_grp_t * grp)224 lck_ticket_init(lck_ticket_t *tlock, __unused lck_grp_t *grp)
225 {
226 	*tlock = (lck_ticket_t){
227 		.lck_ticket_type = LCK_TYPE_TICKET,
228 		.tu = {
229 			.lck_type = LCK_TYPE_TICKET,
230 #if CONFIG_PV_TICKET
231 			.lck_is_pv = has_lock_pv,
232 #endif /* CONFIG_PV_TICKET */
233 			.lck_valid = 1,
234 		},
235 	};
236 
237 #if LCK_GRP_USE_ARG
238 	if (grp) {
239 		lck_grp_reference(grp, &grp->lck_grp_ticketcnt);
240 	}
241 #endif /* LCK_GRP_USE_ARG */
242 }
243 
244 static inline void
hw_lck_ticket_destroy_internal(hw_lck_ticket_t * lck,bool sync LCK_GRP_ARG (lck_grp_t * grp))245 hw_lck_ticket_destroy_internal(hw_lck_ticket_t *lck, bool sync
246     LCK_GRP_ARG(lck_grp_t *grp))
247 {
248 	__assert_only hw_lck_ticket_t tmp;
249 
250 	tmp.lck_value = os_atomic_load(&lck->lck_value, relaxed);
251 
252 	if (__improbable(sync && !tmp.lck_valid && !equal_tickets(tmp.nticket, tmp.cticket))) {
253 		/*
254 		 * If the lock has been invalidated and there are pending
255 		 * reservations, it means hw_lck_ticket_lock_allow_invalid()
256 		 * or hw_lck_ticket_reserve() are being used.
257 		 *
258 		 * Such caller do not guarantee the liveness of the object
259 		 * they try to lock, we need to flush their reservations
260 		 * before proceeding.
261 		 *
262 		 * Because the lock is FIFO, we go through a cycle of
263 		 * locking/unlocking which will have this effect, because
264 		 * the lock is now invalid, new calls to
265 		 * hw_lck_ticket_lock_allow_invalid() will fail before taking
266 		 * a reservation, and we can safely destroy the lock.
267 		 */
268 		hw_lck_ticket_lock(lck, grp);
269 		hw_lck_ticket_unlock(lck);
270 	}
271 
272 	os_atomic_store(&lck->lck_value, 0U, relaxed);
273 
274 #if LCK_GRP_USE_ARG
275 	if (grp) {
276 		lck_grp_deallocate(grp, &grp->lck_grp_ticketcnt);
277 	}
278 #endif /* LCK_GRP_USE_ARG */
279 }
280 
281 void
hw_lck_ticket_destroy(hw_lck_ticket_t * lck,lck_grp_t * grp)282 hw_lck_ticket_destroy(hw_lck_ticket_t *lck, lck_grp_t *grp)
283 {
284 	hw_lck_ticket_verify(lck);
285 	hw_lck_ticket_destroy_internal(lck, true LCK_GRP_ARG(grp));
286 }
287 
288 void
lck_ticket_destroy(lck_ticket_t * tlock,__unused lck_grp_t * grp)289 lck_ticket_destroy(lck_ticket_t *tlock, __unused lck_grp_t *grp)
290 {
291 	lck_ticket_verify(tlock);
292 	assert(tlock->lck_ticket_owner == 0);
293 	tlock->lck_ticket_type = LCK_TYPE_NONE;
294 	hw_lck_ticket_destroy_internal(&tlock->tu, false LCK_GRP_ARG(grp));
295 }
296 
297 bool
hw_lck_ticket_held(hw_lck_ticket_t * lck)298 hw_lck_ticket_held(hw_lck_ticket_t *lck)
299 {
300 	hw_lck_ticket_t tmp;
301 	tmp.tcurnext = os_atomic_load(&lck->tcurnext, relaxed);
302 	return !equal_tickets(tmp.cticket, tmp.nticket);
303 }
304 
305 bool
kdp_lck_ticket_is_acquired(lck_ticket_t * lck)306 kdp_lck_ticket_is_acquired(lck_ticket_t *lck)
307 {
308 	if (not_in_kdp) {
309 		panic("panic: ticket lock acquired check done outside of kernel debugger");
310 	}
311 	return hw_lck_ticket_held(&lck->tu);
312 }
313 
314 static inline void
tlock_mark_owned(lck_ticket_t * tlock,thread_t cthread)315 tlock_mark_owned(lck_ticket_t *tlock, thread_t cthread)
316 {
317 	/*
318 	 * There is a small pre-emption disabled window (also interrupts masked
319 	 * for the pset lock) between the acquisition of the lock and the
320 	 * population of the advisory 'owner' thread field
321 	 * On architectures with a DCAS (ARM v8.1 or x86), conceivably we could
322 	 * populate the next ticket and the thread atomically, with
323 	 * possible overhead, potential loss of micro-architectural fwd progress
324 	 * properties of an unconditional fetch-add, and a 16 byte alignment requirement.
325 	 */
326 	assert3u(tlock->lck_ticket_owner, ==, 0);
327 	os_atomic_store(&tlock->lck_ticket_owner, cthread->ctid, relaxed);
328 }
329 
330 __abortlike
331 static hw_spin_timeout_status_t
hw_lck_ticket_timeout_panic(void * _lock,hw_spin_timeout_t to,hw_spin_state_t st)332 hw_lck_ticket_timeout_panic(void *_lock, hw_spin_timeout_t to, hw_spin_state_t st)
333 {
334 	lck_spinlock_to_info_t lsti;
335 	hw_lck_ticket_t *lck = _lock;
336 	hw_lck_ticket_t tmp;
337 
338 	tmp.lck_value = os_atomic_load(&lck->lck_value, relaxed);
339 
340 	if (pmap_in_ppl()) {
341 		panic("Ticket spinlock[%p] " HW_SPIN_TIMEOUT_FMT "; "
342 		    "cticket: 0x%x, nticket: 0x%x, valid: %d, "
343 		    HW_SPIN_TIMEOUT_DETAILS_FMT,
344 		    lck, HW_SPIN_TIMEOUT_ARG(to, st),
345 		    tmp.cticket, tmp.nticket, tmp.lck_valid,
346 		    HW_SPIN_TIMEOUT_DETAILS_ARG(to, st));
347 	}
348 
349 	lsti = lck_spinlock_timeout_hit(lck, 0);
350 	panic("Ticket spinlock[%p] " HW_SPIN_TIMEOUT_FMT "; "
351 	    "cticket: 0x%x, nticket: 0x%x, waiting for 0x%x, valid: %d, "
352 	    HW_SPIN_TIMEOUT_DETAILS_FMT,
353 	    lck, HW_SPIN_TIMEOUT_ARG(to, st),
354 	    tmp.cticket, tmp.nticket, lsti->extra, tmp.lck_valid,
355 	    HW_SPIN_TIMEOUT_DETAILS_ARG(to, st));
356 }
357 
358 __abortlike
359 static hw_spin_timeout_status_t
lck_ticket_timeout_panic(void * _lock,hw_spin_timeout_t to,hw_spin_state_t st)360 lck_ticket_timeout_panic(void *_lock, hw_spin_timeout_t to, hw_spin_state_t st)
361 {
362 	lck_spinlock_to_info_t lsti;
363 	lck_ticket_t *lck = _lock;
364 	hw_lck_ticket_t tmp;
365 
366 	lsti = lck_spinlock_timeout_hit(&lck->tu, lck->lck_ticket_owner);
367 	tmp.tcurnext = os_atomic_load(&lck->tu.tcurnext, relaxed);
368 
369 	panic("Ticket spinlock[%p] " HW_SPIN_TIMEOUT_FMT "; "
370 	    "cticket: 0x%x, nticket: 0x%x, waiting for 0x%x, "
371 	    "current owner: %p (on CPU %d), "
372 #if DEBUG || DEVELOPMENT
373 	    "orig owner: %p, "
374 #endif /* DEBUG || DEVELOPMENT */
375 	    HW_SPIN_TIMEOUT_DETAILS_FMT,
376 	    lck, HW_SPIN_TIMEOUT_ARG(to, st),
377 	    tmp.cticket, tmp.nticket, lsti->extra,
378 	    (void *)lsti->owner_thread_cur, lsti->owner_cpu,
379 #if DEBUG || DEVELOPMENT
380 	    (void *)lsti->owner_thread_orig,
381 #endif /* DEBUG || DEVELOPMENT */
382 	    HW_SPIN_TIMEOUT_DETAILS_ARG(to, st));
383 }
384 
385 static const struct hw_spin_policy hw_lck_ticket_spin_policy = {
386 	.hwsp_name              = "hw_lck_ticket_lock",
387 	.hwsp_timeout           = &TICKET_LOCK_PANIC_TIMEOUT,
388 	.hwsp_op_timeout        = hw_lck_ticket_timeout_panic,
389 };
390 
391 static const struct hw_spin_policy lck_ticket_spin_policy = {
392 	.hwsp_name              = "lck_ticket_lock",
393 	.hwsp_timeout           = &TICKET_LOCK_PANIC_TIMEOUT,
394 	.hwsp_lock_offset       = offsetof(lck_ticket_t, tu),
395 	.hwsp_op_timeout        = lck_ticket_timeout_panic,
396 };
397 
398 
399 #if CONFIG_PV_TICKET
400 
401 #if DEBUG || DEVELOPMENT
402 SCALABLE_COUNTER_DEFINE(ticket_wflag_cleared);
403 SCALABLE_COUNTER_DEFINE(ticket_wflag_still);
404 SCALABLE_COUNTER_DEFINE(ticket_just_unlock);
405 SCALABLE_COUNTER_DEFINE(ticket_kick_count);
406 SCALABLE_COUNTER_DEFINE(ticket_wait_count);
407 SCALABLE_COUNTER_DEFINE(ticket_already_count);
408 SCALABLE_COUNTER_DEFINE(ticket_spin_count);
409 #endif
410 
411 static inline void
hw_lck_ticket_unlock_inner_pv(hw_lck_ticket_t * lck)412 hw_lck_ticket_unlock_inner_pv(hw_lck_ticket_t *lck)
413 {
414 	const uint8_t cticket = (uint8_t) os_atomic_add(&lck->cticket,
415 	    HW_LCK_TICKET_LOCK_INCREMENT, acq_rel);
416 	if (__improbable(cticket & HW_LCK_TICKET_LOCK_PVWAITFLAG)) {
417 		hw_lck_ticket_unlock_kick_pv(lck, ticket_count(cticket));
418 	} else {
419 		PVTICKET_STATS_INC(just_unlock);
420 	}
421 }
422 #endif /* CONFIG_PV_TICKET */
423 
424 __header_always_inline void
hw_lck_ticket_unlock_inner(hw_lck_ticket_t * lck)425 hw_lck_ticket_unlock_inner(hw_lck_ticket_t *lck)
426 {
427 	_Atomic uint8_t *ctp = (_Atomic uint8_t *)&lck->cticket;
428 	uint8_t cticket;
429 
430 	/*
431 	 * Do not use os_atomic* here, we want non volatile atomics
432 	 * so that the compiler can codegen an `incb` on Intel.
433 	 */
434 	cticket = atomic_load_explicit(ctp, memory_order_relaxed);
435 	atomic_store_explicit(ctp, cticket +
436 	    HW_LCK_TICKET_LOCK_INCREMENT, memory_order_release);
437 }
438 
439 __header_always_inline void
hw_lck_ticket_unlock_internal_nopreempt(hw_lck_ticket_t * lck)440 hw_lck_ticket_unlock_internal_nopreempt(hw_lck_ticket_t *lck)
441 {
442 #if CONFIG_PV_TICKET
443 	if (lck->lck_is_pv) {
444 		hw_lck_ticket_unlock_inner_pv(lck);
445 	} else {
446 		hw_lck_ticket_unlock_inner(lck);
447 	}
448 #else
449 	hw_lck_ticket_unlock_inner(lck);
450 #endif
451 #if CONFIG_DTRACE
452 	LOCKSTAT_RECORD(LS_LCK_TICKET_LOCK_RELEASE, lck);
453 #endif /* CONFIG_DTRACE */
454 }
455 
456 __header_always_inline void
hw_lck_ticket_unlock_internal(hw_lck_ticket_t * lck)457 hw_lck_ticket_unlock_internal(hw_lck_ticket_t *lck)
458 {
459 	hw_lck_ticket_unlock_internal_nopreempt(lck);
460 	lock_enable_preemption();
461 }
462 
463 struct hw_lck_ticket_reserve_arg {
464 	uint8_t mt;
465 	bool    validate;
466 };
467 
468 /*
469  * On contention, poll for ownership
470  * Returns when the current ticket is observed equal to "mt"
471  */
472 __result_use_check
473 static hw_lock_status_t __attribute__((noinline))
hw_lck_ticket_contended(hw_lck_ticket_t * lck,struct hw_lck_ticket_reserve_arg arg,hw_spin_policy_t pol LCK_GRP_ARG (lck_grp_t * grp))474 hw_lck_ticket_contended(
475 	hw_lck_ticket_t        *lck,
476 	struct hw_lck_ticket_reserve_arg arg,
477 	hw_spin_policy_t       pol
478 	LCK_GRP_ARG(lck_grp_t *grp))
479 {
480 	hw_spin_timeout_t to = hw_spin_compute_timeout(pol);
481 	hw_spin_state_t   state = { };
482 	uint32_t          pv_spin_count = 0;
483 	hw_lck_ticket_t   value;
484 
485 #if CONFIG_DTRACE || LOCK_STATS
486 	uint64_t begin = 0;
487 	boolean_t stat_enabled = lck_grp_ticket_spin_enabled(lck LCK_GRP_ARG(grp));
488 
489 	if (__improbable(stat_enabled)) {
490 		begin = mach_absolute_time();
491 	}
492 #endif /* CONFIG_DTRACE || LOCK_STATS */
493 
494 	while (__improbable(!hw_spin_wait_until_n(TICKET_LOCK_SNOOP_LOOPS,
495 	    &lck->lck_value, value.lck_value,
496 	    (pv_spin_count++, equal_tickets(value.cticket, arg.mt))))) {
497 		if (state.hwss_deadline == 0 && !hw_spin_in_ppl(to)) {
498 			/* remember the droid we're looking for */
499 			PERCPU_GET(lck_spinlock_to_info)->extra = arg.mt;
500 		}
501 
502 		if (__improbable(!hw_spin_should_keep_spinning(lck, pol, to, &state))) {
503 #if CONFIG_PV_TICKET
504 			PVTICKET_STATS_ADD(spin_count, pv_spin_count);
505 #endif /* CONFIG_PV_TICKET */
506 #if CONFIG_DTRACE || LOCK_STATS
507 			if (__improbable(stat_enabled)) {
508 				lck_grp_ticket_update_spin(lck LCK_GRP_ARG(grp),
509 				    mach_absolute_time() - begin);
510 			}
511 			lck_grp_ticket_update_miss(lck LCK_GRP_ARG(grp));
512 #endif /* CONFIG_DTRACE || LOCK_STATS */
513 			return HW_LOCK_CONTENDED;
514 		}
515 
516 #if CONFIG_PV_TICKET
517 		if (lck->lck_is_pv) {
518 			os_atomic_or(&lck->cticket, HW_LCK_TICKET_LOCK_PVWAITFLAG, acq_rel);
519 			hw_lck_ticket_lock_wait_pv(lck, arg.mt);
520 		}
521 #endif /* CONFIG_PV_TICKET */
522 	}
523 
524 	/*
525 	 * We now have successfully acquired the lock
526 	 */
527 
528 #if CONFIG_PV_TICKET
529 	PVTICKET_STATS_ADD(spin_count, pv_spin_count);
530 	if (__improbable(value.cticket & HW_LCK_TICKET_LOCK_PVWAITFLAG)) {
531 		/*
532 		 * Try and clear the wait flag
533 		 */
534 		const hw_lck_ticket_t olck = {
535 			.cticket = value.cticket,
536 			.nticket = ticket_count(value.cticket)
537 		    + HW_LCK_TICKET_LOCK_INCREMENT,
538 		};
539 		const hw_lck_ticket_t nlck = {
540 			.cticket = ticket_count(value.cticket),
541 			.nticket = olck.nticket,
542 		};
543 		if (os_atomic_cmpxchg(&lck->tcurnext,
544 		    olck.tcurnext, nlck.tcurnext, acq_rel)) {
545 			PVTICKET_STATS_INC(wflag_cleared);
546 		} else {
547 			PVTICKET_STATS_INC(wflag_still);
548 		}
549 	}
550 #endif /* CONFIG_PV_TICKET */
551 #if CONFIG_DTRACE || LOCK_STATS
552 	if (__improbable(stat_enabled)) {
553 		lck_grp_ticket_update_spin(lck LCK_GRP_ARG(grp),
554 		    mach_absolute_time() - begin);
555 	}
556 	lck_grp_ticket_update_miss(lck LCK_GRP_ARG(grp));
557 	lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
558 #endif /* CONFIG_DTRACE || LOCK_STATS */
559 
560 	if (__improbable(arg.validate && !value.lck_valid)) {
561 		/*
562 		 * We got the lock, however the caller is
563 		 * hw_lck_ticket_lock_allow_invalid() and the
564 		 * lock has been invalidated while we were
565 		 * waiting for our turn.
566 		 *
567 		 * We need to unlock and pretend we failed.
568 		 */
569 		hw_lck_ticket_unlock_internal(lck);
570 		return HW_LOCK_INVALID;
571 	}
572 
573 	os_atomic_thread_fence(acquire);
574 	return HW_LOCK_ACQUIRED;
575 }
576 
577 static void __attribute__((noinline))
lck_ticket_contended(lck_ticket_t * tlock,uint8_t mt,thread_t cthread LCK_GRP_ARG (lck_grp_t * grp))578 lck_ticket_contended(lck_ticket_t *tlock, uint8_t mt, thread_t cthread
579     LCK_GRP_ARG(lck_grp_t *grp))
580 {
581 	if (cthread->ctid == tlock->lck_ticket_owner) {
582 		__lck_ticket_owned_panic(tlock);
583 	}
584 
585 	struct hw_lck_ticket_reserve_arg arg = { .mt = mt };
586 	lck_spinlock_timeout_set_orig_ctid(tlock->lck_ticket_owner);
587 	(void)hw_lck_ticket_contended(&tlock->tu, arg, &lck_ticket_spin_policy
588 	    LCK_GRP_ARG(grp));
589 	lck_spinlock_timeout_set_orig_ctid(0);
590 	tlock_mark_owned(tlock, cthread);
591 }
592 
593 static inline uint32_t
hw_lck_ticket_reserve_orig(hw_lck_ticket_t * lck)594 hw_lck_ticket_reserve_orig(hw_lck_ticket_t *lck)
595 {
596 	/*
597 	 * Atomically load both the entier lock state, and increment the
598 	 * "nticket". Wrap of the ticket field is OK as long as the total
599 	 * number of contending CPUs is < maximum ticket
600 	 */
601 	return os_atomic_add_orig(&lck->lck_value,
602 	           HW_LCK_TICKET_LOCK_INC_WORD, acquire);
603 }
604 
605 __header_always_inline void
hw_lck_ticket_lock_internal(hw_lck_ticket_t * lck LCK_GRP_ARG (lck_grp_t * grp))606 hw_lck_ticket_lock_internal(
607 	hw_lck_ticket_t        *lck
608 	LCK_GRP_ARG(lck_grp_t *grp))
609 {
610 	hw_lck_ticket_t tmp;
611 
612 	HW_LCK_TICKET_VERIFY(lck);
613 	tmp.lck_value = hw_lck_ticket_reserve_orig(lck);
614 
615 	if (__probable(equal_tickets(tmp.cticket, tmp.nticket))) {
616 		return lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
617 	}
618 
619 	/* Contention? branch to out of line contended block */
620 	struct hw_lck_ticket_reserve_arg arg = { .mt = tmp.nticket };
621 	(void)hw_lck_ticket_contended(lck, arg, &hw_lck_ticket_spin_policy
622 	    LCK_GRP_ARG(grp));
623 }
624 
625 void
hw_lck_ticket_lock_nopreempt(hw_lck_ticket_t * lck,lck_grp_t * grp)626 hw_lck_ticket_lock_nopreempt(hw_lck_ticket_t *lck, lck_grp_t *grp)
627 {
628 	hw_lck_ticket_lock_internal(lck LCK_GRP_ARG(grp));
629 }
630 
631 void
hw_lck_ticket_lock(hw_lck_ticket_t * lck,lck_grp_t * grp)632 hw_lck_ticket_lock(hw_lck_ticket_t *lck, lck_grp_t *grp)
633 {
634 	lock_disable_preemption_for_thread(current_thread());
635 	hw_lck_ticket_lock_internal(lck LCK_GRP_ARG(grp));
636 }
637 
638 __header_always_inline hw_lock_status_t
hw_lck_ticket_lock_to_internal(hw_lck_ticket_t * lck,hw_spin_policy_t pol LCK_GRP_ARG (lck_grp_t * grp))639 hw_lck_ticket_lock_to_internal(
640 	hw_lck_ticket_t        *lck,
641 	hw_spin_policy_t        pol
642 	LCK_GRP_ARG(lck_grp_t *grp))
643 {
644 	hw_lck_ticket_t tmp;
645 
646 	HW_LCK_TICKET_VERIFY(lck);
647 	tmp.lck_value = hw_lck_ticket_reserve_orig(lck);
648 
649 	if (__probable(equal_tickets(tmp.cticket, tmp.nticket))) {
650 		lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
651 		return HW_LOCK_ACQUIRED;
652 	}
653 
654 	/* Contention? branch to out of line contended block */
655 	struct hw_lck_ticket_reserve_arg arg = { .mt = tmp.nticket };
656 	return hw_lck_ticket_contended(lck, arg, pol LCK_GRP_ARG(grp));
657 }
658 
659 hw_lock_status_t
hw_lck_ticket_lock_nopreempt_to(hw_lck_ticket_t * lck,hw_spin_policy_t pol,lck_grp_t * grp)660 hw_lck_ticket_lock_nopreempt_to(
661 	hw_lck_ticket_t        *lck,
662 	hw_spin_policy_t        pol,
663 	lck_grp_t              *grp)
664 {
665 	return hw_lck_ticket_lock_to_internal(lck, pol LCK_GRP_ARG(grp));
666 }
667 
668 hw_lock_status_t
hw_lck_ticket_lock_to(hw_lck_ticket_t * lck,hw_spin_policy_t pol,lck_grp_t * grp)669 hw_lck_ticket_lock_to(
670 	hw_lck_ticket_t        *lck,
671 	hw_spin_policy_t        pol,
672 	lck_grp_t              *grp)
673 {
674 	lock_disable_preemption_for_thread(current_thread());
675 	return hw_lck_ticket_lock_to_internal(lck, pol LCK_GRP_ARG(grp));
676 }
677 
678 __header_always_inline void
lck_ticket_lock_internal(lck_ticket_t * tlock,thread_t cthread,__unused lck_grp_t * grp)679 lck_ticket_lock_internal(lck_ticket_t *tlock, thread_t cthread, __unused lck_grp_t *grp)
680 {
681 	hw_lck_ticket_t tmp;
682 
683 	LCK_TICKET_VERIFY(tlock);
684 	tmp.lck_value = hw_lck_ticket_reserve_orig(&tlock->tu);
685 
686 	if (__probable(tmp.cticket == tmp.nticket)) {
687 		tlock_mark_owned(tlock, cthread);
688 		return lck_grp_ticket_update_held(&tlock->tu LCK_GRP_ARG(grp));
689 	}
690 
691 	/* Contention? branch to out of line contended block */
692 	lck_ticket_contended(tlock, tmp.nticket, cthread LCK_GRP_ARG(grp));
693 }
694 
695 void
lck_ticket_lock(lck_ticket_t * tlock,__unused lck_grp_t * grp)696 lck_ticket_lock(lck_ticket_t *tlock, __unused lck_grp_t *grp)
697 {
698 	thread_t cthread = current_thread();
699 
700 	lock_disable_preemption_for_thread(cthread);
701 	lck_ticket_lock_internal(tlock, cthread, grp);
702 }
703 
704 void
lck_ticket_lock_nopreempt(lck_ticket_t * tlock,__unused lck_grp_t * grp)705 lck_ticket_lock_nopreempt(lck_ticket_t *tlock, __unused lck_grp_t *grp)
706 {
707 	thread_t cthread = current_thread();
708 
709 	lck_ticket_lock_internal(tlock, cthread, grp);
710 }
711 
712 __header_always_inline bool
hw_lck_ticket_lock_try_internal(hw_lck_ticket_t * lck,bool nopreempt LCK_GRP_ARG (lck_grp_t * grp))713 hw_lck_ticket_lock_try_internal(
714 	hw_lck_ticket_t        *lck,
715 	bool                    nopreempt
716 	LCK_GRP_ARG(lck_grp_t *grp))
717 {
718 	hw_lck_ticket_t olck, nlck;
719 
720 	HW_LCK_TICKET_VERIFY(lck);
721 	if (!nopreempt) {
722 		lock_disable_preemption_for_thread(current_thread());
723 	}
724 
725 	os_atomic_rmw_loop(&lck->tcurnext, olck.tcurnext, nlck.tcurnext, acquire, {
726 		if (__improbable(!equal_tickets(olck.cticket, olck.nticket))) {
727 		        os_atomic_rmw_loop_give_up({
728 				if (!nopreempt) {
729 				        lock_enable_preemption();
730 				}
731 				return false;
732 			});
733 		}
734 		nlck.cticket = olck.cticket;
735 		nlck.nticket = olck.nticket + HW_LCK_TICKET_LOCK_INCREMENT;
736 	});
737 
738 	lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
739 	return true;
740 }
741 
742 bool
hw_lck_ticket_lock_try(hw_lck_ticket_t * lck,lck_grp_t * grp)743 hw_lck_ticket_lock_try(hw_lck_ticket_t *lck, lck_grp_t *grp)
744 {
745 	return hw_lck_ticket_lock_try_internal(lck, false LCK_GRP_ARG(grp));
746 }
747 
748 bool
hw_lck_ticket_lock_try_nopreempt(hw_lck_ticket_t * lck,lck_grp_t * grp)749 hw_lck_ticket_lock_try_nopreempt(hw_lck_ticket_t *lck, lck_grp_t *grp)
750 {
751 	return hw_lck_ticket_lock_try_internal(lck, true LCK_GRP_ARG(grp));
752 }
753 
754 __header_always_inline bool
lck_ticket_lock_try_internal(lck_ticket_t * tlock,__unused lck_grp_t * grp,bool nopreempt)755 lck_ticket_lock_try_internal(lck_ticket_t *tlock, __unused lck_grp_t *grp, bool nopreempt)
756 {
757 	thread_t cthread = current_thread();
758 	hw_lck_ticket_t olck, nlck;
759 
760 	LCK_TICKET_VERIFY(tlock);
761 	if (!nopreempt) {
762 		lock_disable_preemption_for_thread(cthread);
763 	}
764 
765 	os_atomic_rmw_loop(&tlock->tu.tcurnext, olck.tcurnext, nlck.tcurnext, acquire, {
766 		if (__improbable(!equal_tickets(olck.cticket, olck.nticket))) {
767 		        os_atomic_rmw_loop_give_up({
768 				if (!nopreempt) {
769 				        lock_enable_preemption();
770 				}
771 				return false;
772 			});
773 		}
774 		nlck.cticket = olck.cticket;
775 		nlck.nticket = olck.nticket + HW_LCK_TICKET_LOCK_INCREMENT;
776 	});
777 
778 	tlock_mark_owned(tlock, cthread);
779 	lck_grp_ticket_update_held(&tlock->tu LCK_GRP_ARG(grp));
780 	return true;
781 }
782 
783 bool
lck_ticket_lock_try(lck_ticket_t * tlock,__unused lck_grp_t * grp)784 lck_ticket_lock_try(lck_ticket_t *tlock, __unused lck_grp_t *grp)
785 {
786 	return lck_ticket_lock_try_internal(tlock, grp, false);
787 }
788 
789 bool
lck_ticket_lock_try_nopreempt(lck_ticket_t * tlock,__unused lck_grp_t * grp)790 lck_ticket_lock_try_nopreempt(lck_ticket_t *tlock, __unused lck_grp_t *grp)
791 {
792 	return lck_ticket_lock_try_internal(tlock, grp, true);
793 }
794 
795 /*
796  * Assembly routine that
797  * Returns a "reserved" lock or a lock where `lck_valid` is 0.
798  *
799  * More or less equivalent to this:
800  *
801  *	hw_lck_ticket_t
802  *	hw_lck_ticket_lock_allow_invalid(hw_lck_ticket_t *lck)
803  *	{
804  *		hw_lck_ticket_t o, n;
805  *
806  *		os_atomic_rmw_loop(lck, o, n, acquire, {
807  *			if (__improbable(!o.lck_valid)) {
808  *				os_atomic_rmw_loop_give_up({
809  *					return (hw_lck_ticket_t){ 0 };
810  *				});
811  *			}
812  *			n = o;
813  *			n.nticket++;
814  *		});
815  *		return o;
816  *	}
817  */
818 
819 #if KASAN_TBI
820 extern hw_lck_ticket_t
821 hw_lck_ticket_reserve_orig_allow_invalid(hw_lck_ticket_t *lck, const uint8_t *tag_addr);
822 #else /* KASAN_TBI */
823 extern hw_lck_ticket_t
824 hw_lck_ticket_reserve_orig_allow_invalid(hw_lck_ticket_t *lck);
825 #endif /* KASAN_TBI */
826 
827 bool
hw_lck_ticket_reserve_nopreempt(hw_lck_ticket_t * lck,uint32_t * ticket,lck_grp_t * grp)828 hw_lck_ticket_reserve_nopreempt(hw_lck_ticket_t *lck, uint32_t *ticket, lck_grp_t *grp)
829 {
830 	hw_lck_ticket_t tmp;
831 
832 	HW_LCK_TICKET_VERIFY(lck);
833 	tmp.lck_value = *ticket = hw_lck_ticket_reserve_orig(lck);
834 
835 	if (__probable(tmp.cticket == tmp.nticket)) {
836 		lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
837 		return true;
838 	}
839 
840 	return false;
841 }
842 
843 bool
hw_lck_ticket_reserve(hw_lck_ticket_t * lck,uint32_t * ticket,lck_grp_t * grp)844 hw_lck_ticket_reserve(hw_lck_ticket_t *lck, uint32_t *ticket, lck_grp_t *grp)
845 {
846 	lock_disable_preemption_for_thread(current_thread());
847 
848 	return hw_lck_ticket_reserve_nopreempt(lck, ticket, grp);
849 }
850 
851 hw_lock_status_t
hw_lck_ticket_reserve_allow_invalid(hw_lck_ticket_t * lck,uint32_t * ticket,lck_grp_t * grp)852 hw_lck_ticket_reserve_allow_invalid(hw_lck_ticket_t *lck, uint32_t *ticket, lck_grp_t *grp)
853 {
854 	hw_lck_ticket_t tmp;
855 
856 	lock_disable_preemption_for_thread(current_thread());
857 
858 #if KASAN_TBI
859 	/* Expand the check to also include the tag value. See machine_routines_asm.s for the details */
860 	tmp = hw_lck_ticket_reserve_orig_allow_invalid(lck,
861 	    kasan_tbi_get_tag_address((vm_offset_t)lck));
862 #else /* KASAN_TBI */
863 	tmp = hw_lck_ticket_reserve_orig_allow_invalid(lck);
864 #endif /* KASAN_TBI */
865 	*ticket = tmp.lck_value;
866 
867 	if (__improbable(!tmp.lck_valid)) {
868 		lock_enable_preemption();
869 		return HW_LOCK_INVALID;
870 	}
871 
872 	if (__probable(equal_tickets(tmp.cticket, tmp.nticket))) {
873 		lck_grp_ticket_update_held(lck LCK_GRP_ARG(grp));
874 		return HW_LOCK_ACQUIRED;
875 	}
876 
877 	return HW_LOCK_CONTENDED;
878 }
879 
880 hw_lock_status_t
hw_lck_ticket_wait(hw_lck_ticket_t * lck,uint32_t ticket,hw_spin_policy_t pol,lck_grp_t * grp)881 hw_lck_ticket_wait(
882 	hw_lck_ticket_t        *lck,
883 	uint32_t                ticket,
884 	hw_spin_policy_t        pol,
885 	lck_grp_t              *grp)
886 {
887 	hw_lck_ticket_t tmp = { .lck_value = ticket };
888 	struct hw_lck_ticket_reserve_arg arg = { .mt = tmp.nticket };
889 
890 	if (pol == NULL) {
891 		pol = &hw_lck_ticket_spin_policy;
892 	}
893 	return hw_lck_ticket_contended(lck, arg, pol LCK_GRP_ARG(grp));
894 }
895 
896 hw_lock_status_t
hw_lck_ticket_lock_allow_invalid(hw_lck_ticket_t * lck,hw_spin_policy_t pol,lck_grp_t * grp)897 hw_lck_ticket_lock_allow_invalid(
898 	hw_lck_ticket_t        *lck,
899 	hw_spin_policy_t        pol,
900 	lck_grp_t              *grp)
901 {
902 	hw_lock_status_t st;
903 	hw_lck_ticket_t tmp;
904 
905 	st = hw_lck_ticket_reserve_allow_invalid(lck, &tmp.lck_value, grp);
906 
907 	if (__improbable(st == HW_LOCK_CONTENDED)) {
908 		/* Contention? branch to out of line contended block */
909 		struct hw_lck_ticket_reserve_arg arg = {
910 			.mt = tmp.nticket,
911 			.validate = true,
912 		};
913 		return hw_lck_ticket_contended(lck, arg, pol LCK_GRP_ARG(grp));
914 	}
915 
916 	return st;
917 }
918 
919 void
hw_lck_ticket_invalidate(hw_lck_ticket_t * lck)920 hw_lck_ticket_invalidate(hw_lck_ticket_t *lck)
921 {
922 	hw_lck_ticket_t tmp = { .lck_valid = 1 };
923 
924 	os_atomic_andnot(&lck->lck_value, tmp.lck_value, relaxed);
925 }
926 
927 void
hw_lck_ticket_unlock_nopreempt(hw_lck_ticket_t * lck)928 hw_lck_ticket_unlock_nopreempt(hw_lck_ticket_t *lck)
929 {
930 	HW_LCK_TICKET_VERIFY(lck);
931 	hw_lck_ticket_unlock_internal_nopreempt(lck);
932 }
933 
934 void
hw_lck_ticket_unlock(hw_lck_ticket_t * lck)935 hw_lck_ticket_unlock(hw_lck_ticket_t *lck)
936 {
937 	HW_LCK_TICKET_VERIFY(lck);
938 	hw_lck_ticket_unlock_internal(lck);
939 }
940 
941 void
lck_ticket_unlock_nopreempt(lck_ticket_t * tlock)942 lck_ticket_unlock_nopreempt(lck_ticket_t *tlock)
943 {
944 	LCK_TICKET_VERIFY(tlock);
945 	LCK_TICKET_UNLOCK_VERIFY(tlock);
946 	os_atomic_store(&tlock->lck_ticket_owner, 0, relaxed);
947 	hw_lck_ticket_unlock_internal_nopreempt(&tlock->tu);
948 }
949 
950 void
lck_ticket_unlock(lck_ticket_t * tlock)951 lck_ticket_unlock(lck_ticket_t *tlock)
952 {
953 	LCK_TICKET_VERIFY(tlock);
954 	LCK_TICKET_UNLOCK_VERIFY(tlock);
955 	os_atomic_store(&tlock->lck_ticket_owner, 0, relaxed);
956 	hw_lck_ticket_unlock_internal(&tlock->tu);
957 }
958 
959 void
lck_ticket_assert_owned(const lck_ticket_t * tlock)960 lck_ticket_assert_owned(const lck_ticket_t *tlock)
961 {
962 	if (current_thread()->ctid != tlock->lck_ticket_owner) {
963 		__lck_ticket_not_owned_panic(tlock);
964 	}
965 }
966 
967 void
lck_ticket_assert_not_owned(const lck_ticket_t * tlock)968 lck_ticket_assert_not_owned(const lck_ticket_t *tlock)
969 {
970 	if (current_thread()->ctid == tlock->lck_ticket_owner) {
971 		__lck_ticket_owned_panic(tlock);
972 	}
973 }
974