summaryrefslogtreecommitdiff
path: root/src/common/tusb_fifo.c
blob: e5855fcbc01b0eea4a6c03c6a202e69490e99503 (plain)
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
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
/*
 * SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org)
 * SPDX-FileCopyrightText: Copyright (c) 2020 Reinhard Panhuber
 * SPDX-License-Identifier: MIT
 *
 * This file is part of the TinyUSB stack.
 */

#include "osal/osal.h"
#include "tusb_fifo.h"

#define TU_FIFO_DBG 0


#if OSAL_MUTEX_REQUIRED

TU_ATTR_ALWAYS_INLINE static inline void ff_lock(osal_mutex_t mutex) {
  if (mutex != NULL) {
    osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER);
  }
}

TU_ATTR_ALWAYS_INLINE static inline void ff_unlock(osal_mutex_t mutex) {
  if (mutex != NULL) {
    osal_mutex_unlock(mutex);
  }
}

#else
  #define ff_lock(_mutex)
  #define ff_unlock(_mutex)

#endif

//--------------------------------------------------------------------+
// Setup API
//--------------------------------------------------------------------+
bool tu_fifo_config(tu_fifo_t *f, void *buffer, uint16_t depth, bool overwritable) {
  // Limit index space to 2*depth - this allows for a fast "modulo" calculation
  // but limits the maximum depth to 2^16/2 = 2^15 and buffer overflows are detectable
  // only if overflow happens once (important for unsupervised DMA applications)
  if (depth > 0x8000) {
    return false;
  }

  ff_lock(f->mutex_wr);
  ff_lock(f->mutex_rd);

  f->buffer       = (uint8_t *)buffer;
  f->depth        = depth;
  f->overwritable = overwritable;
  f->rd_idx       = 0u;
  f->wr_idx       = 0u;

  ff_unlock(f->mutex_wr);
  ff_unlock(f->mutex_rd);

  return true;
}

// clear fifo by resetting read and write indices
void tu_fifo_clear(tu_fifo_t *f) {
  ff_lock(f->mutex_wr);
  ff_lock(f->mutex_rd);

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

  ff_unlock(f->mutex_wr);
  ff_unlock(f->mutex_rd);
}

// Change the fifo overwritable mode
void tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) {
  if (f->overwritable == overwritable) {
    return;
  }

  ff_lock(f->mutex_wr);
  ff_lock(f->mutex_rd);

  f->overwritable = overwritable;

  ff_unlock(f->mutex_wr);
  ff_unlock(f->mutex_rd);
}

//--------------------------------------------------------------------+
// Hardware FIFO API
// Support different data access width and address increment scheme
// Can support multiple i.e both 16 and 32-bit data access if needed
//--------------------------------------------------------------------+
#if CFG_TUSB_FIFO_HWFIFO_API
  #if CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE > 0
    #define HWFIFO_ADDR_NEXT_N(_hwfifo, _const, _n) _hwfifo = (_const volatile void *)((uintptr_t)(_hwfifo) + _n)
  #else
    #define HWFIFO_ADDR_NEXT_N(_hwfifo, _const, _n)
  #endif

  #define HWFIFO_ADDR_NEXT(_hwfifo, _const) HWFIFO_ADDR_NEXT_N(_hwfifo, _const, CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE)

  // the fixed ratio works since in the only case of dynamic/multiple data_stride (rusb2): addr_stride is 0
  #define HWFIFO_ADDR_DATA_RATIO (CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE / CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE)

//------------- Write -------------//
  #ifndef CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE
TU_ATTR_ALWAYS_INLINE static inline void stride_write(volatile void *hwfifo, const void *src, uint8_t data_stride) {
  (void)data_stride; // possible unused
    #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 4
      #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 4
  if (data_stride == 4)
      #endif
  {
    *((volatile uint32_t *)hwfifo) = tu_unaligned_read32(src);
  }
    #endif

    #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 2
      #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 2
  if (data_stride == 2)
      #endif
  {
    *((volatile uint16_t *)hwfifo) = tu_unaligned_read16(src);
  }
    #endif

    #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1
  *((volatile uint8_t *)hwfifo) = *(const uint8_t *)src;
    #endif
}

