Patchwork [17/23] bsd-user: add shims for thread related system calls

login
register
mail settings
Submitter Stacey Son
Date June 24, 2013, 2:03 a.m.
Message ID <1372039435-41921-18-git-send-email-sson@FreeBSD.org>
Download mbox | patch
Permalink /patch/253929/
State New
Headers show

Comments

Stacey Son - June 24, 2013, 2:03 a.m.
This change adds support for thread and user mutex related system call shims.
Much of this change is FreeBSD dependent.  Also, the thread start up code is
architecture dependent and is only included for mips/mips64 for now. Stubs
were added for the other architectures.

Signed-off-by: Stacey Son <sson@FreeBSD.org>
---
 bsd-user/Makefile.objs                |    4 +-
 bsd-user/freebsd/os-thread.c          |  802 +++++++++++++++++++++++++++++++++
 bsd-user/freebsd/os-thread.h          |  742 ++++++++++++++++++++++++++++++
 bsd-user/freebsd/qemu-os.h            |    5 +
 bsd-user/i386/target_arch_signal.h    |   12 +
 bsd-user/i386/target_arch_thread.h    |   30 ++
 bsd-user/main.c                       |  225 +++++++++-
 bsd-user/mips/target_arch_signal.h    |    8 +-
 bsd-user/mips/target_arch_thread.h    |   45 ++
 bsd-user/mips64/target_arch_signal.h  |    8 +-
 bsd-user/mips64/target_arch_thread.h  |   45 ++
 bsd-user/qemu.h                       |   35 ++
 bsd-user/sparc/target_arch_signal.h   |   12 +
 bsd-user/sparc/target_arch_thread.h   |   30 ++
 bsd-user/sparc64/target_arch_signal.h |   12 +
 bsd-user/sparc64/target_arch_thread.h |   30 ++
 bsd-user/syscall.c                    |   70 +++
 bsd-user/syscall_defs.h               |   91 ++++
 bsd-user/x86_64/target_arch_signal.h  |   12 +
 bsd-user/x86_64/target_arch_thread.h  |   30 ++
 configure                             |   11 +
 include/qemu/tls.h                    |    2 +-
 22 files changed, 2232 insertions(+), 29 deletions(-)
 create mode 100644 bsd-user/freebsd/os-thread.c
 create mode 100644 bsd-user/freebsd/os-thread.h
 create mode 100644 bsd-user/i386/target_arch_thread.h
 create mode 100644 bsd-user/mips/target_arch_thread.h
 create mode 100644 bsd-user/mips64/target_arch_thread.h
 create mode 100644 bsd-user/sparc/target_arch_thread.h
 create mode 100644 bsd-user/sparc64/target_arch_thread.h
 create mode 100644 bsd-user/x86_64/target_arch_thread.h
Peter Maydell - June 24, 2013, 5:37 p.m.
On 24 June 2013 03:03, Stacey Son <sson@freebsd.org> wrote:
> 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)
>    ;;

I think this is the wrong way to do this. target_nptl should
be a linux-user specific define (and we really ought to try
to get rid of it altogether there at some point).

For bsd-user, the ideal would be if you could just support
guest threads always, ie no #defines or configure checks needed.
If you do need a configure check, then it should be its own,
totally separate from CONFIG_NPTL. In any case, definitely
do not make the Linux mistake of having "do we support threads?"
be target specific if you can avoid it. (You might not be
able to avoid it, though -- not sure :-( )

(I suspect these other MIPS targets should actually have
target_nptl set as your patch fragment does, but that should
be done as a linux-user bugfix patch, not buried in the
bsd-user support patchset.)

> 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

This should be its own patch (especially as it affects the
system emulation code on FreeBSD hosts).

thanks
-- PMM
Stacey Son - June 24, 2013, 8:23 p.m.
On Jun 24, 2013, at 12:37 PM, Peter Maydell wrote:

> On 24 June 2013 03:03, Stacey Son <sson@freebsd.org> wrote:
>> 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)
>>   ;;
> 
> I think this is the wrong way to do this. target_nptl should
> be a linux-user specific define (and we really ought to try
> to get rid of it altogether there at some point).
> 
> For bsd-user, the ideal would be if you could just support
> guest threads always, ie no #defines or configure checks needed.
> If you do need a configure check, then it should be its own,
> totally separate from CONFIG_NPTL. In any case, definitely
> do not make the Linux mistake of having "do we support threads?"
> be target specific if you can avoid it. (You might not be
> able to avoid it, though -- not sure :-( )

Yes, my plan is to get rid of the NPTL flag at some point for bsd-user once the FreeBSD thread emulation code proves to be stable.  (Yes, I was lazy and didn't create a new thread flag for FreeBSD in configure.)  It seems to fairly stable now but it is nice to turn off thread emulation now and then to eliminate the possibility of threads being a problem.  I suspect that we can just turn threads up for good and be all right, however.

> (I suspect these other MIPS targets should actually have
> target_nptl set as your patch fragment does, but that should
> be done as a linux-user bugfix patch, not buried in the
> bsd-user support patchset.)
> 
>> 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
> 
> This should be its own patch (especially as it affects the
> system emulation code on FreeBSD hosts).

Yes, that is a good point.  I separate into its own patch so it can be reviewed and tested individually.

-stacey.

Patch

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