kern_umtx.c revision 117743
1/*
2 * Copyright (c) 2002, Jeffrey Roberson <jeff@freebsd.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice unmodified, this list of conditions, and the following
10 *    disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/kern/kern_umtx.c 117743 2003-07-18 17:58:37Z mtm $");
29
30#include <sys/param.h>
31#include <sys/kernel.h>
32#include <sys/lock.h>
33#include <sys/malloc.h>
34#include <sys/mutex.h>
35#include <sys/proc.h>
36#include <sys/signalvar.h>
37#include <sys/sysent.h>
38#include <sys/systm.h>
39#include <sys/sysproto.h>
40#include <sys/sx.h>
41#include <sys/thr.h>
42#include <sys/umtx.h>
43
44struct umtx_q {
45	LIST_ENTRY(umtx_q)	uq_next;	/* Linked list for the hash. */
46	TAILQ_HEAD(, thread) uq_tdq;	/* List of threads blocked here. */
47	struct umtx	*uq_umtx;	/* Pointer key component. */
48	pid_t		uq_pid;		/* Pid key component. */
49};
50
51#define	UMTX_QUEUES	128
52#define	UMTX_HASH(pid, umtx)						\
53    (((uintptr_t)pid + ((uintptr_t)umtx & ~65535)) % UMTX_QUEUES)
54
55LIST_HEAD(umtx_head, umtx_q);
56static struct umtx_head queues[UMTX_QUEUES];
57static MALLOC_DEFINE(M_UMTX, "umtx", "UMTX queue memory");
58
59static struct mtx umtx_lock;
60MTX_SYSINIT(umtx, &umtx_lock, "umtx", MTX_DEF);
61
62#define	UMTX_LOCK()	mtx_lock(&umtx_lock);
63#define	UMTX_UNLOCK()	mtx_unlock(&umtx_lock);
64
65
66static struct umtx_q *umtx_lookup(struct thread *, struct umtx *umtx);
67static struct umtx_q *umtx_insert(struct thread *, struct umtx *umtx);
68
69static struct umtx_q *
70umtx_lookup(struct thread *td, struct umtx *umtx)
71{
72	struct umtx_head *head;
73	struct umtx_q *uq;
74	pid_t pid;
75
76	pid = td->td_proc->p_pid;
77
78	head = &queues[UMTX_HASH(td->td_proc->p_pid, umtx)];
79
80	LIST_FOREACH(uq, head, uq_next) {
81		if (uq->uq_pid == pid && uq->uq_umtx == umtx)
82			return (uq);
83	}
84
85	return (NULL);
86}
87
88/*
89 * Insert a thread onto the umtx queue.
90 */
91static struct umtx_q *
92umtx_insert(struct thread *td, struct umtx *umtx)
93{
94	struct umtx_head *head;
95	struct umtx_q *uq;
96	pid_t pid;
97
98	pid = td->td_proc->p_pid;
99
100	if ((uq = umtx_lookup(td, umtx)) == NULL) {
101		struct umtx_q *ins;
102
103		UMTX_UNLOCK();
104		ins = malloc(sizeof(*uq), M_UMTX, M_ZERO | M_WAITOK);
105		UMTX_LOCK();
106
107		/*
108		 * Some one else could have succeeded while we were blocked
109		 * waiting on memory.
110		 */
111		if ((uq = umtx_lookup(td, umtx)) == NULL) {
112			head = &queues[UMTX_HASH(pid, umtx)];
113			uq = ins;
114			uq->uq_pid = pid;
115			uq->uq_umtx = umtx;
116			LIST_INSERT_HEAD(head, uq, uq_next);
117			TAILQ_INIT(&uq->uq_tdq);
118		} else
119			free(ins, M_UMTX);
120	}
121
122	/*
123	 * Insert us onto the end of the TAILQ.
124	 */
125	TAILQ_INSERT_TAIL(&uq->uq_tdq, td, td_umtx);
126
127	return (uq);
128}
129
130static void
131umtx_remove(struct umtx_q *uq, struct thread *td)
132{
133	TAILQ_REMOVE(&uq->uq_tdq, td, td_umtx);
134
135	if (TAILQ_EMPTY(&uq->uq_tdq)) {
136		LIST_REMOVE(uq, uq_next);
137		free(uq, M_UMTX);
138	}
139}
140
141int
142_umtx_lock(struct thread *td, struct _umtx_lock_args *uap)
143    /* struct umtx *umtx */
144{
145	struct umtx_q *uq;
146	struct umtx *umtx;
147	intptr_t owner;
148	intptr_t old;
149	int error;
150
151	uq = NULL;
152
153	/*
154	 * Care must be exercised when dealing with this structure.  It
155	 * can fault on any access.
156	 */
157	umtx = uap->umtx;
158
159	for (;;) {
160		/*
161		 * Try the uncontested case.  This should be done in userland.
162		 */
163		owner = casuptr((intptr_t *)&umtx->u_owner,
164		    UMTX_UNOWNED, (intptr_t)td);
165
166		/* The address was invalid. */
167		if (owner == -1)
168			return (EFAULT);
169
170		/* The acquire succeeded. */
171		if (owner == UMTX_UNOWNED)
172			return (0);
173
174		/* If no one owns it but it is contested try to acquire it. */
175		if (owner == UMTX_CONTESTED) {
176			owner = casuptr((intptr_t *)&umtx->u_owner,
177			    UMTX_CONTESTED, ((intptr_t)td | UMTX_CONTESTED));
178
179			/* The address was invalid. */
180			if (owner == -1)
181				return (EFAULT);
182
183			if (owner == UMTX_CONTESTED)
184				return (0);
185
186			/* If this failed the lock has changed, restart. */
187			continue;
188		}
189
190
191		UMTX_LOCK();
192		uq = umtx_insert(td, umtx);
193		UMTX_UNLOCK();
194
195		/*
196		 * Set the contested bit so that a release in user space
197		 * knows to use the system call for unlock.  If this fails
198		 * either some one else has acquired the lock or it has been
199		 * released.
200		 */
201		old = casuptr((intptr_t *)&umtx->u_owner, owner,
202		    owner | UMTX_CONTESTED);
203
204		/* The address was invalid. */
205		if (old == -1) {
206			UMTX_LOCK();
207			umtx_remove(uq, td);
208			UMTX_UNLOCK();
209			return (EFAULT);
210		}
211
212		/*
213		 * We set the contested bit, sleep. Otherwise the lock changed
214		 * and we need to retry or we lost a race to the thread
215		 * unlocking the umtx.
216		 */
217		UMTX_LOCK();
218		mtx_lock_spin(&sched_lock);
219		if (old == owner && (td->td_flags & TDF_UMTXWAKEUP) == 0) {
220			mtx_unlock_spin(&sched_lock);
221			error = msleep(td, &umtx_lock,
222			    td->td_priority | PCATCH, "umtx", 0);
223			mtx_lock_spin(&sched_lock);
224		} else
225			error = 0;
226		td->td_flags &= ~TDF_UMTXWAKEUP;
227		mtx_unlock_spin(&sched_lock);
228
229		umtx_remove(uq, td);
230		UMTX_UNLOCK();
231
232		/*
233		 * If we caught a signal we might have to retry or exit
234		 * immediately.
235		 */
236		if (error)
237			return (error);
238	}
239
240	return (0);
241}
242
243int
244_umtx_unlock(struct thread *td, struct _umtx_unlock_args *uap)
245    /* struct umtx *umtx */
246{
247	struct thread *blocked;
248	struct umtx *umtx;
249	struct umtx_q *uq;
250	intptr_t owner;
251	intptr_t old;
252
253	umtx = uap->umtx;
254
255	/*
256	 * Make sure we own this mtx.
257	 *
258	 * XXX Need a {fu,su}ptr this is not correct on arch where
259	 * sizeof(intptr_t) != sizeof(long).
260	 */
261	if ((owner = fuword(&umtx->u_owner)) == -1)
262		return (EFAULT);
263
264	if ((struct thread *)(owner & ~UMTX_CONTESTED) != td)
265		return (EPERM);
266
267	/* We should only ever be in here for contested locks */
268	KASSERT((owner & UMTX_CONTESTED) != 0, ("contested umtx is not."));
269
270	/*
271	 * When unlocking the umtx, it must be marked as unowned if
272	 * there is zero or one thread only waiting for it.
273	 * Otherwise, it must be marked as contested.
274	 */
275	UMTX_LOCK();
276	uq = umtx_lookup(td, umtx);
277	if (uq == NULL ||
278	    (uq != NULL && (blocked = TAILQ_FIRST(&uq->uq_tdq)) != NULL &&
279	    TAILQ_NEXT(blocked, td_umtx) == NULL)) {
280		UMTX_UNLOCK();
281		old = casuptr((intptr_t *)&umtx->u_owner, owner,
282		    UMTX_UNOWNED);
283		if (old == -1)
284			return (EFAULT);
285		KASSERT(old != owner, ("improper umtx access"));
286
287		/*
288		 * Recheck the umtx queue to make sure another thread
289		 * didn't put itself on it after it was unlocked.
290		 */
291		UMTX_LOCK();
292		uq = umtx_lookup(td, umtx);
293		if (uq != NULL &&
294		    ((blocked = TAILQ_FIRST(&uq->uq_tdq)) != NULL &&
295		    TAILQ_NEXT(blocked, td_umtx) != NULL))
296			old = casuptr((intptr_t *)&umtx->u_owner,
297			    UMTX_UNOWNED, UMTX_CONTESTED);
298		UMTX_UNLOCK();
299	} else {
300		UMTX_UNLOCK();
301		old = casuptr((intptr_t *)&umtx->u_owner,
302		    owner, UMTX_CONTESTED);
303		KASSERT(old != -1 && old != owner, ("improper umtx access"));
304	}
305
306	if (old == -1)
307		return (EFAULT);
308
309	/*
310	 * If there is a thread waiting on the umtx, wake it up.
311	 */
312	if (blocked != NULL) {
313		mtx_lock_spin(&sched_lock);
314		blocked->td_flags |= TDF_UMTXWAKEUP;
315		mtx_unlock_spin(&sched_lock);
316		wakeup(blocked);
317	}
318
319	return (0);
320}
321