// Copy from fifo to fixed address buffer (usually a tx register) with TU_FIFO_FIXED_ADDR_RW32 mode
void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) {
  // Write full available 16/32 bit words to dest
  const uint8_t data_stride = (access_mode != NULL) ? access_mode->data_stride : CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE;
  while (len >= data_stride) {
    stride_write(hwfifo, src, data_stride);
    src += data_stride;
    len -= data_stride;
    HWFIFO_ADDR_NEXT(hwfifo, );
  }

    #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE > 1
      #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS
  // 16-bit access is allowed for odd bytes
  if (len >= 2) {
    *((volatile uint16_t *)hwfifo) = tu_unaligned_read16(src);
    src += 2;
    len -= 2;
    HWFIFO_ADDR_NEXT_N(hwfifo, , 2);
  }
      #endif

      #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS
  // 8-bit access is allowed for odd bytes
  while (len > 0) {
    *((volatile uint8_t *)hwfifo) = *src++;
    len--;
    HWFIFO_ADDR_NEXT_N(hwfifo, , 1);
  }
      #else

  // Write odd bytes i.e 1 byte for 16 bit or 1-3 bytes for 32 bit
  if (len > 0) {
    uint32_t tmp = 0u;
    memcpy(&tmp, src, len);
    stride_write(hwfifo, &tmp, data_stride);
    HWFIFO_ADDR_NEXT(hwfifo, );
  }
      #endif
    #endif
}
  #endif

//------------- Read -------------//
  #ifndef CFG_TUSB_FIFO_HWFIFO_CUSTOM_READ
TU_ATTR_ALWAYS_INLINE static inline void stride_read(const volatile void *hwfifo, void *dest, uint8_t data_stride) {
  (void)data_stride; // possible unused

    #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 4
      #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 4
  if (data_stride == 4)
      #endif
  {
    tu_unaligned_write32(dest, *((const volatile uint32_t *)hwfifo));
  }
    #endif

    #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 2
      #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 2
  if (data_stride == 2)
      #endif
  {
    tu_unaligned_write16(dest, *((const volatile uint16_t *)hwfifo));
  }
    #endif

    #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1
  *(uint8_t *)dest = *((const volatile uint8_t *)hwfifo);
    #endif
}

void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, const tu_hwfifo_access_t *access_mode) {
  // Reading full available 16/32-bit hwfifo and write to fifo
  const uint8_t data_stride = (access_mode != NULL) ? access_mode->data_stride : CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE;
  while (len >= data_stride) {
    stride_read(hwfifo, dest, data_stride);
    dest += data_stride;
    len -= data_stride;
    HWFIFO_ADDR_NEXT(hwfifo, const);
  }

    #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE > 1
      #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS
  // 16-bit access is allowed for odd bytes
  if (len >= 2) {
    tu_unaligned_write16(dest, *((const volatile uint16_t *)hwfifo));
    dest += 2;
    len -= 2;
    HWFIFO_ADDR_NEXT_N(hwfifo, const, 2);
  }
      #endif

      #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS
  // 8-bit access is allowed for odd bytes
  while (len > 0) {
    *dest++ = *((const volatile uint8_t *)hwfifo);
    len--;
    HWFIFO_ADDR_NEXT_N(hwfifo, const, 1);
  }
      #else
  // Read odd bytes i.e 1 byte for 16 bit or 1-3 bytes for 32 bit
  if (len > 0) {
    uint32_t tmp;
    stride_read(hwfifo, &tmp, data_stride);
    memcpy(dest, &tmp, len);
    HWFIFO_ADDR_NEXT(hwfifo, const);
  }
      #endif
    #endif
}
  #endif

