ump_osu_locks.c 16.2 KB
Newer Older
Dmitriy Beykun's avatar
Dmitriy Beykun committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
/*
 * Copyright (C) 2010-2012 ARM Limited. All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#if ((!defined _XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 600))
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif


#define _POSIX_C_SOURCE 200112L

#include <ump/ump_osu.h>
#include <ump/ump_debug.h>

#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>

/**
 * @file ump_osu_locks.c
 * File implements the user side of the OS interface
 */

/** @opt Most of the time, we use the plain mutex type of osu_lock, and so
 * only require the flags and mutex members. This costs 2 extra DWORDS, but
 * most of the time we don't use those DWORDS.
 * Therefore, ANY_UNLOCK type osu_locks can be implemented as a second
 * structure containing the member _ump_osu_lock_t lock_t, plus the extra
 * state required. Then, we use &container->lock_t when passing out of the
 * OSU api, and CONTAINER_OF() when passing back in to recover the original
 * structure. */

/** Private declaration of the OSU lock type */
struct _ump_osu_lock_t_struct
{
	/** At present, only two types of mutex, so we store this information as
	 * the flags supplied at init time */
	_ump_osu_lock_flags_t flags;

	pthread_mutex_t mutex; /**< Used in both plain and ANY_UNLOCK osu_locks */

	/* Extra State for ANY_UNLOCK osu_locks. These are UNINITIALIZED when
	 * flags does not contain _UMP_OSU_LOCKFLAG_ANYUNLOCK: */
	pthread_cond_t condition;  /**< The condition object to use while blocking */
	ump_bool state;  /**< The boolean which indicates the event's state */

	UMP_DEBUG_CODE(
				  /** debug checking of locks */
				  _ump_osu_lock_mode_t locked_as;
	) /* UMP_DEBUG_CODE */

};

/* Provide two statically initialized locks */
UMP_STATIC _ump_osu_lock_t _ump_osu_static_locks[] =
{
	{
	    _UMP_OSU_LOCKFLAG_STATIC,
	    PTHREAD_MUTEX_INITIALIZER,
	    PTHREAD_COND_INITIALIZER,
	    UMP_FALSE,
		UMP_DEBUG_CODE( _UMP_OSU_LOCKMODE_UNDEF )
	},
	{
	    _UMP_OSU_LOCKFLAG_STATIC,
	    PTHREAD_MUTEX_INITIALIZER,
	    PTHREAD_COND_INITIALIZER,
	    UMP_FALSE,
		UMP_DEBUG_CODE( _UMP_OSU_LOCKMODE_UNDEF )
	},
	{
	    _UMP_OSU_LOCKFLAG_STATIC,
	    PTHREAD_MUTEX_INITIALIZER,
	    PTHREAD_COND_INITIALIZER,
	    UMP_FALSE,
		UMP_DEBUG_CODE( _UMP_OSU_LOCKMODE_UNDEF )
	},
	{
	    _UMP_OSU_LOCKFLAG_STATIC,
	    PTHREAD_MUTEX_INITIALIZER,
	    PTHREAD_COND_INITIALIZER,
	    UMP_FALSE,
		UMP_DEBUG_CODE( _UMP_OSU_LOCKMODE_UNDEF )
	},
};

/* Critical section for auto_init */
UMP_STATIC pthread_mutex_t	static_auto_init_mutex = PTHREAD_MUTEX_INITIALIZER;


