/
atommutex.c
executable file
·720 lines (633 loc) · 25 KB
/
atommutex.c
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
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
/*
* Copyright (c) 2010, Kelvin Lawson. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. No personal names or organizations' names associated with the
* Atomthreads project may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
* Mutex library.
*
*
* This module implements a mutual exclusion library with the following
* features:
*
* \par Flexible blocking APIs
* Threads which wish to take a mutex lock can choose whether to block,
* block with timeout, or not block if the mutex is already locked by a
* different thread.
*
* \par Interrupt-safe calls
* Some APIs can be called from interrupt context, but because a mutex
* must be owned by a particular thread the lock/unlock calls must be
* performed by threads only (i.e. it does not make sense to allow
* calls from interrupt context). All APIs are documented with their
* capability of being called from interrupt context. Any attempt to
* make a call which cannot be made from interrupt context will be
* automatically and safely prevented.
*
* \par Priority-based queueing
* Where multiple threads are blocking on a mutex, they are woken in
* order of the threads' priorities. Where multiple threads of the same
* priority are blocking, they are woken in FIFO order.
*
* \par Recursive locks
* A mutex can be locked recursively by the same thread up to a
* maximum recursion level of 255. An internal count of locks is
* maintained and the mutex is only released when the count reaches
* zero (when the thread has been unlocked the same number of times
* as it was locked). This makes a mutex more suitable for use as
* mutual exclusions than a semaphore with initial count of 1.
*
* \par Thread ownership
* Once a thread has locked a mutex, only that thread may release the
* lock. This is another feature which makes the mutex more suitable
* for mutual exclusion than a semaphore with initial count 1. It
* prevents programming errors whereby the wrong thread is used to
* perform the unlock. This cannot be done for semaphores which do not
* have a concept of ownership (because it must be possible to use them
* to signal between threads).
*
* \par Smart mutex deletion
* Where a mutex is deleted while threads are blocking on it, all blocking
* threads are woken and returned a status code to indicate the reason for
* being woken.
*
*
* \n <b> Usage instructions: </b> \n
*
* All mutex objects must be initialised before use by calling
* atomMutexCreate(). Once initialised atomMutexGet() and atomMutexPut()
* are used to lock and unlock the mutex respectively. A mutex may be
* locked recursively by the same thread, allowing for simplified code
* structure.
*
* While a thread owns the lock on a mutex, no other thread can take the lock.
* These other threads will block until the mutex is released by the current
* owner (unless the calling parameters request no blocking, in which case the
* lock request will return with an error). If a mutex is released while
* threads are blocking on it, the highest priority thread is woken. Where
* multiple threads of the same priority are blocking, they are woken in the
* order in which the threads started blocking.
*
* A mutex which is no longer required can be deleted using atomMutexDelete().
* This function automatically wakes up any threads which are waiting on the
* deleted mutex.
*
*/
#ifndef STAND_ALONE
#include <stdio.h>
#endif
#include "atom.h"
#include "atommutex.h"
#include "atomtimer.h"
/* Local data types */
typedef struct mutex_timer
{
ATOM_TCB *tcb_ptr; /* Thread which is suspended with timeout */
ATOM_MUTEX *mutex_ptr; /* Mutex the thread is suspended on */
} MUTEX_TIMER;
/* Forward declarations */
static void atomMutexTimerCallback (POINTER cb_data);
/**
* \b atomMutexCreate
*
* Initialises a mutex object.
*
* Must be called before calling any other mutex library routines on a
* mutex. Objects can be deleted later using atomMutexDelete().
*
* Does not set the owner of a mutex. atomMutexGet() must be called after
* creation in order to actually take ownership.
*
* Does not allocate storage, the caller provides the mutex object.
*
* This function can be called from interrupt context.
*
* @param[in] mutex Pointer to mutex object
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameters
*/
uint8_t atomMutexCreate (ATOM_MUTEX *mutex)
{
uint8_t status;
/* Parameter check */
if (mutex == NULL)
{
/* Bad mutex pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Start with no owner (unlocked) */
mutex->owner = NULL;
/* Reset the initial lock count */
mutex->count = 0;
/* Initialise the suspended threads queue */
mutex->suspQ = NULL;
/* Successful */
status = ATOM_OK;
}
return (status);
}
/**
* \b atomMutexDelete
*
* Deletes a mutex object.
*
* Any threads currently suspended on the mutex will be woken up with
* return status ATOM_ERR_DELETED. If called at thread context then the
* scheduler will be called during this function which may schedule in one
* of the woken threads depending on relative priorities.
*
* This function can be called from interrupt context, but loops internally
* waking up all threads blocking on the mutex, so the potential
* execution cycles cannot be determined in advance.
*
* @param[in] mutex Pointer to mutex object
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue
* @retval ATOM_ERR_TIMER Problem cancelling a timeout on a woken thread
*/
uint8_t atomMutexDelete (ATOM_MUTEX *mutex)
{
uint8_t status;
CRITICAL_STORE;
ATOM_TCB *tcb_ptr;
uint8_t woken_threads = FALSE;
/* Parameter check */
if (mutex == NULL)
{
/* Bad mutex pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Default to success status unless errors occur during wakeup */
status = ATOM_OK;
/* Wake up all suspended tasks */
while (1)
{
/* Enter critical region */
CRITICAL_START ();
/* Check if any threads are suspended */
tcb_ptr = tcbDequeueHead (&mutex->suspQ);
/* A thread is suspended on the mutex */
if (tcb_ptr)
{
/* Return error status to the waiting thread */
tcb_ptr->suspend_wake_status = ATOM_ERR_DELETED;
/* Put the thread on the ready queue */
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Quit the loop, returning error */
status = ATOM_ERR_QUEUE;
break;
}
/* If there's a timeout on this suspension, cancel it */
if (tcb_ptr->suspend_timo_cb)
{
/* Cancel the callback */
if (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* Quit the loop, returning error */
status = ATOM_ERR_TIMER;
break;
}
/* Flag as no timeout registered */
tcb_ptr->suspend_timo_cb = NULL;
}
/* Exit critical region */
CRITICAL_END ();
/* Request a reschedule */
woken_threads = TRUE;
}
/* No more suspended threads */
else
{
/* Exit critical region and quit the loop */
CRITICAL_END ();
break;
}
}
/* Call scheduler if any threads were woken up */
if (woken_threads == TRUE)
{
/**
* Only call the scheduler if we are in thread context, otherwise
* it will be called on exiting the ISR by atomIntExit().
*/
if (atomCurrentContext())
atomSched (FALSE);
}
}
return (status);
}
/**
* \b atomMutexGet
*
* Take the lock on a mutex.
*
* This takes ownership of a mutex if it is not currently owned. Ownership
* is held by this thread until a corresponding call to atomMutexPut() by
* the same thread.
*
* Can be called recursively by the original locking thread (owner).
* Recursive calls are counted, and ownership is not relinquished until
* the number of unlock (atomMutexPut()) calls by the owner matches the
* number of lock (atomMutexGet()) calls.
*
* No thread other than the owner can lock or unlock the mutex while it is
* locked by another thread.
*
* Depending on the \c timeout value specified the call will do one of
* the following if the mutex is already locked by another thread:
*
* \c timeout == 0 : Call will block until the mutex is available \n
* \c timeout > 0 : Call will block until available up to the specified timeout \n
* \c timeout == -1 : Return immediately if mutex is locked by another thread \n
*
* If the call needs to block and \c timeout is zero, it will block
* indefinitely until the owning thread calls atomMutexPut() or
* atomMutexDelete() is called on the mutex.
*
* If the call needs to block and \c timeout is non-zero, the call will only
* block for the specified number of system ticks after which time, if the
* thread was not already woken, the call will return with \c ATOM_TIMEOUT.
*
* If the call would normally block and \c timeout is -1, the call will
* return immediately with \c ATOM_WOULDBLOCK.
*
* This function can only be called from thread context. A mutex has the
* concept of an owner thread, so it is never valid to make a mutex call
* from interrupt context when there is no thread to associate with.
*
* @param[in] mutex Pointer to mutex object
* @param[in] timeout Max system ticks to block (0 = forever)
*
* @retval ATOM_OK Success
* @retval ATOM_TIMEOUT Mutex timed out before being woken
* @retval ATOM_WOULDBLOCK Called with timeout == -1 but count is zero
* @retval ATOM_ERR_DELETED Mutex was deleted while suspended
* @retval ATOM_ERR_CONTEXT Not called in thread context and attempted to block
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting the thread on the suspend queue
* @retval ATOM_ERR_TIMER Problem registering the timeout
* @retval ATOM_ERR_OVF The recursive lock count would have overflowed (>255)
*/
uint8_t atomMutexGet (ATOM_MUTEX *mutex, int32_t timeout)
{
CRITICAL_STORE;
uint8_t status;
MUTEX_TIMER timer_data;
ATOM_TIMER timer_cb;
ATOM_TCB *curr_tcb_ptr;
/* Check parameters */
if (mutex == NULL)
{
/* Bad mutex pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Get the current TCB */
curr_tcb_ptr = atomCurrentContext();
/* Protect access to the mutex object and OS queues */
CRITICAL_START ();
/**
* Check we are at thread context. Because mutexes have the concept of
* owner threads, it is never valid to call here from an ISR,
* regardless of whether we will block.
*/
if (curr_tcb_ptr == NULL)
{
/* Exit critical region */
CRITICAL_END ();
/* Not currently in thread context, can't suspend */
status = ATOM_ERR_CONTEXT;
}
/* Otherwise if mutex is owned by another thread, block the calling thread */
else if ((mutex->owner != NULL) && (mutex->owner != curr_tcb_ptr))
{
/* If called with timeout >= 0, we should block */
if (timeout >= 0)
{
/* Add current thread to the suspend list on this mutex */
if (tcbEnqueuePriority (&mutex->suspQ, curr_tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* There was an error putting this thread on the suspend list */
status = ATOM_ERR_QUEUE;
}
else
{
/* Set suspended status for the current thread */
curr_tcb_ptr->suspended = TRUE;
/* Track errors */
status = ATOM_OK;
/* Register a timer callback if requested */
if (timeout)
{
/* Fill out the data needed by the callback to wake us up */
timer_data.tcb_ptr = curr_tcb_ptr;
timer_data.mutex_ptr = mutex;
/* Fill out the timer callback request structure */
timer_cb.cb_func = atomMutexTimerCallback;
timer_cb.cb_data = (POINTER)&timer_data;
timer_cb.cb_ticks = timeout;
/**
* Store the timer details in the TCB so that we can
* cancel the timer callback if the mutex is put
* before the timeout occurs.
*/
curr_tcb_ptr->suspend_timo_cb = &timer_cb;
/* Register a callback on timeout */
if (atomTimerRegister (&timer_cb) != ATOM_OK)
{
/* Timer registration failed */
status = ATOM_ERR_TIMER;
/* Clean up and return to the caller */
(void)tcbDequeueEntry (&mutex->suspQ, curr_tcb_ptr);
curr_tcb_ptr->suspended = FALSE;
curr_tcb_ptr->suspend_timo_cb = NULL;
}
}
/* Set no timeout requested */
else
{
/* No need to cancel timeouts on this one */
curr_tcb_ptr->suspend_timo_cb = NULL;
}
/* Exit critical region */
CRITICAL_END ();
/* Check no errors have occurred */
if (status == ATOM_OK)
{
/**
* Current thread now blocking, schedule in a new
* one. We already know we are in thread context
* so can call the scheduler from here.
*/
atomSched (FALSE);
/**
* Normal atomMutexPut() wakeups will set ATOM_OK status,
* while timeouts will set ATOM_TIMEOUT and mutex
* deletions will set ATOM_ERR_DELETED. */
status = curr_tcb_ptr->suspend_wake_status;
/**
* If we were woken up by another thread relinquishing
* the mutex and handing this thread ownership, then
* the relinquishing thread will set status to ATOM_OK
* and will make this thread the owner. Setting the
* owner before waking the thread ensures that no other
* thread can preempt and take ownership of the mutex
* between this thread being made ready to run, and
* actually being scheduled back in here.
*/
if (status == ATOM_OK)
{
/**
* Since this thread has just gained ownership, the
* lock count is zero and should be incremented
* once for this call.
*/
mutex->count++;
}
}
}
}
else
{
/* timeout == -1, requested not to block and mutex is owned by another thread */
CRITICAL_END();
status = ATOM_WOULDBLOCK;
}
}
else
{
/* Thread is not owned or is owned by us, we can claim ownership */
/* Increment the lock count, checking for count overflow */
if (mutex->count == 255)
{
/* Don't increment, just return error status */
status = ATOM_ERR_OVF;
}
else
{
/* Increment the count and return to the calling thread */
mutex->count++;
/* If the mutex is not locked, mark the calling thread as the new owner */
if (mutex->owner == NULL)
{
mutex->owner = curr_tcb_ptr;
}
/* Successful */
status = ATOM_OK;
}
/* Exit critical region */
CRITICAL_END ();
}
}
return (status);
}
/**
* \b atomMutexPut
*
* Give back the lock on a mutex.
*
* This checks that the mutex is owned by the calling thread, and decrements
* the recursive lock count. Once the lock count reaches zero, the lock is
* considered relinquished and no longer owned by this thread.
*
* If the lock is relinquished and there are threads blocking on the mutex, the
* call will wake up the highest priority thread suspended. Only one thread is
* woken per call to atomMutexPut(). If multiple threads of the same priority
* are suspended, they are woken in order of suspension (FIFO).
*
* This function can only be called from thread context. A mutex has the
* concept of an owner thread, so it is never valid to make a mutex call
* from interrupt context when there is no thread to associate with.
*
* @param[in] mutex Pointer to mutex object
*
* @retval ATOM_OK Success
* @retval ATOM_ERR_PARAM Bad parameter
* @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue
* @retval ATOM_ERR_TIMER Problem cancelling a timeout for a woken thread
* @retval ATOM_ERR_OWNERSHIP Attempt to unlock mutex not owned by this thread
*/
uint8_t atomMutexPut (ATOM_MUTEX * mutex)
{
uint8_t status;
CRITICAL_STORE;
ATOM_TCB *tcb_ptr, *curr_tcb_ptr;
/* Check parameters */
if (mutex == NULL)
{
/* Bad mutex pointer */
status = ATOM_ERR_PARAM;
}
else
{
/* Get the current TCB */
curr_tcb_ptr = atomCurrentContext();
/* Protect access to the mutex object and OS queues */
CRITICAL_START ();
/* Check if the calling thread owns this mutex */
if (mutex->owner != curr_tcb_ptr)
{
/* Exit critical region */
CRITICAL_END ();
/* Attempt to unlock by non-owning thread */
status = ATOM_ERR_OWNERSHIP;
}
else
{
/* Lock is owned by this thread, decrement the recursive lock count */
mutex->count--;
/* Once recursive lock count reaches zero, we relinquish ownership */
if (mutex->count == 0)
{
/* Relinquish ownership */
mutex->owner = NULL;
/* If any threads are blocking on this mutex, wake them now */
if (mutex->suspQ)
{
/**
* Threads are woken up in priority order, with a FIFO system
* used on same priority threads. We always take the head,
* ordering is taken care of by an ordered list enqueue.
*/
tcb_ptr = tcbDequeueHead (&mutex->suspQ);
if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK)
{
/* Exit critical region */
CRITICAL_END ();
/* There was a problem putting the thread on the ready queue */
status = ATOM_ERR_QUEUE;
}
else
{
/* Set OK status to be returned to the waiting thread */
tcb_ptr->suspend_wake_status = ATOM_OK;
/* Set this thread as the new owner of the mutex */
mutex->owner = tcb_ptr;
/* If there's a timeout on this suspension, cancel it */
if ((tcb_ptr->suspend_timo_cb != NULL)
&& (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK))
{
/* There was a problem cancelling a timeout on this mutex */
status = ATOM_ERR_TIMER;
}
else
{
/* Flag as no timeout registered */
tcb_ptr->suspend_timo_cb = NULL;
/* Successful */
status = ATOM_OK;
}
/* Exit critical region */
CRITICAL_END ();
/**
* The scheduler may now make a policy decision to
* thread switch. We already know we are in thread
* context so can call the scheduler from here.
*/
atomSched (FALSE);
}
}
else
{
/**
* Relinquished ownership and no threads waiting.
* Nothing to do.
*/
/* Exit critical region */
CRITICAL_END ();
/* Successful */
status = ATOM_OK;
}
}
else
{
/**
* Decremented lock but still retain ownership due to
* recursion. Nothing to do.
*/
/* Exit critical region */
CRITICAL_END ();
/* Successful */
status = ATOM_OK;
}
}
}
return (status);
}
/**
* \b atomMutexTimerCallback
*
* This is an internal function not for use by application code.
*
* Timeouts on suspended threads are notified by the timer system through
* this generic callback. The timer system calls us back with a pointer to
* the relevant \c MUTEX_TIMER object which is used to retrieve the
* mutex details.
*
* @param[in] cb_data Pointer to a MUTEX_TIMER object
*/
static void atomMutexTimerCallback (POINTER cb_data)
{
MUTEX_TIMER *timer_data_ptr;
CRITICAL_STORE;
/* Get the MUTEX_TIMER structure pointer */
timer_data_ptr = (MUTEX_TIMER *)cb_data;
/* Check parameter is valid */
if (timer_data_ptr)
{
/* Enter critical region */
CRITICAL_START ();
/* Set status to indicate to the waiting thread that it timed out */
timer_data_ptr->tcb_ptr->suspend_wake_status = ATOM_TIMEOUT;
/* Flag as no timeout registered */
timer_data_ptr->tcb_ptr->suspend_timo_cb = NULL;
/* Remove this thread from the mutex's suspend list */
(void)tcbDequeueEntry (&timer_data_ptr->mutex_ptr->suspQ, timer_data_ptr->tcb_ptr);
/* Put the thread on the ready queue */
(void)tcbEnqueuePriority (&tcbReadyQ, timer_data_ptr->tcb_ptr);
/* Exit critical region */
CRITICAL_END ();
/**
* Note that we don't call the scheduler now as it will be called
* when we exit the ISR by atomIntExit().
*/
}
}