// push to sw fifo from hwfifo
static void hwff_push_n(const tu_fifo_t *f, const void *app_buf, uint16_t n, uint16_t wr_ptr,
                        const tu_hwfifo_access_t *access_mode) {
  uint16_t lin_bytes  = f->depth - wr_ptr;
  uint16_t wrap_bytes = n - lin_bytes;
  uint8_t *ff_buf     = f->buffer + wr_ptr;

  const volatile void *hwfifo = (const volatile void *)app_buf;
  if (n <= lin_bytes) {
    // Linear only case
    tu_hwfifo_read(hwfifo, ff_buf, n, access_mode);
  } else {
    // Wrap around case
  #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1
    tu_hwfifo_read(hwfifo, ff_buf, lin_bytes, access_mode);     // linear part
    HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_bytes);
    tu_hwfifo_read(hwfifo, f->buffer, wrap_bytes, access_mode); // wrapped part
  #else
    // Write full words to the linear part of the buffer
    const uint8_t  data_stride = access_mode->data_stride;
    const uint32_t odd_mask    = data_stride - 1;
    uint16_t       lin_even    = (uint16_t)(lin_bytes & ~odd_mask);
    tu_hwfifo_read(hwfifo, ff_buf, lin_even, access_mode);
    HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_even * HWFIFO_ADDR_DATA_RATIO);
    ff_buf += lin_even;

    // There could be an odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary
    // combine it with the wrapped part to form a full word for data stride
    const uint8_t lin_odd = (uint8_t)(lin_bytes & odd_mask);
    if (lin_odd > 0) {
      const uint8_t wrap_odd = (uint8_t)tu_min16(wrap_bytes, data_stride - lin_odd);
      uint8_t       buf_temp[4];
      tu_hwfifo_read(hwfifo, buf_temp, lin_odd + wrap_odd, access_mode);
      HWFIFO_ADDR_NEXT(hwfifo, const);

      for (uint8_t i = 0; i < lin_odd; ++i) {
        ff_buf[i] = buf_temp[i];
      }
      for (uint8_t i = 0; i < wrap_odd; ++i) {
        f->buffer[i] = buf_temp[lin_odd + i];
      }

      wrap_bytes -= wrap_odd;
      ff_buf = f->buffer + wrap_odd; // wrap around
    } else {
      ff_buf = f->buffer;            // wrap around to beginning
    }

    // Write data wrapped part
    if (wrap_bytes > 0) {
      tu_hwfifo_read(hwfifo, ff_buf, wrap_bytes, access_mode);
    }
  #endif
  }
}

// pull from sw fifo to hwfifo
static void hwff_pull_n(const tu_fifo_t *f, void *app_buf, uint16_t n, uint16_t rd_ptr,
                        const tu_hwfifo_access_t *access_mode) {
  uint16_t       lin_bytes  = f->depth - rd_ptr;
  uint16_t       wrap_bytes = n - lin_bytes; // only used if wrapped
  const uint8_t *ff_buf     = f->buffer + rd_ptr;

  volatile void *hwfifo = (volatile void *)app_buf;

  if (n <= lin_bytes) {
    // Linear only case
    tu_hwfifo_write(hwfifo, ff_buf, n, access_mode);
  } else {
    // Wrap around case
  #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1
    tu_hwfifo_write(hwfifo, ff_buf, lin_bytes, access_mode);     // linear part
    HWFIFO_ADDR_NEXT_N(hwfifo, , lin_bytes);
    tu_hwfifo_write(hwfifo, f->buffer, wrap_bytes, access_mode); // wrapped part
  #else
    // Read full words from linear part
    const uint8_t  data_stride = access_mode->data_stride;
    const uint32_t odd_mask    = data_stride - 1;
    uint16_t       lin_even    = (uint16_t)(lin_bytes & ~odd_mask);
    tu_hwfifo_write(hwfifo, ff_buf, lin_even, access_mode);
    HWFIFO_ADDR_NEXT_N(hwfifo, , lin_even * HWFIFO_ADDR_DATA_RATIO);
    ff_buf += lin_even;

    // There could be odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary
    const uint8_t lin_odd = (uint8_t)(lin_bytes & odd_mask);
    if (lin_odd > 0) {
      const uint8_t wrap_odd = (uint8_t)tu_min16(wrap_bytes, data_stride - lin_odd);

      uint8_t buf_temp[4];
      for (uint8_t i = 0; i < lin_odd; ++i) {
        buf_temp[i] = ff_buf[i];
      }
      for (uint8_t i = 0; i < wrap_odd; ++i) {
        buf_temp[lin_odd + i] = f->buffer[i];
      }

      tu_hwfifo_write(hwfifo, buf_temp, lin_odd + wrap_odd, access_mode);
      HWFIFO_ADDR_NEXT(hwfifo, );

      wrap_bytes -= wrap_odd;
      ff_buf = f->buffer + wrap_odd; // wrap around
    } else {
      ff_buf = f->buffer;            // wrap around to beginning
    }

    // Read data wrapped part
    if (wrap_bytes > 0) {
      tu_hwfifo_write(hwfifo, ff_buf, wrap_bytes, access_mode);
    }
  #endif
  }
}
#endif