_ump_osu_errcode_t _ump_osu_lock_auto_init( _ump_osu_lock_t **pplock, _ump_osu_lock_flags_t flags, u32 initial, u32 order )
{
	int call_result;
	/* Validate parameters: */
	UMP_DEBUG_ASSERT_POINTER( pplock );

	/** @opt We don't lock the Critical Section or do anything if this is already non-null */
	if ( NULL != *pplock)
	{
		return _UMP_OSU_ERR_OK;
	}

	/* We MIGHT need to initialize it, lock the Critical Section and check again */
	call_result = pthread_mutex_lock(&static_auto_init_mutex);
	/* It would be a programming error for this to fail: */
	UMP_DEBUG_ASSERT( 0 == call_result,
					   ("failed to lock critical section\n") );

	if ( NULL != *pplock )
	{
		/*
			We caught a race condition to initialize this osu_lock.
			The other thread won the race, so the osu_lock is now initialized.
		*/
		call_result = pthread_mutex_unlock(&static_auto_init_mutex);

		UMP_DEBUG_ASSERT(0 == call_result,
						  ("failed to unlock critical section\n"));

		return _UMP_OSU_ERR_OK;
	}

	/* We're the first thread in: initialize the osu_lock */
	*pplock = _ump_osu_lock_init( flags, initial, order );

	if ( NULL == *pplock )
	{
		/* osu_lock creation failed */
		call_result = pthread_mutex_unlock(&static_auto_init_mutex);
		UMP_DEBUG_ASSERT(0 == call_result,
						  ("failed to unlock critical section\n"));

		return _UMP_OSU_ERR_FAULT;
	}


	/* osu_lock created OK */
	call_result = pthread_mutex_unlock(&static_auto_init_mutex);

	UMP_DEBUG_ASSERT(0 == call_result,
					  ("failed to unlock critical section\n"));

	UMP_IGNORE( call_result );

	return _UMP_OSU_ERR_OK;
}


_ump_osu_lock_t *_ump_osu_lock_init( _ump_osu_lock_flags_t flags, u32 initial, u32 order )
{
	_ump_osu_lock_t * lock;
	pthread_mutexattr_t mutex_attributes;

	UMP_IGNORE(order); /* order isn't implemented yet, for now callers should set it to zero. */

	/* Validate parameters: */
	/* Flags acceptable */
	UMP_DEBUG_ASSERT( 0 == ( flags & ~( _UMP_OSU_LOCKFLAG_ANYUNLOCK)),
					   ("incorrect flags or trying to initialise a statically initialized lock, %.8X\n", flags) );

	/* Parameter initial SBZ - for future expansion */
	UMP_DEBUG_ASSERT( 0 == initial,
					   ("initial must be zero\n") );

	if (0 != pthread_mutexattr_init(&mutex_attributes))
	{
		return NULL;
	}

#if UMP_DEBUG_EXTENDED_MUTEX_LOCK_CHECKING
#define UMP_PTHREADS_MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK
#else
#define UMP_PTHREADS_MUTEX_TYPE PTHREAD_MUTEX_DEFAULT
#endif

	if (0 != pthread_mutexattr_settype(&mutex_attributes, UMP_PTHREADS_MUTEX_TYPE))
	{
		/** Return NULL on failure */
		pthread_mutexattr_destroy(&mutex_attributes);
		return NULL;

	}

#undef UMP_PTHREADS_MUTEX_TYPE

	/** @opt use containing structures for the ANY_UNLOCK type, to
	 * save 2 DWORDS when not in use */
	lock = _ump_osu_malloc( sizeof(_ump_osu_lock_t) );

	if( NULL == lock )
	{
		/** Return NULL on failure */
		pthread_mutexattr_destroy(&mutex_attributes);
		return NULL;
	}

	if (0 != pthread_mutex_init( &lock->mutex, &mutex_attributes ))
	{
		pthread_mutexattr_destroy(&mutex_attributes);
		_ump_osu_free( lock );
		return NULL;
	}

	/* done with the mutexattr object */
	pthread_mutexattr_destroy(&mutex_attributes);

	/* ANY_UNLOCK type */
	if ( flags & _UMP_OSU_LOCKFLAG_ANYUNLOCK )
	{
		if (0 != pthread_cond_init( &lock->condition, NULL ))
		{
			/* cleanup */
			pthread_mutex_destroy( &lock->mutex );
			_ump_osu_free( lock );
			return NULL;
		}
		lock->state = UMP_FALSE; /* mark as unlocked by default */
	}

	lock->flags = flags;

	/** Debug lock checking */
	UMP_DEBUG_CODE( lock->locked_as = _UMP_OSU_LOCKMODE_UNDEF	);

	return lock;
}

