diff options
Diffstat (limited to 'kernel/futex.c')
| -rw-r--r-- | kernel/futex.c | 27 | 
1 files changed, 23 insertions, 4 deletions
diff --git a/kernel/futex.c b/kernel/futex.c index a5d2e74c89e0..c20f06f38ef3 100644 --- a/kernel/futex.c +++ b/kernel/futex.c @@ -1295,10 +1295,20 @@ static int wake_futex_pi(u32 __user *uaddr, u32 uval, struct futex_q *this,  	if (unlikely(should_fail_futex(true)))  		ret = -EFAULT; -	if (cmpxchg_futex_value_locked(&curval, uaddr, uval, newval)) +	if (cmpxchg_futex_value_locked(&curval, uaddr, uval, newval)) {  		ret = -EFAULT; -	else if (curval != uval) -		ret = -EINVAL; +	} else if (curval != uval) { +		/* +		 * If a unconditional UNLOCK_PI operation (user space did not +		 * try the TID->0 transition) raced with a waiter setting the +		 * FUTEX_WAITERS flag between get_user() and locking the hash +		 * bucket lock, retry the operation. +		 */ +		if ((FUTEX_TID_MASK & curval) == uval) +			ret = -EAGAIN; +		else +			ret = -EINVAL; +	}  	if (ret) {  		raw_spin_unlock_irq(&pi_state->pi_mutex.wait_lock);  		return ret; @@ -1525,8 +1535,8 @@ void requeue_futex(struct futex_q *q, struct futex_hash_bucket *hb1,  	if (likely(&hb1->chain != &hb2->chain)) {  		plist_del(&q->list, &hb1->chain);  		hb_waiters_dec(hb1); -		plist_add(&q->list, &hb2->chain);  		hb_waiters_inc(hb2); +		plist_add(&q->list, &hb2->chain);  		q->lock_ptr = &hb2->lock;  	}  	get_futex_key_refs(key2); @@ -2623,6 +2633,15 @@ retry:  		if (ret == -EFAULT)  			goto pi_faulted;  		/* +		 * A unconditional UNLOCK_PI op raced against a waiter +		 * setting the FUTEX_WAITERS bit. Try again. +		 */ +		if (ret == -EAGAIN) { +			spin_unlock(&hb->lock); +			put_futex_key(&key); +			goto retry; +		} +		/*  		 * wake_futex_pi has detected invalid state. Tell user  		 * space.  		 */  |