//--------------------------------------------------------------------+
// Pull & Push
// copy data to/from fifo without updating read/write pointers
//--------------------------------------------------------------------+
// send n items to fifo WITHOUT updating write pointer
static void ff_push_n(const tu_fifo_t *f, const void *app_buf, uint16_t n, uint16_t wr_ptr) {
  uint16_t lin_bytes  = f->depth - wr_ptr;
  uint16_t wrap_bytes = n - lin_bytes;
  uint8_t *ff_buf     = f->buffer + wr_ptr;

  if (n <= lin_bytes) {
    // Linear only case
    memcpy(ff_buf, app_buf, n);
  } else {
    // Wrap around case
    memcpy(ff_buf, app_buf, lin_bytes);                                    // linear part
    memcpy(f->buffer, ((const uint8_t *)app_buf) + lin_bytes, wrap_bytes); // wrapped part
  }
}

// get n items from fifo WITHOUT updating read pointer
static void ff_pull_n(const tu_fifo_t *f, void *app_buf, uint16_t n, uint16_t rd_ptr) {
  uint16_t       lin_bytes  = f->depth - rd_ptr;
  uint16_t       wrap_bytes = n - lin_bytes; // only used if wrapped
  const uint8_t *ff_buf     = f->buffer + rd_ptr;

  // single byte access
  if (n <= lin_bytes) {
    // Linear only
    memcpy(app_buf, ff_buf, n);
  } else {
    // Wrap around
    memcpy(app_buf, ff_buf, lin_bytes);                            // linear part
    memcpy((uint8_t *)app_buf + lin_bytes, f->buffer, wrap_bytes); // wrapped part
  }
}

//--------------------------------------------------------------------+
// Index Helper
//--------------------------------------------------------------------+

// Advance an absolute index
// "absolute" index is only in the range of [0..2*depth)
static uint16_t advance_index(uint16_t depth, uint16_t idx, uint16_t offset) {
  // We limit the index space of p such that a correct wrap around happens
  // Check for a wrap around or if we are in unused index space - This has to be checked first!!
  // We are exploiting the wrap around to the correct index
  uint16_t new_idx = (uint16_t)(idx + offset);
  if ((idx > new_idx) || (new_idx >= 2 * depth)) {
    const uint16_t non_used_index_space = (uint16_t)(UINT16_MAX - (2 * depth - 1));
    new_idx                             = (uint16_t)(new_idx + non_used_index_space);
  }

  return new_idx;
}

// index to pointer (0..depth-1), simply a modulo with minus.
TU_ATTR_ALWAYS_INLINE static inline uint16_t idx2ptr(uint16_t depth, uint16_t idx) {
  // Only run at most 3 times since index is limit in the range of [0..2*depth)
  while (idx >= depth) {
    idx -= depth;
  }
  return idx;
}

// Works on local copies of w
// When an overwritable fifo is overflowed, rd_idx will be re-index so that it forms a full fifo
static uint16_t correct_read_index(tu_fifo_t *f, uint16_t wr_idx) {
  uint16_t rd_idx;
  if (wr_idx >= f->depth) {
    rd_idx = wr_idx - f->depth;
  } else {
    rd_idx = wr_idx + f->depth;
  }

  f->rd_idx = rd_idx;
  return rd_idx;
}

//--------------------------------------------------------------------+
// n-API
//--------------------------------------------------------------------+

// Works on local copies of w and r
// Must be protected by read mutex since in case of an overflow read pointer gets modified
uint16_t tu_fifo_peek_n_access_mode(tu_fifo_t *f, void *p_buffer, uint16_t n, uint16_t wr_idx, uint16_t rd_idx,
                                    const tu_hwfifo_access_t *access_mode) {
  uint16_t count = tu_ff_overflow_count(f->depth, wr_idx, rd_idx);
  if (count == 0) {
    return 0; // nothing to peek
  }

  // Check overflow and correct if required
  if (count > f->depth) {
    rd_idx = correct_read_index(f, wr_idx);
    count  = f->depth;
  }

  if (count < n) {
    n = count; // limit to available count
  }

  const uint16_t rd_ptr = idx2ptr(f->depth, rd_idx);

#if CFG_TUSB_FIFO_HWFIFO_API
  if (access_mode != NULL) {
    hwff_pull_n(f, p_buffer, n, rd_ptr, access_mode);
  } else
#endif
  {
    (void)access_mode;
    ff_pull_n(f, p_buffer, n, rd_ptr);
  }

  return n;
}

