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