diff --git a/bsd-user/Makefile.objs b/bsd-user/Makefile.objs
index 9869837..b691ffc 100644
--- a/bsd-user/Makefile.objs
+++ b/bsd-user/Makefile.objs
@@ -2,5 +2,5 @@ obj-y = main.o bsdload.o elfload.o mmap.o signal.o strace.o syscall.o \
 	        uaccess.o bsd-mem.o bsd-proc.o bsd-socket.o \
 			$(HOST_ABI_DIR)/os-proc.o \
 			$(HOST_ABI_DIR)/os-socket.o $(HOST_ABI_DIR)/os-stat.o \
-			$(HOST_ABI_DIR)/os-sys.o \
+			$(HOST_ABI_DIR)/os-sys.o $(HOST_ABI_DIR)/os-thread.o \
 			$(HOST_ABI_DIR)/os-time.o $(TARGET_ABI_DIR)/target_arch_cpu.o
diff --git a/bsd-user/freebsd/os-thread.c b/bsd-user/freebsd/os-thread.c
new file mode 100644
index 0000000..c6f4a82
--- /dev/null
+++ b/bsd-user/freebsd/os-thread.c
@@ -0,0 +1,936 @@
+/*
+ *  FreeBSD thr emulation support code
+ *
+ *  Copyright (c) 2013 Stacey D. Son
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/mman.h>
+#include <sys/thr.h>
+#include <sys/umtx.h>
+#include <sys/rtprio.h>
+
+#include <machine/atomic.h>
+
+#include <time.h>
+
+#include "qemu.h"
+#include "qemu-os.h"
+#include "target_arch_cpu.h"
+#include "target_arch_thread.h"
+
+#define NEW_STACK_SIZE  0x40000
+
+/* sys/_umtx.h */
+struct target_umtx {
+    abi_ulong   u_owner;    /* Owner of the mutex. */
+};
+
+struct target_umutex {
+    uint32_t    m_owner;    /* Owner of the mutex */
+    uint32_t    m_flags;    /* Flags of the mutex */
+    uint32_t    m_ceiling[2];   /* Priority protect ceiling */
+    uint32_t    m_spare[4];
+};
+
+struct target_ucond {
+    uint32_t    c_has_waiters;  /* Has waiters in kernel */
+    uint32_t    c_flags;    /* Flags of the condition variable */
+    uint32_t    c_clockid;  /* Clock id */
+    uint32_t    c_spare[1];
+};
+
+struct target_urwlock {
+    uint32_t    rw_state;
+    uint32_t    rw_flags;
+    uint32_t    rw_blocked_readers;
+    uint32_t    rw_blocked_writers;
+    uint32_t    rw_spare[4];
+};
+
+struct target__usem {
+    uint32_t    _has_waiters;
+    uint32_t    _count;
+    uint32_t    _flags;
+};
+
+static pthread_mutex_t new_thread_lock = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t *new_freebsd_thread_lock_ptr = &new_thread_lock;
+
+static void rtp_to_schedparam(const struct rtprio *rtp, int *policy,
+        struct sched_param *param)
+{
+
+    switch (rtp->type) {
+    case RTP_PRIO_REALTIME:
+        *policy = SCHED_RR;
+        param->sched_priority = RTP_PRIO_MAX - rtp->prio;
+        break;
+
+    case RTP_PRIO_FIFO:
+        *policy = SCHED_FIFO;
+        param->sched_priority = RTP_PRIO_MAX - rtp->prio;
+        break;
+
+    default:
+        *policy = SCHED_OTHER;
+        param->sched_priority = 0;
+        break;
+    }
+}
+
+void *new_freebsd_thread_start(void *arg)
+{
+    new_freebsd_thread_info_t *info = arg;
+    CPUArchState *env;
+    CPUState *cpu;
+    TaskState *ts;
+    long tid;
+
+    env = info->env;
+    cpu = ENV_GET_CPU(env);
+    thread_cpu = cpu;
+
+    ts = (TaskState *)env->opaque;
+    (void)thr_self(&tid);
+
+    /* copy out the TID info */
+    if (info->param.child_tid) {
+        put_user(tid, info->param.child_tid, abi_long);
+    }
+    if (info->param.parent_tid) {
+        put_user(info->parent_tid, info->param.parent_tid, abi_long);
+    }
+
+    /* Set arch dependent registers to start thread. */
+    target_thread_set_upcall(env, info->param.start_func, info->param.arg,
+        info->param.stack_base, info->param.stack_size);
+
+    /* Enable signals */
+    sigprocmask(SIG_SETMASK, &info->sigmask, NULL);
+    /* Signal to the parent that we're ready. */
+    pthread_mutex_lock(&info->mutex);
+    pthread_cond_broadcast(&info->cond);
+    pthread_mutex_unlock(&info->mutex);
+    /* Wait until the parent has finished initializing the TLS state. */
+    pthread_mutex_lock(new_freebsd_thread_lock_ptr);
+    pthread_mutex_unlock(new_freebsd_thread_lock_ptr);
+
+    cpu_loop(env);
+    /* never exits */
+
+    return NULL;
+}
+
+/*
+ * FreeBSD user mutex (_umtx) emulation
+ */
+static int tcmpset_al(abi_ulong *addr, abi_ulong a, abi_ulong b)
+{
+    abi_ulong current = tswapal(a);
+    abi_ulong new = tswapal(b);
+
+#ifdef TARGET_ABI32
+    return atomic_cmpset_acq_32(addr, current, new);
+#else
+    return atomic_cmpset_acq_64(addr, current, new);
+#endif
+}
+
+static int tcmpset_32(uint32_t *addr, uint32_t a, uint32_t b)
+{
+    uint32_t current = tswap32(a);
+    uint32_t new = tswap32(b);
+
+    return atomic_cmpset_acq_32(addr, current, new);
+}
+
+abi_long freebsd_umtx_wait_uint(abi_ulong obj, uint32_t val,
+        struct timespec *timeout)
+{
+
+    return get_errno(_umtx_op(g2h(obj), UMTX_OP_WAIT_UINT, val, NULL, timeout));
+}
+
+abi_long freebsd_umtx_wait_uint_private(abi_ulong obj, uint32_t val,
+        struct timespec *timeout)
+{
+
+    return get_errno(_umtx_op(g2h(obj), UMTX_OP_WAIT_UINT_PRIVATE, val, NULL,
+                timeout));
+}
+
+abi_long freebsd_umtx_wake_private(abi_ulong obj, uint32_t val)
+{
+
+    return get_errno(_umtx_op(g2h(obj), UMTX_OP_WAKE_PRIVATE, val, NULL, NULL));
+}
+
+#if defined(__FreeBSD_version) && __FreeBSD_version > 900000
+#if defined(UMTX_OP_NWAKE_PRIVATE)
+abi_long freebsd_umtx_nwake_private(abi_ulong obj, uint32_t val)
+{
+
+    return get_errno(_umtx_op(g2h(obj), UMTX_OP_NWAKE_PRIVATE, val, NULL,
+                NULL));
+}
+#endif
+
+#if defined(UMTX_OP_MUTEX_WAKE2)
+abi_long freebsd_umtx_mutex_wake2(abi_ulong obj, uint32_t val)
+{
+    if (!access_ok(VERIFY_WRITE, obj, sizeof(struct target_ucond))) {
+        return -TARGET_EFAULT;
+    }
+    return get_errno(_umtx_op(g2h(obj), UMTX_OP_MUTEX_WAKE2, val, NULL, NULL));
+}
+#endif
+
+abi_long freebsd_umtx_sem_wait(abi_ulong obj, struct timespec *timeout)
+{
+
+    /* XXX Assumes struct _usem is opauque to the user */
+    if (!access_ok(VERIFY_WRITE, obj, sizeof(struct target__usem))) {
+        return -TARGET_EFAULT;
+    }
+    return get_errno(_umtx_op(g2h(obj), UMTX_OP_SEM_WAIT, 0, NULL, timeout));
+}
+
+abi_long freebsd_umtx_sem_wake(abi_ulong obj, uint32_t val)
+{
+
+    return get_errno(_umtx_op(g2h(obj), UMTX_OP_SEM_WAKE, val, NULL, NULL));
+}
+#endif
+
+abi_long t2h_freebsd_rtprio(struct rtprio *host_rtp, abi_ulong target_addr)
+{
+    struct target_freebsd_rtprio *target_rtp;
+
+    if (!lock_user_struct(VERIFY_READ, target_rtp, target_addr, 1)) {
+        return -TARGET_EFAULT;
+    }
+    __get_user(host_rtp->type, &target_rtp->type);
+    __get_user(host_rtp->prio, &target_rtp->prio);
+    unlock_user_struct(target_rtp, target_addr, 0);
+    return 0;
+}
+
+abi_long h2t_freebsd_rtprio(abi_ulong target_addr, struct rtprio *host_rtp)
+{
+    struct target_freebsd_rtprio *target_rtp;
+
+    if (!lock_user_struct(VERIFY_WRITE, target_rtp, target_addr, 0)) {
+        return -TARGET_EFAULT;
+    }
+    __put_user(host_rtp->type, &target_rtp->type);
+    __put_user(host_rtp->prio, &target_rtp->prio);
+    unlock_user_struct(target_rtp, target_addr, 1);
+    return 0;
+}
+
+abi_long freebsd_lock_umtx(abi_ulong target_addr, abi_long id,
+        struct timespec *timeout)
+{
+    abi_long ret;
+    abi_long owner;
+
+    /*
+     * XXX Note that memory at umtx_addr can change and so we need to be
+     * careful and check for faults.
+     */
+    for (;;) {
+        struct target_umtx *target_umtx;
+
+        if (!lock_user_struct(VERIFY_WRITE, target_umtx, target_addr, 0)) {
+            return -TARGET_EFAULT;
+        }
+        /* Check the simple uncontested case. */
+        if (tcmpset_al(&target_umtx->u_owner,
+                TARGET_UMTX_UNOWNED, id)) {
+            unlock_user_struct(target_umtx, target_addr, 1);
+            return 0;
+        }
+        /* Check to see if the lock is contested but free. */
+        __get_user(owner, &target_umtx->u_owner);
+
+        if (TARGET_UMTX_CONTESTED == owner) {
+            if (tcmpset_al(&target_umtx->u_owner, TARGET_UMTX_CONTESTED,
+                        id | TARGET_UMTX_CONTESTED)) {
+                unlock_user_struct(target_umtx, target_addr, 1);
+                return 0;
+            }
+            /* We failed because it changed on us, restart. */
+            unlock_user_struct(target_umtx, target_addr, 1);
+            continue;
+        }
+
+        /* Set the contested bit and sleep. */
+        do {
+            __get_user(owner, &target_umtx->u_owner);
+            if (owner & TARGET_UMTX_CONTESTED) {
+                break;
+            }
+        } while (!tcmpset_al(&target_umtx->u_owner, owner,
+                    owner | TARGET_UMTX_CONTESTED));
+
+        __get_user(owner, &target_umtx->u_owner);
+        unlock_user_struct(target_umtx, target_addr, 1);
+
+        /* Byte swap, if needed, to match what is stored in user mem. */
+        owner = tswapal(owner);
+#ifdef TARGET_ABI32
+        ret = get_errno(_umtx_op(target_umtx, UMTX_OP_WAIT_UINT, owner,
+            NULL, timeout));
+#else
+        ret = get_errno(_umtx_op(target_umtx, UMTX_OP_WAIT, owner,
+            NULL, timeout));
+#endif
+        if (is_error(ret)) {
+            return ret;
+        }
+    }
+}
+
+abi_long freebsd_unlock_umtx(abi_ulong target_addr, abi_long id)
+{
+    abi_ulong owner;
+    struct target_umtx *target_umtx;
+
+    if (!lock_user_struct(VERIFY_WRITE, target_umtx, target_addr, 0)) {
+        return -TARGET_EFAULT;
+    }
+    __get_user(owner, &target_umtx->u_owner);
+    if ((owner & ~TARGET_UMTX_CONTESTED) != id) {
+        unlock_user_struct(target_umtx, target_addr, 1);
+        return -TARGET_EPERM;
+    }
+    /* Check the simple uncontested case. */
+    if ((owner & ~TARGET_UMTX_CONTESTED) == 0) {
+        if (tcmpset_al(&target_umtx->u_owner, owner,
+            TARGET_UMTX_UNOWNED)) {
+            unlock_user_struct(target_umtx, target_addr, 1);
+            return 0;
+        }
+    }
+    /* This is a contested lock. Unlock it. */
+    __put_user(TARGET_UMTX_UNOWNED, &target_umtx->u_owner);
+    unlock_user_struct(target_umtx, target_addr, 1);
+
+    /* Wake up all those contesting it. */
+    _umtx_op(target_umtx, UMTX_OP_WAKE, 0, 0, 0);
+
+    return 0;
+}
+
+abi_long freebsd_umtx_wait(abi_ulong targ_addr, abi_ulong id,
+        struct timespec *ts)
+{
+
+    /* We want to check the user memory but not lock it.  We might sleep. */
+    if (!access_ok(VERIFY_READ, targ_addr, sizeof(abi_ulong))) {
+        return -TARGET_EFAULT;
+    }
+
+    /* id has already been byte swapped to match what may be in user mem. */
+#ifdef TARGET_ABI32
+    return get_errno(_umtx_op(g2h(targ_addr), UMTX_OP_WAIT_UINT, id, NULL, ts));
+#else
+    return get_errno(_umtx_op(g2h(targ_addr), UMTX_OP_WAIT, id, NULL, ts));
+#endif
+}
+
+abi_long freebsd_umtx_wake(abi_ulong target_addr, uint32_t n_wake)
+{
+
+    return get_errno(_umtx_op(g2h(target_addr), UMTX_OP_WAKE, n_wake, NULL, 0));
+}
+
+abi_long freebsd_umtx_mutex_wake(abi_ulong obj, abi_long val)
+{
+
+    return get_errno(_umtx_op(g2h(obj), UMTX_OP_MUTEX_WAKE, val, NULL, NULL));
+}
+
+abi_long freebsd_lock_umutex(abi_ulong target_addr, uint32_t id,
+        struct timespec *ts, int mode)
+{
+    uint32_t owner, flags;
+    int ret = 0;
+
+    for (;;) {
+        struct target_umutex *target_umutex;
+
+        if (!lock_user_struct(VERIFY_WRITE, target_umutex, target_addr, 0)) {
+            return -TARGET_EFAULT;
+        }
+
+        __get_user(owner, &target_umutex->m_owner);
+
+        if (TARGET_UMUTEX_WAIT == mode) {
+            if (TARGET_UMUTEX_UNOWNED == owner ||
+                    TARGET_UMUTEX_CONTESTED == owner) {
+                unlock_user_struct(target_umutex, target_addr, 1);
+                return 0;
+            }
+        } else {
+            if (tcmpset_32(&target_umutex->m_owner, TARGET_UMUTEX_UNOWNED,
+                        id)) {
+                /* The acquired succeeded. */
+                unlock_user_struct(target_umutex, target_addr, 1);
+                return 0;
+            }
+
+            /* If no one owns it but it is contested try to acquire it. */
+            if (TARGET_UMUTEX_CONTESTED == owner) {
+                if (tcmpset_32(&target_umutex->m_owner, TARGET_UMUTEX_CONTESTED,
+                            id | TARGET_UMUTEX_CONTESTED)) {
+                    unlock_user_struct(target_umutex, target_addr, 1);
+                    return 0;
+                }
+                /* The lock changed so restart. */
+                unlock_user_struct(target_umutex, target_addr, 1);
+                continue;
+            }
+        }
+
+        __get_user(flags, &target_umutex->m_flags);
+        if ((flags & TARGET_UMUTEX_ERROR_CHECK) != 0 &&
+                (owner & ~TARGET_UMUTEX_CONTESTED) == id) {
+            unlock_user_struct(target_umutex, target_addr, 1);
+            return -TARGET_EDEADLK;
+        }
+
+        if (TARGET_UMUTEX_TRY == mode) {
+            unlock_user_struct(target_umutex, target_addr, 1);
+            return -TARGET_EBUSY;
+        }
+
+        /*
+         * If we caught a signal, we have retried and now
+         * exit immediately.
+         */
+        if (is_error(ret)) {
+            unlock_user_struct(target_umutex, target_addr, 1);
+            return ret;
+        }
+
+        /* Set the contested bit and sleep. */
+        if (!tcmpset_32(&target_umutex->m_owner, owner,
+                    owner | TARGET_UMUTEX_CONTESTED)) {
+            unlock_user_struct(target_umutex, target_addr, 1);
+            continue;
+        }
+
+        owner = owner | TARGET_UMUTEX_CONTESTED;
+        unlock_user_struct(target_umutex, target_addr, 1);
+
+        /* Byte swap, if needed, to match what is stored in user mem. */
+        owner = tswap32(owner);
+        ret = get_errno(_umtx_op(target_umutex, UMTX_OP_WAIT_UINT, owner, NULL,
+                    ts));
+    }
+
+    if (ts == NULL) {
+        /*
+         * In the case of no timeout do a restart on this syscall,
+         * if interrupted.
+         */
+        if (ret == -TARGET_EINTR) {
+            ret = -TARGET_ERESTART;
+        }
+    }
+    return ret;
+}
+
+abi_long freebsd_unlock_umutex(abi_ulong target_addr, uint32_t id)
+{
+    struct target_umutex *target_umutex;
+    uint32_t owner;
+
+
+    if (!lock_user_struct(VERIFY_WRITE, target_umutex, target_addr, 0)) {
+        return -TARGET_EFAULT;
+    }
+    /* Make sure we own this mutex. */
+    __get_user(owner, &target_umutex->m_owner);
+    if ((owner & ~TARGET_UMUTEX_CONTESTED) != id) {
+        unlock_user_struct(target_umutex, target_addr, 1);
+        return -TARGET_EPERM;
+    }
+    if ((owner & TARGET_UMUTEX_CONTESTED) == 0) {
+        if (tcmpset_32(&target_umutex->m_owner, owner, TARGET_UMTX_UNOWNED)) {
+            unlock_user_struct(target_umutex, target_addr, 1);
+            return 0;
+        }
+    }
+    /* This is a contested lock. Unlock it. */
+    __put_user(TARGET_UMUTEX_UNOWNED, &target_umutex->m_owner);
+    unlock_user_struct(target_umutex, target_addr, 1);
+
+    /* And wake up all those contesting it. */
+    return get_errno(_umtx_op(g2h(target_addr), UMTX_OP_WAKE, 0, 0, 0));
+}
+
+/*
+ * _cv_mutex is keeps other threads from doing a signal or broadcast until
+ * the thread is actually asleep and ready.  This is a global mutex for all
+ * condition vars so I am sure performance may be a problem if there are lots
+ * of CVs.
+ */
+static struct umutex _cv_mutex = { 0, 0, { 0, 0 }, { 0, 0, 0, 0 } };
+
+
+/*
+ * wflags CVWAIT_CHECK_UNPARKING, CVWAIT_ABSTIME, CVWAIT_CLOCKID
+ */
+abi_long freebsd_cv_wait(abi_ulong target_ucond_addr,
+        abi_ulong target_umtx_addr, struct timespec *ts, int wflags)
+{
+    abi_long ret;
+    long tid;
+
+    if (!access_ok(VERIFY_WRITE, target_ucond_addr,
+                sizeof(struct target_ucond))) {
+        return -TARGET_EFAULT;
+    }
+
+    /* Check the clock ID if needed. */
+    if ((wflags & TARGET_CVWAIT_CLOCKID) != 0) {
+        struct target_ucond *target_ucond;
+        uint32_t clockid;
+
+        if (!lock_user_struct(VERIFY_WRITE, target_ucond, target_ucond_addr,
+                    0)) {
+            return -TARGET_EFAULT;
+        }
+        __get_user(clockid, &target_ucond->c_clockid);
+        unlock_user_struct(target_ucond, target_ucond_addr, 1);
+        if (clockid < CLOCK_REALTIME || clockid >= CLOCK_THREAD_CPUTIME_ID) {
+            /* Only HW clock id will work. */
+            return -TARGET_EINVAL;
+        }
+    }
+
+    thr_self(&tid);
+
+    /* Lock the _cv_mutex so we can safely unlock the user mutex */
+    _umtx_op(&_cv_mutex, UMTX_OP_MUTEX_LOCK, 0, NULL, NULL);
+
+    /* unlock the user mutex */
+    ret = freebsd_unlock_umutex(target_umtx_addr, tid);
+    if (is_error(ret)) {
+        _umtx_op(&_cv_mutex, UMTX_OP_MUTEX_UNLOCK, 0, NULL, NULL);
+        return ret;
+    }
+
+    /* UMTX_OP_CV_WAIT unlocks _cv_mutex */
+    ret = get_errno(_umtx_op(g2h(target_ucond_addr), UMTX_OP_CV_WAIT, wflags,
+                &_cv_mutex, ts));
+
+    return ret;
+}
+
+abi_long freebsd_cv_signal(abi_ulong target_ucond_addr)
+{
+    abi_long ret;
+
+    if (!access_ok(VERIFY_WRITE, target_ucond_addr,
+                sizeof(struct target_ucond))) {
+        return -TARGET_EFAULT;
+    }
+
+    /* Lock the _cv_mutex to prevent a race in do_cv_wait(). */
+    _umtx_op(&_cv_mutex, UMTX_OP_MUTEX_LOCK, 0, NULL, NULL);
+    ret = get_errno(_umtx_op(g2h(target_ucond_addr), UMTX_OP_CV_SIGNAL, 0,
+        NULL, NULL));
+    _umtx_op(&_cv_mutex, UMTX_OP_MUTEX_UNLOCK, 0, NULL, NULL);
+
+    return ret;
+}
+
+abi_long freebsd_cv_broadcast(abi_ulong target_ucond_addr)
+{
+    int ret;
+
+    if (!access_ok(VERIFY_WRITE, target_ucond_addr,
+                sizeof(struct target_ucond))) {
+        return -TARGET_EFAULT;
+    }
+
+    /* Lock the _cv_mutex to prevent a race in do_cv_wait(). */
+    _umtx_op(&_cv_mutex, UMTX_OP_MUTEX_LOCK, 0, NULL, NULL);
+    ret = get_errno(_umtx_op(g2h(target_ucond_addr), UMTX_OP_CV_BROADCAST, 0,
+                NULL, NULL));
+    _umtx_op(&_cv_mutex, UMTX_OP_MUTEX_UNLOCK, 0, NULL, NULL);
+
+    return ret;
+}
+
+abi_long freebsd_rw_rdlock(abi_ulong target_addr, long fflag,
+        struct timespec *ts)
+{
+    struct target_urwlock *target_urwlock;
+    uint32_t flags, wrflags;
+    uint32_t state;
+    uint32_t blocked_readers;
+    abi_long ret;
+
+    if (!lock_user_struct(VERIFY_WRITE, target_urwlock, target_addr, 0)) {
+        return -TARGET_EFAULT;
+    }
+
+    __get_user(flags, &target_urwlock->rw_flags);
+    wrflags = TARGET_URWLOCK_WRITE_OWNER;
+    if (!(fflag & TARGET_URWLOCK_PREFER_READER) &&
+            !(flags & TARGET_URWLOCK_PREFER_READER)) {
+        wrflags |= TARGET_URWLOCK_WRITE_WAITERS;
+    }
+    for (;;) {
+        __get_user(state, &target_urwlock->rw_state);
+        /* try to lock it */
+        while (!(state & wrflags)) {
+            if (TARGET_URWLOCK_READER_COUNT(state) ==
+                TARGET_URWLOCK_MAX_READERS) {
+                unlock_user_struct(target_urwlock,
+                    target_addr, 1);
+                return -TARGET_EAGAIN;
+            }
+            if (tcmpset_32(&target_urwlock->rw_state, state,
+                (state + 1))) {
+                /* The acquired succeeded. */
+                unlock_user_struct(target_urwlock,
+                    target_addr, 1);
+                return 0;
+            }
+            __get_user(state, &target_urwlock->rw_state);
+        }
+        /* set read contention bit */
+        if (!tcmpset_32(&target_urwlock->rw_state, state,
+            state | TARGET_URWLOCK_READ_WAITERS)) {
+            /* The state has changed.  Start over. */
+            continue;
+        }
+
+        /* contention bit is set, increase read waiter count */
+        __get_user(blocked_readers, &target_urwlock->rw_blocked_readers);
+        while (!tcmpset_32(&target_urwlock->rw_blocked_readers,
+                    blocked_readers, blocked_readers + 1)) {
+            __get_user(blocked_readers, &target_urwlock->rw_blocked_readers);
+        }
+
+        while (state & wrflags) {
+            /* sleep/wait */
+            unlock_user_struct(target_urwlock, target_addr, 1);
+            ret = get_errno(_umtx_op(&target_urwlock->rw_blocked_readers,
+                        UMTX_OP_WAIT_UINT, blocked_readers, NULL, ts));
+            if (is_error(ret)) {
+                return ret;
+            }
+            if (!lock_user_struct(VERIFY_WRITE, target_urwlock,
+                        target_addr, 0)) {
+                return -TARGET_EFAULT;
+            }
+            __get_user(state, &target_urwlock->rw_state);
+        }
+
+        /* decrease read waiter count */
+        __get_user(blocked_readers, &target_urwlock->rw_blocked_readers);
+        while (!tcmpset_32(&target_urwlock->rw_blocked_readers,
+                    blocked_readers, (blocked_readers - 1))) {
+            __get_user(blocked_readers, &target_urwlock->rw_blocked_readers);
+        }
+        if (blocked_readers == 1) {
+            /* clear read contention bit */
+            __get_user(state, &target_urwlock->rw_state);
+            while (!tcmpset_32(&target_urwlock->rw_state, state,
+                state & ~TARGET_URWLOCK_READ_WAITERS)) {
+                __get_user(state, &target_urwlock->rw_state);
+            }
+        }
+    }
+}
+
+abi_long freebsd_rw_wrlock(abi_ulong target_addr, long fflag,
+        struct timespec *ts)
+{
+    struct target_urwlock *target_urwlock;
+    uint32_t blocked_readers, blocked_writers;
+    uint32_t state;
+    abi_long ret;
+
+    if (!lock_user_struct(VERIFY_WRITE, target_urwlock, target_addr, 0)) {
+        return -TARGET_EFAULT;
+    }
+    blocked_readers = 0;
+    for (;;) {
+        __get_user(state, &target_urwlock->rw_state);
+        while (!(state & TARGET_URWLOCK_WRITE_OWNER) &&
+            TARGET_URWLOCK_READER_COUNT(state) == 0) {
+            if (tcmpset_32(&target_urwlock->rw_state, state,
+                        state | TARGET_URWLOCK_WRITE_OWNER)) {
+                unlock_user_struct(target_urwlock, target_addr, 1);
+                return 0;
+            }
+            __get_user(state, &target_urwlock->rw_state);
+        }
+
+        if (!(state & (TARGET_URWLOCK_WRITE_OWNER |
+                        TARGET_URWLOCK_WRITE_WAITERS)) &&
+                blocked_readers != 0) {
+            ret = get_errno(_umtx_op(&target_urwlock->rw_blocked_readers,
+                UMTX_OP_WAKE, INT_MAX, NULL, NULL));
+            return ret;
+        }
+        /* re-read the state */
+        __get_user(state, &target_urwlock->rw_state);
+
+        /* and set TARGET_URWLOCK_WRITE_WAITERS */
+        while (((state & TARGET_URWLOCK_WRITE_OWNER) ||
+                    TARGET_URWLOCK_READER_COUNT(state) != 0) &&
+                (state & TARGET_URWLOCK_WRITE_WAITERS) == 0) {
+            if (tcmpset_32(&target_urwlock->rw_state, state,
+                        state | TARGET_URWLOCK_WRITE_WAITERS)) {
+                break;
+            }
+            __get_user(state, &target_urwlock->rw_state);
+        }
+
+        /* contention bit is set, increase write waiter count */
+        __get_user(blocked_writers, &target_urwlock->rw_blocked_writers);
+        while (!tcmpset_32(&target_urwlock->rw_blocked_writers,
+                    blocked_writers, blocked_writers + 1)) {
+            __get_user(blocked_writers, &target_urwlock->rw_blocked_writers);
+        }
+
+        /* sleep */
+        while ((state & TARGET_URWLOCK_WRITE_OWNER) ||
+                (TARGET_URWLOCK_READER_COUNT(state) != 0)) {
+            unlock_user_struct(target_urwlock, target_addr, 1);
+            ret = get_errno(_umtx_op(&target_urwlock->rw_blocked_writers,
+                        UMTX_OP_WAIT_UINT, blocked_writers, NULL, ts));
+            if (is_error(ret)) {
+                return ret;
+            }
+            if (!lock_user_struct(VERIFY_WRITE, target_urwlock, target_addr,
+                        0)) {
+                return -TARGET_EFAULT;
+            }
+            __get_user(state, &target_urwlock->rw_state);
+        }
+
+        /* decrease the write waiter count */
+        __get_user(blocked_writers, &target_urwlock->rw_blocked_writers);
+        while (!tcmpset_32(&target_urwlock->rw_blocked_writers,
+                    blocked_writers, (blocked_writers - 1))) {
+            __get_user(blocked_writers, &target_urwlock->rw_blocked_writers);
+        }
+        if (blocked_writers == 1) {
+            /* clear write contention bit */
+            __get_user(state, &target_urwlock->rw_state);
+            while (!tcmpset_32(&target_urwlock->rw_state, state,
+                        state & ~TARGET_URWLOCK_WRITE_WAITERS)) {
+                __get_user(state, &target_urwlock->rw_state);
+            }
+            __get_user(blocked_readers, &target_urwlock->rw_blocked_readers);
+        } else {
+            blocked_readers = 0;
+        }
+    }
+}
+
+abi_long freebsd_rw_unlock(abi_ulong target_addr)
+{
+    struct target_urwlock *target_urwlock;
+    uint32_t flags, state, count;
+    void *q = NULL;
+
+    if (!lock_user_struct(VERIFY_WRITE, target_urwlock, target_addr, 0)) {
+        return -TARGET_EFAULT;
+    }
+
+    __get_user(flags, &target_urwlock->rw_flags);
+    __get_user(state, &target_urwlock->rw_state);
+
+    if (state & TARGET_URWLOCK_WRITE_OWNER) {
+        for (;;) {
+            if (!tcmpset_32(&target_urwlock->rw_state, state,
+                state & ~TARGET_URWLOCK_WRITE_OWNER)) {
+                __get_user(state, &target_urwlock->rw_state);
+                if (!(state & TARGET_URWLOCK_WRITE_OWNER)) {
+                    unlock_user_struct(target_urwlock,
+                        target_addr, 1);
+                    return -TARGET_EPERM;
+                }
+            } else {
+                break;
+            }
+        }
+    } else if (TARGET_URWLOCK_READER_COUNT(state) != 0) {
+        /* decrement reader count */
+        for (;;) {
+            if (!tcmpset_32(&target_urwlock->rw_state, state, (state  - 1))) {
+                if (TARGET_URWLOCK_READER_COUNT(state) == 0) {
+                    unlock_user_struct(target_urwlock,
+                        target_addr, 1);
+                        return -TARGET_EPERM;
+                 }
+            } else {
+                break;
+            }
+        }
+    } else {
+        unlock_user_struct(target_urwlock, target_addr, 1);
+        return -TARGET_EPERM;
+    }
+
+    count = 0;
+
+    if (!(flags & TARGET_URWLOCK_PREFER_READER)) {
+        if (state & TARGET_URWLOCK_WRITE_WAITERS) {
+            count = 1;
+            q = &target_urwlock->rw_blocked_writers;
+        } else if (state & TARGET_URWLOCK_READ_WAITERS) {
+            count = INT_MAX;
+            q = &target_urwlock->rw_blocked_readers;
+        }
+    } else {
+        if (state & TARGET_URWLOCK_READ_WAITERS) {
+            count = INT_MAX;
+            q = &target_urwlock->rw_blocked_readers;
+        } else if (state & TARGET_URWLOCK_WRITE_WAITERS) {
+            count = 1;
+            q = &target_urwlock->rw_blocked_writers;
+        }
+    }
+
+    unlock_user_struct(target_urwlock, target_addr, 1);
+    if (q != NULL) {
+        return get_errno(_umtx_op(q, UMTX_OP_WAKE, count, NULL, NULL));
+    } else {
+        return 0;
+    }
+}
+
+abi_long do_freebsd_thr_new(CPUArchState *env,
+        abi_ulong target_param_addr, int32_t param_size)
+{
+    new_freebsd_thread_info_t info;
+    pthread_attr_t attr;
+    TaskState *ts;
+    CPUArchState *new_env;
+    struct target_freebsd_thr_param *target_param;
+    abi_ulong target_rtp_addr;
+    struct target_freebsd_rtprio *target_rtp;
+    struct rtprio *rtp_ptr, rtp;
+    TaskState *parent_ts = (TaskState *)env->opaque;
+    sigset_t sigmask;
+    struct sched_param sched_param;
+    int sched_policy;
+    int ret = 0;
+
+    memset(&info, 0, sizeof(info));
+
+    if (!lock_user_struct(VERIFY_READ, target_param, target_param_addr, 1)) {
+        return -TARGET_EFAULT;
+    }
+    info.param.start_func = tswapal(target_param->start_func);
+    info.param.arg = tswapal(target_param->arg);
+    info.param.stack_base = tswapal(target_param->stack_base);
+    info.param.stack_size = tswapal(target_param->stack_size);
+    info.param.tls_base = tswapal(target_param->tls_base);
+    info.param.tls_size = tswapal(target_param->tls_size);
+    info.param.child_tid = tswapal(target_param->child_tid);
+    info.param.parent_tid = tswapal(target_param->parent_tid);
+    info.param.flags = tswap32(target_param->flags);
+    target_rtp_addr = info.param.rtp = tswapal(target_param->rtp);
+    unlock_user(target_param, target_param_addr, 0);
+
+    thr_self(&info.parent_tid);
+
+    if (target_rtp_addr) {
+        if (!lock_user_struct(VERIFY_READ, target_rtp, target_rtp_addr, 1)) {
+            return -TARGET_EFAULT;
+        }
+        rtp.type = tswap16(target_rtp->type);
+        rtp.prio = tswap16(target_rtp->prio);
+        unlock_user(target_rtp, target_rtp_addr, 0);
+        rtp_ptr = &rtp;
+    } else {
+        rtp_ptr = NULL;
+    }
+
+    /* Create a new CPU instance. */
+    ts = g_malloc0(sizeof(TaskState));
+    init_task_state(ts);
+    new_env = cpu_copy(env);
+    target_cpu_reset(new_env);
+
+    /* init regs that differ from the parent thread. */
+    target_cpu_clone_regs(new_env, info.param.stack_base);
+    new_env->opaque = ts;
+    ts->bprm = parent_ts->bprm;
+    ts->info = parent_ts->info;
+
+    target_cpu_set_tls(env, info.param.tls_base);
+
+    /* Grab a mutex so that thread setup appears atomic. */
+    pthread_mutex_lock(new_freebsd_thread_lock_ptr);
+
+    pthread_mutex_init(&info.mutex, NULL);
+    pthread_mutex_lock(&info.mutex);
+    pthread_cond_init(&info.cond, NULL);
+    info.env = new_env;
+
+    /* XXX check return values... */
+    pthread_attr_init(&attr);
+    pthread_attr_setstacksize(&attr, NEW_STACK_SIZE);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+    if (rtp_ptr) {
+        rtp_to_schedparam(&rtp, &sched_policy, &sched_param);
+        pthread_attr_setschedpolicy(&attr, sched_policy);
+        pthread_attr_setschedparam(&attr, &sched_param);
+    }
+
+    /*
+     * It is not safe to deliver signals until the child has finished
+     * initializing, so temporarily block all signals.
+     */
+    sigfillset(&sigmask);
+    sigprocmask(SIG_BLOCK, &sigmask, &info.sigmask);
+
+    ret = pthread_create(&info.thread, &attr, new_freebsd_thread_start, &info);
+    /* XXX Free new CPU state if thread creation fails. */
+
+    sigprocmask(SIG_SETMASK, &info.sigmask, NULL);
+    pthread_attr_destroy(&attr);
+    if (ret == 0) {
+        /* Wait for the child to initialize. */
+        pthread_cond_wait(&info.cond, &info.mutex);
+    } else {
+        /* Creation of new thread failed. */
+        ret = -host_to_target_errno(errno);
+    }
+
+    pthread_mutex_unlock(&info.mutex);
+    pthread_cond_destroy(&info.cond);
+    pthread_mutex_destroy(&info.mutex);
+    pthread_mutex_unlock(new_freebsd_thread_lock_ptr);
+
+    return ret;
+}
diff --git a/bsd-user/freebsd/os-thread.h b/bsd-user/freebsd/os-thread.h
new file mode 100644
index 0000000..9f30471
--- /dev/null
+++ b/bsd-user/freebsd/os-thread.h
@@ -0,0 +1,510 @@
+/*
+ *  FreeBSD thread and user mutex related system call shims
+ *
+ *  Copyright (c) 2013 Stacey D. Son
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __FREEBSD_OS_THREAD_H_
+#define __FREEBSD_OS_THREAD_H_
+
+#include <sys/thr.h>
+#include <sys/rtprio.h>
+
+#include "qemu-os.h"
+
+static abi_long do_freebsd_thr_create(CPUArchState *env, abi_ulong target_ctx,
+        abi_ulong target_id, int flags)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_create()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_self(abi_ulong target_id)
+{
+    abi_long ret;
+    long tid;
+
+    ret = get_errno(thr_self(&tid));
+    if (!is_error(ret)) {
+        if (put_user_sal(tid, target_id)) {
+            return -TARGET_EFAULT;
+        }
+    }
+    return ret;
+}
+
+static abi_long do_freebsd_thr_exit(CPUArchState *cpu_env, abi_ulong tid_addr)
+{
+    CPUState *cpu = ENV_GET_CPU(cpu_env);
+    TaskState *ts;
+
+    /*
+     * XXX This probably breaks if a signal arrives.
+     * We should disable signals.
+     */
+    cpu_list_lock();
+    /* Remove the CPU from the list. */
+    QTAILQ_REMOVE(&cpus, cpu, node);
+    cpu_list_unlock();
+    if (tid_addr) {
+        /* Signal target userland that it can free the stack. */
+        if (!put_user_sal(1, tid_addr)) {
+            freebsd_umtx_wake(tid_addr, INT_MAX);
+        }
+    }
+    thread_cpu = NULL;
+    object_unref(OBJECT(ENV_GET_CPU(cpu_env)));
+    ts = ((CPUArchState *)cpu_env)->opaque;
+    g_free(ts);
+    pthread_exit(NULL);
+    /* Doesn't return */
+    return 0;
+}
+
+static abi_long do_freebsd_thr_kill(long id, int sig)
+{
+
+    return get_errno(thr_kill(id, sig));
+}
+
+static abi_long do_freebsd_thr_kill2(pid_t pid, long id, int sig)
+{
+
+    return get_errno(thr_kill2(pid, id, sig));
+}
+
+static abi_long do_freebsd_thr_suspend(abi_ulong target_ts)
+{
+    abi_long ret;
+    struct timespec ts;
+
+    if (target_ts != 0) {
+        if (t2h_freebsd_timespec(&ts, target_ts)) {
+            return -TARGET_EFAULT;
+        }
+        ret = thr_suspend(&ts);
+    } else {
+        ret = thr_suspend(NULL);
+    }
+    return ret;
+}
+
+static abi_long do_freebsd_thr_wake(long tid)
+{
+
+    return get_errno(thr_wake(tid));
+}
+
+static abi_long do_freebsd_thr_set_name(long tid, abi_ulong target_name)
+{
+    abi_long ret;
+    void *p;
+
+    p = lock_user_string(target_name);
+    if (p == NULL) {
+        return -TARGET_EFAULT;
+    }
+    ret = thr_set_name(tid, p);
+    unlock_user(p, target_name, 0);
+
+    return ret;
+}
+
+static abi_long do_freebsd_rtprio_thread(int function, lwpid_t lwpid,
+        abi_ulong target_addr)
+{
+    int ret;
+    struct rtprio rtp;
+
+    ret = t2h_freebsd_rtprio(&rtp, target_addr);
+    if (!is_error(ret)) {
+        ret = get_errno(rtprio_thread(function, lwpid, &rtp));
+    }
+    if (!is_error(ret)) {
+        ret = h2t_freebsd_rtprio(target_addr, &rtp);
+    }
+    return ret;
+}
+
+static abi_long do_freebsd_getcontext(void *cpu_env, abi_ulong arg1)
+{
+    abi_long ret;
+    target_ucontext_t *ucp;
+    sigset_t sigmask;
+
+    if (arg1 == 0) {
+        return -TARGET_EINVAL;
+    }
+    ret = get_errno(sigprocmask(0, NULL, &sigmask));
+    if (!is_error(ret)) {
+        ucp = lock_user(VERIFY_WRITE, arg1, sizeof(target_ucontext_t), 0);
+        if (ucp == 0) {
+            return -TARGET_EFAULT;
+        }
+        ret = get_mcontext(cpu_env, &ucp->uc_mcontext, TARGET_MC_GET_CLEAR_RET);
+        host_to_target_sigset(&ucp->uc_sigmask, &sigmask);
+        memset(ucp->__spare__, 0, sizeof(ucp->__spare__));
+        unlock_user(ucp, arg1, sizeof(target_ucontext_t));
+    }
+    return ret;
+}
+
+static abi_long do_freebsd_setcontext(void *cpu_env, abi_ulong arg1)
+{
+    abi_long ret;
+    target_ucontext_t *ucp;
+    sigset_t sigmask;
+    if (arg1 == 0) {
+        return -TARGET_EINVAL;
+    }
+    ucp = lock_user(VERIFY_READ, arg1, sizeof(target_ucontext_t), 1);
+    if (ucp == 0) {
+        return -TARGET_EFAULT;
+    }
+    ret = set_mcontext(cpu_env, &ucp->uc_mcontext, 0);
+    target_to_host_sigset(&sigmask, &ucp->uc_sigmask);
+    unlock_user(ucp, arg1, sizeof(target_ucontext_t));
+    if (!is_error(ret)) {
+        (void)sigprocmask(SIG_SETMASK, &sigmask, NULL);
+    }
+    return ret;
+}
+
+/* swapcontext(2) */
+static abi_long do_freebsd_swapcontext(void *cpu_env, abi_ulong arg1,
+        abi_ulong arg2)
+{
+    abi_long ret;
+    target_ucontext_t *ucp;
+    sigset_t sigmask;
+
+    if (arg1 == 0 || arg2 == 0) {
+        return -TARGET_EINVAL;
+    }
+    /* Save current context in arg1. */
+    ret = get_errno(sigprocmask(0, NULL,  &sigmask));
+    if (!is_error(ret)) {
+        ucp = lock_user(VERIFY_WRITE, arg1, sizeof(target_ucontext_t), 0);
+        if (ucp == 0) {
+            return -TARGET_EFAULT;
+        }
+        ret = get_mcontext(cpu_env, &ucp->uc_mcontext, TARGET_MC_GET_CLEAR_RET);
+        host_to_target_sigset(&ucp->uc_sigmask, &sigmask);
+        memset(ucp->__spare__, 0, sizeof(ucp->__spare__));
+        unlock_user(ucp, arg1, sizeof(target_ucontext_t));
+    }
+    if (is_error(ret)) {
+            return ret;
+    }
+
+    /* Restore the context in arg2 to the current context. */
+    ucp = lock_user(VERIFY_READ, arg2, sizeof(target_ucontext_t), 1);
+    if (ucp == 0) {
+        return -TARGET_EFAULT;
+    }
+    ret = set_mcontext(cpu_env, &ucp->uc_mcontext, 0);
+    target_to_host_sigset(&sigmask, &ucp->uc_sigmask);
+    unlock_user(ucp, arg2, sizeof(target_ucontext_t));
+    if (!is_error(ret)) {
+        (void)sigprocmask(SIG_SETMASK, &sigmask, NULL);
+    }
+    return ret;
+}
+
+
+/* undocumented _umtx_lock() */
+static inline abi_long do_freebsd__umtx_lock(abi_ulong target_addr)
+{
+    abi_long ret;
+    long tid;
+
+    ret = get_errno(thr_self(&tid));
+    if (is_error(ret)) {
+        return ret;
+    }
+    return freebsd_lock_umtx(target_addr, tid, NULL);
+}
+
+/* undocumented _umtx_unlock() */
+static inline abi_long do_freebsd__umtx_unlock(abi_ulong target_addr)
+{
+    abi_long ret;
+    long tid;
+
+    ret = get_errno(thr_self(&tid));
+    if (is_error(ret)) {
+        return ret;
+    }
+    return freebsd_unlock_umtx(target_addr, tid);
+}
+
+/* undocumented _umtx_op(void *obj, int op, u_long val, void *uaddr,
+                           void *target_ts); */
+static inline abi_long do_freebsd__umtx_op(abi_ulong obj, int op, abi_ulong val,
+        abi_ulong uaddr, abi_ulong target_ts)
+{
+    abi_long ret;
+    struct timespec ts;
+    long tid;
+
+    switch (op) {
+    case TARGET_UMTX_OP_LOCK:
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        if (target_ts != 0) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_lock_umtx(obj, tid, &ts);
+        } else {
+            ret = freebsd_lock_umtx(obj, tid, NULL);
+        }
+        break;
+
+    case TARGET_UMTX_OP_UNLOCK:
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        ret = freebsd_unlock_umtx(obj, tid);
+        break;
+
+    case TARGET_UMTX_OP_WAIT:
+        /* args: obj *, val, ts * */
+        if (target_ts != 0) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_umtx_wait(obj, tswapal(val), &ts);
+        } else {
+            ret = freebsd_umtx_wait(obj, tswapal(val), NULL);
+        }
+        break;
+
+    case TARGET_UMTX_OP_WAKE:
+        /* args: obj *, nr_wakeup */
+        ret = freebsd_umtx_wake(obj, val);
+        break;
+
+    case TARGET_UMTX_OP_MUTEX_LOCK:
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        if (target_ts) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_lock_umutex(obj, tid, &ts, 0);
+        } else {
+            ret = freebsd_lock_umutex(obj, tid, NULL, 0);
+        }
+        break;
+
+    case TARGET_UMTX_OP_MUTEX_UNLOCK:
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        ret = freebsd_unlock_umutex(obj, tid);
+        break;
+
+    case TARGET_UMTX_OP_MUTEX_TRYLOCK:
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        ret = freebsd_lock_umutex(obj, tid, NULL, TARGET_UMUTEX_TRY);
+        break;
+
+    case TARGET_UMTX_OP_MUTEX_WAIT:
+        ret = get_errno(thr_self(&tid));
+        if (is_error(ret)) {
+            return ret;
+        }
+        if (target_ts != 0) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_lock_umutex(obj, tid, &ts, TARGET_UMUTEX_WAIT);
+        } else {
+            ret = freebsd_lock_umutex(obj, tid, NULL, TARGET_UMUTEX_WAIT);
+        }
+        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:
+        /*
+         * Initialization of the struct conv is done by
+         * bzero'ing everything in userland.
+         */
+        if (target_ts != 0) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_cv_wait(obj, uaddr, &ts, val);
+        } else {
+            ret = freebsd_cv_wait(obj, uaddr, NULL, val);
+        }
+        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:
+        if (!access_ok(VERIFY_READ, obj, sizeof(abi_ulong))) {
+            return -TARGET_EFAULT;
+        }
+        if (target_ts != 0) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_umtx_wait_uint(obj, tswap32((uint32_t)val), &ts);
+        } else {
+            ret = freebsd_umtx_wait_uint(obj, tswap32((uint32_t)val), NULL);
+        }
+        break;
+
+    case TARGET_UMTX_OP_WAIT_UINT_PRIVATE:
+        if (!access_ok(VERIFY_READ, obj, sizeof(abi_ulong))) {
+            return -TARGET_EFAULT;
+        }
+        if (target_ts != 0) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_umtx_wait_uint_private(obj, tswap32((uint32_t)val),
+                    &ts);
+        } else {
+            ret = freebsd_umtx_wait_uint_private(obj, tswap32((uint32_t)val),
+                    NULL);
+        }
+        break;
+
+    case TARGET_UMTX_OP_WAKE_PRIVATE:
+        /* Don't need to do access_ok(). */
+        ret = freebsd_umtx_wake_private(obj, val);
+        break;
+
+#if defined(__FreeBSD_version) && __FreeBSD_version > 900000 && defined(TARGET_UMTX_OP_NWAKE_PRIVATE)
+    case TARGET_UMTX_OP_NWAKE_PRIVATE:
+        {
+            int i;
+            abi_ulong *uaddr;
+            uint32_t imax = tswap32(INT_MAX);
+
+            if (!access_ok(VERIFY_READ, obj, val * sizeof(uint32_t))) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_umtx_nwake_private(obj, val);
+
+            uaddr = (abi_ulong *)g2h(obj);
+            ret = 0;
+            for (i = 0; i < (int32_t)val; i++) {
+                ret = freebsd_umtx_wake_private(tswapal(uaddr[i]), imax);
+                if (is_error(ret)) {
+                    break;
+                }
+            }
+        }
+        break;
+#endif
+
+    case TARGET_UMTX_OP_RW_RDLOCK:
+        if (target_ts != 0) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_rw_rdlock(obj, val, &ts);
+        } else {
+            ret = freebsd_rw_rdlock(obj, val, NULL);
+        }
+        break;
+
+    case TARGET_UMTX_OP_RW_WRLOCK:
+        if (target_ts != 0) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_rw_wrlock(obj, val, &ts);
+        } else {
+            ret = freebsd_rw_wrlock(obj, val, NULL);
+        }
+        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
+
+#if defined(__FreeBSD_version) && __FreeBSD_version > 900000
+    case TARGET_UMTX_OP_SEM_WAIT:
+        if (target_ts != 0) {
+            if (t2h_freebsd_timespec(&ts, target_ts)) {
+                return -TARGET_EFAULT;
+            }
+            ret = freebsd_umtx_sem_wait(obj, &ts);
+        } else {
+            ret = freebsd_umtx_sem_wait(obj, NULL);
+        }
+        break;
+
+    case TARGET_UMTX_OP_SEM_WAKE:
+        /* Don't need to do access_ok(). */
+        ret = freebsd_umtx_sem_wake(obj, val);
+        break;
+#endif
+    default:
+        return -TARGET_EINVAL;
+    }
+    return ret;
+}
+
+#endif /* !__FREEBSD_OS_THREAD_H_ */
diff --git a/bsd-user/freebsd/qemu-os.h b/bsd-user/freebsd/qemu-os.h
index 90d8eb4..b5510dc 100644
--- a/bsd-user/freebsd/qemu-os.h
+++ b/bsd-user/freebsd/qemu-os.h
@@ -64,4 +64,10 @@ abi_long h2t_freebsd_fhandle(abi_ulong target_addr, fhandle_t *host_fh);
 abi_long h2t_freebsd_statfs(abi_ulong target_addr, struct statfs *host_statfs);
 abi_long target_to_host_fcntl_cmd(int cmd);
 
