]> git.saurik.com Git - apple/xnu.git/blob - bsd/kern/sys_ulock.c
xnu-3789.70.16.tar.gz
[apple/xnu.git] / bsd / kern / sys_ulock.c
1 /*
2 * Copyright (c) 2015 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 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/ioctl.h>
32 #include <sys/file_internal.h>
33 #include <sys/proc_internal.h>
34 #include <sys/kernel.h>
35 #include <sys/guarded.h>
36 #include <sys/stat.h>
37 #include <sys/malloc.h>
38 #include <sys/sysproto.h>
39 #include <sys/pthread_shims.h>
40
41 #include <mach/mach_types.h>
42
43 #include <kern/cpu_data.h>
44 #include <kern/mach_param.h>
45 #include <kern/kern_types.h>
46 #include <kern/assert.h>
47 #include <kern/kalloc.h>
48 #include <kern/thread.h>
49 #include <kern/clock.h>
50 #include <kern/ledger.h>
51 #include <kern/policy_internal.h>
52 #include <kern/task.h>
53 #include <kern/telemetry.h>
54 #include <kern/waitq.h>
55 #include <kern/sched_prim.h>
56 #include <kern/zalloc.h>
57 #include <kern/debug.h>
58
59 #include <pexpert/pexpert.h>
60
61 #define XNU_TEST_BITMAP
62 #include <kern/bits.h>
63
64 #include <sys/ulock.h>
65
66 /*
67 * How ulock promotion works:
68 *
69 * There’s a requested policy field on every thread called ‘promotions’, which
70 * expresses which ulock promotions are happening to this thread.
71 * The promotion priority saturates until the promotion count goes to 0.
72 *
73 * We also track effective promotion qos, which is the qos before clamping.
74 * This value is used for promoting a thread that another thread is waiting on,
75 * so that the lock owner reinflates to the right priority after unclamping.
76 *
77 * This also works for non-QoS threads, which can donate base priority to QoS
78 * and non-QoS threads alike.
79 *
80 * ulock wait applies a promotion to the owner communicated through
81 * UL_UNFAIR_LOCK as waiters block, and that promotion is saturated as long as
82 * there is still an owner. In ulock wake, if the waker is still the owner,
83 * then it clears its ownership and drops the boost. It does NOT transfer
84 * ownership/priority boost to the new thread. Instead, it selects the
85 * waiting thread with the highest base priority to be woken next, and
86 * relies on that thread to carry the torch for the other waiting threads.
87 */
88
89 static lck_grp_t *ull_lck_grp;
90 static lck_mtx_t ull_table_lock;
91
92 #define ull_global_lock() lck_mtx_lock(&ull_table_lock)
93 #define ull_global_unlock() lck_mtx_unlock(&ull_table_lock)
94
95 #define ull_lock(ull) lck_mtx_lock(&ull->ull_lock)
96 #define ull_unlock(ull) lck_mtx_unlock(&ull->ull_lock)
97 #define ull_assert_owned(ull) LCK_MTX_ASSERT(&ull->ull_lock, LCK_MTX_ASSERT_OWNED)
98
99 #define ULOCK_TO_EVENT(ull) ((event_t)ull)
100 #define EVENT_TO_ULOCK(event) ((ull_t *)event)
101
102 typedef struct __attribute__((packed)) {
103 user_addr_t ulk_addr;
104 pid_t ulk_pid;
105 } ulk_t;
106
107 inline static bool
108 ull_key_match(ulk_t *a, ulk_t *b)
109 {
110 return ((a->ulk_pid == b->ulk_pid) &&
111 (a->ulk_addr == b->ulk_addr));
112 }
113
114 typedef struct ull {
115 /*
116 * ull_owner is the most recent known value for the owner of this ulock
117 * i.e. it may be out of date WRT the real value in userspace.
118 */
119 thread_t ull_owner; /* holds +1 thread reference */
120 ulk_t ull_key;
121 ulk_t ull_saved_key;
122 lck_mtx_t ull_lock;
123 int32_t ull_nwaiters;
124 int32_t ull_max_nwaiters;
125 int32_t ull_refcount;
126 struct promote_token ull_promote_token;
127 queue_chain_t ull_hash_link;
128 uint8_t ull_opcode;
129 } ull_t;
130
131 static const bool ull_debug = false;
132
133 extern void ulock_initialize(void);
134 extern void kdp_ulock_find_owner(struct waitq * waitq, event64_t event, thread_waitinfo_t *waitinfo);
135
136 #define ULL_MUST_EXIST 0x0001
137 static ull_t *ull_get(ulk_t *, uint32_t);
138 static void ull_put(ull_t *);
139
140 static thread_t ull_promote_owner_locked(ull_t* ull, thread_t thread);
141
142 #if DEVELOPMENT || DEBUG
143 static int ull_simulate_copyin_fault = 0;
144 static int ull_panic_on_corruption = 0;
145
146 static void
147 ull_dump(ull_t *ull)
148 {
149 kprintf("ull\t%p\n", ull);
150 kprintf("ull_key.ulk_pid\t%d\n", ull->ull_key.ulk_pid);
151 kprintf("ull_key.ulk_addr\t%p\n", (void *)(ull->ull_key.ulk_addr));
152 kprintf("ull_saved_key.ulk_pid\t%d\n", ull->ull_saved_key.ulk_pid);
153 kprintf("ull_saved_key.ulk_addr\t%p\n", (void *)(ull->ull_saved_key.ulk_addr));
154 kprintf("ull_nwaiters\t%d\n", ull->ull_nwaiters);
155 kprintf("ull_max_nwaiters\t%d\n", ull->ull_max_nwaiters);
156 kprintf("ull_refcount\t%d\n", ull->ull_refcount);
157 kprintf("ull_opcode\t%d\n\n", ull->ull_opcode);
158 kprintf("ull_owner\t0x%llx\n\n", thread_tid(ull->ull_owner));
159 kprintf("ull_promote_token\t%d, %d\n\n", ull->ull_promote_token.pt_basepri, ull->ull_promote_token.pt_qos);
160 }
161 #endif
162
163 static int ull_hash_buckets;
164 static queue_head_t *ull_bucket;
165 static uint32_t ull_nzalloc = 0;
166 static zone_t ull_zone;
167
168 static __inline__ uint32_t
169 ull_hash_index(char *key, size_t length)
170 {
171 uint32_t hash = jenkins_hash(key, length);
172
173 hash &= (ull_hash_buckets - 1);
174
175 return hash;
176 }
177
178 /* Ensure that the key structure is packed,
179 * so that no undefined memory is passed to
180 * ull_hash_index()
181 */
182 static_assert(sizeof(ulk_t) == sizeof(user_addr_t) + sizeof(pid_t));
183
184 #define ULL_INDEX(keyp) ull_hash_index((char *)keyp, sizeof *keyp)
185
186 void
187 ulock_initialize(void)
188 {
189 ull_lck_grp = lck_grp_alloc_init("ulocks", NULL);
190 lck_mtx_init(&ull_table_lock, ull_lck_grp, NULL);
191
192 assert(thread_max > 16);
193 /* Size ull_hash_buckets based on thread_max.
194 * Round up to nearest power of 2, then divide by 4
195 */
196 ull_hash_buckets = (1 << (bit_ceiling(thread_max) - 2));
197
198 kprintf("%s>thread_max=%d, ull_hash_buckets=%d\n", __FUNCTION__, thread_max, ull_hash_buckets);
199 assert(ull_hash_buckets >= thread_max/4);
200
201 ull_bucket = (queue_head_t *)kalloc(sizeof(queue_head_t) * ull_hash_buckets);
202 assert(ull_bucket != NULL);
203
204 for (int i = 0; i < ull_hash_buckets; i++) {
205 queue_init(&ull_bucket[i]);
206 }
207
208 ull_zone = zinit(sizeof(ull_t),
209 thread_max * sizeof(ull_t),
210 0, "ulocks");
211
212 zone_change(ull_zone, Z_NOENCRYPT, TRUE);
213
214 #if DEVELOPMENT || DEBUG
215 if (!PE_parse_boot_argn("ulock_panic_on_corruption",
216 &ull_panic_on_corruption, sizeof(ull_panic_on_corruption))) {
217 ull_panic_on_corruption = 0;
218 }
219 #endif
220 }
221
222 #if DEVELOPMENT || DEBUG
223 /* Count the number of hash entries for a given pid.
224 * if pid==0, dump the whole table.
225 */
226 static int
227 ull_hash_dump(pid_t pid)
228 {
229 int count = 0;
230 ull_global_lock();
231 if (pid == 0) {
232 kprintf("%s>total number of ull_t allocated %d\n", __FUNCTION__, ull_nzalloc);
233 kprintf("%s>BEGIN\n", __FUNCTION__);
234 }
235 for (int i = 0; i < ull_hash_buckets; i++) {
236 if (!queue_empty(&ull_bucket[i])) {
237 ull_t *elem;
238 if (pid == 0) {
239 kprintf("%s>index %d:\n", __FUNCTION__, i);
240 }
241 qe_foreach_element(elem, &ull_bucket[i], ull_hash_link) {
242 if ((pid == 0) || (pid == elem->ull_key.ulk_pid)) {
243 ull_dump(elem);
244 count++;
245 }
246 }
247 }
248 }
249 if (pid == 0) {
250 kprintf("%s>END\n", __FUNCTION__);
251 ull_nzalloc = 0;
252 }
253 ull_global_unlock();
254 return count;
255 }
256 #endif
257
258 static ull_t *
259 ull_alloc(ulk_t *key)
260 {
261 ull_t *ull = (ull_t *)zalloc(ull_zone);
262 assert(ull != NULL);
263
264 ull->ull_refcount = 1;
265 ull->ull_key = *key;
266 ull->ull_saved_key = *key;
267 ull->ull_nwaiters = 0;
268 ull->ull_max_nwaiters = 0;
269 ull->ull_opcode = 0;
270
271 ull->ull_owner = THREAD_NULL;
272 ull->ull_promote_token = PROMOTE_TOKEN_INIT;
273
274 lck_mtx_init(&ull->ull_lock, ull_lck_grp, NULL);
275
276 ull_nzalloc++;
277 return ull;
278 }
279
280 static void
281 ull_free(ull_t *ull)
282 {
283 assert(ull->ull_owner == THREAD_NULL);
284
285 lck_mtx_assert(&ull->ull_lock, LCK_ASSERT_NOTOWNED);
286
287 lck_mtx_destroy(&ull->ull_lock, ull_lck_grp);
288
289 zfree(ull_zone, ull);
290 }
291
292 /* Finds an existing ulock structure (ull_t), or creates a new one.
293 * If MUST_EXIST flag is set, returns NULL instead of creating a new one.
294 * The ulock structure is returned with ull_lock locked
295 *
296 * TODO: Per-bucket lock to reduce contention on global lock
297 */
298 static ull_t *
299 ull_get(ulk_t *key, uint32_t flags)
300 {
301 ull_t *ull = NULL;
302 uint i = ULL_INDEX(key);
303 ull_t *elem;
304 ull_global_lock();
305 qe_foreach_element(elem, &ull_bucket[i], ull_hash_link) {
306 ull_lock(elem);
307 if (ull_key_match(&elem->ull_key, key)) {
308 ull = elem;
309 break;
310 } else {
311 ull_unlock(elem);
312 }
313 }
314 if (ull == NULL) {
315 if (flags & ULL_MUST_EXIST) {
316 /* Must already exist (called from wake) */
317 ull_global_unlock();
318 return NULL;
319 }
320
321 /* NRG maybe drop the ull_global_lock before the kalloc,
322 * then take the lock and check again for a key match
323 * and either use the new ull_t or free it.
324 */
325
326 ull = ull_alloc(key);
327
328 if (ull == NULL) {
329 ull_global_unlock();
330 return NULL;
331 }
332
333 ull_lock(ull);
334
335 enqueue(&ull_bucket[i], &ull->ull_hash_link);
336 }
337
338 ull->ull_refcount++;
339
340 ull_global_unlock();
341
342 return ull; /* still locked */
343 }
344
345 /*
346 * Must be called with ull_lock held
347 */
348 static void
349 ull_put(ull_t *ull)
350 {
351 ull_assert_owned(ull);
352 int refcount = --ull->ull_refcount;
353 assert(refcount == 0 ? (ull->ull_key.ulk_pid == 0 && ull->ull_key.ulk_addr == 0) : 1);
354 ull_unlock(ull);
355
356 if (refcount > 0) {
357 return;
358 }
359
360 ull_global_lock();
361 remqueue(&ull->ull_hash_link);
362 ull_global_unlock();
363
364 #if DEVELOPMENT || DEBUG
365 if (ull_debug) {
366 kprintf("%s>", __FUNCTION__);
367 ull_dump(ull);
368 }
369 #endif
370 ull_free(ull);
371 }
372
373 int
374 ulock_wait(struct proc *p, struct ulock_wait_args *args, int32_t *retval)
375 {
376 uint opcode = args->operation & UL_OPCODE_MASK;
377 uint flags = args->operation & UL_FLAGS_MASK;
378 int ret = 0;
379 thread_t self = current_thread();
380 int id = thread_tid(self);
381 ulk_t key;
382
383 /* involved threads - each variable holds +1 ref if not null */
384 thread_t owner_thread = THREAD_NULL;
385 thread_t old_owner = THREAD_NULL;
386 thread_t old_lingering_owner = THREAD_NULL;
387 sched_call_t workq_callback = NULL;
388
389 if (ull_debug) {
390 kprintf("[%d]%s>ENTER opcode %d addr %llx value %llx timeout %d flags %x\n", id, __FUNCTION__, opcode, (unsigned long long)(args->addr), args->value, args->timeout, flags);
391 }
392
393 if ((flags & ULF_WAIT_MASK) != flags) {
394 ret = EINVAL;
395 goto munge_retval;
396 }
397
398 boolean_t set_owner = FALSE;
399
400 switch (opcode) {
401 case UL_UNFAIR_LOCK:
402 set_owner = TRUE;
403 break;
404 case UL_COMPARE_AND_WAIT:
405 break;
406 default:
407 if (ull_debug) {
408 kprintf("[%d]%s>EINVAL opcode %d addr 0x%llx flags 0x%x\n",
409 id, __FUNCTION__, opcode,
410 (unsigned long long)(args->addr), flags);
411 }
412 ret = EINVAL;
413 goto munge_retval;
414 }
415
416 /* 32-bit lock type for UL_COMPARE_AND_WAIT and UL_UNFAIR_LOCK */
417 uint32_t value = 0;
418
419 if ((args->addr == 0) || (args->addr % _Alignof(_Atomic(typeof(value))))) {
420 ret = EINVAL;
421 goto munge_retval;
422 }
423
424 key.ulk_pid = p->p_pid;
425 key.ulk_addr = args->addr;
426
427 if (flags & ULF_WAIT_WORKQ_DATA_CONTENTION) {
428 workq_callback = workqueue_get_sched_callback();
429 workq_callback = thread_disable_sched_call(self, workq_callback);
430 }
431
432 ull_t *ull = ull_get(&key, 0);
433 if (ull == NULL) {
434 ret = ENOMEM;
435 goto munge_retval;
436 }
437 /* ull is locked */
438
439 ull->ull_nwaiters++;
440
441 if (ull->ull_nwaiters > ull->ull_max_nwaiters) {
442 ull->ull_max_nwaiters = ull->ull_nwaiters;
443 }
444
445 if (ull->ull_opcode == 0) {
446 ull->ull_opcode = opcode;
447 } else if (ull->ull_opcode != opcode) {
448 ull_unlock(ull);
449 ret = EDOM;
450 goto out;
451 }
452
453 /*
454 * We don't want this copyin to get wedged behind VM operations,
455 * but we have to read the userspace value under the ull lock for correctness.
456 *
457 * Until <rdar://problem/24999882> exists,
458 * fake it by disabling preemption across copyin, which forces any
459 * vm_fault we encounter to fail.
460 */
461 uint64_t val64; /* copyin_word always zero-extends to 64-bits */
462
463 disable_preemption();
464 int copy_ret = copyin_word(args->addr, &val64, sizeof(value));
465 enable_preemption();
466
467 value = (uint32_t)val64;
468
469 #if DEVELOPMENT || DEBUG
470 /* Occasionally simulate copyin finding the user address paged out */
471 if (((ull_simulate_copyin_fault == p->p_pid) || (ull_simulate_copyin_fault == 1)) && (copy_ret == 0)) {
472 static _Atomic int fault_inject = 0;
473 if (__c11_atomic_fetch_add(&fault_inject, 1, __ATOMIC_RELAXED) % 73 == 0) {
474 copy_ret = EFAULT;
475 }
476 }
477 #endif
478 if (copy_ret != 0) {
479 ull_unlock(ull);
480
481 /* copyin() will return an error if the access to the user addr would have faulted,
482 * so just return and let the user level code fault it in.
483 */
484 ret = copy_ret;
485 goto out;
486 }
487
488 if (value != args->value) {
489 /* Lock value has changed from expected so bail out */
490 ull_unlock(ull);
491 if (ull_debug) {
492 kprintf("[%d]%s>Lock value %d has changed from expected %d so bail out\n",
493 id, __FUNCTION__, value, (uint32_t)(args->value));
494 }
495 goto out;
496 }
497
498 if (set_owner) {
499 mach_port_name_t owner_name = ulock_owner_value_to_port_name(args->value);
500 owner_thread = port_name_to_thread_for_ulock(owner_name);
501
502 /* HACK: don't bail on MACH_PORT_DEAD, to avoid blowing up the no-tsd pthread lock */
503 if (owner_name != MACH_PORT_DEAD && owner_thread == THREAD_NULL) {
504 #if DEBUG || DEVELOPMENT
505 if (ull_panic_on_corruption) {
506 if (flags & ULF_NO_ERRNO) {
507 // ULF_NO_ERRNO is used by libplatform ulocks, but not libdispatch ones.
508 // Don't panic on libdispatch ulock corruptions; the userspace likely
509 // mismanaged a dispatch queue.
510 panic("ulock_wait: ulock is corrupted; value=0x%x, ull=%p",
511 (uint32_t)(args->value), ull);
512 }
513 }
514 #endif
515 /*
516 * Translation failed - even though the lock value is up to date,
517 * whatever was stored in the lock wasn't actually a thread port.
518 */
519 ull_unlock(ull);
520 ret = EOWNERDEAD;
521 goto out;
522 }
523 /* owner_thread has a +1 reference */
524
525 /*
526 * At this point, I know:
527 * a) owner_thread is definitely the current owner, because I just read the value
528 * b) owner_thread is either:
529 * i) holding the user lock or
530 * ii) has just unlocked the user lock after I looked
531 * and is heading toward the kernel to call ull_wake.
532 * If so, it's going to have to wait for the ull mutex.
533 *
534 * Therefore, I can promote its priority to match mine, and I can rely on it to
535 * come by later to issue the wakeup and lose its promotion.
536 */
537
538 old_owner = ull_promote_owner_locked(ull, owner_thread);
539 }
540
541 wait_result_t wr;
542 uint32_t timeout = args->timeout;
543 thread_set_pending_block_hint(self, kThreadWaitUserLock);
544 if (timeout) {
545 wr = assert_wait_timeout(ULOCK_TO_EVENT(ull), THREAD_ABORTSAFE, timeout, NSEC_PER_USEC);
546 } else {
547 wr = assert_wait(ULOCK_TO_EVENT(ull), THREAD_ABORTSAFE);
548 }
549
550 ull_unlock(ull);
551
552 if (ull_debug) {
553 kprintf("[%d]%s>after assert_wait() returned %d\n", id, __FUNCTION__, wr);
554 }
555
556 if (set_owner && owner_thread != THREAD_NULL && wr == THREAD_WAITING) {
557 wr = thread_handoff(owner_thread);
558 /* owner_thread ref is consumed */
559 owner_thread = THREAD_NULL;
560 } else {
561 /* NRG At some point this should be a continuation based block, so that we can avoid saving the full kernel context. */
562 wr = thread_block(NULL);
563 }
564 if (ull_debug) {
565 kprintf("[%d]%s>thread_block() returned %d\n", id, __FUNCTION__, wr);
566 }
567 switch (wr) {
568 case THREAD_AWAKENED:
569 break;
570 case THREAD_TIMED_OUT:
571 ret = ETIMEDOUT;
572 break;
573 case THREAD_INTERRUPTED:
574 case THREAD_RESTART:
575 default:
576 ret = EINTR;
577 break;
578 }
579
580 out:
581 ull_lock(ull);
582 *retval = --ull->ull_nwaiters;
583 if (ull->ull_nwaiters == 0) {
584 /*
585 * If the wait was canceled early, we might need to
586 * clear out the lingering owner reference before
587 * freeing the ull.
588 */
589 if (ull->ull_owner != THREAD_NULL) {
590 old_lingering_owner = ull_promote_owner_locked(ull, THREAD_NULL);
591 }
592
593 assert(ull->ull_owner == THREAD_NULL);
594
595 ull->ull_key.ulk_pid = 0;
596 ull->ull_key.ulk_addr = 0;
597 ull->ull_refcount--;
598 assert(ull->ull_refcount > 0);
599 }
600 ull_put(ull);
601
602 if (owner_thread != THREAD_NULL) {
603 thread_deallocate(owner_thread);
604 }
605
606 if (old_owner != THREAD_NULL) {
607 thread_deallocate(old_owner);
608 }
609
610 if (old_lingering_owner != THREAD_NULL) {
611 thread_deallocate(old_lingering_owner);
612 }
613
614 assert(*retval >= 0);
615
616 munge_retval:
617 if (workq_callback) {
618 thread_reenable_sched_call(self, workq_callback);
619 }
620
621 if ((flags & ULF_NO_ERRNO) && (ret != 0)) {
622 *retval = -ret;
623 ret = 0;
624 }
625 return ret;
626 }
627
628 int
629 ulock_wake(struct proc *p, struct ulock_wake_args *args, __unused int32_t *retval)
630 {
631 uint opcode = args->operation & UL_OPCODE_MASK;
632 uint flags = args->operation & UL_FLAGS_MASK;
633 int ret = 0;
634 int id = thread_tid(current_thread());
635 ulk_t key;
636
637 /* involved threads - each variable holds +1 ref if not null */
638 thread_t wake_thread = THREAD_NULL;
639 thread_t old_owner = THREAD_NULL;
640
641 if (ull_debug) {
642 kprintf("[%d]%s>ENTER opcode %d addr %llx flags %x\n",
643 id, __FUNCTION__, opcode, (unsigned long long)(args->addr), flags);
644 }
645
646 if ((flags & ULF_WAKE_MASK) != flags) {
647 ret = EINVAL;
648 goto munge_retval;
649 }
650
651 #if DEVELOPMENT || DEBUG
652 if (opcode == UL_DEBUG_HASH_DUMP_PID) {
653 *retval = ull_hash_dump(p->p_pid);
654 return ret;
655 } else if (opcode == UL_DEBUG_HASH_DUMP_ALL) {
656 *retval = ull_hash_dump(0);
657 return ret;
658 } else if (opcode == UL_DEBUG_SIMULATE_COPYIN_FAULT) {
659 ull_simulate_copyin_fault = (int)(args->wake_value);
660 return ret;
661 }
662 #endif
663
664 if (args->addr == 0) {
665 ret = EINVAL;
666 goto munge_retval;
667 }
668
669 if (flags & ULF_WAKE_THREAD) {
670 if (flags & ULF_WAKE_ALL) {
671 ret = EINVAL;
672 goto munge_retval;
673 }
674 mach_port_name_t wake_thread_name = (mach_port_name_t)(args->wake_value);
675 wake_thread = port_name_to_thread_for_ulock(wake_thread_name);
676 if (wake_thread == THREAD_NULL) {
677 ret = ESRCH;
678 goto munge_retval;
679 }
680 }
681
682 key.ulk_pid = p->p_pid;
683 key.ulk_addr = args->addr;
684
685 ull_t *ull = ull_get(&key, ULL_MUST_EXIST);
686 if (ull == NULL) {
687 if (wake_thread != THREAD_NULL) {
688 thread_deallocate(wake_thread);
689 }
690 ret = ENOENT;
691 goto munge_retval;
692 }
693 /* ull is locked */
694
695 boolean_t clear_owner = FALSE; /* need to reset owner */
696
697 switch (opcode) {
698 case UL_UNFAIR_LOCK:
699 clear_owner = TRUE;
700 break;
701 case UL_COMPARE_AND_WAIT:
702 break;
703 default:
704 if (ull_debug) {
705 kprintf("[%d]%s>EINVAL opcode %d addr 0x%llx flags 0x%x\n",
706 id, __FUNCTION__, opcode, (unsigned long long)(args->addr), flags);
707 }
708 ret = EINVAL;
709 goto out_locked;
710 }
711
712 if (opcode != ull->ull_opcode) {
713 if (ull_debug) {
714 kprintf("[%d]%s>EDOM - opcode mismatch - opcode %d addr 0x%llx flags 0x%x\n",
715 id, __FUNCTION__, opcode, (unsigned long long)(args->addr), flags);
716 }
717 ret = EDOM;
718 goto out_locked;
719 }
720
721 if (!clear_owner) {
722 assert(ull->ull_owner == THREAD_NULL);
723 }
724
725 if (flags & ULF_WAKE_ALL) {
726 thread_wakeup(ULOCK_TO_EVENT(ull));
727 } else if (flags & ULF_WAKE_THREAD) {
728 kern_return_t kr = thread_wakeup_thread(ULOCK_TO_EVENT(ull), wake_thread);
729 if (kr != KERN_SUCCESS) {
730 assert(kr == KERN_NOT_WAITING);
731 ret = EALREADY;
732 }
733 } else {
734 /*
735 * TODO: WAITQ_SELECT_MAX_PRI forces a linear scan of the (hashed) global waitq.
736 * Move to a ulock-private, priority sorted waitq to avoid that.
737 *
738 * TODO: 'owner is not current_thread (or null)' likely means we can avoid this wakeup
739 * <rdar://problem/25487001>
740 */
741 thread_wakeup_one_with_pri(ULOCK_TO_EVENT(ull), WAITQ_SELECT_MAX_PRI);
742 }
743
744 /*
745 * Reaching this point means I previously moved the lock to 'unowned' state in userspace.
746 * Therefore I need to relinquish my promotion.
747 *
748 * However, someone else could have locked it after I unlocked, and then had a third thread
749 * block on the lock, causing a promotion of some other owner.
750 *
751 * I don't want to stomp over that, so only remove the promotion if I'm the current owner.
752 */
753
754 if (ull->ull_owner == current_thread()) {
755 old_owner = ull_promote_owner_locked(ull, THREAD_NULL);
756 }
757
758 out_locked:
759 ull_put(ull);
760
761 if (wake_thread != THREAD_NULL) {
762 thread_deallocate(wake_thread);
763 }
764
765 if (old_owner != THREAD_NULL) {
766 thread_deallocate(old_owner);
767 }
768
769 munge_retval:
770 if ((flags & ULF_NO_ERRNO) && (ret != 0)) {
771 *retval = -ret;
772 ret = 0;
773 }
774 return ret;
775 }
776
777 /*
778 * Change ull_owner to be new_owner, and update it with the properties
779 * of the current thread.
780 *
781 * Records the highest current promotion value in ull_promote_token, and applies that
782 * to any new owner.
783 *
784 * Returns +1 ref to the old ull_owner if it is going away.
785 */
786 static thread_t
787 ull_promote_owner_locked(ull_t* ull,
788 thread_t new_owner)
789 {
790 if (new_owner != THREAD_NULL && ull->ull_owner == new_owner) {
791 thread_user_promotion_update(new_owner, current_thread(), &ull->ull_promote_token);
792 return THREAD_NULL;
793 }
794
795 thread_t old_owner = ull->ull_owner;
796 ull->ull_owner = THREAD_NULL;
797
798 if (new_owner != THREAD_NULL) {
799 /* The ull_owner field now owns a +1 ref on thread */
800 thread_reference(new_owner);
801 ull->ull_owner = new_owner;
802
803 thread_user_promotion_add(new_owner, current_thread(), &ull->ull_promote_token);
804 } else {
805 /* No new owner - clear the saturated promotion value */
806 ull->ull_promote_token = PROMOTE_TOKEN_INIT;
807 }
808
809 if (old_owner != THREAD_NULL) {
810 thread_user_promotion_drop(old_owner);
811 }
812
813 /* Return the +1 ref from the ull_owner field */
814 return old_owner;
815 }
816
817 void
818 kdp_ulock_find_owner(__unused struct waitq * waitq, event64_t event, thread_waitinfo_t * waitinfo)
819 {
820 ull_t *ull = EVENT_TO_ULOCK(event);
821 assert(kdp_is_in_zone(ull, "ulocks"));
822
823 if (ull->ull_opcode == UL_UNFAIR_LOCK) {// owner is only set if it's an os_unfair_lock
824 waitinfo->owner = thread_tid(ull->ull_owner);
825 waitinfo->context = ull->ull_key.ulk_addr;
826 } else if (ull->ull_opcode == UL_COMPARE_AND_WAIT) { // otherwise, this is a spinlock
827 waitinfo->owner = 0;
828 waitinfo->context = ull->ull_key.ulk_addr;
829 } else {
830 panic("%s: Invalid ulock opcode %d addr %p", __FUNCTION__, ull->ull_opcode, (void*)ull);
831 }
832 return;
833 }