// Read n items without removing it from the FIFO, correct read pointer if overflowed
uint16_t tu_fifo_peek_n(tu_fifo_t *f, void *p_buffer, uint16_t n) {
  ff_lock(f->mutex_rd);
  const uint16_t wr_idx = f->wr_idx;
  const uint16_t rd_idx = f->rd_idx;
  const uint16_t ret = tu_fifo_peek_n_access_mode(f, p_buffer, n, wr_idx, rd_idx, NULL);
  ff_unlock(f->mutex_rd);
  return ret;
}

// Read n items from fifo with access mode
uint16_t tu_fifo_read_n_access_mode(tu_fifo_t *f, void *buffer, uint16_t n, const tu_hwfifo_access_t *access_mode) {
  ff_lock(f->mutex_rd);

  // Peek the data: f->rd_idx might get modified in case of an overflow so we can not use a local variable
  const uint16_t wr_idx = f->wr_idx;
  n         = tu_fifo_peek_n_access_mode(f, buffer, n, wr_idx, f->rd_idx, access_mode);
  f->rd_idx = advance_index(f->depth, f->rd_idx, n);

  ff_unlock(f->mutex_rd);
  return n;
}

// Write n items to fifo with access mode
uint16_t tu_fifo_write_n_access_mode(tu_fifo_t *f, const void *data, uint16_t n,
                                     const tu_hwfifo_access_t *access_mode) {
  if (n == 0) {
    return 0;
  }

  ff_lock(f->mutex_wr);

  uint16_t wr_idx = f->wr_idx;
  uint16_t rd_idx = f->rd_idx;

  const uint8_t *buf8 = (const uint8_t *)data;

  TU_LOG(TU_FIFO_DBG, "rd = %3u, wr = %3u, count = %3u, remain = %3u, n = %3u:  ", rd_idx, wr_idx,
         tu_ff_overflow_count(f->depth, wr_idx, rd_idx), tu_ff_remaining_local(f->depth, wr_idx, rd_idx), n);

  if (!f->overwritable) {
    // limit up to full
    const uint16_t remain = tu_ff_remaining_local(f->depth, wr_idx, rd_idx);
    n                     = tu_min16(n, remain);
  } else {
    // In over-writable mode, fifo_write() is allowed even when fifo is full. In such case,
    // oldest data in fifo i.e. at read pointer data will be overwritten
    // Note: we can modify read buffer contents however we must not modify the read index itself within a write
    // function! Since it would end up in a race condition with read functions!
    if (n >= f->depth) {
      // Only copy last part
      if (access_mode == NULL) {
        buf8 += (n - f->depth);
      } else {
        // TODO should read from hw fifo to discard data, however reading an odd number could
        // accidentally discard data.
      }

      n = f->depth;

      // We start writing at the read pointer's position since we fill the whole buffer
      wr_idx = rd_idx;
    } else {
      const uint16_t overflowable_count = tu_ff_overflow_count(f->depth, wr_idx, rd_idx);
      if (overflowable_count + n >= 2 * f->depth) {
        // Double overflowed
        // Index is bigger than the allowed range [0,2*depth)
        // re-position write index to have a full fifo after pushed
        wr_idx = advance_index(f->depth, rd_idx, f->depth - n);

        // TODO we should also shift out n bytes from read index since we avoid changing rd index !!
        // However memmove() is expensive due to actual copying + wrapping consideration.
        // Also race condition could happen anyway if read() is invoke while moving result in corrupted memory
        // currently deliberately not implemented --> result in incorrect data read back
      } else {
        // normal + single overflowed:
        // Index is in the range of [0,2*depth) and thus detect and recoverable. Recovering is handled in read()
        // Therefore we just increase write index
        // we will correct (re-position) read index later on in fifo_read() function
      }
    }
  }

  if (n) {
    const uint16_t wr_ptr = idx2ptr(f->depth, wr_idx);
    TU_LOG(TU_FIFO_DBG, "actual_n = %u, wr_ptr = %u", n, wr_ptr);

#if CFG_TUSB_FIFO_HWFIFO_API
    if (access_mode != NULL) {
      hwff_push_n(f, buf8, n, wr_ptr, access_mode);
    } else
#endif
    {
      ff_push_n(f, buf8, n, wr_ptr);
    }
    f->wr_idx = advance_index(f->depth, wr_idx, n);

    TU_LOG(TU_FIFO_DBG, "\tnew_wr = %u\r\n", f->wr_idx);
  }

  ff_unlock(f->mutex_wr);

  return n;
}