+/* os-thread.c */
+abi_long t2h_freebsd_rtprio(struct rtprio *host_rtp, abi_ulong target_addr);
+abi_long h2t_freebsd_rtprio(abi_ulong target_addr, struct rtprio *host_rtp);
+abi_long do_freebsd_thr_new(CPUArchState *env, abi_ulong target_param_addr,
+        int32_t param_size);
+
 #endif /* !_QEMU_OS_H_ */
diff --git a/bsd-user/main.c b/bsd-user/main.c
index 03053b0..47675cd 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -60,45 +60,127 @@ unsigned long target_sgrowsiz = TARGET_SGROWSIZ; /* amount to grow stack */
 
 char qemu_proc_pathname[PATH_MAX];  /* full path to exeutable */
 
-/* These are no-ops because we are not threadsafe.  */
+/* Helper routines for implementing atomic operations. */
+
+/*
+ * To implement exclusive operations we force all cpus to synchronize.
+ * We don't require a full sync, only that no cpus are executing guest code.
+ * The alternative is to map target atomic ops onto host eqivalents,
+ * which requires quite a lot of per host/target work.
+ */
+static pthread_mutex_t cpu_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t exclusive_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t exclusive_cond = PTHREAD_COND_INITIALIZER;
+static pthread_cond_t exclusive_resume = PTHREAD_COND_INITIALIZER;
+static int pending_cpus;
+
+/* Make sure everything is in a consistent state for calling fork(). */
 void fork_start(void)
 {
+    pthread_mutex_lock(&tcg_ctx.tb_ctx.tb_lock);
+    pthread_mutex_lock(&exclusive_lock);
+    mmap_fork_start();
 }
 
 void fork_end(int child)
 {
+    mmap_fork_end(child);
     if (child) {
+        CPUState *cpu, *next_cpu;
+        /*
+         * Child processes created by fork() only have a single thread.
+         * Discard information about the parent threads.
+         */
+        CPU_FOREACH_SAFE(cpu, next_cpu) {
+            if (cpu != thread_cpu) {
+                QTAILQ_REMOVE(&cpus, thread_cpu, node);
+            }
+        }
+        pending_cpus = 0;
+        pthread_mutex_init(&exclusive_lock, NULL);
+        pthread_mutex_init(&cpu_list_mutex, NULL);
+        pthread_cond_init(&exclusive_cond, NULL);
+        pthread_cond_init(&exclusive_resume, NULL);
+        pthread_mutex_init(&tcg_ctx.tb_ctx.tb_lock, NULL);
         gdbserver_fork((CPUArchState *)thread_cpu->env_ptr);
+    } else {
+        pthread_mutex_unlock(&exclusive_lock);
+        pthread_mutex_unlock(&tcg_ctx.tb_ctx.tb_lock);
     }
 }
 
