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 bb8f279060..98fcd61009 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
