Add the _umtx_op system call implementation with support for
UMTX lock/unlock, mutex operations, condition variables, reader-writer
locks, semaphores, shared memory, and robust lists.

Signed-off-by: Stacey Son <[email protected]>
Signed-off-by: Kyle Evans <[email protected]>
Signed-off-by: Jessica Clarke <[email protected]>
Signed-off-by: Sean Bruno <[email protected]>
Signed-off-by: Warner Losh <[email protected]>
Assisted-by: Claude Opus 4.6 (1M context)
---
 bsd-user/freebsd/os-thread.h | 352 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 352 insertions(+)

diff --git a/bsd-user/freebsd/os-thread.h b/bsd-user/freebsd/os-thread.h
index d063ea1b05..c9a5e76338 100644
--- a/bsd-user/freebsd/os-thread.h
+++ b/bsd-user/freebsd/os-thread.h
@@ -240,5 +240,357 @@ static inline abi_long 
do_freebsd_swapcontext(CPUArchState *env, abi_ulong arg1,
  * undocumented _umtx_op(void *obj, int op, u_long val, void *uaddr,
  *                           void *target_time);
  */
+static inline abi_long do_freebsd__umtx_op(abi_ulong obj, int op, abi_ulong 
val,
+        abi_ulong uaddr, abi_ulong target_time)
+{
+    abi_long ret;
+#ifndef _UMTX_OPTIMIZED
+    struct _umtx_time ut[2];
+    struct timespec ts;
+    size_t utsz;
+    long tid;
+#endif
+
+    switch (op) {
+    case TARGET_UMTX_OP_WAIT:
+        /* args: obj *, val, (void *)sizeof(ut), ut * */
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(VERIFY_READ, target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_umtx_wait(obj, val, uaddr,
+                                safe_g2h_untagged(target_time));
+#else
+        if (target_time != 0) {
+            ret = t2h_freebsd_umtx_time(target_time, uaddr, ut, &utsz);
+            if (is_error(ret)) {
+                return ret;
+            }
+            ret = freebsd_umtx_wait(obj, tswapal(val), utsz, &ut);
+        } else {
+            ret = freebsd_umtx_wait(obj, tswapal(val), 0, NULL);
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_WAKE:
+        /* args: obj *, nr_wakeup */
+        ret = freebsd_umtx_wake(obj, val);
+        break;
+
+    case TARGET_UMTX_OP_MUTEX_LOCK:
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(VERIFY_READ, target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_lock_umutex(obj, 0, safe_g2h_untagged(target_time), 
uaddr,
+                                  0, val);
+#else
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        if (target_time != 0) {
+            ret = t2h_freebsd_umtx_time(target_time, uaddr, ut, &utsz);
+            if (is_error(ret)) {
+                return ret;
+            }
+            ret = freebsd_lock_umutex(obj, tid, ut, utsz, 0, tswapal(val));
+        } else {
+            ret = freebsd_lock_umutex(obj, tid, NULL, 0, 0, tswapal(val));
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_MUTEX_UNLOCK:
+#ifdef _UMTX_OPTIMIZED
+        ret = freebsd_unlock_umutex(obj, 0);
+#else
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        ret = freebsd_unlock_umutex(obj, tid);
+#endif
+        break;
+
+    case TARGET_UMTX_OP_MUTEX_TRYLOCK:
+#ifdef _UMTX_OPTIMIZED
+        ret = freebsd_lock_umutex(obj, 0, NULL, 0, TARGET_UMUTEX_TRY, val);
+#else
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        ret = freebsd_lock_umutex(obj, tid, NULL, 0, TARGET_UMUTEX_TRY,
+            tswapal(val));
+#endif
+        break;
+
+    case TARGET_UMTX_OP_MUTEX_WAIT:
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(VERIFY_READ, target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_lock_umutex(obj, 0, safe_g2h_untagged(target_time), 
uaddr,
+            TARGET_UMUTEX_WAIT, val);
+#else
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        if (target_time != 0) {
+            ret = t2h_freebsd_umtx_time(target_time, uaddr, ut, &utsz);
+            if (is_error(ret)) {
+                return ret;
+            }
+            ret = freebsd_lock_umutex(obj, tid, ut, utsz, TARGET_UMUTEX_WAIT,
+                tswapal(val));
+        } else {
+            ret = freebsd_lock_umutex(obj, tid, NULL, 0, TARGET_UMUTEX_WAIT,
+                tswapal(val));
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_MUTEX_WAKE:
+        /* Don't need to do access_ok(). */
+        ret = freebsd_umtx_mutex_wake(obj, val);
+        break;
+
+    case TARGET_UMTX_OP_SET_CEILING:
+        ret = 0; /* XXX quietly ignore these things for now */
+        break;
+
+    case TARGET_UMTX_OP_CV_WAIT:
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(VERIFY_READ, target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_cv_wait(obj, uaddr, safe_g2h_untagged(target_time), val);
+#else
+        /*
+         * Initialization of the struct conv is done by
+         * bzero'ing everything in userland.
+         */
+        if (target_time != 0) {
+            if (t2h_freebsd_timespec(&ts, target_time)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_cv_wait(obj, uaddr, &ts, val);
+        } else {
+            ret = freebsd_cv_wait(obj, uaddr, NULL, val);
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_CV_SIGNAL:
+        /*
+         * XXX
+         * User code may check if c_has_waiters is zero.  Other
+         * than that it is assume that user code doesn't do
+         * much with the struct conv fields and is pretty
+         * much opauque to userland.
+         */
+        ret = freebsd_cv_signal(obj);
+        break;
+
+    case TARGET_UMTX_OP_CV_BROADCAST:
+        /*
+         * XXX
+         * User code may check if c_has_waiters is zero.  Other
+         * than that it is assume that user code doesn't do
+         * much with the struct conv fields and is pretty
+         * much opauque to userland.
+         */
+        ret = freebsd_cv_broadcast(obj);
+        break;
+
+    case TARGET_UMTX_OP_WAIT_UINT:
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(VERIFY_READ, target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_umtx_wait_uint(obj, val, uaddr,
+                                     safe_g2h_untagged(target_time));
+#else
+        if (!access_ok(VERIFY_READ, obj, sizeof(abi_ulong))) {
+            return -TARGET_EFAULT;
+        }
+        /* args: obj *, val, (void *)sizeof(ut), ut * */
+        if (target_time != 0) {
+            ret = t2h_freebsd_umtx_time(target_time, uaddr, ut, &utsz);
+            if (is_error(ret)) {
+                return ret;
+            }
+            ret = freebsd_umtx_wait_uint(obj, tswap32((uint32_t)val),
+                                         utsz, &ut);
+        } else {
+            ret = freebsd_umtx_wait_uint(obj, tswap32((uint32_t)val), 0, NULL);
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_WAIT_UINT_PRIVATE:
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(VERIFY_READ, target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_umtx_wait_uint_private(obj, val, uaddr,
+            safe_g2h_untagged(target_time));
+#else
+        if (!access_ok(VERIFY_READ, obj, sizeof(abi_ulong))) {
+            return -TARGET_EFAULT;
+        }
+        /* args: obj *, val, (void *)sizeof(ut), ut * */
+        if (target_time != 0) {
+            ret = t2h_freebsd_umtx_time(target_time, uaddr, ut, &utsz);
+            if (is_error(ret)) {
+                return ret;
+            }
+            ret = freebsd_umtx_wait_uint_private(obj, tswap32((uint32_t)val),
+                    utsz, &ut);
+        } else {
+            ret = freebsd_umtx_wait_uint_private(obj, tswap32((uint32_t)val),
+                    0, NULL);
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_WAKE_PRIVATE:
+        /* Don't need to do access_ok(). */
+        ret = freebsd_umtx_wake_private(obj, val);
+        break;
+
+    case TARGET_UMTX_OP_RW_RDLOCK:
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(VERIFY_READ, target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_rw_rdlock(obj, val, uaddr,
+                                safe_g2h_untagged(target_time));
+#else
+        if (target_time != 0) {
+            ret = t2h_freebsd_umtx_time(target_time, uaddr, ut, &utsz);
+            if (is_error(ret)) {
+                return ret;
+            }
+            ret = freebsd_rw_rdlock(obj, val, utsz, &ut);
+        } else {
+            ret = freebsd_rw_rdlock(obj, val, 0, NULL);
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_RW_WRLOCK:
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(VERIFY_READ, target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_rw_wrlock(obj, val, uaddr,
+                                safe_g2h_untagged(target_time));
+#else
+        if (target_time != 0) {
+            ret = t2h_freebsd_umtx_time(target_time, uaddr, ut, &utsz);
+            if (is_error(ret)) {
+                return ret;
+            }
+            ret = freebsd_rw_wrlock(obj, val, utsz, &ut);
+        } else {
+            ret = freebsd_rw_wrlock(obj, val, 0, NULL);
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_RW_UNLOCK:
+        ret = freebsd_rw_unlock(obj);
+        break;
+
+#ifdef UMTX_OP_MUTEX_WAKE2
+    case TARGET_UMTX_OP_MUTEX_WAKE2:
+        ret = freebsd_umtx_mutex_wake2(obj, val);
+        break;
+#endif /* UMTX_OP_MUTEX_WAKE2 */
+
+#ifdef UMTX_OP_NWAKE_PRIVATE
+    case TARGET_UMTX_OP_NWAKE_PRIVATE:
+        ret = freebsd_umtx_nwake_private(obj, val);
+        break;
+#endif /* UMTX_OP_NWAKE_PRIVATE */
+
+    case TARGET_UMTX_OP_SEM2_WAIT:
+        /* args: obj *, val, (void *)sizeof(ut), ut * */
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(
+            (uaddr > sizeof(struct target_freebsd__umtx_time) ? VERIFY_WRITE :
+             VERIFY_READ), target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_umtx_sem2_wait(obj, uaddr,
+                                     safe_g2h_untagged(target_time));
+#else
+        if (target_time != 0) {
+            ret = t2h_freebsd_umtx_time(target_time, uaddr, ut, &utsz);
+            if (is_error(ret)) {
+                return ret;
+            }
+            /* Kernel writes out the ut[1] if utsz >= _umtx_time + timespec. */
+            ret = freebsd_umtx_sem2_wait(obj, utsz, ut);
+            if (ret == -TARGET_EINTR && (ut[0]._flags & UMTX_ABSTIME) == 0 &&
+                utsz >= sizeof(struct target_freebsd__umtx_time) +
+                sizeof(struct target_freebsd_timespec)) {
+                abi_ulong cret;
+
+                cret = h2t_freebsd_timespec(target_time +
+                    sizeof(struct target_freebsd__umtx_time), &ut[1]._timeout);
+                if (is_error(cret)) {
+                    ret = cret;
+                }
+            }
+        } else {
+            ret = freebsd_umtx_sem2_wait(obj, 0, NULL);
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_SEM2_WAKE:
+        /* Don't need to do access_ok(). */
+        ret = freebsd_umtx_sem2_wake(obj);
+        break;
+    case TARGET_UMTX_OP_SEM_WAIT:
+        /* args: obj *, val, (void *)sizeof(ut), ut * */
+#ifdef _UMTX_OPTIMIZED
+        if (target_time != 0 && !access_ok(VERIFY_READ, target_time, uaddr)) {
+            return -TARGET_EFAULT;
+        }
+        ret = freebsd_umtx_sem_wait(obj, uaddr, 
safe_g2h_untagged(target_time));
+#else
+        if (target_time != 0) {
+            ret = t2h_freebsd_umtx_time(target_time, uaddr, ut, &utsz);
+            if (is_error(ret)) {
+                return ret;
+            }
+            ret = freebsd_umtx_sem_wait(obj, utsz, ut);
+        } else {
+            ret = freebsd_umtx_sem_wait(obj, 0, NULL);
+        }
+#endif
+        break;
+
+    case TARGET_UMTX_OP_SEM_WAKE:
+        /* Don't need to do access_ok(). */
+        ret = freebsd_umtx_sem_wake(obj);
+        break;
+    case UMTX_OP_SHM:
+        ret = freebsd_umtx_shm(uaddr, val);
+        break;
+    case TARGET_UMTX_OP_ROBUST_LISTS:
+        ret = freebsd_umtx_robust_list(uaddr, val);
+        break;
+    default:
+        return -TARGET_EINVAL;
+    }
+    return ret;
+}
 
 #endif /* FREEBSD_OS_THREAD_H */

-- 
2.52.0


Reply via email to