+/*
+ * Wait for pending exclusive operations to complete.  The exclusive lock
+ * must be held.
+ */
 static inline void exclusive_idle(void)
 {
+    while (pending_cpus) {
+        pthread_cond_wait(&exclusive_resume, &exclusive_lock);
+    }
 }
 
-static inline void start_exclusive(void)
+/* Start an exclusive operation.  Must only be called outside of cpu_exec. */
+void start_exclusive(void)
 {
+    CPUState *other_cpu;
+
+    pthread_mutex_lock(&exclusive_lock);
+    exclusive_idle();
+
+    pending_cpus = 1;
+    /* Make all other cpus stop executing. */
+    CPU_FOREACH(other_cpu) {
+        if (other_cpu->running) {
+            pending_cpus++;
+            cpu_exit(other_cpu);
+        }
+    }
+    if (pending_cpus > 1) {
+        pthread_cond_wait(&exclusive_cond, &exclusive_lock);
+    }
 }
 
-static inline void end_exclusive(void)
+/* Finish an exclusive operation. */
+void end_exclusive(void)
 {
+    pending_cpus = 0;
+    pthread_cond_broadcast(&exclusive_resume);
+    pthread_mutex_unlock(&exclusive_lock);
 }
 
-static inline void cpu_exec_start(CPUState *env)
+/* Wait for exclusive ops to finish, and begin cpu execution. */
+void cpu_exec_start(CPUState *cpu)
 {
+    pthread_mutex_lock(&exclusive_lock);
+    exclusive_idle();
+    cpu->running = true;
+    pthread_mutex_unlock(&exclusive_lock);
 }
 