_ump_osu_errcode_t _ump_osu_lock_timed_wait( _ump_osu_lock_t *lock, _ump_osu_lock_mode_t mode, u64 timeout)
{
	/* absolute time specifier */
	struct timespec ts;
	struct timeval tv;

	/* Parameter validation */
	UMP_DEBUG_ASSERT_POINTER( lock );

	UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_RW == mode,
					   ("unrecognised mode, %.8X\n", mode) );
	UMP_DEBUG_ASSERT( _UMP_OSU_LOCKFLAG_ANYUNLOCK == lock->flags, ("Timed operations only implemented for ANYUNLOCK type locks"));

	/* calculate the realtime timeout value */

	if (0 != gettimeofday(&tv, NULL))
	{
		UMP_DEBUG_PRINT(1,("Could not get the current realtime value to calculate the absolute value for a timed mutex lock with a timeout"));
		return _UMP_OSU_ERR_FAULT;
	}

	tv.tv_usec += timeout;

#define UMP_USECS_PER_SECOND 1000000LL
#define UMP_NANOSECS_PER_USEC 1000LL

	/* did we overflow a second in the usec part? */
	while (tv.tv_usec >= UMP_USECS_PER_SECOND)
	{
		tv.tv_usec -= UMP_USECS_PER_SECOND;
		tv.tv_sec++;
	}

	/* copy to the correct struct */
	ts.tv_sec = tv.tv_sec;
	ts.tv_nsec = (tv.tv_usec * UMP_NANOSECS_PER_USEC);

#undef UMP_USECS_PER_SECOND
#undef UMP_NANOSECS_PER_USEC

	/* lock the mutex protecting access to the state field */
	pthread_mutex_lock( &lock->mutex );
	/* loop while locked (state is UMP_TRUE) */
	/* pthread_cond_timedwait unlocks the mutex, wait, and locks the mutex once unblocked (either due to the event or the timeout) */
	while ( UMP_TRUE == lock->state )
	{
		int res;
		res = pthread_cond_timedwait( &lock->condition, &lock->mutex, &ts );
		if (0 == res) continue; /* test the state variable again (loop condition) */
		else if (ETIMEDOUT == res)
		{
			/* timeout, need to clean up and return the correct error code */
			pthread_mutex_unlock(&lock->mutex);
			return _UMP_OSU_ERR_TIMEOUT;
		}
		else
		{
			UMP_DEBUG_PRINT(1, ("Unexpected return from pthread_cond_timedwait 0x%08X\n", res));

			pthread_mutex_unlock(&lock->mutex);
			return _UMP_OSU_ERR_FAULT;
		}
	
	}

	/* DEBUG tracking of previously locked state - occurs while lock is obtained */
	UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_UNDEF == lock->locked_as,
			("This lock was already locked\n") );
	UMP_DEBUG_CODE( lock->locked_as = mode );

	/* the state is UMP_FALSE (unlocked), so we set it to UMP_TRUE to indicate that it's locked and can return knowing that we own the lock */
	lock->state = UMP_TRUE;
	/* final unlock of the mutex */
	pthread_mutex_unlock(&lock->mutex);

	return _UMP_OSU_ERR_OK;

}