uint16_t tu_fifo_discard_n(tu_fifo_t *f, uint16_t n) {
  const uint16_t count = tu_min16(n, tu_fifo_count(f)); // limit to available count
  ff_lock(f->mutex_rd);
  f->rd_idx = advance_index(f->depth, f->rd_idx, count);
  ff_unlock(f->mutex_rd);

  return count;
}

//--------------------------------------------------------------------+
// One API
//--------------------------------------------------------------------+

// peek() using local write/read index, correct read index if overflowed
// Be careful, caller must not lock mutex, since this Will also try to lock mutex
static bool ff_peek_local(tu_fifo_t *f, void *buf, uint16_t wr_idx, uint16_t rd_idx) {
  const uint16_t ovf_count = tu_ff_overflow_count(f->depth, wr_idx, rd_idx);
  if (ovf_count == 0) {
    return false; // nothing to peek
  }

  // Correct read index if overflow
  if (ovf_count > f->depth) {
    ff_lock(f->mutex_rd);
    rd_idx = correct_read_index(f, wr_idx);
    ff_unlock(f->mutex_rd);
  }

  const uint16_t rd_ptr = idx2ptr(f->depth, rd_idx);
  memcpy(buf, f->buffer + rd_ptr, 1);

  return true;
}

// Read one element out of the buffer, correct read index if overflowed
bool tu_fifo_read(tu_fifo_t *f, void *buffer) {
  // Peek the data
  // f->rd_idx might get modified in case of an overflow so we can not use a local variable
  const uint16_t wr_idx = f->wr_idx;
  const bool ret = ff_peek_local(f, buffer, wr_idx, f->rd_idx);
  if (ret) {
    ff_lock(f->mutex_rd);
    f->rd_idx = advance_index(f->depth, f->rd_idx, 1);
    ff_unlock(f->mutex_rd);
  }

  return ret;
}

// Read one item without removing it from the FIFO, correct read index if overflowed
bool tu_fifo_peek(tu_fifo_t *f, void *p_buffer) {
  const uint16_t wr_idx = f->wr_idx;
  const uint16_t rd_idx = f->rd_idx;
  return ff_peek_local(f, p_buffer, wr_idx, rd_idx);
}

// Write one element into the buffer
bool tu_fifo_write(tu_fifo_t *f, const void *data) {
  bool ret;
  ff_lock(f->mutex_wr);

  const uint16_t wr_idx = f->wr_idx;

  if (tu_fifo_full(f) && !f->overwritable) {
    ret = false;
  } else {
    const uint16_t wr_ptr = idx2ptr(f->depth, wr_idx);
    memcpy(f->buffer + wr_ptr, data, 1);
    f->wr_idx = advance_index(f->depth, wr_idx, 1);
    ret       = true;
  }

  ff_unlock(f->mutex_wr);

  return ret;
}

//--------------------------------------------------------------------+
// Index API
//--------------------------------------------------------------------+

/******************************************************************************/
/*!
    @brief Advance write pointer - intended to be used in combination with DMA.
    It is possible to fill the FIFO by use of a DMA in circular mode. Within
    DMA ISRs you may update the write pointer to be able to read from the FIFO.
    As long as the DMA is the only process writing into the FIFO this is safe
    to use.

    USE WITH CARE - WE DO NOT CONDUCT SAFETY CHECKS HERE!

    @param[in]  f
                Pointer to the FIFO buffer to manipulate
    @param[in]  n
                Number of items the write pointer moves forward
 */
/******************************************************************************/
void tu_fifo_advance_write_pointer(tu_fifo_t *f, uint16_t n) {
  f->wr_idx = advance_index(f->depth, f->wr_idx, n);
}

// Correct the read index in case tu_fifo_overflow() returned true!
void tu_fifo_correct_read_pointer(tu_fifo_t *f) {
  ff_lock(f->mutex_rd);
  correct_read_index(f, f->wr_idx);
  ff_unlock(f->mutex_rd);
}

/******************************************************************************/
/*!
    @brief Advance read pointer - intended to be used in combination with DMA.
    It is possible to read from the FIFO by use of a DMA in linear mode. Within
    DMA ISRs you may update the read pointer to be able to again write into the
    FIFO. As long as the DMA is the only process reading from the FIFO this is
    safe to use.

    USE WITH CARE - WE DO NOT CONDUCT SAFETY CHECKS HERE!

    @param[in]  f
                Pointer to the FIFO buffer to manipulate
    @param[in]  n
                Number of items the read pointer moves forward
 */