-
-static inline void cpu_exec_end(CPUState *env)
+/* Mark cpu as not excuting, and release pending exclusive ops. */
+void cpu_exec_end(CPUState *cpu)
 {
+    pthread_mutex_lock(&exclusive_lock);
+    cpu->running = false;
+    if (pending_cpus > 1) {
+        pending_cpus--;
+        if (pending_cpus == 1) {
+            pthread_cond_signal(&exclusive_cond);
+        }
+    }
+    exclusive_idle();
+    pthread_mutex_unlock(&exclusive_lock);
 }
 
 void cpu_list_lock(void)
 {
+    pthread_mutex_lock(&cpu_list_mutex);
 }
 
 void cpu_list_unlock(void)
 {
+    pthread_mutex_unlock(&cpu_list_mutex);
 }
 
 void cpu_loop(CPUArchState *env)
diff --git a/bsd-user/mmap.c b/bsd-user/mmap.c
index f8ef423..849624f 100644
--- a/bsd-user/mmap.c
+++ b/bsd-user/mmap.c
@@ -29,13 +29,37 @@
 
 // #define DEBUG_MMAP
 
-/* We aren't threadsafe to start with, so no need to worry about locking.  */
+pthread_mutex_t mmap_mutex = PTHREAD_MUTEX_INITIALIZER;
+static __thread int mmap_lock_count;
+
 void mmap_lock(void)
 {
+    if (mmap_lock_count++ == 0) {
+        pthread_mutex_lock(&mmap_mutex);
+    }
 }
 
 void mmap_unlock(void)
 {
+    if (--mmap_lock_count == 0) {
+        pthread_mutex_unlock(&mmap_mutex);
+    }
+}
+
+/* Grab lock to make sure things are in a consistent state after fork().  */
+void mmap_fork_start(void)
+{
+    if (mmap_lock_count)
+        abort();
+    pthread_mutex_lock(&mmap_mutex);
+}
+
+void mmap_fork_end(int child)
+{
+    if (child)
+        pthread_mutex_init(&mmap_mutex, NULL);
+    else
+        pthread_mutex_unlock(&mmap_mutex);
 }
 
 /* NOTE: all the constants are the HOST ones, but addresses are target. */
