1/*	$OpenBSD: rthread_mutex.c,v 1.5 2019/02/13 13:09:32 mpi Exp $ */
2/*
3 * Copyright (c) 2017 Martin Pieuchot <mpi@openbsd.org>
4 * Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <errno.h>
20#include <pthread.h>
21#include <stdint.h>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25
26#include "rthread.h"
27#include "cancel.h"
28#include "synch.h"
29
30/*
31 * States defined in "Futexes Are Tricky" 5.2
32 */
33enum {
34	UNLOCKED = 0,
35	LOCKED = 1,	/* locked without waiter */
36	CONTENDED = 2,	/* threads waiting for this mutex */
37};
38
39#define SPIN_COUNT	128
40#if defined(__i386__) || defined(__amd64__)
41#define SPIN_WAIT()	asm volatile("pause": : : "memory")
42#else
43#define SPIN_WAIT()	do { } while (0)
44#endif
45
46static _atomic_lock_t static_init_lock = _SPINLOCK_UNLOCKED;
47
48int
49pthread_mutex_init(pthread_mutex_t *mutexp, const pthread_mutexattr_t *attr)
50{
51	pthread_mutex_t mutex;
52
53	mutex = calloc(1, sizeof(*mutex));
54	if (mutex == NULL)
55		return (ENOMEM);
56
57	if (attr == NULL) {
58		mutex->type = PTHREAD_MUTEX_DEFAULT;
59		mutex->prioceiling = -1;
60	} else {
61		mutex->type = (*attr)->ma_type;
62		mutex->prioceiling = (*attr)->ma_protocol ==
63		    PTHREAD_PRIO_PROTECT ? (*attr)->ma_prioceiling : -1;
64	}
65	*mutexp = mutex;
66
67	return (0);
68}
69DEF_STRONG(pthread_mutex_init);
70
71int
72pthread_mutex_destroy(pthread_mutex_t *mutexp)
73{
74	pthread_mutex_t mutex;
75
76	if (mutexp == NULL || *mutexp == NULL)
77		return (EINVAL);
78
79	mutex = *mutexp;
80	if (mutex) {
81		if (mutex->lock != UNLOCKED) {
82#define MSG "pthread_mutex_destroy on mutex with waiters!\n"
83			write(2, MSG, sizeof(MSG) - 1);
84#undef MSG
85			return (EBUSY);
86		}
87		free((void *)mutex);
88		*mutexp = NULL;
89	}
90
91	return (0);
92}
93DEF_STRONG(pthread_mutex_destroy);
94
95static int
96_rthread_mutex_trylock(pthread_mutex_t mutex, int trywait,
97    const struct timespec *abs)
98{
99	pthread_t self = pthread_self();
100
101	if (atomic_cas_uint(&mutex->lock, UNLOCKED, LOCKED) == UNLOCKED) {
102		membar_enter_after_atomic();
103		mutex->owner = self;
104		return (0);
105	}
106
107	if (mutex->owner == self) {
108		int type = mutex->type;
109
110		/* already owner?  handle recursive behavior */
111		if (type != PTHREAD_MUTEX_RECURSIVE) {
112			if (trywait || type == PTHREAD_MUTEX_ERRORCHECK)
113				return (trywait ? EBUSY : EDEADLK);
114
115			/* self-deadlock is disallowed by strict */
116			if (type == PTHREAD_MUTEX_STRICT_NP && abs == NULL)
117				abort();
118
119			/* self-deadlock, possibly until timeout */
120			while (_twait(&mutex->type, type, CLOCK_REALTIME,
121			    abs) != ETIMEDOUT)
122				;
123			return (ETIMEDOUT);
124		} else {
125			if (mutex->count == INT_MAX)
126				return (EAGAIN);
127			mutex->count++;
128			return (0);
129		}
130	}
131
132	return (EBUSY);
133}
134
135static int
136_rthread_mutex_timedlock(pthread_mutex_t *mutexp, int trywait,
137    const struct timespec *abs, int timed)
138{
139	pthread_t self = pthread_self();
140	pthread_mutex_t mutex;
141	unsigned int i, lock;
142	int error = 0;
143
144	if (mutexp == NULL)
145		return (EINVAL);
146
147	/*
148	 * If the mutex is statically initialized, perform the dynamic
149	 * initialization. Note: _thread_mutex_lock() in libc requires
150	 * pthread_mutex_lock() to perform the mutex init when *mutexp
151	 * is NULL.
152	 */
153	if (*mutexp == NULL) {
154		_spinlock(&static_init_lock);
155		if (*mutexp == NULL)
156			error = pthread_mutex_init(mutexp, NULL);
157		_spinunlock(&static_init_lock);
158		if (error != 0)
159			return (EINVAL);
160	}
161
162	mutex = *mutexp;
163	_rthread_debug(5, "%p: mutex_%slock %p (%p)\n", self,
164	    (timed ? "timed" : (trywait ? "try" : "")), (void *)mutex,
165	    (void *)mutex->owner);
166
167	error = _rthread_mutex_trylock(mutex, trywait, abs);
168	if (error != EBUSY || trywait)
169		return (error);
170
171	/* Try hard to not enter the kernel. */
172	for (i = 0; i < SPIN_COUNT; i++) {
173		if (mutex->lock == UNLOCKED)
174			break;
175
176		SPIN_WAIT();
177	}
178
179	lock = atomic_cas_uint(&mutex->lock, UNLOCKED, LOCKED);
180	if (lock == UNLOCKED) {
181		membar_enter_after_atomic();
182		mutex->owner = self;
183		return (0);
184	}
185
186	if (lock != CONTENDED) {
187		/* Indicate that we're waiting on this mutex. */
188		lock = atomic_swap_uint(&mutex->lock, CONTENDED);
189	}
190
191	while (lock != UNLOCKED) {
192		error = _twait(&mutex->lock, CONTENDED, CLOCK_REALTIME, abs);
193		if (error == ETIMEDOUT)
194			return (error);
195		/*
196		 * We cannot know if there's another waiter, so in
197		 * doubt set the state to CONTENDED.
198		 */
199		lock = atomic_swap_uint(&mutex->lock, CONTENDED);
200	};
201
202	membar_enter_after_atomic();
203	mutex->owner = self;
204	return (0);
205}
206
207int
208pthread_mutex_trylock(pthread_mutex_t *mutexp)
209{
210	return (_rthread_mutex_timedlock(mutexp, 1, NULL, 0));
211}
212
213int
214pthread_mutex_timedlock(pthread_mutex_t *mutexp, const struct timespec *abs)
215{
216	return (_rthread_mutex_timedlock(mutexp, 0, abs, 1));
217}
218
219int
220pthread_mutex_lock(pthread_mutex_t *mutexp)
221{
222	return (_rthread_mutex_timedlock(mutexp, 0, NULL, 0));
223}
224DEF_STRONG(pthread_mutex_lock);
225
226int
227pthread_mutex_unlock(pthread_mutex_t *mutexp)
228{
229	pthread_t self = pthread_self();
230	pthread_mutex_t mutex;
231
232	if (mutexp == NULL)
233		return (EINVAL);
234
235	if (*mutexp == NULL)
236#if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_ERRORCHECK
237		return (EPERM);
238#elif PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_NORMAL
239		return(0);
240#else
241		abort();
242#endif
243
244	mutex = *mutexp;
245	_rthread_debug(5, "%p: mutex_unlock %p (%p)\n", self, (void *)mutex,
246	    (void *)mutex->owner);
247
248	if (mutex->owner != self) {
249	_rthread_debug(5, "%p: different owner %p (%p)\n", self, (void *)mutex,
250	    (void *)mutex->owner);
251		if (mutex->type == PTHREAD_MUTEX_ERRORCHECK ||
252		    mutex->type == PTHREAD_MUTEX_RECURSIVE) {
253			return (EPERM);
254		} else {
255			/*
256			 * For mutex type NORMAL our undefined behavior for
257			 * unlocking an unlocked mutex is to succeed without
258			 * error.  All other undefined behaviors are to
259			 * abort() immediately.
260			 */
261			if (mutex->owner == NULL &&
262			    mutex->type == PTHREAD_MUTEX_NORMAL)
263				return (0);
264			else
265				abort();
266
267		}
268	}
269
270	if (mutex->type == PTHREAD_MUTEX_RECURSIVE) {
271		if (mutex->count > 0) {
272			mutex->count--;
273			return (0);
274		}
275	}
276
277	mutex->owner = NULL;
278	membar_exit_before_atomic();
279	if (atomic_dec_int_nv(&mutex->lock) != UNLOCKED) {
280		mutex->lock = UNLOCKED;
281		_wake(&mutex->lock, 1);
282	}
283
284	return (0);
285}
286DEF_STRONG(pthread_mutex_unlock);
287