diff --git a/bsd-user/Makefile.objs b/bsd-user/Makefile.objs
index cc7a82b..2608337 100644
--- a/bsd-user/Makefile.objs
+++ b/bsd-user/Makefile.objs
@@ -1,4 +1,4 @@
 obj-y = main.o bsdload.o elfload.o mmap.o signal.o strace.o syscall.o \
 	        uaccess.o bsd-mem.o bsd-proc.o $(TARGET_OS)/os-time.o \
-		    $(TARGET_OS)/os-proc.o bsd-socket.o $(TARGET_OS)/os-socket.o \
-			$(TARGET_OS)/os-stat.o
+			$(TARGET_OS)/os-proc.o bsd-socket.o $(TARGET_OS)/os-socket.o \
+			$(TARGET_OS)/os-stat.o $(TARGET_OS)/os-thread.o
diff --git a/bsd-user/freebsd/os-thread.c b/bsd-user/freebsd/os-thread.c
new file mode 100644
index 0000000..98873f3
--- /dev/null
+++ b/bsd-user/freebsd/os-thread.c
@@ -0,0 +1,802 @@
+/*
+ *  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/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_thread.h"
+
+/* 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;
+};
+
+#if defined(CONFIG_USE_NPTL)
+
+static pthread_mutex_t new_thread_lock = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t *new_freebsd_thread_lock_ptr = &new_thread_lock;
+
+void *new_freebsd_thread_start(void *arg)
+{
+    new_freebsd_thread_info_t *info = arg;
+    CPUArchState *env;
+    TaskState *ts;
+    long tid;
+
+    env = info->env;
+    thread_env = env;
+    fork_end(1);
+
+    ts = (TaskState *)thread_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. */
+    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;
+}
+#endif /* CONFIG_USE_NPTL */
+
+/*
+ * 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
+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));
+}
+
+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));
+}
+
+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;
+    }
+}
+
+
diff --git a/bsd-user/freebsd/os-thread.h b/bsd-user/freebsd/os-thread.h
new file mode 100644
index 0000000..4c8b6ce
--- /dev/null
+++ b/bsd-user/freebsd/os-thread.h
@@ -0,0 +1,742 @@
+/*
+ *  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"
+
+#if defined(CONFIG_USE_NPTL)
+
+#define NEW_STACK_SIZE  0x40000
+
+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 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;
+    }
+}
+
+static 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. */
+    fork_start();
+    ts = g_malloc0(sizeof(TaskState));
+    init_task_state(ts);
+    new_env = cpu_copy(env);
+#if defined(TARGET_I386) || defined(TARGET_SPARC) || defined(TARGET_PPC)
+    cpu_reset(ENV_GET_CPU(new_env));
+#endif
+
+    /* init regs that differ from the parent thread. */
+    cpu_clone_regs(new_env, info.param.stack_base);
+    new_env->opaque = ts;
+    ts->bprm = parent_ts->bprm;
+    ts->info = parent_ts->info;
+
+#if defined(TARGET_MIPS) || defined(TARGET_ARM)
+    cpu_set_tls(env, info.param.tls_base);
+#endif
+
+    /* 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 return values should be checked... */
+    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. */
+
+    fork_end(0);
+
+    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. Free new CPU state. */
+        cpu_list_lock();
+        env->next_cpu = new_env->next_cpu;
+        cpu_list_unlock();
+        g_free(ts);
+        object_unref(OBJECT(ENV_GET_CPU(env)));
+    }
+
+    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;
+}
+
+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)
+{
+
+    if (first_cpu->next_cpu) {
+        TaskState *ts;
+        CPUArchState **lastp, *p;
+
+        /*
+         * *XXX This probably breaks if a signal arrives.
+         * We should disable signals.
+         */
+        cpu_list_lock();
+        lastp = &first_cpu;
+        p = first_cpu;
+        while (p && p != (CPUArchState *)cpu_env) {
+            lastp = &p->next_cpu;
+            p = p->next_cpu;
+        }
+        /*
+         * if we didn't find the CPU for this thread then something
+         * is horribly wrong.
+         */
+        if (!p) {
+            abort();
+        }
+        /* Remove the CPU from the list. */
+        *lastp = p->next_cpu;
+        cpu_list_unlock();
+        ts = ((CPUArchState *)cpu_env)->opaque;
+
+        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_env = NULL;
+        object_unref(OBJECT(ENV_GET_CPU(cpu_env)));
+        g_free(ts);
+        pthread_exit(NULL);
+    }
+
+    gdb_exit(cpu_env, 0); /* XXX need to put in the correct exit status here? */
+    _exit(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;
+}
+
+#else /* ! CONFIG_USE_NPTL */
+
+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;
+}
+#endif /* ! CONFIG_USE_NPTL */
+
+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
+    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 0dd65f5..eac9085 100644
--- a/bsd-user/freebsd/qemu-os.h
+++ b/bsd-user/freebsd/qemu-os.h
@@ -23,6 +23,7 @@
 #include <sys/types.h>
 #include <sys/mount.h>
 #include <sys/timex.h>