/******************************************************************************/
void tu_fifo_advance_read_pointer(tu_fifo_t *f, uint16_t n) {
  f->rd_idx = advance_index(f->depth, f->rd_idx, n);
}

/******************************************************************************/
/*!
   @brief Get read info

   Returns the length and pointer from which bytes can be read in a linear manner.
   This is of major interest for DMA transmissions. If returned length is zero the
   corresponding pointer is invalid.
   The read pointer does NOT get advanced, use tu_fifo_advance_read_pointer() to
   do so!
   @param[in]       f
                    Pointer to FIFO
   @param[out]      *info
                    Pointer to struct which holds the desired infos
 */
/******************************************************************************/
void tu_fifo_get_read_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info) {
  // Operate on temporary values in case they change in between
  uint16_t wr_idx = f->wr_idx;
  uint16_t rd_idx = f->rd_idx;

  uint16_t cnt = tu_ff_overflow_count(f->depth, wr_idx, rd_idx);

  // Check overflow and correct if required - may happen in case a DMA wrote too fast
  if (cnt > f->depth) {
    ff_lock(f->mutex_rd);
    rd_idx = correct_read_index(f, wr_idx);
    ff_unlock(f->mutex_rd);

    cnt = f->depth;
  }

  // Check if fifo is empty
  if (cnt == 0) {
    info->linear.len  = 0;
    info->wrapped.len = 0;
    info->linear.ptr  = NULL;
    info->wrapped.ptr = NULL;
    return;
  }

  // Get relative pointers
  uint16_t wr_ptr = idx2ptr(f->depth, wr_idx);
  uint16_t rd_ptr = idx2ptr(f->depth, rd_idx);

  // Copy pointer to buffer to start reading from
  info->linear.ptr = &f->buffer[rd_ptr];

  // Check if there is a wrap around necessary
  if (wr_ptr > rd_ptr) {
    // Non wrapping case
    info->linear.len = cnt;

    info->wrapped.len = 0;
    info->wrapped.ptr = NULL;
  } else {
    info->linear.len = f->depth - rd_ptr; // Also the case if FIFO was full

    info->wrapped.len = cnt - info->linear.len;
    info->wrapped.ptr = f->buffer;
  }
}

/******************************************************************************/
/*!
   @brief Get linear write info

   Returns the length and pointer to which bytes can be written into FIFO in a linear manner.
   This is of major interest for DMA transmissions not using circular mode. If a returned length is zero the
   corresponding pointer is invalid. The returned lengths summed up are the currently free space in the FIFO.
   The write pointer does NOT get advanced, use tu_fifo_advance_write_pointer() to do so!
   TAKE CARE TO NOT OVERFLOW THE BUFFER MORE THAN TWO TIMES THE FIFO DEPTH - IT CAN NOT RECOVERE OTHERWISE!
   @param[in]       f
                    Pointer to FIFO
   @param[out]      *info
                    Pointer to struct which holds the desired infos
 */
/******************************************************************************/
void tu_fifo_get_write_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info) {
  uint16_t wr_idx = f->wr_idx;
  uint16_t rd_idx = f->rd_idx;
  uint16_t remain = tu_ff_remaining_local(f->depth, wr_idx, rd_idx);

  if (remain == 0) {
    info->linear.len  = 0;
    info->wrapped.len = 0;
    info->linear.ptr  = NULL;
    info->wrapped.ptr = NULL;
    return;
  }

  // Get relative pointers
  uint16_t wr_ptr = idx2ptr(f->depth, wr_idx);
  uint16_t rd_ptr = idx2ptr(f->depth, rd_idx);

  // Copy pointer to buffer to start writing to
  info->linear.ptr = &f->buffer[wr_ptr];

  if (wr_ptr < rd_ptr) {
    // Non wrapping case
    info->linear.len  = rd_ptr - wr_ptr;
    info->wrapped.len = 0;
    info->wrapped.ptr = NULL;
  } else {
    info->linear.len  = f->depth - wr_ptr;
    info->wrapped.len = remain - info->linear.len; // Remaining length - n already was limited to remain or FIFO depth
    info->wrapped.ptr = f->buffer;                 // Always start of buffer
  }
}