Skip to content

Commit 8285277

Browse files
committed
add fifo implementation note
- handle/fix double overflowed with write() - other minor clean upp
1 parent 7598967 commit 8285277

File tree

2 files changed

+113
-44
lines changed

2 files changed

+113
-44
lines changed

src/common/tusb_fifo.c

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_si
8181
// Limit index space to 2*depth - this allows for a fast "modulo" calculation
8282
// but limits the maximum depth to 2^16/2 = 2^15 and buffer overflows are detectable
8383
// only if overflow happens once (important for unsupervised DMA applications)
84-
f->max_pointer_idx = (uint16_t) (2*depth - 1);
85-
f->non_used_index_space = UINT16_MAX - f->max_pointer_idx;
84+
//f->max_pointer_idx = (uint16_t) (2*depth - 1);
85+
f->non_used_index_space = UINT16_MAX - (2*f->depth-1);
8686

8787
f->rd_idx = f->wr_idx = 0;
8888

@@ -320,15 +320,15 @@ static void _ff_pull_n(tu_fifo_t* f, void* app_buf, uint16_t n, uint16_t rel, tu
320320
// Index (free-running and real buffer pointer)
321321
//--------------------------------------------------------------------+
322322

323-
// Advance an absolute pointer
323+
// Advance an absolute index
324324
// "absolute" index is only in the range of [0..2*depth)
325-
static uint16_t advance_pointer(tu_fifo_t* f, uint16_t p, uint16_t offset)
325+
static uint16_t advance_pointer(tu_fifo_t* f, uint16_t idx, uint16_t offset)
326326
{
327327
// We limit the index space of p such that a correct wrap around happens
328328
// Check for a wrap around or if we are in unused index space - This has to be checked first!!
329329
// We are exploiting the wrap around to the correct index
330-
uint16_t next_p = (uint16_t) (p + offset);
331-
if ( (p > next_p) || (next_p > f->max_pointer_idx) )
330+
uint16_t next_p = (uint16_t) (idx + offset);
331+
if ( (idx > next_p) || (next_p >= 2*f->depth) )
332332
{
333333
next_p = (uint16_t) (next_p + f->non_used_index_space);
334334
}
@@ -343,7 +343,7 @@ static uint16_t backward_pointer(tu_fifo_t* f, uint16_t p, uint16_t offset)
343343
// Check for a wrap around or if we are in unused index space - This has to be checked first!!
344344
// We are exploiting the wrap around to the correct index
345345
uint16_t new_p = (uint16_t) (p - offset);
346-
if ( (p < new_p) || (new_p > f->max_pointer_idx) )
346+
if ( (p < new_p) || (new_p >= 2*f->depth) )
347347
{
348348
new_p = (uint16_t) (new_p - f->non_used_index_space);
349349
}
@@ -374,15 +374,15 @@ static inline uint16_t _tu_fifo_count(tu_fifo_t* f, uint16_t wr_idx, uint16_t rd
374374
}
375375

376376
// Works on local copies of w and r
377-
static inline bool _tu_fifo_empty(uint16_t wAbs, uint16_t rAbs)
377+
static inline bool _tu_fifo_empty(uint16_t wr_idx, uint16_t rd_idx)
378378
{
379-
return wAbs == rAbs;
379+
return wr_idx == rd_idx;
380380
}
381381

382382
// Works on local copies of w and r
383383
static inline bool _tu_fifo_full(tu_fifo_t* f, uint16_t wAbs, uint16_t rAbs)
384384
{
385-
return (_tu_fifo_count(f, wAbs, rAbs) == f->depth);
385+
return _tu_fifo_count(f, wAbs, rAbs) == f->depth;
386386
}
387387

388388
// Works on local copies of w and r
@@ -391,9 +391,9 @@ static inline bool _tu_fifo_full(tu_fifo_t* f, uint16_t wAbs, uint16_t rAbs)
391391
// write more than 2*depth-1 items in one rush without updating write pointer. Otherwise
392392
// write pointer wraps and you pointer states are messed up. This can only happen if you
393393
// use DMAs, write functions do not allow such an error.
394-
static inline bool _tu_fifo_overflowed(tu_fifo_t* f, uint16_t wAbs, uint16_t rAbs)
394+
static inline bool _tu_fifo_overflowed(tu_fifo_t* f, uint16_t wr_idx, uint16_t rd_idx)
395395
{
396-
return (_tu_fifo_count(f, wAbs, rAbs) > f->depth);
396+
return (_tu_fifo_count(f, wr_idx, rd_idx) > f->depth);
397397
}
398398

399399
// Works on local copies of w
@@ -466,57 +466,71 @@ static uint16_t _tu_fifo_write_n(tu_fifo_t* f, const void * data, uint16_t n, tu
466466
{
467467
if ( n == 0 ) return 0;
468468

469-
TU_LOG(TU_FIFO_DBG, "rd = %u, wr = %02u, n = %u: ", f->rd_idx, f->wr_idx, n);
470-
471469
_ff_lock(f->mutex_wr);
472470

473471
uint16_t wr_idx = f->wr_idx;
474472
uint16_t rd_idx = f->rd_idx;
475473

476474
uint8_t const* buf8 = (uint8_t const*) data;
475+
uint16_t overflowable_count = _tu_fifo_count(f, wr_idx, rd_idx);
477476
uint16_t const remain = _tu_fifo_remaining(f, wr_idx, rd_idx);
478477

479-
if ( n > remain)
478+
TU_LOG(TU_FIFO_DBG, "rd = %3u, wr = %3u, count = %3u, remain = %3u, n = %3u: ", rd_idx, wr_idx, _tu_fifo_count(f, wr_idx, rd_idx), remain, n);
479+
480+
if ( !f->overwritable )
480481
{
481-
if ( !f->overwritable )
482-
{
483-
// limit up to full
484-
n = remain;
485-
}
486-
else
482+
// limit up to full
483+
n = tu_min16(n, remain);
484+
}
485+
else
486+
{
487+
// In over-writable mode, fifo_write() is allowed even when fifo is full. In such case,
488+
// oldest data in fifo i.e at read pointer data will be overwritten
489+
// Note: we can modify read buffer contents but we must not modify the read index itself within a write function!
490+
// Since it would end up in a race condition with read functions!
491+
if ( n >= f->depth )
487492
{
488-
// oldest data in fifo i.e read pointer data will be overwritten
489-
// Note: we modify read data but we do not want to modify the read pointer within a write function!
490-
// since it would end up in a race condition with read functions!
491-
// Note2: race condition could still occur if tu_fifo_read() is called while we modify its buffer (corrupted data)
492-
493-
if ( n >= f->depth )
493+
// Only copy last part
494+
if ( copy_mode == TU_FIFO_COPY_INC )
494495
{
495-
// Only copy last part
496-
buf8 = buf8 + (n - f->depth) * f->item_size;
497-
n = f->depth;
498-
499-
// We start writing at the read pointer's position since we fill the complete
500-
// buffer and we do not want to modify the read pointer within a write function!
501-
// This would end up in a race condition with read functions!
502-
wr_idx = rd_idx;
496+
buf8 += (n - f->depth) * f->item_size;
503497
}else
504498
{
505-
// TODO shift out oldest data from read pointer !!!
499+
// TODO should read from hw fifo to discard data, however reading an odd number could
500+
// accidentally discard data.
506501
}
502+
503+
n = f->depth;
504+
505+
// We start writing at the read pointer's position since we fill the whole buffer
506+
wr_idx = rd_idx;
507+
}else if (overflowable_count + n >= 2*f->depth)
508+
{
509+
// Double overflowed
510+
// re-position write index to have a full fifo after pushed
511+
wr_idx = advance_pointer(f, rd_idx, f->depth - n);
512+
513+
// TODO we should also shift out n bytes from read index since we avoid changing rd index !!
514+
// However memmove() is expensive due to actual copying + wrapping consideration.
515+
// Also race condition could happen anyway if read() is invoke while moving result in corrupted memory
516+
// currently deliberately not implemented --> result in incorrect data read back
517+
}else
518+
{
519+
// normal + single overflowed: just increase write index
520+
// we will correct (re-position) read index later on in fifo_read() function
507521
}
508522
}
509523

510524
if (n)
511525
{
512526
uint16_t wr_ptr = idx2ptr(wr_idx, f->depth);
513527

514-
TU_LOG(TU_FIFO_DBG, "actual_n = %u, wr_rel = %u", n, wr_ptr);
528+
TU_LOG(TU_FIFO_DBG, "actual_n = %u, wr_ptr = %u", n, wr_ptr);
515529

516530
// Write data
517531
_ff_push_n(f, buf8, n, wr_ptr, copy_mode);
518532

519-
// Advance pointer
533+
// Advance index
520534
f->wr_idx = advance_pointer(f, wr_idx, n);
521535

522536
TU_LOG(TU_FIFO_DBG, "\tnew_wr = %u\n", f->wr_idx);
@@ -850,8 +864,8 @@ bool tu_fifo_clear(tu_fifo_t *f)
850864
_ff_lock(f->mutex_rd);
851865

852866
f->rd_idx = f->wr_idx = 0;
853-
f->max_pointer_idx = (uint16_t) (2*f->depth-1);
854-
f->non_used_index_space = UINT16_MAX - f->max_pointer_idx;
867+
//f->max_pointer_idx = (uint16_t) (2*f->depth-1);
868+
f->non_used_index_space = UINT16_MAX - (2*f->depth-1);
855869

856870
_ff_unlock(f->mutex_wr);
857871
_ff_unlock(f->mutex_rd);

src/common/tusb_fifo.h

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,74 @@ extern "C" {
5252
#define tu_fifo_mutex_t osal_mutex_t
5353
#endif
5454

55+
/* Write/Read index is always in the range of:
56+
* 0 .. 2*depth-1
57+
* The extra window allow us to determine the fifo state of empty or full with only 2 indexes
58+
* Following are examples with depth = 3
59+
*
60+
* - empty: W = R
61+
* |
62+
* -------------------------
63+
* | 0 | RW| 2 | 3 | 4 | 5 |
64+
*
65+
* - full 1: W > R
66+
* |
67+
* -------------------------
68+
* | 0 | R | 2 | 3 | W | 5 |
69+
*
70+
* - full 2: W < R
71+
* |
72+
* -------------------------
73+
* | 0 | 1 | W | 3 | 4 | R |
74+
*
75+
* - Number of items in the fifo can be determined in either cases:
76+
* - case W > R: Count = W - R
77+
* - case W < R: Count = 2*depth - (R - W)
78+
*
79+
* In non-overwritable mode, computed Count (in above 2 cases) is at most equal to depth.
80+
* However, in over-writable mode, write index can be repeatedly increased and count can be
81+
* temporarily larger than depth (overflowed condition) e.g
82+
*
83+
* - Overflowed 1: write(3), write(1)
84+
* In this case we will adjust Read index when read()/peek() is called so that count = depth.
85+
* |
86+
* -------------------------
87+
* | R | 1 | 2 | 3 | W | 5 |
88+
*
89+
* - Double Overflowed: write(3), write(1), write(2)
90+
* Continue to write after overflowed to 2nd overflowed.
91+
* We must prevent 2nd overflowed since it will cause incorrect computed of count, in above example
92+
* if not handled the fifo will be empty instead of continue-to-be full. Since we must not modify
93+
* read index in write() function, which cause race condition. We will re-position write index so that
94+
* after data is written it is a full fifo i.e W = depth - R
95+
*
96+
* re-position W = 1 before write(2)
97+
* Note: we should also move data from mem[4] to read index as well, but deliberately skipped here
98+
* since it is an expensive operation !!!
99+
* |
100+
* -------------------------
101+
* | R | W | 2 | 3 | 4 | 5 |
102+
*
103+
* perform write(2), result is still a full fifo.
104+
*
105+
* |
106+
* -------------------------
107+
* | R | 1 | 2 | W | 4 | 5 |
108+
109+
*/
55110
typedef struct
56111
{
57112
uint8_t* buffer ; ///< buffer pointer
58113
uint16_t depth ; ///< max items
59114
uint16_t item_size ; ///< size of each item
115+
60116
bool overwritable ;
61117

62118
uint16_t non_used_index_space ; ///< required for non-power-of-two buffer length
63-
uint16_t max_pointer_idx ; ///< maximum absolute pointer index
119+
//uint16_t max_pointer_idx ; ///< maximum absolute pointer index
64120

65-
volatile uint16_t wr_idx ; ///< write pointer
66-
volatile uint16_t rd_idx ; ///< read pointer
121+
volatile uint16_t wr_idx ; ///< write index
122+
volatile uint16_t rd_idx ; ///< read index
67123

68124
#if CFG_FIFO_MUTEX
69125
tu_fifo_mutex_t mutex_wr;
@@ -87,7 +143,6 @@ typedef struct
87143
.item_size = sizeof(_type), \
88144
.overwritable = _overwritable, \
89145
.non_used_index_space = UINT16_MAX - (2*(_depth)-1), \
90-
.max_pointer_idx = 2*(_depth)-1, \
91146
}
92147

93148
#define TU_FIFO_DEF(_name, _depth, _type, _overwritable) \

0 commit comments

Comments
 (0)