_ump_osu_errcode_t _ump_osu_lock_wait( _ump_osu_lock_t *lock, _ump_osu_lock_mode_t mode)
{
	/* Parameter validation */
	UMP_DEBUG_ASSERT_POINTER( lock );

	UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_RW == mode,
					   ("unrecognised mode, %.8X\n", mode) );

	/** @note since only one flag can be set, we use a switch statement here.
	 * Otherwise, MUST add an enum into the _ump_osu_lock_t to store the
	 * implemented lock type */
	switch ( lock->flags )
	{
	case _UMP_OSU_LOCKFLAG_STATIC:
	case 0:
		/* Usual Mutex type */
		{
			int call_result;
			call_result = pthread_mutex_lock( &lock->mutex );
			UMP_DEBUG_ASSERT( 0 == call_result,
							   ("pthread_mutex_lock call failed with error code %d\n", call_result));
			UMP_IGNORE( call_result );
		}

		/* DEBUG tracking of previously locked state - occurs while lock is obtained */
		UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_UNDEF == lock->locked_as,
						   ("This lock was already locked\n") );
		UMP_DEBUG_CODE( lock->locked_as = mode );
		break;

	case _UMP_OSU_LOCKFLAG_ANYUNLOCK:
		/** @note Use of bitflags in a case statement ONLY works because this
		 * is the ONLY flag that is supported */

		/* lock the mutex protecting access to the state field */
		pthread_mutex_lock( &lock->mutex );
		/* loop while locked (state is UMP_TRUE) */
		/* pthread_cond_wait unlocks the mutex, wait, and locks the mutex once unblocked */
		while ( UMP_TRUE == lock->state ) pthread_cond_wait( &lock->condition, &lock->mutex );

		/* DEBUG tracking of previously locked state - occurs while lock is obtained */
		UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_UNDEF == lock->locked_as,
						   ("This lock was already locked\n") );
		UMP_DEBUG_CODE( lock->locked_as = mode );

		/* the state is UMP_FALSE (unlocked), so we set it to UMP_TRUE to indicate that it's locked and can return knowing that we own the lock */
		lock->state = UMP_TRUE;
		/* final unlock of the mutex */
		pthread_mutex_unlock(&lock->mutex);
		break;

	default:
		UMP_DEBUG_ERROR( ("lock has incorrect flags==%.8X\n", lock->flags) );
		break;
	}

	return _UMP_OSU_ERR_OK;
}

_ump_osu_errcode_t _ump_osu_lock_trywait( _ump_osu_lock_t *lock, _ump_osu_lock_mode_t mode)
{
	_ump_osu_errcode_t err = _UMP_OSU_ERR_FAULT;
	/* Parameter validation */
	UMP_DEBUG_ASSERT_POINTER( lock );

	UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_RW == mode,
					   ("unrecognised mode, %.8X\n", mode) );

	/** @note since only one flag can be set, we use a switch statement here.
	 * Otherwise, MUST add an enum into the _ump_osu_lock_t to store the
	 * implemented lock type */
	switch ( lock->flags )
	{
	case _UMP_OSU_LOCKFLAG_STATIC:
	case 0:
		/* Usual Mutex type */
		{
			/* This is not subject to UMP_CHECK - overriding the result would cause a programming error */
			if ( 0 == pthread_mutex_trylock( &lock->mutex ) )
			{
				err = _UMP_OSU_ERR_OK;

				/* DEBUG tracking of previously locked state - occurs while lock is obtained */
				UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_UNDEF == lock->locked_as
								   || mode == lock->locked_as,
								   ("tried as mode==%.8X, but was locked as %.8X\n", mode, lock->locked_as) );
				UMP_DEBUG_CODE( lock->locked_as = mode );
			}
		}
		break;

	case _UMP_OSU_LOCKFLAG_ANYUNLOCK:
		/** @note Use of bitflags in a case statement ONLY works because this
		 * is the ONLY flag that is supported */

		/* lock the mutex protecting access to the state field */
		pthread_mutex_lock(&lock->mutex);

		if ( UMP_FALSE == lock->state)
		{
			/* unlocked, take the lock */
			lock->state = UMP_TRUE;
			err = _UMP_OSU_ERR_OK;
		}

		/* DEBUG tracking of previously locked state - occurs while lock is obtained */
		/* Can do this regardless of whether we obtained ANYUNLOCK: */


		UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_UNDEF == lock->locked_as
						   || mode == lock->locked_as,
						   ("tried as mode==%.8X, but was locked as %.8X\n", mode, lock->locked_as) );
		/* If we were already locked, this does no harm, because of the above assert: */
		UMP_DEBUG_CODE( lock->locked_as = mode );

		pthread_mutex_unlock(&lock->mutex);
		break;

	default:
		UMP_DEBUG_ERROR( ("lock has incorrect flags==%.8X\n", lock->flags) );
		break;
	}

	return err;
}


