Add umtx support for 32bit process on AMD64 machine.

This commit is contained in:
David Xu 2006-09-22 00:52:54 +00:00
parent 8d2b600b3e
commit 1eec02f538
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=162536
3 changed files with 444 additions and 83 deletions

View File

@ -741,7 +741,9 @@
451 AUE_GETAUDIT_ADDR UNIMPL getaudit_addr 451 AUE_GETAUDIT_ADDR UNIMPL getaudit_addr
452 AUE_SETAUDIT_ADDR UNIMPL setaudit_addr 452 AUE_SETAUDIT_ADDR UNIMPL setaudit_addr
453 AUE_AUDITCTL UNIMPL auditctl 453 AUE_AUDITCTL UNIMPL auditctl
454 AUE_NULL UNIMPL _umtx_op 454 AUE_NULL STD { int freebsd32_umtx_op(void *obj, int op,\
uintptr_t val, void *uaddr, \
void *uaddr2); }
455 AUE_NULL UNIMPL thr_new 455 AUE_NULL UNIMPL thr_new
456 AUE_NULL NOPROTO { int sigqueue(pid_t pid, int signum, \ 456 AUE_NULL NOPROTO { int sigqueue(pid_t pid, int signum, \
void *value); } void *value); }

View File

@ -28,6 +28,7 @@
#include <sys/cdefs.h> #include <sys/cdefs.h>
__FBSDID("$FreeBSD$"); __FBSDID("$FreeBSD$");
#include "opt_compat.h"
#include <sys/param.h> #include <sys/param.h>
#include <sys/kernel.h> #include <sys/kernel.h>
#include <sys/limits.h> #include <sys/limits.h>
@ -49,6 +50,10 @@ __FBSDID("$FreeBSD$");
#include <vm/vm_map.h> #include <vm/vm_map.h>
#include <vm/vm_object.h> #include <vm/vm_object.h>
#ifdef COMPAT_IA32
#include <compat/freebsd32/freebsd32_proto.h>
#endif
#define TYPE_SIMPLE_LOCK 0 #define TYPE_SIMPLE_LOCK 0
#define TYPE_SIMPLE_WAIT 1 #define TYPE_SIMPLE_WAIT 1
#define TYPE_NORMAL_UMUTEX 2 #define TYPE_NORMAL_UMUTEX 2
@ -517,7 +522,7 @@ umtx_key_release(struct umtx_key *key)
* Lock a umtx object. * Lock a umtx object.
*/ */
static int static int
_do_lock(struct thread *td, struct umtx *umtx, uintptr_t id, int timo) _do_lock_umtx(struct thread *td, struct umtx *umtx, uintptr_t id, int timo)
{ {
struct umtx_q *uq; struct umtx_q *uq;
intptr_t owner; intptr_t owner;
@ -615,7 +620,7 @@ _do_lock(struct thread *td, struct umtx *umtx, uintptr_t id, int timo)
* Lock a umtx object. * Lock a umtx object.
*/ */
static int static int
do_lock(struct thread *td, struct umtx *umtx, uintptr_t id, do_lock_umtx(struct thread *td, struct umtx *umtx, uintptr_t id,
struct timespec *timeout) struct timespec *timeout)
{ {
struct timespec ts, ts2, ts3; struct timespec ts, ts2, ts3;
@ -623,7 +628,7 @@ do_lock(struct thread *td, struct umtx *umtx, uintptr_t id,
int error; int error;
if (timeout == NULL) { if (timeout == NULL) {
error = _do_lock(td, umtx, id, 0); error = _do_lock_umtx(td, umtx, id, 0);
/* Mutex locking is restarted if it is interrupted. */ /* Mutex locking is restarted if it is interrupted. */
if (error == EINTR) if (error == EINTR)
error = ERESTART; error = ERESTART;
@ -632,7 +637,7 @@ do_lock(struct thread *td, struct umtx *umtx, uintptr_t id,
timespecadd(&ts, timeout); timespecadd(&ts, timeout);
TIMESPEC_TO_TIMEVAL(&tv, timeout); TIMESPEC_TO_TIMEVAL(&tv, timeout);
for (;;) { for (;;) {
error = _do_lock(td, umtx, id, tvtohz(&tv)); error = _do_lock_umtx(td, umtx, id, tvtohz(&tv));
if (error != ETIMEDOUT) if (error != ETIMEDOUT)
break; break;
getnanouptime(&ts2); getnanouptime(&ts2);
@ -655,7 +660,7 @@ do_lock(struct thread *td, struct umtx *umtx, uintptr_t id,
* Unlock a umtx object. * Unlock a umtx object.
*/ */
static int static int
do_unlock(struct thread *td, struct umtx *umtx, uintptr_t id) do_unlock_umtx(struct thread *td, struct umtx *umtx, uintptr_t id)
{ {
struct umtx_key key; struct umtx_key key;
intptr_t owner; intptr_t owner;
@ -716,11 +721,215 @@ do_unlock(struct thread *td, struct umtx *umtx, uintptr_t id)
return (0); return (0);
} }
#ifdef COMPAT_IA32
/*
* Lock a umtx object.
*/
static int
_do_lock_umtx32(struct thread *td, uint32_t *m, uint32_t id, int timo)
{
struct umtx_q *uq;
uint32_t owner;
uint32_t old;
int error = 0;
uq = td->td_umtxq;
/*
* Care must be exercised when dealing with umtx structure. It
* can fault on any access.
*/
for (;;) {
/*
* Try the uncontested case. This should be done in userland.
*/
owner = casuword32(m, UMUTEX_UNOWNED, id);
/* The acquire succeeded. */
if (owner == UMUTEX_UNOWNED)
return (0);
/* The address was invalid. */
if (owner == -1)
return (EFAULT);
/* If no one owns it but it is contested try to acquire it. */
if (owner == UMUTEX_CONTESTED) {
owner = casuword32(m,
UMUTEX_CONTESTED, id | UMUTEX_CONTESTED);
if (owner == UMUTEX_CONTESTED)
return (0);
/* The address was invalid. */
if (owner == -1)
return (EFAULT);
/* If this failed the lock has changed, restart. */
continue;
}
/*
* If we caught a signal, we have retried and now
* exit immediately.
*/
if (error != 0)
return (error);
if ((error = umtx_key_get(m, TYPE_SIMPLE_LOCK,
AUTO_SHARE, &uq->uq_key)) != 0)
return (error);
umtxq_lock(&uq->uq_key);
umtxq_busy(&uq->uq_key);
umtxq_insert(uq);
umtxq_unbusy(&uq->uq_key);
umtxq_unlock(&uq->uq_key);
/*
* Set the contested bit so that a release in user space
* knows to use the system call for unlock. If this fails
* either some one else has acquired the lock or it has been
* released.
*/
old = casuword32(m, owner, owner | UMUTEX_CONTESTED);
/* The address was invalid. */
if (old == -1) {
umtxq_lock(&uq->uq_key);
umtxq_remove(uq);
umtxq_unlock(&uq->uq_key);
umtx_key_release(&uq->uq_key);
return (EFAULT);
}
/*
* We set the contested bit, sleep. Otherwise the lock changed
* and we need to retry or we lost a race to the thread
* unlocking the umtx.
*/
umtxq_lock(&uq->uq_key);
if (old == owner)
error = umtxq_sleep(uq, "umtx", timo);
umtxq_remove(uq);
umtxq_unlock(&uq->uq_key);
umtx_key_release(&uq->uq_key);
}
return (0);
}
/*
* Lock a umtx object.
*/
static int
do_lock_umtx32(struct thread *td, void *m, uint32_t id,
struct timespec *timeout)
{
struct timespec ts, ts2, ts3;
struct timeval tv;
int error;
if (timeout == NULL) {
error = _do_lock_umtx32(td, m, id, 0);
/* Mutex locking is restarted if it is interrupted. */
if (error == EINTR)
error = ERESTART;
} else {
getnanouptime(&ts);
timespecadd(&ts, timeout);
TIMESPEC_TO_TIMEVAL(&tv, timeout);
for (;;) {
error = _do_lock_umtx32(td, m, id, tvtohz(&tv));
if (error != ETIMEDOUT)
break;
getnanouptime(&ts2);
if (timespeccmp(&ts2, &ts, >=)) {
error = ETIMEDOUT;
break;
}
ts3 = ts;
timespecsub(&ts3, &ts2);
TIMESPEC_TO_TIMEVAL(&tv, &ts3);
}
/* Timed-locking is not restarted. */
if (error == ERESTART)
error = EINTR;
}
return (error);
}
/*
* Unlock a umtx object.
*/
static int
do_unlock_umtx32(struct thread *td, uint32_t *m, uint32_t id)
{
struct umtx_key key;
uint32_t owner;
uint32_t old;
int error;
int count;
/*
* Make sure we own this mtx.
*
* XXX Need a {fu,su}ptr this is not correct on arch where
* sizeof(intptr_t) != sizeof(long).
*/
owner = fuword32(m);
if (owner == -1)
return (EFAULT);
if ((owner & ~UMUTEX_CONTESTED) != id)
return (EPERM);
/* This should be done in userland */
if ((owner & UMUTEX_CONTESTED) == 0) {
old = casuword32(m, owner, UMUTEX_UNOWNED);
if (old == -1)
return (EFAULT);
if (old == owner)
return (0);
owner = old;
}
/* We should only ever be in here for contested locks */
if ((error = umtx_key_get(m, TYPE_SIMPLE_LOCK, AUTO_SHARE,
&key)) != 0)
return (error);
umtxq_lock(&key);
umtxq_busy(&key);
count = umtxq_count(&key);
umtxq_unlock(&key);
/*
* When unlocking the umtx, it must be marked as unowned if
* there is zero or one thread only waiting for it.
* Otherwise, it must be marked as contested.
*/
old = casuword32(m, owner,
count <= 1 ? UMUTEX_UNOWNED : UMUTEX_CONTESTED);
umtxq_lock(&key);
umtxq_signal(&key,1);
umtxq_unbusy(&key);
umtxq_unlock(&key);
umtx_key_release(&key);
if (old == -1)
return (EFAULT);
if (old != owner)
return (EINVAL);
return (0);
}
#endif
/* /*
* Fetch and compare value, sleep on the address if value is not changed. * Fetch and compare value, sleep on the address if value is not changed.
*/ */
static int static int
do_wait(struct thread *td, struct umtx *umtx, uintptr_t id, struct timespec *timeout) do_wait(struct thread *td, void *addr, uintptr_t id,
struct timespec *timeout, int compat32)
{ {
struct umtx_q *uq; struct umtx_q *uq;
struct timespec ts, ts2, ts3; struct timespec ts, ts2, ts3;
@ -729,14 +938,17 @@ do_wait(struct thread *td, struct umtx *umtx, uintptr_t id, struct timespec *tim
int error = 0; int error = 0;
uq = td->td_umtxq; uq = td->td_umtxq;
if ((error = umtx_key_get(umtx, TYPE_SIMPLE_WAIT, AUTO_SHARE, if ((error = umtx_key_get(addr, TYPE_SIMPLE_WAIT, AUTO_SHARE,
&uq->uq_key)) != 0) &uq->uq_key)) != 0)
return (error); return (error);
umtxq_lock(&uq->uq_key); umtxq_lock(&uq->uq_key);
umtxq_insert(uq); umtxq_insert(uq);
umtxq_unlock(&uq->uq_key); umtxq_unlock(&uq->uq_key);
tmp = fuword(&umtx->u_owner); if (compat32 == 0)
tmp = fuword(addr);
else
tmp = fuword32(addr);
if (tmp != id) { if (tmp != id) {
umtxq_lock(&uq->uq_key); umtxq_lock(&uq->uq_key);
umtxq_remove(uq); umtxq_remove(uq);
@ -1963,97 +2175,243 @@ int
_umtx_lock(struct thread *td, struct _umtx_lock_args *uap) _umtx_lock(struct thread *td, struct _umtx_lock_args *uap)
/* struct umtx *umtx */ /* struct umtx *umtx */
{ {
return _do_lock(td, uap->umtx, td->td_tid, 0); return _do_lock_umtx(td, uap->umtx, td->td_tid, 0);
} }
int int
_umtx_unlock(struct thread *td, struct _umtx_unlock_args *uap) _umtx_unlock(struct thread *td, struct _umtx_unlock_args *uap)
/* struct umtx *umtx */ /* struct umtx *umtx */
{ {
return do_unlock(td, uap->umtx, td->td_tid); return do_unlock_umtx(td, uap->umtx, td->td_tid);
} }
static int
__umtx_op_lock_umtx(struct thread *td, struct _umtx_op_args *uap)
{
struct timespec *ts, timeout;
int error;
/* Allow a null timespec (wait forever). */
if (uap->uaddr2 == NULL)
ts = NULL;
else {
error = copyin(uap->uaddr2, &timeout, sizeof(timeout));
if (error != 0)
return (error);
if (timeout.tv_nsec >= 1000000000 ||
timeout.tv_nsec < 0) {
return (EINVAL);
}
ts = &timeout;
}
return (do_lock_umtx(td, uap->obj, uap->val, ts));
}
static int
__umtx_op_unlock_umtx(struct thread *td, struct _umtx_op_args *uap)
{
return (do_unlock_umtx(td, uap->obj, uap->val));
}
static int
__umtx_op_wait(struct thread *td, struct _umtx_op_args *uap)
{
struct timespec *ts, timeout;
int error;
if (uap->uaddr2 == NULL)
ts = NULL;
else {
error = copyin(uap->uaddr2, &timeout, sizeof(timeout));
if (error != 0)
return (error);
if (timeout.tv_nsec >= 1000000000 ||
timeout.tv_nsec < 0)
return (EINVAL);
ts = &timeout;
}
return do_wait(td, uap->obj, uap->val, ts, 0);
}
static int
__umtx_op_wake(struct thread *td, struct _umtx_op_args *uap)
{
return (kern_umtx_wake(td, uap->obj, uap->val));
}
static int
__umtx_op_lock_umutex(struct thread *td, struct _umtx_op_args *uap)
{
struct timespec *ts, timeout;
int error;
/* Allow a null timespec (wait forever). */
if (uap->uaddr2 == NULL)
ts = NULL;
else {
error = copyin(uap->uaddr2, &timeout,
sizeof(timeout));
if (error != 0)
return (error);
if (timeout.tv_nsec >= 1000000000 ||
timeout.tv_nsec < 0) {
return (EINVAL);
}
ts = &timeout;
}
return do_lock_umutex(td, uap->obj, ts, 0);
}
static int
__umtx_op_trylock_umutex(struct thread *td, struct _umtx_op_args *uap)
{
return do_lock_umutex(td, uap->obj, NULL, 1);
}
static int
__umtx_op_unlock_umutex(struct thread *td, struct _umtx_op_args *uap)
{
return do_unlock_umutex(td, uap->obj);
}
static int
__umtx_op_set_ceiling(struct thread *td, struct _umtx_op_args *uap)
{
return do_set_ceiling(td, uap->obj, uap->val, uap->uaddr1);
}
typedef int (*_umtx_op_func)(struct thread *td, struct _umtx_op_args *uap);
static _umtx_op_func op_table[] = {
__umtx_op_lock_umtx, /* UMTX_OP_LOCK */
__umtx_op_unlock_umtx, /* UMTX_OP_UNLOCK */
__umtx_op_wait, /* UMTX_OP_WAIT */
__umtx_op_wake, /* UMTX_OP_WAKE */
__umtx_op_trylock_umutex, /* UMTX_OP_MUTEX_TRYLOCK */
__umtx_op_lock_umutex, /* UMTX_OP_MUTEX_LOCK */
__umtx_op_unlock_umutex, /* UMTX_OP_MUTEX_UNLOCK */
__umtx_op_set_ceiling /* UMTX_OP_SET_CEILING */
};
int int
_umtx_op(struct thread *td, struct _umtx_op_args *uap) _umtx_op(struct thread *td, struct _umtx_op_args *uap)
{ {
struct timespec timeout; if (uap->op >= 0 && uap->op < UMTX_OP_MAX)
struct timespec *ts; return (*op_table[uap->op])(td, uap);
return (EINVAL);
}
#ifdef COMPAT_IA32
struct timespec32 {
u_int32_t tv_sec;
u_int32_t tv_nsec;
};
static inline int
copyin_timeout32(void *addr, struct timespec *tsp)
{
struct timespec32 ts32;
int error; int error;
switch(uap->op) { error = copyin(addr, &ts32, sizeof(struct timespec32));
case UMTX_OP_MUTEX_LOCK: if (error == 0) {
/* Allow a null timespec (wait forever). */ tsp->tv_sec = ts32.tv_sec;
if (uap->uaddr2 == NULL) tsp->tv_nsec = ts32.tv_nsec;
ts = NULL;
else {
error = copyin(uap->uaddr2, &timeout, sizeof(timeout));
if (error != 0)
break;
if (timeout.tv_nsec >= 1000000000 ||
timeout.tv_nsec < 0) {
error = EINVAL;
break;
}
ts = &timeout;
}
error = do_lock_umutex(td, uap->obj, ts, 0);
break;
case UMTX_OP_MUTEX_UNLOCK:
error = do_unlock_umutex(td, uap->obj);
break;
case UMTX_OP_LOCK:
/* Allow a null timespec (wait forever). */
if (uap->uaddr2 == NULL)
ts = NULL;
else {
error = copyin(uap->uaddr2, &timeout, sizeof(timeout));
if (error != 0)
break;
if (timeout.tv_nsec >= 1000000000 ||
timeout.tv_nsec < 0) {
error = EINVAL;
break;
}
ts = &timeout;
}
error = do_lock(td, uap->obj, uap->val, ts);
break;
case UMTX_OP_UNLOCK:
error = do_unlock(td, uap->obj, uap->val);
break;
case UMTX_OP_WAIT:
/* Allow a null timespec (wait forever). */
if (uap->uaddr2 == NULL)
ts = NULL;
else {
error = copyin(uap->uaddr2, &timeout, sizeof(timeout));
if (error != 0)
break;
if (timeout.tv_nsec >= 1000000000 ||
timeout.tv_nsec < 0) {
error = EINVAL;
break;
}
ts = &timeout;
}
error = do_wait(td, uap->obj, uap->val, ts);
break;
case UMTX_OP_WAKE:
error = kern_umtx_wake(td, uap->obj, uap->val);
break;
case UMTX_OP_MUTEX_TRYLOCK:
error = do_lock_umutex(td, uap->obj, NULL, 1);
break;
case UMTX_OP_SET_CEILING:
error = do_set_ceiling(td, uap->obj, uap->val, uap->uaddr1);
break;
default:
error = EINVAL;
break;
} }
return (error); return (error);
} }
static int
__umtx_op_lock_umtx_compat32(struct thread *td, struct _umtx_op_args *uap)
{
struct timespec *ts, timeout;
int error;
/* Allow a null timespec (wait forever). */
if (uap->uaddr2 == NULL)
ts = NULL;
else {
error = copyin_timeout32(uap->uaddr2, &timeout);
if (error != 0)
return (error);
if (timeout.tv_nsec >= 1000000000 ||
timeout.tv_nsec < 0) {
return (EINVAL);
}
ts = &timeout;
}
return (do_lock_umtx32(td, uap->obj, uap->val, ts));
}
static int
__umtx_op_unlock_umtx_compat32(struct thread *td, struct _umtx_op_args *uap)
{
return (do_unlock_umtx32(td, uap->obj, (uint32_t)uap->val));
}
static int
__umtx_op_wait_compat32(struct thread *td, struct _umtx_op_args *uap)
{
struct timespec *ts, timeout;
int error;
if (uap->uaddr2 == NULL)
ts = NULL;
else {
error = copyin_timeout32(uap->uaddr2, &timeout);
if (error != 0)
return (error);
if (timeout.tv_nsec >= 1000000000 ||
timeout.tv_nsec < 0)
return (EINVAL);
ts = &timeout;
}
return do_wait(td, uap->obj, uap->val, ts, 1);
}
static int
__umtx_op_lock_umutex_compat32(struct thread *td, struct _umtx_op_args *uap)
{
struct timespec *ts, timeout;
int error;
/* Allow a null timespec (wait forever). */
if (uap->uaddr2 == NULL)
ts = NULL;
else {
error = copyin_timeout32(uap->uaddr2, &timeout);
if (error != 0)
return (error);
if (timeout.tv_nsec >= 1000000000 ||
timeout.tv_nsec < 0)
return (EINVAL);
ts = &timeout;
}
return do_lock_umutex(td, uap->obj, ts, 0);
}
static _umtx_op_func op_table_compat32[] = {
__umtx_op_lock_umtx_compat32, /* UMTX_OP_LOCK */
__umtx_op_unlock_umtx_compat32, /* UMTX_OP_UNLOCK */
__umtx_op_wait_compat32, /* UMTX_OP_WAIT */
__umtx_op_wake, /* UMTX_OP_WAKE */
__umtx_op_lock_umutex_compat32, /* UMTX_OP_MUTEX_TRYLOCK */
__umtx_op_trylock_umutex, /* UMTX_OP_MUTEX_LOCK */
__umtx_op_unlock_umutex, /* UMTX_OP_MUTEX_UNLOCK */
__umtx_op_set_ceiling /* UMTX_OP_SET_CEILING */
};
int
freebsd32_umtx_op(struct thread *td, struct freebsd32_umtx_op_args *uap)
{
if (uap->op >= 0 && uap->op < UMTX_OP_MAX)
return (*op_table_compat32[uap->op])(td,
(struct _umtx_op_args *)uap);
return (EINVAL);
}
#endif
void void
umtx_thread_init(struct thread *td) umtx_thread_init(struct thread *td)
{ {

View File

@ -68,6 +68,7 @@ struct umutex {
#define UMTX_OP_MUTEX_LOCK 5 #define UMTX_OP_MUTEX_LOCK 5
#define UMTX_OP_MUTEX_UNLOCK 6 #define UMTX_OP_MUTEX_UNLOCK 6
#define UMTX_OP_SET_CEILING 7 #define UMTX_OP_SET_CEILING 7
#define UMTX_OP_MAX 8
#ifndef _KERNEL #ifndef _KERNEL