diff --git a/bsd-user/netbsd/os-thread.c b/bsd-user/netbsd/os-thread.c
new file mode 100644
index 0000000..a4af765
--- /dev/null
+++ b/bsd-user/netbsd/os-thread.c
@@ -0,0 +1 @@
+/* XXX NetBSD thread related helpers */
diff --git a/bsd-user/netbsd/os-thread.h b/bsd-user/netbsd/os-thread.h
new file mode 100644
index 0000000..073b0a0
--- /dev/null
+++ b/bsd-user/netbsd/os-thread.h
@@ -0,0 +1,133 @@
+#ifndef __NETBSD_OS_THREAD_H_
+#define __NETBSD_OS_THREAD_H_
+
+/*
+ * XXX To support FreeBSD binaries on NetBSD these syscalls will need to
+ * be emulated.
+ */
+static abi_long do_freebsd_thr_create(CPUArchState *env, abi_ulong target_ctx,
+        abi_ulong target_id, int flags)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_create()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_create(CPUArchState *env, abi_ulong thread_ctx,
+        abi_ulong target_id, int flags)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_create()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_new(CPUArchState *env,
+        abi_ulong target_param_addr, int32_t param_size)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_new()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_self(abi_ulong target_id)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_self()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_exit(CPUArchState *cpu_env, abi_ulong tid_addr)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_exit()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_kill(long id, int sig)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_kill()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_kill2(pid_t pid, long id, int sig)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_kill2()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_suspend(abi_ulong target_ts)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_suspend()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_wake(long tid)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_wake()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_set_name(long tid, abi_ulong target_name)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_set_name()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_rtprio_thread(int function, lwpid_t lwpid,
+        abi_ulong target_addr)
+{
+
+    qemu_log("qemu: Unsupported syscall rtprio_thread()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_getcontext(void *cpu_env, abi_ulong arg1)
+{
+
+    qemu_log("qemu: Unsupported syscall getcontext()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_setcontext(void *cpu_env, abi_ulong arg1)
+{
+
+    qemu_log("qemu: Unsupported syscall setcontext()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_swapcontext(void *cpu_env, abi_ulong arg1,
+        abi_ulong arg2)
+{
+
+    qemu_log("qemu: Unsupported syscall swapcontext()\n");
+    return -TARGET_ENOSYS;
+}
+
+static inline abi_long do_freebsd__umtx_lock(abi_ulong target_addr)
+{
+
+    qemu_log("qemu: Unsupported syscall _umtx_lock()\n");
+    return -TARGET_ENOSYS;
+}
+
+static inline abi_long do_freebsd__umtx_unlock(abi_ulong target_addr)
+{
+
+    qemu_log("qemu: Unsupported syscall _umtx_unlock()\n");
+    return -TARGET_ENOSYS;
+}
+
+static inline abi_long do_freebsd__umtx_op(abi_ulong obj, int op, abi_ulong val,
+        abi_ulong uaddr, abi_ulong target_ts)
+{
+
+    qemu_log("qemu: Unsupported syscall _umtx_op()\n");
+    return -TARGET_ENOSYS;
+}
+
+#endif /* ! __NETBSD_OS_THREAD_H_ */
diff --git a/bsd-user/openbsd/os-thread.c b/bsd-user/openbsd/os-thread.c
new file mode 100644
index 0000000..d125281
--- /dev/null
+++ b/bsd-user/openbsd/os-thread.c
@@ -0,0 +1 @@
+/* XXX OpenBSD thread related helpers */
diff --git a/bsd-user/openbsd/os-thread.h b/bsd-user/openbsd/os-thread.h
new file mode 100644
index 0000000..962a769
--- /dev/null
+++ b/bsd-user/openbsd/os-thread.h
@@ -0,0 +1,133 @@
+#ifndef __OPENBSD_OS_THREAD_H_
+#define __OPENBSD_OS_THREAD_H_
+
+/*
+ * XXX To support FreeBSD binaries on OpenBSD these syscalls will need to
+ * be emulated.
+ */
+static abi_long do_freebsd_thr_create(CPUArchState *env, abi_ulong target_ctx,
+        abi_ulong target_id, int flags)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_create()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_create(CPUArchState *env, abi_ulong thread_ctx,
+        abi_ulong target_id, int flags)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_create()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_new(CPUArchState *env,
+        abi_ulong target_param_addr, int32_t param_size)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_new()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_self(abi_ulong target_id)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_self()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_exit(CPUArchState *cpu_env, abi_ulong tid_addr)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_exit()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_kill(long id, int sig)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_kill()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_kill2(pid_t pid, long id, int sig)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_kill2()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_suspend(abi_ulong target_ts)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_suspend()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_wake(long tid)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_wake()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_thr_set_name(long tid, abi_ulong target_name)
+{
+
+    qemu_log("qemu: Unsupported syscall thr_set_name()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_rtprio_thread(int function, lwpid_t lwpid,
+        abi_ulong target_addr)
+{
+
+    qemu_log("qemu: Unsupported syscall rtprio_thread()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_getcontext(void *cpu_env, abi_ulong arg1)
+{
+
+    qemu_log("qemu: Unsupported syscall getcontext()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_setcontext(void *cpu_env, abi_ulong arg1)
+{
+
+    qemu_log("qemu: Unsupported syscall setcontext()\n");
+    return -TARGET_ENOSYS;
+}
+
+static abi_long do_freebsd_swapcontext(void *cpu_env, abi_ulong arg1,
+        abi_ulong arg2)
+{
+
+    qemu_log("qemu: Unsupported syscall swapcontext()\n");
+    return -TARGET_ENOSYS;
+}
+
+static inline abi_long do_freebsd__umtx_lock(abi_ulong target_addr)
+{
+
+    qemu_log("qemu: Unsupported syscall _umtx_lock()\n");
+    return -TARGET_ENOSYS;
+}
+
+static inline abi_long do_freebsd__umtx_unlock(abi_ulong target_addr)
+{
+
+    qemu_log("qemu: Unsupported syscall _umtx_unlock()\n");
+    return -TARGET_ENOSYS;
+}
+
+static inline abi_long do_freebsd__umtx_op(abi_ulong obj, int op, abi_ulong val,
+        abi_ulong uaddr, abi_ulong target_ts)
+{
+
+    qemu_log("qemu: Unsupported syscall _umtx_op()\n");
+    return -TARGET_ENOSYS;
+}
+
+#endif /* ! __OPENBSD_OS_THREAD_H_ */
diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h
index 613a89e..4b2add2 100644
--- a/bsd-user/qemu.h
+++ b/bsd-user/qemu.h
@@ -42,11 +42,7 @@ extern enum BSDType bsd_type;
 #include "target_os_signal.h"
 #include "exec/gdbstub.h"
 
-#if defined(CONFIG_USE_NPTL)
 #define THREAD __thread
-#else
-#define THREAD
-#endif
 
 /* This struct is used to hold certain information about the image.
  * Basically, it replicates in user space what would be certain
@@ -67,6 +63,8 @@ struct image_info {
     abi_ulong entry;
     abi_ulong code_offset;
     abi_ulong data_offset;
+    abi_ulong arg_start;
+    abi_ulong arg_end;
     int       personality;
 };
 
@@ -89,6 +87,15 @@ struct emulated_sigtable {
 typedef struct TaskState {
     struct TaskState *next;
     int used; /* non zero if used */
+#ifdef TARGET_ARM
+    int swi_errno;
+#endif
+#if defined(TARGET_ARM) || defined(TARGET_M68K) || defined(TARGET_UNICORE32)
+    /* Extra fields for semihosted binaries. */
+    uint32_t heap_base;
+    uint32_t heap_limit;
+    uint32_t stack_base;
+#endif
     struct image_info *info;
     struct bsd_binprm *bprm;
 
@@ -231,10 +238,8 @@ void mmap_unlock(void);
 abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size);
 void cpu_list_lock(void);
 void cpu_list_unlock(void);
-#if defined(CONFIG_USE_NPTL)
 void mmap_fork_start(void);
 void mmap_fork_end(int child);
-#endif
 
 /* main.c */
 extern unsigned long target_maxtsiz;
@@ -244,10 +249,15 @@ extern unsigned long target_dflssiz;
 extern unsigned long target_maxssiz;
 extern unsigned long target_sgrowsiz;
 extern char qemu_proc_pathname[];
+void start_exclusive(void);
+void end_exclusive(void);
+void cpu_exec_start(CPUState *cpu);
+void cpu_exec_end(CPUState *cpu);
 
 /* syscall.c */
 abi_long get_errno(abi_long ret);
 int is_error(abi_long ret);
+int host_to_target_errno(int err);
 
 /* os-proc.c */
 abi_long freebsd_exec_common(abi_ulong path_or_fd, abi_ulong guest_argp,
@@ -258,6 +268,41 @@ abi_long do_freebsd_sysctl(CPUArchState *env, abi_ulong namep, int32_t namelen,
         abi_ulong oldp, abi_ulong oldlenp, abi_ulong newp, abi_ulong newlen);
 abi_long do_freebsd_sysarch(void *cpu_env, abi_long arg1, abi_long arg2);
 
+/* os-thread.c */
+extern pthread_mutex_t *new_freebsd_thread_lock_ptr;
+void *new_freebsd_thread_start(void *arg);
+abi_long freebsd_lock_umtx(abi_ulong target_addr, abi_long tid,
+        struct timespec *timeout);
+abi_long freebsd_unlock_umtx(abi_ulong target_addr, abi_long id);
+abi_long freebsd_umtx_wait(abi_ulong targ_addr, abi_ulong id,
+        struct timespec *ts);
+abi_long freebsd_umtx_wake(abi_ulong target_addr, uint32_t n_wake);
+abi_long freebsd_umtx_mutex_wake(abi_ulong target_addr, abi_long val);
+abi_long freebsd_umtx_wait_uint(abi_ulong obj, uint32_t val,
+                struct timespec *timeout);
+abi_long freebsd_umtx_wait_uint_private(abi_ulong obj, uint32_t val,
+                struct timespec *timeout);
+abi_long freebsd_umtx_wake_private(abi_ulong obj, uint32_t val);
+#if defined(__FreeBSD_version) && __FreeBSD_version > 900000
+abi_long freebsd_umtx_nwake_private(abi_ulong obj, uint32_t val);
+abi_long freebsd_umtx_mutex_wake2(abi_ulong obj, uint32_t val);
+abi_long freebsd_umtx_sem_wait(abi_ulong obj, struct timespec *timeout);
+abi_long freebsd_umtx_sem_wake(abi_ulong obj, uint32_t val);
+#endif
+abi_long freebsd_lock_umutex(abi_ulong target_addr, uint32_t id,
+        struct timespec *ts, int mode);
+abi_long freebsd_unlock_umutex(abi_ulong target_addr, uint32_t id);
+abi_long freebsd_cv_wait(abi_ulong target_ucond_addr,
+                abi_ulong target_umtx_addr, struct timespec *ts, int wflags);
+abi_long freebsd_cv_signal(abi_ulong target_ucond_addr);
+abi_long freebsd_cv_broadcast(abi_ulong target_ucond_addr);
+abi_long freebsd_rw_rdlock(abi_ulong target_addr, long fflag,
+        struct timespec *ts);
+abi_long freebsd_rw_wrlock(abi_ulong target_addr, long fflag,
+        struct timespec *ts);
+abi_long freebsd_rw_unlock(abi_ulong target_addr);
+
+
 /* user access */
 
 #define VERIFY_READ 0
@@ -483,8 +528,6 @@ static inline int regpairs_aligned(void *cpu_env)
 }
 #endif
 
-#if defined(CONFIG_USE_NPTL)
 #include <pthread.h>
-#endif
 
 #endif /* QEMU_H */
diff --git a/bsd-user/syscall.c b/bsd-user/syscall.c
index 286c71e..0a851fe 100644
--- a/bsd-user/syscall.c
+++ b/bsd-user/syscall.c
@@ -32,7 +32,6 @@
 #include "qemu-common.h"
 
 #define target_to_host_bitmask(x, tbl) (x)
-static int host_to_target_errno(int err);
 
 /* BSD independent syscall shims */
 #include "bsd-file.h"
@@ -47,6 +46,7 @@ static int host_to_target_errno(int err);
 #include "os-signal.h"
 #include "os-socket.h"
 #include "os-stat.h"
+#include "os-thread.h"
 
 /* #define DEBUG */
 
@@ -64,7 +64,7 @@ abi_long get_errno(abi_long ret)
     }
 }
 
-static int host_to_target_errno(int err)
+int host_to_target_errno(int err)
 {
     /* XXX need to translate host errnos here */
     return err;
@@ -1110,6 +1110,73 @@ abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1,
         break;
 
         /*
+         * thread system calls
+         */
+    case TARGET_FREEBSD_NR_thr_create: /* thr_create(2) */
+        ret = do_freebsd_thr_create(cpu_env, arg1, arg2, arg3);
+        break;
+
+    case TARGET_FREEBSD_NR_thr_new: /* thr_new(2) */
+        ret = do_freebsd_thr_new(cpu_env, arg1, arg2);
+        break;
+
+    case TARGET_FREEBSD_NR_thr_set_name: /* thr_set_name(2) */
+        ret = do_freebsd_thr_set_name(arg1, arg2);
+        break;
+
+    case TARGET_FREEBSD_NR_thr_self: /* thr_self(2) */
+        ret = do_freebsd_thr_self(arg1);
+        break;
+
+    case TARGET_FREEBSD_NR_thr_suspend: /* thr_suspend(2) */
+        ret = do_freebsd_thr_suspend(arg1);
+        break;
+
+    case TARGET_FREEBSD_NR_thr_wake: /* thr_wake(2) */
+        ret = do_freebsd_thr_wake(arg1);
+        break;
+
+    case TARGET_FREEBSD_NR_thr_kill: /* thr_kill(2) */
+        ret = do_freebsd_thr_kill(arg1, arg2);
+        break;
+
+    case TARGET_FREEBSD_NR_thr_kill2: /* thr_kill2(2) */
+        ret = do_freebsd_thr_kill2(arg1, arg2, arg3);
+        break;
+
+    case TARGET_FREEBSD_NR_thr_exit: /* thr_exit(2) */
+        ret = do_freebsd_thr_exit(cpu_env, arg1);
+        break;
+
+    case TARGET_FREEBSD_NR_rtprio_thread: /* rtprio_thread(2) */
+        ret = do_freebsd_rtprio_thread(arg1, arg2, arg3);
+        break;
+
+    case TARGET_FREEBSD_NR_getcontext: /* getcontext(2) */
+        ret = do_freebsd_getcontext(cpu_env, arg1);
+        break;
+
+    case TARGET_FREEBSD_NR_setcontext: /* setcontext(2) */
+        ret = do_freebsd_setcontext(cpu_env, arg1);
+        break;
+
+    case TARGET_FREEBSD_NR_swapcontext: /* swapcontext(2) */
+        ret = do_freebsd_swapcontext(cpu_env, arg1, arg2);
+        break;
+
+    case TARGET_FREEBSD_NR__umtx_lock: /* undocumented */
+        ret = do_freebsd__umtx_lock(arg1);
+        break;
+
+    case TARGET_FREEBSD_NR__umtx_unlock: /* undocumented */
+        ret = do_freebsd__umtx_unlock(arg1);
+        break;
+
+    case TARGET_FREEBSD_NR__umtx_op: /* undocumented */
+        ret = do_freebsd__umtx_op(arg1, arg2, arg3, arg4, arg5);
+        break;
+
+        /*
          * sys{ctl, arch, call}
          */
     case TARGET_FREEBSD_NR___sysctl: /* sysctl(3) */