void _ump_osu_lock_signal( _ump_osu_lock_t *lock, _ump_osu_lock_mode_t mode )
{
	/* Parameter validation */
	UMP_DEBUG_ASSERT_POINTER( lock );

	UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_RW == mode,
					   ("unrecognised mode, %.8X\n", mode) );

	/** @note since only one flag can be set, we use a switch statement here.
	 * Otherwise, MUST add an enum into the _ump_osu_lock_t to store the
	 * implemented lock type */
	switch ( lock->flags )
	{
	case _UMP_OSU_LOCKFLAG_STATIC:
	case 0:
		/* Usual Mutex type */

		/* DEBUG tracking of previously locked state - occurs while lock is obtained */
		UMP_DEBUG_ASSERT( mode == lock->locked_as,
						   ("This lock was locked as==%.8X, but tried to unlock as mode==%.8X\n", lock->locked_as, mode));
		UMP_DEBUG_CODE( lock->locked_as = _UMP_OSU_LOCKMODE_UNDEF );

		{
			int call_result;
			call_result = pthread_mutex_unlock( &lock->mutex );
			UMP_DEBUG_ASSERT( 0 == call_result,
							   ("pthread_mutex_lock call failed with error code %d\n", call_result));
			UMP_IGNORE( call_result );
		}
		break;

	case _UMP_OSU_LOCKFLAG_ANYUNLOCK:
		/** @note Use of bitflags in a case statement ONLY works because this
		 * is the ONLY flag that is supported */

		pthread_mutex_lock(&lock->mutex);
		UMP_DEBUG_ASSERT( UMP_TRUE == lock->state, ("Unlocking a _ump_osu_lock_t %p which is not locked\n", lock));

		/* DEBUG tracking of previously locked state - occurs while lock is obtained */
		UMP_DEBUG_ASSERT( mode == lock->locked_as,
						   ("This lock was locked as==%.8X, but tried to unlock as %.8X\n", lock->locked_as, mode ));
		UMP_DEBUG_CODE( lock->locked_as = _UMP_OSU_LOCKMODE_UNDEF );

		/* mark as unlocked */
		lock->state = UMP_FALSE;

		/* signal the condition, only wake a single thread */
		pthread_cond_signal(&lock->condition);

		pthread_mutex_unlock(&lock->mutex);
		break;

	default:
		UMP_DEBUG_ERROR( ("lock has incorrect flags==%.8X\n", lock->flags) );
		break;
	}
}

void _ump_osu_lock_term( _ump_osu_lock_t *lock )
{
	int call_result;
	UMP_DEBUG_ASSERT_POINTER( lock );

	/** Debug lock checking: */
	/* Lock is signalled on terminate - not a guarantee, since we could be locked immediately beforehand */
	UMP_DEBUG_ASSERT( _UMP_OSU_LOCKMODE_UNDEF == lock->locked_as,
					   ("cannot terminate held lock\n") );

	call_result = pthread_mutex_destroy( &lock->mutex );
	UMP_DEBUG_ASSERT( 0 == call_result,
					   ("Incorrect mutex use detected: pthread_mutex_destroy call failed with error code %d\n", call_result) );

	/* Destroy extra state for ANY_UNLOCK type osu_locks */
	if ( lock->flags & _UMP_OSU_LOCKFLAG_ANYUNLOCK )
	{
		UMP_DEBUG_ASSERT( UMP_FALSE == lock->state, ("terminate called on locked object %p\n", lock));
		call_result = pthread_cond_destroy(&lock->condition);
		UMP_DEBUG_ASSERT( 0 == call_result,
						   ("Incorrect condition-variable use detected: pthread_cond_destroy call failed with error code %d\n", call_result) );
	}

	UMP_IGNORE(call_result);

	_ump_osu_free( lock );
}

_ump_osu_lock_t *_ump_osu_lock_static( u32 nr )
{
	UMP_DEBUG_ASSERT( nr < UMP_OSU_STATIC_LOCK_COUNT,
	                   ("provided static lock index (%d) out of bounds (0 < nr < %d)\n", nr, UMP_OSU_STATIC_LOCK_COUNT) );
	return &_ump_osu_static_locks[nr];
}