+#include <sys/rtprio.h>
 #include <sys/select.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
@@ -62,4 +63,8 @@ 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);
+
 #endif /* !_QEMU_OS_H_ */
diff --git a/bsd-user/i386/target_arch_signal.h b/bsd-user/i386/target_arch_signal.h
index 2e89529..7fd1399 100644
--- a/bsd-user/i386/target_arch_signal.h
+++ b/bsd-user/i386/target_arch_signal.h
@@ -27,6 +27,8 @@
 #define TARGET_MINSIGSTKSZ  (512 * 4)               /* min sig stack size */
 #define TARGET_SIGSTKSZ     (MINSIGSTKSZ + 32768)   /* recommended size */
 
+#define TARGET_MC_GET_CLEAR_RET 0x0001
+
 struct target_sigcontext {
     /* to be added */
 };
@@ -53,9 +55,19 @@ struct target_sigframe {
     uint32_t    __spare__[2];
 };
 
+/* Compare to i386/i386/machdep.c get_mcontext() */
+static inline abi_long get_mcontext(CPUX86State *regs,
+        target_mcontext_t *mcp, int flags)
+{
+    /* XXX */
+    return -TARGET_EOPNOTSUPP;
+}
+
+/* Compare to i386/i386/machdep.c set_mcontext() */
 static inline abi_long set_mcontext(CPUX86State *regs,
         target_mcontext_t *mcp, int srflag)
 {
+    /* XXX */
     return -TARGET_EOPNOTSUPP;
 }
 
diff --git a/bsd-user/i386/target_arch_thread.h b/bsd-user/i386/target_arch_thread.h
new file mode 100644
index 0000000..8307dce
--- /dev/null
+++ b/bsd-user/i386/target_arch_thread.h
@@ -0,0 +1,30 @@
+/*
+ *  i386 thread support
+ *
+ *  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 _TARGET_ARCH_THREAD_H_
+#define _TARGET_ARCH_THREAD_H_
+
+/* Compare to vm_machdep.c cpu_set_upcall_kse() */
+static inline void
+thread_set_upcall(CPUX86State *regs, abi_ulong entry,
+    abi_ulong arg, abi_ulong stack_base, abi_ulong stack_size)
+{
+    /* XXX */
+}
+
+#endif /* !_TARGET_ARCH_THREAD_H_ */
diff --git a/bsd-user/main.c b/bsd-user/main.c
index 1fa5e7f..bcfc7ce 100644
--- a/bsd-user/main.c
+++ b/bsd-user/main.c
@@ -31,7 +31,6 @@
 
 #include "qemu.h"
 #include "qemu-common.h"
-/* For tb_lock */
 #include "cpu.h"
 #include "tcg.h"
 #include "qemu/timer.h"
@@ -99,41 +98,187 @@ int cpu_get_pic_interrupt(CPUX86State *env)
 }
 #endif
 
-/* These are no-ops because we are not threadsafe.  */
-static inline void cpu_exec_start(CPUArchState *env)
+#if defined(CONFIG_USE_NPTL)
+/* 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();
 }
 
-static inline void cpu_exec_end(CPUArchState *env)
+void fork_end(int child)
 {
+    mmap_fork_end(child);
+    if (child) {
+        /*
+         * Child processes created by fork() only have a single thread.
+         * Discard information about the parent threads.
+         */
+        first_cpu = thread_env;
+        thread_env->next_cpu = NULL;
+        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(thread_env);
+    } else {
+        pthread_mutex_unlock(&exclusive_lock);
+        pthread_mutex_unlock(&tcg_ctx.tb_ctx.tb_lock);
+    }
 }
 
