Workaround for a known fifobuf bug.
[bertos.git] / mware / fifobuf.h
1 /*!
2  * \file
3  * <!--
4  * Copyright 2003, 2004 Develer S.r.l. (http://www.develer.com/)
5  * Copyright 2001 Bernardo Innocenti <bernie@develer.com>
6  * This file is part of DevLib - See devlib/README for information.
7  * -->
8  *
9  * \version $Id$
10  *
11  * \author Bernardo Innocenti <bernie@develer.com>
12  *
13  * \brief General pourpose FIFO buffer implemented with a ring buffer
14  *
15  * \li \c begin points to the first buffer element;
16  * \li \c end points to the last buffer element (unlike the STL convention);
17  * \li \c head points to the element to be extracted next;
18  * \li \c tail points to the location following the last insertion;
19  * \li when any of the pointers advances beyond \c end, it is reset
20  *     back to \c begin.
21  *
22  * \code
23  *
24  *  +-----------------------------------+
25  *  |  empty  |   valid data   |  empty |
26  *  +-----------------------------------+
27  *  ^         ^                ^        ^
28  *  begin    head             tail     end
29  *
30  * \endcode
31  *
32  * The buffer is EMPTY when \c head and \c tail point to the same location:
33  *              \code head == tail \endcode
34  *
35  * The buffer is FULL when \c tail points to the location immediately
36  * after \c head:
37  *              \code tail == head - 1 \endcode
38  *
39  * The buffer is also FULL when \c tail points to the last buffer
40  * location and head points to the first one:
41  *              \code head == begin && tail == end \endcode
42  */
43
44 /*#*
45  *#* $Log$
46  *#* Revision 1.18  2004/11/16 21:55:12  bernie
47  *#* Workaround for a known fifobuf bug.
48  *#*
49  *#* Revision 1.17  2004/09/14 20:57:00  bernie
50  *#* Use debug.h instead of kdebug.h.
51  *#*
52  *#* Revision 1.16  2004/09/06 21:39:08  bernie
53  *#* Simplify code using ATOMIC().
54  *#*
55  *#* Revision 1.15  2004/08/29 22:05:16  bernie
56  *#* Rename BITS_PER_PTR to CPU_BITS_PER_PTR.
57  *#*
58  *#* Revision 1.14  2004/08/25 14:12:09  rasky
59  *#* Aggiornato il comment block dei log RCS
60  *#*
61  *#* Revision 1.13  2004/08/24 13:16:11  bernie
62  *#* Add type-size definitions for preprocessor.
63  *#*
64  *#* Revision 1.12  2004/08/02 20:20:29  aleph
65  *#* Merge from project_ks
66  *#*
67  *#* Revision 1.11  2004/07/30 14:15:53  rasky
68  *#* Nuovo supporto unificato per detect della CPU
69  *#*
70  *#* Revision 1.10  2004/07/29 22:57:09  bernie
71  *#* Doxygen fix.
72  *#*
73  *#* Revision 1.9  2004/07/20 23:54:27  bernie
74  *#* fifo_flush_locked(): New function;
75  *#* Revamp documentation.
76  *#*
77  *#* Revision 1.8  2004/07/20 23:47:39  bernie
78  *#* Finally remove redundant protos.
79  *#*
80  *#* Revision 1.7  2004/07/20 23:46:29  bernie
81  *#* Finally remove redundant protos.
82  *#*
83  *#* Revision 1.6  2004/06/06 17:18:04  bernie
84  *#* Remove redundant declaration of fifo_isempty_locked().
85  *#*
86  *#* Revision 1.5  2004/06/06 16:50:35  bernie
87  *#* Import fixes for race conditions from project_ks.
88  *#*
89  *#* Revision 1.4  2004/06/06 16:11:17  bernie
90  *#* Protect MetroWerks specific pragmas with #ifdef's
91  *#*/
92
93 #ifndef MWARE_FIFO_H
94 #define MWARE_FIFO_H
95
96 #include <cpu.h>
97
98 typedef struct FIFOBuffer
99 {
100         unsigned char * volatile head;
101         unsigned char * volatile tail;
102         unsigned char *begin;
103         unsigned char *end;
104 } FIFOBuffer;
105
106
107 #define ASSERT_VALID_FIFO(fifo) \
108         ATOMIC( \
109                 ASSERT((fifo)->head >= (fifo)->begin); \
110                 ASSERT((fifo)->head <= (fifo)->end); \
111                 ASSERT((fifo)->tail >= (fifo)->begin); \
112                 ASSERT((fifo)->tail <= (fifo)->end); \
113         )
114
115
116 /*!
117  * Check whether the fifo is empty
118  *
119  * \note Calling fifo_isempty() is safe while a concurrent
120  *       execution context is calling fifo_push() or fifo_pop()
121  *       only if the CPU can atomically update a pointer
122  *       (which the AVR and other 8-bit processors can't do).
123  *
124  * \sa fifo_isempty_locked
125  */
126 INLINE bool fifo_isempty(const FIFOBuffer *fb)
127 {
128         //ASSERT_VALID_FIFO(fb);
129         return fb->head == fb->tail;
130 }
131
132
133 /*!
134  * Check whether the fifo is full
135  *
136  * \note Calling fifo_isfull() is safe while a concurrent
137  *       execution context is calling fifo_pop() and the
138  *       CPU can update a pointer atomically.
139  *       It is NOT safe when the other context calls
140  *       fifo_push().
141  *       This limitation is not usually problematic in a
142  *       consumer/producer scenario because the
143  *       fifo_isfull() and fifo_push() are usually called
144  *       in the producer context.
145  */
146 INLINE bool fifo_isfull(const FIFOBuffer *fb)
147 {
148         //ASSERT_VALID_FIFO(fb);
149         return
150                 ((fb->head == fb->begin) && (fb->tail == fb->end))
151                 || (fb->tail == fb->head - 1);
152 }
153
154
155 /*!
156  * Pop a character from the fifo buffer.
157  *
158  * \note Calling \c fifo_push() on a full buffer is undefined.
159  *       The caller must make sure the buffer has at least
160  *       one free slot before calling this function.
161  *
162  * \note It is safe to call fifo_pop() and fifo_push() from
163  *       concurrent contexts, unless the CPU can't update
164  *       a pointer atomically (which the AVR and other 8-bit
165  *       processors can't do).
166  *
167  * \sa fifo_push_locked
168  */
169 INLINE void fifo_push(FIFOBuffer *fb, unsigned char c)
170 {
171 #ifdef __MWERKS__
172 #pragma interrupt called
173 #endif
174         //ASSERT_VALID_FIFO(fb);
175
176         /* Write at tail position */
177         *(fb->tail) = c;
178
179         if (UNLIKELY(fb->tail == fb->end))
180                 /* wrap tail around */
181                 fb->tail = fb->begin;
182         else
183                 /* Move tail forward */
184                 fb->tail++;
185 }
186
187
188 /*!
189  * Pop a character from the fifo buffer.
190  *
191  * \note Calling \c fifo_pop() on an empty buffer is undefined.
192  *       The caller must make sure the buffer contains at least
193  *       one character before calling this function.
194  *
195  * \note It is safe to call fifo_pop() and fifo_push() from
196  *       concurrent contexts.
197  */
198 INLINE unsigned char fifo_pop(FIFOBuffer *fb)
199 {
200 #ifdef __MWERKS__
201 #pragma interrupt called
202 #endif
203         //ASSERT_VALID_FIFO(fb);
204
205         if (UNLIKELY(fb->head == fb->end))
206         {
207                 /* wrap head around */
208                 fb->head = fb->begin;
209                 return *(fb->end);
210         }
211         else
212                 /* move head forward */
213                 return *(fb->head++);
214 }
215
216
217 /*!
218  * Make the fifo empty, discarding all its current contents.
219  */
220 INLINE void fifo_flush(FIFOBuffer *fb)
221 {
222         //ASSERT_VALID_FIFO(fb);
223         fb->head = fb->tail;
224 }
225
226
227 #if CPU_REG_BITS >= CPU_BITS_PER_PTR
228
229         /*
230          * 16/32bit CPUs that can update a pointer with a single write
231          * operation, no need to disable interrupts.
232          */
233         #define fifo_isempty_locked(fb) fifo_isempty((fb))
234         #define fifo_push_locked(fb, c) fifo_push((fb), (c))
235         #define fifo_pop_locked(fb)     fifo_pop((fb))
236         #define fifo_flush_locked(fb)   fifo_flush((fb))
237
238 #else /* CPU_REG_BITS < CPU_BITS_PER_PTR */
239
240         /*!
241          * Similar to fifo_isempty(), but with stronger guarantees for
242          * concurrent access between user and interrupt code.
243          *
244          * \note This is actually only needed for 8-bit processors.
245          *
246          * \sa fifo_isempty()
247          */
248         INLINE bool fifo_isempty_locked(const FIFOBuffer *fb)
249         {
250                 bool result;
251                 ATOMIC(result = fifo_isempty(fb));
252                 return result;
253         }
254
255
256         /*!
257          * Similar to fifo_push(), but with stronger guarantees for
258          * concurrent access between user and interrupt code.
259          *
260          * \note This is actually only needed for 8-bit processors.
261          *
262          * \sa fifo_push()
263          */
264         INLINE void fifo_push_locked(FIFOBuffer *fb, unsigned char c)
265         {
266                 ATOMIC(fifo_push(fb, c));
267         }
268
269         /* Probably not really needed, but hard to prove. */
270         INLINE unsigned char fifo_pop_locked(FIFOBuffer *fb)
271         {
272                 unsigned char c;
273                 ATOMIC(c = fifo_pop(fb));
274                 return c;
275         }
276
277         /*!
278          * Similar to fifo_flush(), but with stronger guarantees for
279          * concurrent access between user and interrupt code.
280          *
281          * \note This is actually only needed for 8-bit processors.
282          *
283          * \sa fifo_flush()
284          */
285         INLINE void fifo_flush_locked(FIFOBuffer *fb)
286         {
287                 ATOMIC(fifo_flush(fb));
288         }
289
290 #endif /* CPU_REG_BITS < BITS_PER_PTR */
291
292
293 /*!
294  * Thread safe version of fifo_isfull()
295  */
296 INLINE bool fifo_isfull_locked(const FIFOBuffer *_fb)
297 {
298         bool result;
299         ATOMIC(result = fifo_isfull(_fb));
300         return result;
301 }
302
303
304 /*!
305  * FIFO Initialization.
306  */
307 INLINE void fifo_init(FIFOBuffer *fb, unsigned char *buf, size_t size)
308 {
309         /* FIFO buffers have a known bug with 1-byte buffers. */
310         ASSERT(size > 1);
311
312         fb->head = fb->tail = fb->begin = buf;
313         fb->end = buf + size - 1;
314 }
315
316
317 #if 0
318
319 /*
320  * UNTESTED: if uncommented, to be moved in fifobuf.c
321  */
322 void fifo_pushblock(FIFOBuffer *fb, unsigned char *block, size_t len)
323 {
324         size_t freelen;
325
326         /* Se c'e' spazio da tail alla fine del buffer */
327         if (fb->tail >= fb->head)
328         {
329                 freelen = fb->end - fb->tail + 1;
330
331                 /* C'e' abbastanza spazio per scrivere tutto il blocco? */
332                 if (freelen < len)
333                 {
334                         /* Scrivi quello che entra fino alla fine del buffer */
335                         memcpy(fb->tail, block, freelen);
336                         block += freelen;
337                         len -= freelen;
338                         fb->tail = fb->begin;
339                 }
340                 else
341                 {
342                         /* Scrivi tutto il blocco */
343                         memcpy(fb->tail, block, len);
344                         fb->tail += len;
345                         return;
346                 }
347         }
348
349         for(;;)
350         {
351                 while (!(freelen = fb->head - fb->tail - 1))
352                         Delay(FIFO_POLLDELAY);
353
354                 /* C'e' abbastanza spazio per scrivere tutto il blocco? */
355                 if (freelen < len)
356                 {
357                         /* Scrivi quello che entra fino alla fine del buffer */
358                         memcpy(fb->tail, block, freelen);
359                         block += freelen;
360                         len -= freelen;
361                         fb->tail += freelen;
362                 }
363                 else
364                 {
365                         /* Scrivi tutto il blocco */
366                         memcpy(fb->tail, block, len);
367                         fb->tail += len;
368                         return;
369                 }
370         }
371 }
372 #endif
373
374 #endif /* MWARE_FIFO_H */
375