Repository navigation
fifo: keep overwritable fifo full and ordered on double overflow #4486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
47d3ffd
221e786
669403f
f62a491
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -411,6 +411,17 @@ static uint16_t advance_index(uint16_t depth, uint16_t idx, uint16_t offset) { | |
| return new_idx; | ||
| } | ||
|
|
||
| // Advance write index by n after data is written. In overwritable mode, if the write would make the overflow count | ||
| // reach 2*depth (double overflow), the index would alias to an empty fifo. Drop one depth from it instead: the | ||
| // write pointer is unchanged and the fifo is still full, holding the last depth items in order. | ||
| static uint16_t advance_write_index(uint16_t depth, uint16_t wr_idx, uint16_t rd_idx, uint16_t n) { | ||
| const uint16_t new_idx = advance_index(depth, wr_idx, n); | ||
| if (tu_ff_overflow_count(depth, wr_idx, rd_idx) + n >= 2 * depth) { | ||
| return advance_index(depth, new_idx, depth); | ||
| } | ||
| 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) | ||
|
|
@@ -534,25 +545,9 @@ uint16_t tu_fifo_write_n_access_mode(tu_fifo_t *f, const void *data, uint16_t n, | |
|
|
||
| // 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 | ||
| } | ||
| } | ||
| // Otherwise (n < depth) the data is written at the write index. Single overflow is recoverable (handled in | ||
| // read(), which re-positions the read index), double overflow is handled by advance_write_index() | ||
| } | ||
|
|
||
| if (n) { | ||
|
|
@@ -567,7 +562,7 @@ uint16_t tu_fifo_write_n_access_mode(tu_fifo_t *f, const void *data, uint16_t n, | |
| { | ||
| ff_push_n(f, buf8, n, wr_ptr); | ||
| } | ||
| f->wr_idx = advance_index(f->depth, wr_idx, n); | ||
| f->wr_idx = advance_write_index(f->depth, wr_idx, rd_idx, n); | ||
|
|
||
| TU_LOG(TU_FIFO_DBG, "\tnew_wr = %u\r\n", f->wr_idx); | ||
| } | ||
|
|
@@ -645,7 +640,7 @@ bool tu_fifo_write(tu_fifo_t *f, const void *data) { | |
| } 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); | ||
| f->wr_idx = advance_write_index(f->depth, wr_idx, f->rd_idx, 1); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new correction can expose a full buffer of already-read bytes. With This goes beyond the documented mix of old and new data. Make the correction and publication safe against concurrent read-index changes, and add a test for this sequence. Knowledge Base Used: OS abstraction and runtime services Prompt To Fix With AIThis is a comment left during a code review.
Path: src/common/tusb_fifo.c
Line: 643
Comment:
**Concurrent reads replay old bytes**
The new correction can expose a full buffer of already-read bytes. With `depth=64`, `wr_idx=127`, and `rd_idx=0`, pause the writer after it captures `rd_idx` but before it stores `wr_idx`. A concurrent reader can correct `rd_idx` to 63 and drain 64 bytes, leaving `rd_idx=127`. The writer then resumes and publishes `wr_idx=64`, so `tu_fifo_count()` reports 64 stale bytes instead of the one pending byte reported by the old code.
This goes beyond the documented mix of old and new data. Make the correction and publication safe against concurrent read-index changes, and add a test for this sequence.
**Knowledge Base Used:** [OS abstraction and runtime services](https://app.greptile.com/-/switch-tenant?tenant=tinyusb&next=%2Fcustom-context%2Fknowledge-base%2Fhathach%2Ftinyusb%2F-%2Fdocs%2Fosal-and-runtime-services.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly. |
||
| ret = true; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
Repository: hathach/tinyusb
Length of output: 8839
Script executed:
Repository: hathach/tinyusb
Length of output: 42850
Coordinate overflow adjustment with read-index recovery
When a bulk write snapshots
rd_idxand a concurrent peek corrects it before publication,advance_write_indexcan apply the extra depth using the stale snapshot. For depth 4, this can leave a count of 1 instead of 4, making three retained items inaccessible.Coordinate the overflow decision and write-index publication with read-side recovery. Torn-data tolerance does not cover this index loss.