-static inline void start_exclusive(void)
+/*
+ * 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 end_exclusive(void)
+/* Start an exclusive operation.  Must only be called outside of cpu_exec. */
+static inline void
+start_exclusive(void)
 {
+    CPUArchState *other;
+    CPUState *other_cpu;
+
+    pthread_mutex_lock(&exclusive_lock);
+    exclusive_idle();
+
+    pending_cpus = 1;
+    /* Make all other cpus stop executing. */
+    for (other = first_cpu; other; other = other->next_cpu) {
+        other_cpu = ENV_GET_CPU(other);
+        if (other_cpu->running) {
+            pending_cpus++;
+            cpu_exit(other);
+        }
+    }
+    if (pending_cpus > 1) {
+        pthread_cond_wait(&exclusive_cond, &exclusive_lock);
+    }
 }
 
-void fork_start(void)
+/* Finish an exclusive operation. */
+static inline void
+end_exclusive(void)
 {
+    pending_cpus = 0;
+    pthread_cond_broadcast(&exclusive_resume);
+    pthread_mutex_unlock(&exclusive_lock);
 }
 
-void fork_end(int child)
+/* Wait for exclusive ops to finish, and begin cpu execution. */
+static inline void
+cpu_exec_start(CPUState *env)
+{
+    pthread_mutex_lock(&exclusive_lock);
+    exclusive_idle();
+    env->running = 1;
+    pthread_mutex_unlock(&exclusive_lock);
+}
+
+/* Mark cpu as not excuting, and release pending exclusive ops. */
+static inline void
+cpu_exec_end(CPUState *env)
+{
+    pthread_mutex_lock(&exclusive_lock);
+    env->running = 0;
+    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);
+}
+
+#else /* ! CONFIG_USE_NPTL */
+
+/* These are no-ops because we are not threadsafe.  */
+void
+fork_start(void)
+{
+}
+
+void
+fork_end(int child)
 {
     if (child) {
         gdbserver_fork(thread_env);
     }
 }
 
-void cpu_list_lock(void)
+static inline void
+exclusive_idle(void)
+{
+}
+
+static inline void
+start_exclusive(void)
+{
+}
+
+static inline void
+end_exclusive(void)
+{
+}
+
+static inline void
+cpu_exec_start(CPUState *env)
+{
+}
+
+
+static inline void
+cpu_exec_end(CPUState *env)
+{
+}
+
+void
+cpu_list_lock(void)
 {
 }
 
-void cpu_list_unlock(void)
+void
+cpu_list_unlock(void)
 {
 }
+#endif /* ! CONFIG_USE_NPTL */
 
 #ifdef TARGET_I386
 /***********************************************************/
@@ -422,19 +567,67 @@ void cpu_loop(CPUX86State *env)
 
 /* Compare to sys/mips/mips/trap.c */
 
