1 /*
2 Copyright (c) 2012, Broadcom Europe Ltd
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 are met:
7     * Redistributions of source code must retain the above copyright
8       notice, this list of conditions and the following disclaimer.
9     * Redistributions in binary form must reproduce the above copyright
10       notice, this list of conditions and the following disclaimer in the
11       documentation and/or other materials provided with the distribution.
12     * Neither the name of the copyright holder nor the
13       names of its contributors may be used to endorse or promote products
14       derived from this software without specific prior written permission.
15 
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 
28 /*=============================================================================
29 VideoCore OS Abstraction Layer - event flags implemented via mutexes
30 =============================================================================*/
31 
32 #include "interface/vcos/vcos.h"
33 #include "interface/vcos/generic/vcos_generic_event_flags.h"
34 
35 #include <stddef.h>
36 
37 /** A structure created by a thread that waits on the event flags
38   * for a particular combination of flags to arrive.
39   */
40 typedef struct VCOS_EVENT_WAITER_T
41 {
42    VCOS_UNSIGNED requested_events;  /**< The events wanted */
43    VCOS_UNSIGNED actual_events;     /**< Actual events found */
44    VCOS_UNSIGNED op;                /**< The event operation to be used */
45    VCOS_STATUS_T return_status;     /**< The return status the waiter should pass back */
46    VCOS_EVENT_FLAGS_T *flags;       /**< Pointer to the original 'flags' structure */
47    VCOS_THREAD_T *thread;           /**< Thread waiting */
48    struct VCOS_EVENT_WAITER_T *next;
49 } VCOS_EVENT_WAITER_T;
50 
51 #ifndef NDEBUG
52 static int waiter_list_valid(VCOS_EVENT_FLAGS_T *flags);
53 #endif
54 static void event_flags_timer_expired(void *cxt);
55 
vcos_generic_event_flags_create(VCOS_EVENT_FLAGS_T * flags,const char * name)56 VCOS_STATUS_T vcos_generic_event_flags_create(VCOS_EVENT_FLAGS_T *flags, const char *name)
57 {
58    VCOS_STATUS_T rc;
59    if ((rc=vcos_mutex_create(&flags->lock, name)) != VCOS_SUCCESS)
60    {
61       return rc;
62    }
63 
64    flags->events = 0;
65    flags->waiters.head = flags->waiters.tail = 0;
66    return rc;
67 }
68 
vcos_generic_event_flags_set(VCOS_EVENT_FLAGS_T * flags,VCOS_UNSIGNED bitmask,VCOS_OPTION op)69 void vcos_generic_event_flags_set(VCOS_EVENT_FLAGS_T *flags,
70                                   VCOS_UNSIGNED bitmask,
71                                   VCOS_OPTION op)
72 {
73    vcos_assert(flags);
74    vcos_mutex_lock(&flags->lock);
75    if (op == VCOS_OR)
76    {
77       flags->events |= bitmask;
78    }
79    else if (op == VCOS_AND)
80    {
81       flags->events &= bitmask;
82    }
83    else
84    {
85       vcos_assert(0);
86    }
87 
88    /* Now wake up any threads that have now become signalled. */
89    if (flags->waiters.head != NULL)
90    {
91       VCOS_UNSIGNED consumed_events = 0;
92       VCOS_EVENT_WAITER_T **pcurrent_waiter = &flags->waiters.head;
93       VCOS_EVENT_WAITER_T *prev_waiter = NULL;
94 
95       /* Walk the chain of tasks suspend on this event flag group to determine
96        * if any of their requests can be satisfied.
97        */
98       while ((*pcurrent_waiter) != NULL)
99       {
100          VCOS_EVENT_WAITER_T *curr_waiter = *pcurrent_waiter;
101 
102          /* Determine if this request has been satisfied */
103 
104          /* First, find the event flags in common. */
105          VCOS_UNSIGNED waiter_satisfied = flags->events & curr_waiter->requested_events;
106 
107          /* Second, determine if all the event flags must match */
108          if (curr_waiter->op & VCOS_AND)
109          {
110             /* All requested events must be present */
111             waiter_satisfied = (waiter_satisfied == curr_waiter->requested_events);
112          }
113 
114          /* Wake this one up? */
115          if (waiter_satisfied)
116          {
117 
118             if (curr_waiter->op & VCOS_CONSUME)
119             {
120                consumed_events |= curr_waiter->requested_events;
121             }
122 
123             /* remove this block from the list, taking care at the end */
124             *pcurrent_waiter = curr_waiter->next;
125             if (curr_waiter->next == NULL)
126                flags->waiters.tail = prev_waiter;
127 
128             vcos_assert(waiter_list_valid(flags));
129 
130             curr_waiter->return_status = VCOS_SUCCESS;
131             curr_waiter->actual_events = flags->events;
132 
133             _vcos_thread_sem_post(curr_waiter->thread);
134          }
135          else
136          {
137             /* move to next element in the list */
138             prev_waiter = *pcurrent_waiter;
139             pcurrent_waiter = &(curr_waiter->next);
140          }
141       }
142 
143       flags->events &= ~consumed_events;
144 
145    }
146 
147    vcos_mutex_unlock(&flags->lock);
148 }
149 
vcos_generic_event_flags_delete(VCOS_EVENT_FLAGS_T * flags)150 void vcos_generic_event_flags_delete(VCOS_EVENT_FLAGS_T *flags)
151 {
152    vcos_mutex_delete(&flags->lock);
153 }
154 
vcos_generic_event_flags_get(VCOS_EVENT_FLAGS_T * flags,VCOS_UNSIGNED bitmask,VCOS_OPTION op,VCOS_UNSIGNED suspend,VCOS_UNSIGNED * retrieved_bits)155 extern VCOS_STATUS_T vcos_generic_event_flags_get(VCOS_EVENT_FLAGS_T *flags,
156                                                   VCOS_UNSIGNED bitmask,
157                                                   VCOS_OPTION op,
158                                                   VCOS_UNSIGNED suspend,
159                                                   VCOS_UNSIGNED *retrieved_bits)
160 {
161    VCOS_EVENT_WAITER_T waitreq;
162    VCOS_STATUS_T rc = VCOS_EAGAIN;
163    int satisfied = 0;
164 
165    vcos_assert(flags);
166 
167    /* default retrieved bits to 0 */
168    *retrieved_bits = 0;
169 
170    vcos_mutex_lock(&flags->lock);
171    switch (op & VCOS_EVENT_FLAG_OP_MASK)
172    {
173    case VCOS_AND:
174       if ((flags->events & bitmask) == bitmask)
175       {
176          *retrieved_bits = flags->events;
177          rc = VCOS_SUCCESS;
178          satisfied = 1;
179          if (op & VCOS_CONSUME)
180             flags->events &= ~bitmask;
181       }
182       break;
183 
184    case VCOS_OR:
185       if (flags->events & bitmask)
186       {
187          *retrieved_bits = flags->events;
188          rc = VCOS_SUCCESS;
189          satisfied = 1;
190          if (op & VCOS_CONSUME)
191             flags->events &= ~bitmask;
192       }
193       break;
194 
195    default:
196       vcos_assert(0);
197       rc = VCOS_EINVAL;
198       break;
199    }
200 
201    if (!satisfied && suspend)
202    {
203       /* Have to go to sleep.
204        *
205        * Append to tail so we get FIFO ordering.
206        */
207       waitreq.requested_events = bitmask;
208       waitreq.op = op;
209       waitreq.return_status = VCOS_EAGAIN;
210       waitreq.flags = flags;
211       waitreq.actual_events = 0;
212       waitreq.thread = vcos_thread_current();
213       waitreq.next = 0;
214       vcos_assert(waitreq.thread != (VCOS_THREAD_T*)-1);
215       VCOS_QUEUE_APPEND_TAIL(&flags->waiters, &waitreq);
216 
217       if (suspend != (VCOS_UNSIGNED)-1)
218          _vcos_task_timer_set(event_flags_timer_expired, &waitreq, suspend);
219 
220       vcos_mutex_unlock(&flags->lock);
221       /* go to sleep and wait to be signalled or timeout */
222 
223       _vcos_thread_sem_wait();
224 
225       *retrieved_bits = waitreq.actual_events;
226       rc = waitreq.return_status;
227 
228       /* cancel the timer - do not do this while holding the mutex as it
229        * might be waiting for the timeout function to complete, which will
230        * try to take the mutex.
231        */
232       if (suspend != (VCOS_UNSIGNED)-1)
233          _vcos_task_timer_cancel();
234    }
235    else
236    {
237       vcos_mutex_unlock(&flags->lock);
238    }
239 
240    return rc;
241 }
242 
243 /** Called when a get call times out. Remove this thread's
244   * entry from the waiting queue, then resume the thread.
245   */
event_flags_timer_expired(void * cxt)246 static void event_flags_timer_expired(void *cxt)
247 {
248    VCOS_EVENT_WAITER_T *waitreq = (VCOS_EVENT_WAITER_T *)cxt;
249    VCOS_EVENT_FLAGS_T *flags = waitreq->flags;
250    VCOS_EVENT_WAITER_T **plist;
251    VCOS_EVENT_WAITER_T *prev = NULL;
252    VCOS_THREAD_T *thread = 0;
253 
254    vcos_assert(flags);
255 
256    vcos_mutex_lock(&flags->lock);
257 
258    /* walk the list of waiting threads on this event group, and remove
259     * the one that has expired.
260     *
261     * FIXME: could use doubly-linked list if lots of threads are found
262     * to be waiting on a single event flag instance.
263     */
264    plist = &flags->waiters.head;
265    while (*plist != NULL)
266    {
267       if (*plist == waitreq)
268       {
269          int at_end;
270          /* found it */
271          thread = (*plist)->thread;
272          at_end = ((*plist)->next == NULL);
273 
274          /* link past */
275          *plist = (*plist)->next;
276          if (at_end)
277             flags->waiters.tail = prev;
278 
279          break;
280       }
281       prev = *plist;
282       plist = &(*plist)->next;
283    }
284    vcos_assert(waiter_list_valid(flags));
285 
286    vcos_mutex_unlock(&flags->lock);
287 
288    if (thread)
289    {
290       _vcos_thread_sem_post(thread);
291    }
292 }
293 
294 #ifndef NDEBUG
295 
waiter_list_valid(VCOS_EVENT_FLAGS_T * flags)296 static int waiter_list_valid(VCOS_EVENT_FLAGS_T *flags)
297 {
298    int valid;
299    /* Either both head and tail are NULL, or neither are NULL */
300    if (flags->waiters.head == NULL)
301    {
302       valid = (flags->waiters.tail == NULL);
303    }
304    else
305    {
306       valid = (flags->waiters.tail != NULL);
307    }
308 
309    /* If head and tail point at the same non-NULL element, then there
310     * is only one element in the list.
311     */
312    if (flags->waiters.head && (flags->waiters.head == flags->waiters.tail))
313    {
314       valid = (flags->waiters.head->next == NULL);
315    }
316    return valid;
317 }
318 
319 #endif
320