+static int do_store_exclusive(CPUMIPSState *env)
+{
+    target_ulong addr;
+    target_ulong page_addr;
+    target_ulong val;
+    int flags;
+    int segv = 0;
+    int reg;
+    int d;
+
+    addr = env->lladdr;
+    page_addr = addr & TARGET_PAGE_MASK;
+    start_exclusive();
+    mmap_lock();
+    flags = page_get_flags(page_addr);
+    if ((flags & PAGE_READ) == 0) {
+        segv = 1;
+    } else {
+        reg = env->llreg & 0x1f;
+        d = (env->llreg & 0x20) != 0;
+        if (d) {
+            segv = get_user_s64(val, addr);
+        } else {
+            segv = get_user_s32(val, addr);
+        }
+        if (!segv) {
+            if (val != env->llval) {
+                env->active_tc.gpr[reg] = 0;
+            } else {
+                if (d) {
+                    segv = put_user_u64(env->llnewval, addr);
+                } else {
+                    segv = put_user_u32(env->llnewval, addr);
+                }
+                if (!segv) {
+                    env->active_tc.gpr[reg] = 1;
+                }
+            }
+        }
+    }
+    env->lladdr = -1;
+    if (!segv) {
+        env->active_tc.PC += 4;
+    }
+    mmap_unlock();
+    end_exclusive();
+    return segv;
+}
+
 void cpu_loop(CPUMIPSState *env)
 {
-#if 0 /* not yet */
+    CPUState *cs = CPU(mips_env_get_cpu(env));
     target_siginfo_t info;
-#endif
     int trapnr;
     abi_long ret;
     unsigned int syscall_num;
 
     for (;;) {
-        cpu_exec_start(env);
+        cpu_exec_start(cs);
         trapnr = cpu_mips_exec(env);
-        cpu_exec_end(env);
+        cpu_exec_end(cs);
         switch (trapnr) {
         case EXCP_SYSCALL: /* syscall exception */
             syscall_num = env->active_tc.gpr[2]; /* v0 */
@@ -521,7 +714,6 @@ void cpu_loop(CPUMIPSState *env)
             env->active_tc.gpr[2] = ret; /* v0 <- ret */
             break;
 
-#if 0 /* not yet */
         case EXCP_TLBL: /* TLB miss on load */
         case EXCP_TLBS: /* TLB miss on store */
         case EXCP_AdEL: /* bad address on load */
@@ -541,13 +733,11 @@ void cpu_loop(CPUMIPSState *env)
             info.target_si_code = 0;
             queue_signal(env, info.target_si_signo, &info);
             break;
-#endif /* not yet */
 
         case EXCP_INTERRUPT: /* async interrupt */
             /* just indicate that signals should be handled asap */
             break;
 
-#if 0 /* not yet */
         case EXCP_DEBUG: /* cpu stopped after a breakpoint */
             {
                 int sig;
@@ -571,7 +761,6 @@ void cpu_loop(CPUMIPSState *env)
                 queue_signal(env, info.target_si_signo, &info);
             }
             break;
-#endif /* not yet */
 
         default:
             fprintf(stderr, "qemu: unhandled CPU exception "
diff --git a/bsd-user/mips/target_arch_signal.h b/bsd-user/mips/target_arch_signal.h
index f68034f..6be34c6 100644
--- a/bsd-user/mips/target_arch_signal.h
+++ b/bsd-user/mips/target_arch_signal.h
@@ -124,8 +124,8 @@ set_sigtramp_args(CPUMIPSState *regs, int sig, struct target_sigframe *frame,
  * Compare to mips/mips/pm_machdep.c get_mcontext()
  * Assumes that the memory is locked if mcp points to user memory.
  */
-static inline abi_long
-get_mcontext(CPUMIPSState *regs, target_mcontext_t *mcp, int flags)
+static inline abi_long get_mcontext(CPUMIPSState *regs, target_mcontext_t *mcp,
+        int flags)
 {
     int i, err = 0;
 
@@ -180,8 +180,8 @@ get_mcontext(CPUMIPSState *regs, target_mcontext_t *mcp, int flags)
 }
 
 /* Compare to mips/mips/pm_machdep.c set_mcontext() */
-static inline abi_long set_mcontext(CPUMIPSState *regs,
-        target_mcontext_t *mcp, int srflag)
+static inline abi_long set_mcontext(CPUMIPSState *regs, target_mcontext_t *mcp,
+        int srflag)
 {
     int i, err = 0;
 
diff --git a/bsd-user/mips/target_arch_thread.h b/bsd-user/mips/target_arch_thread.h
new file mode 100644
index 0000000..9384941
--- /dev/null
+++ b/bsd-user/mips/target_arch_thread.h
@@ -0,0 +1,45 @@
+/*
+ *  mips thread support
+ *
+ *  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 _TARGET_ARCH_THREAD_H_
+#define _TARGET_ARCH_THREAD_H_
+
+/* Compare to mips/mips/vm_machdep.c cpu_set_upcall_kse() */
+static inline void
+thread_set_upcall(CPUMIPSState *regs, abi_ulong entry,
+    abi_ulong arg, abi_ulong stack_base, abi_ulong stack_size)
+{
+    abi_ulong sp;
+
+    /*
+     * At the point where a function is called, sp must be 8
+     * byte aligned[for compatibility with 64-bit CPUs]
+     * in ``See MIPS Run'' by D. Sweetman, p. 269
+     * align stack
+     */
+    sp = ((stack_base + stack_size) & ~0x7) - TARGET_CALLFRAME_SIZ;
+
+    /* t9 = pc = start function entry */
+    regs->active_tc.gpr[25] = regs->active_tc.PC = entry;
+    /* a0 = arg */
+    regs->active_tc.gpr[4] = arg;
+    /* sp = top of the stack */
+    regs->active_tc.gpr[29] = sp;
+}
+
+#endif /* !_TARGET_ARCH_THREAD_H_ */
diff --git a/bsd-user/mips64/target_arch_signal.h b/bsd-user/mips64/target_arch_signal.h
index 5edcc3a..ab84e0f 100644
--- a/bsd-user/mips64/target_arch_signal.h
+++ b/bsd-user/mips64/target_arch_signal.h
@@ -123,8 +123,8 @@ set_sigtramp_args(CPUMIPSState *regs, int sig, struct target_sigframe *frame,
  * Compare to mips/mips/pm_machdep.c get_mcontext()
  * Assumes that the memory is locked if mcp points to user memory.
  */
-static inline abi_long
-get_mcontext(CPUMIPSState *regs, target_mcontext_t *mcp, int flags)
+static inline abi_long get_mcontext(CPUMIPSState *regs, target_mcontext_t *mcp,
+        int flags)
 {
     int i, err = 0;
 
@@ -179,8 +179,8 @@ get_mcontext(CPUMIPSState *regs, target_mcontext_t *mcp, int flags)
 }
 
 /* Compare to mips/mips/pm_machdep.c set_mcontext() */
-static inline abi_long set_mcontext(CPUMIPSState *regs,
-        target_mcontext_t *mcp, int srflag)
+static inline abi_long set_mcontext(CPUMIPSState *regs, target_mcontext_t *mcp,
+        int srflag)
 {
     int i, err = 0;
 
diff --git a/bsd-user/mips64/target_arch_thread.h b/bsd-user/mips64/target_arch_thread.h
new file mode 100644
index 0000000..bf363af
--- /dev/null
+++ b/bsd-user/mips64/target_arch_thread.h
@@ -0,0 +1,45 @@
+/*
+ *  mips64 thread support
+ *
+ *  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 _MIPS64_ARCH_THREAD_H_
+#define _MIPS64_ARCH_THREAD_H_
+
+/* Compare to mips/mips/vm_machdep.c cpu_set_upcall_kse() */
+static inline void
+thread_set_upcall(CPUMIPSState *regs, abi_ulong entry,
+    abi_ulong arg, abi_ulong stack_base, abi_ulong stack_size)
+{
+    abi_ulong sp;
+
+    /*
+     * At the point where a function is called, sp must be 8
+     * byte aligned[for compatibility with 64-bit CPUs]
+     * in ``See MIPS Run'' by D. Sweetman, p. 269
+     * align stack
+     */
+    sp = ((stack_base + stack_size) & ~0x7) - TARGET_CALLFRAME_SIZ;
+
+    /* t9 = pc = start function entry */
+    regs->active_tc.gpr[25] = regs->active_tc.PC = entry;
+    /* a0 = arg */
+    regs->active_tc.gpr[4] = arg;
+    /* sp = top of the stack */
+    regs->active_tc.gpr[29] = sp;
+}
+
+#endif /* !_MIPS64_ARCH_THREAD_H_ */
diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h
index 7ded869..f063974 100644
--- a/bsd-user/qemu.h
+++ b/bsd-user/qemu.h
@@ -243,6 +243,41 @@ int is_error(abi_long ret);
 abi_long freebsd_exec_common(abi_ulong path_or_fd, abi_ulong guest_argp,
         abi_ulong guest_envp, int do_fexec);
 
+/* 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
diff --git a/bsd-user/sparc/target_arch_signal.h b/bsd-user/sparc/target_arch_signal.h
index 275d1ef..70b89b9 100644
--- a/bsd-user/sparc/target_arch_signal.h
+++ b/bsd-user/sparc/target_arch_signal.h
@@ -10,6 +10,8 @@
 #define TARGET_MINSIGSTKSZ  (1024 * 4)              /* min sig stack size */
 #define TARGET_SIGSTKSZ     (MINSIGSTKSZ + 32768)   /* recommended size */
 
+#define TARGET_MC_GET_CLEAR_RET 0x0001
+
 struct target_sigcontext {
     /* to be added */
 };
@@ -36,9 +38,19 @@ struct target_sigframe {
     uint32_t    __spare__[2];
 };
 
+/* Compare to sparc64/sparc64/machdep.c get_mcontext() */
+static inline abi_long get_mcontext(CPUSPARCState *regs,
+        target_mcontext_t *mcp, int flags)
+{
+    /* XXX */
+    return -TARGET_EOPNOTSUPP;
+}
+
+/* Compare to sparc64/space64/machdep.c set_mcontext() */
 static inline abi_long set_mcontext(CPUSPARCState *regs,
         target_mcontext_t *mcp, int srflag)
 {
+    /* XXX */
     return -TARGET_EOPNOTSUPP;
 }
 
diff --git a/bsd-user/sparc/target_arch_thread.h b/bsd-user/sparc/target_arch_thread.h
new file mode 100644
index 0000000..ddfbbe2
--- /dev/null
+++ b/bsd-user/sparc/target_arch_thread.h
@@ -0,0 +1,30 @@
+/*
+ *  sparc thread support
+ *
+ *  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 _TARGET_ARCH_THREAD_H_
+#define _TARGET_ARCH_THREAD_H_
+
+/* Compare to vm_machdep.c cpu_set_upcall_kse() */
+static inline void
+thread_set_upcall(CPUSPARCState *regs, abi_ulong entry,
+    abi_ulong arg, abi_ulong stack_base, abi_ulong stack_size)
+{
+    /* XXX */
+}
+
+#endif /* !_TARGET_ARCH_THREAD_H_ */
diff --git a/bsd-user/sparc64/target_arch_signal.h b/bsd-user/sparc64/target_arch_signal.h
index 8126383..cc075ed 100644
--- a/bsd-user/sparc64/target_arch_signal.h
+++ b/bsd-user/sparc64/target_arch_signal.h
@@ -27,6 +27,8 @@
 #define TARGET_MINSIGSTKSZ  (1024 * 4)              /* min sig stack size */
 #define TARGET_SIGSTKSZ     (MINSIGSTKSZ + 32768)   /* recommended size */
 
+#define TARGET_MC_GET_CLEAR_RET 0x0001
+
 struct target_sigcontext {
     /* to be added */
 };
@@ -53,9 +55,19 @@ struct target_sigframe {
     uint32_t    __spare__[2];
 };
 
+/* Compare to sparc64/sparc64/machdep.c get_mcontext() */
+static inline abi_long get_mcontext(CPUSPARCState *regs,
+        target_mcontext_t *mcp, int flags)
+{
+    /* XXX */
+    return -TARGET_EOPNOTSUPP;
+}
+
+/* Compare to sparc64/space64/machdep.c set_mcontext() */
 static inline abi_long set_mcontext(CPUSPARCState *regs,
         target_mcontext_t *mcp, int srflag)
 {
+    /* XXX */
     return -TARGET_EOPNOTSUPP;
 }
 
diff --git a/bsd-user/sparc64/target_arch_thread.h b/bsd-user/sparc64/target_arch_thread.h
new file mode 100644
index 0000000..0f06d14
--- /dev/null
+++ b/bsd-user/sparc64/target_arch_thread.h
@@ -0,0 +1,30 @@
+/*
+ *  sparc64 thread support
+ *
+ *  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 _TARGET_ARCH_THREAD_H_
+#define _TARGET_ARCH_THREAD_H_
+
+/* Compare to vm_machdep.c cpu_set_upcall_kse() */
+static inline void
+thread_set_upcall(CPUSPARCState *regs, abi_ulong entry,
+    abi_ulong arg, abi_ulong stack_base, abi_ulong stack_size)
+{
+    /* XXX */
+}
+
+#endif /* !_TARGET_ARCH_THREAD_H_ */
diff --git a/bsd-user/syscall.c b/bsd-user/syscall.c
index fee602b..86aa471 100644
--- a/bsd-user/syscall.c
+++ b/bsd-user/syscall.c
@@ -34,17 +34,20 @@
 #define target_to_host_bitmask(x, tbl) (x)
 static int host_to_target_errno(int err);
 
+/* BSD independent syscall shims */
 #include "bsd-file.h"
 #include "bsd-mem.h"
 #include "bsd-proc.h"
 #include "bsd-signal.h"
 #include "bsd-socket.h"
 
+/* *BSD dependent syscall shims */
 #include "os-time.h"
 #include "os-proc.h"
 #include "os-signal.h"
 #include "os-socket.h"
 #include "os-stat.h"
+#include "os-thread.h"
 
 /* #define DEBUG */
 
@@ -1271,6 +1274,73 @@ abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1,
                 arg6, arg7, arg8);
         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;
+
 
     case TARGET_FREEBSD_NR_break:
         ret = do_obreak(arg1);
diff --git a/bsd-user/syscall_defs.h b/bsd-user/syscall_defs.h
index 0db7126..b4e0739 100644
--- a/bsd-user/syscall_defs.h
+++ b/bsd-user/syscall_defs.h
@@ -498,4 +498,95 @@ struct target_freebsd_flock {
     int32_t l_sysid;
 } QEMU_PACKED;
 
+/*
+ * FreeBSD thread and user mutex support.
+ */
+
+/* sys/thr.h */
+#define TARGET_THR_SUSPENDED    0x0001
+#define TARGET_THR_SYSTEM_SCOPE 0x0002
+
+struct target_freebsd_thr_param {
+    abi_ulong   start_func; /* thread entry function. */
+    abi_ulong   arg;        /* argument for entry function. */
+    abi_ulong   stack_base; /* stack base address. */
+    abi_ulong   stack_size; /* stack size. */
+    abi_ulong   tls_base;   /* tls base address. */
+    abi_ulong   tls_size;   /* tls size. */
+    abi_ulong   child_tid;  /* address to store new TID. */
+    abi_ulong   parent_tid; /* parent access the new TID here. */
+    int32_t     flags;      /* thread flags. */
+    abi_ulong   rtp;        /* Real-time scheduling priority. */
+    abi_ulong   spare[3];   /* spares. */
+};
+
+/* sys/rtprio.h */
+struct target_freebsd_rtprio {
+    uint16_t    type;
+    uint16_t    prio;
+};
+
+typedef struct {
+    CPUArchState *env;
+    long parent_tid;
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+    pthread_t thread;
+    sigset_t sigmask;
+    struct target_freebsd_thr_param param;
+} new_freebsd_thread_info_t;
+
+/* sys/utmx.h */
+/* op code for _umtx_op */
+#define TARGET_UMTX_OP_LOCK                 0
+#define TARGET_UMTX_OP_UNLOCK               1
+#define TARGET_UMTX_OP_WAIT                 2
+#define TARGET_UMTX_OP_WAKE                 3
+#define TARGET_UMTX_OP_MUTEX_TRYLOCK        4
+#define TARGET_UMTX_OP_MUTEX_LOCK           5
+#define TARGET_UMTX_OP_MUTEX_UNLOCK         6
+#define TARGET_UMTX_OP_SET_CEILING          7
+#define TARGET_UMTX_OP_CV_WAIT              8
+#define TARGET_UMTX_OP_CV_SIGNAL            9
+#define TARGET_UMTX_OP_CV_BROADCAST         10
+#define TARGET_UMTX_OP_WAIT_UINT            11
+#define TARGET_UMTX_OP_RW_RDLOCK            12
+#define TARGET_UMTX_OP_RW_WRLOCK            13
+#define TARGET_UMTX_OP_RW_UNLOCK            14
+#define TARGET_UMTX_OP_WAIT_UINT_PRIVATE    15
+#define TARGET_UMTX_OP_WAKE_PRIVATE         16
+#define TARGET_UMTX_OP_MUTEX_WAIT           17
+#define TARGET_UMTX_OP_MUTEX_WAKE           18
+#define TARGET_UMTX_OP_SEM_WAIT             19
+#define TARGET_UMTX_OP_SEM_WAKE             20
+#define TARGET_UMTX_OP_NWAKE_PRIVATE        21
+#define TARGET_UMTX_OP_MUTEX_WAKE2          22
+#define TARGET_UMTX_OP_MAX                  23
+
+/* flags for UMTX_OP_CV_WAIT */
+#define TARGET_CVWAIT_CHECK_UNPARKING       0x01
+#define TARGET_CVWAIT_ABSTIME               0x02
+#define TARGET_CVWAIT_CLOCKID               0x04
+
+#define TARGET_UMTX_UNOWNED                 0x0
+#define TARGET_UMUTEX_UNOWNED               0x0
+#define TARGET_UMTX_CONTESTED               (abi_long)(0x8000000000000000)
+#define TARGET_UMUTEX_CONTESTED             0x80000000U
+
+/* flags for umutex */
+#define TARGET_UMUTEX_ERROR_CHECK   0x0002  /* Error-checking mutex */
+#define TARGET_UMUTEX_PRIO_INHERIT  0x0004  /* Priority inherited mutex */
+#define TARGET_UMUTEX_PRIO_PROTECT  0x0008  /* Priority protect mutex */
+
+#define TARGET_UMUTEX_TRY           1
+#define TARGET_UMUTEX_WAIT          2
+
+/* urwlock flags */
+#define TARGET_URWLOCK_PREFER_READER    0x0002
+#define TARGET_URWLOCK_WRITE_OWNER      0x80000000U
+#define TARGET_URWLOCK_WRITE_WAITERS    0x40000000U
+#define TARGET_URWLOCK_READ_WAITERS     0x20000000U
+#define TARGET_URWLOCK_MAX_READERS      0x1fffffffU
+#define TARGET_URWLOCK_READER_COUNT(c)  ((c) & TARGET_URWLOCK_MAX_READERS)
+
 #endif /* ! _SYSCALL_DEFS_H_ */
diff --git a/bsd-user/x86_64/target_arch_signal.h b/bsd-user/x86_64/target_arch_signal.h
index d43f5f0..7525a56 100644
--- a/bsd-user/x86_64/target_arch_signal.h
+++ b/bsd-user/x86_64/target_arch_signal.h
@@ -27,6 +27,8 @@
 #define TARGET_MINSIGSTKSZ  (512 * 4)               /* min sig stack size */
 #define TARGET_SIGSTKSZ     (MINSIGSTKSZ + 32768)   /* recommended size */
 
+#define TARGET_MC_GET_CLEAR_RET 0x0001
+
 struct target_sigcontext {
     /* to be added */
 };
@@ -53,9 +55,19 @@ struct target_sigframe {
     uint32_t    __spare__[2];
 };
 
+/* Compare to amd64/amd64/machdep.c get_mcontext() */
+static inline abi_long get_mcontext(CPUX86State *regs,
+                target_mcontext_t *mcp, int flags)
+{
+        /* XXX */
+        return -TARGET_EOPNOTSUPP;
+}
+
+/* Compare to amd64/amd64/machdep.c set_mcontext() */
 static inline abi_long set_mcontext(CPUX86State *regs,
         target_mcontext_t *mcp, int srflag)
 {
+    /* XXX */
     return -TARGET_EOPNOTSUPP;
 }
 
diff --git a/bsd-user/x86_64/target_arch_thread.h b/bsd-user/x86_64/target_arch_thread.h
new file mode 100644
index 0000000..32a833f
--- /dev/null
+++ b/bsd-user/x86_64/target_arch_thread.h
@@ -0,0 +1,30 @@
+/*
+ *  x86_64 thread support
+ *
+ *  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 _TARGET_ARCH_THREAD_H_
+#define _TARGET_ARCH_THREAD_H_
+
+/* Compare to vm_machdep.c cpu_set_upcall_kse() */
+static inline void
+thread_set_upcall(CPUX86State *regs, abi_ulong entry,
+    abi_ulong arg, abi_ulong stack_base, abi_ulong stack_size)
+{
+    /* XXX */
+}
+
+#endif /* !_TARGET_ARCH_THREAD_H_ */
diff --git a/configure b/configure
index ba90975..812ea3f 100755
--- a/configure
+++ b/configure
@@ -1438,6 +1438,15 @@ fi
 
 if test "$nptl" != "no" ; then
   cat > $TMPC <<EOF
+#ifdef __FreeBSD__
+#include <sys/thr.h>
+int main(void) {
+#if !defined(THR_SUSPENDED)
+#error bork
+#endif
+  return 0;
+}
+#else
 #include <sched.h>
 #include <linux/futex.h>
 int main(void) {
@@ -1446,6 +1455,7 @@ int main(void) {
 #endif
   return 0;
 }
+#endif
 EOF
 
   if compile_object ; then
@@ -4202,6 +4212,7 @@ case "$target_name" in
     TARGET_ARCH=mips64
     TARGET_BASE_ARCH=mips
     echo "TARGET_ABI_MIPSN64=y" >> $config_target_mak
+    target_nptl="yes"
   ;;
   moxie)
   ;;
diff --git a/include/qemu/tls.h b/include/qemu/tls.h
index b92ea9d..ae7d79d 100644
--- a/include/qemu/tls.h
+++ b/include/qemu/tls.h
@@ -38,7 +38,7 @@
  * TODO: proper implementations via Win32 .tls sections and
  * POSIX pthread_getspecific.
  */
-#ifdef __linux__
+#if defined(__linux__) || defined(__FreeBSD__)
 #define DECLARE_TLS(type, x) extern DEFINE_TLS(type, x)
 #define DEFINE_TLS(type, x)  __thread __typeof__(type) tls__##x
 #define tls_var(x)           tls__##x
