Bluetooth: Controller: Replace prepare pipeline with ordered list#79444
Bluetooth: Controller: Replace prepare pipeline with ordered list#79444cvinayak wants to merge 1 commit intozephyrproject-rtos:mainfrom
Conversation
d3fd40c to
d377960
Compare
01d6254 to
5224961
Compare
|
5224961 to
d5aea11
Compare
37f9ac4 to
7ba61e4
Compare
1f03b00 to
3308317
Compare
|
I can’t post comments to GitHub from here. Please paste the following into a PR comment: Summary of what changed
Why this is a good change
Things to double-check
Overall assessment
|
3308317 to
ed1da0f
Compare
ed1da0f to
8c40860
Compare
|
There was a problem hiding this comment.
Pull Request Overview
This PR replaces the MFIFO (multi-producer, multi-consumer FIFO) prepare pipeline with an ordered linked list implementation in the Bluetooth controller. The new implementation maintains events sorted by their ticks_at_expire value, eliminating the need for O(n) searches through the pipeline.
Key Changes:
- Replaced MFIFO-based prepare pipeline with a custom ordered linked list structure
- Updated iterator functions to use
void **instead ofuint8_tindices for traversal - Simplified preemption logic in
nordic/lll/lll.cby removing complex short-prepare search code
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
| subsys/bluetooth/controller/ll_sw/ull.c | Implements new ordered linked list structure, enqueue/dequeue logic with time-based ordering, and memory management for pipeline events |
| subsys/bluetooth/controller/ll_sw/openisa/lll/lll.c | Updates iterator usage from uint8_t index to void* pointer and adds list termination checks |
| subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c | Updates iterator usage, simplifies preemption logic by removing short-prepare search loops, and converts do-while to while loop in prepare_dequeue_iter_ready_get |
| subsys/bluetooth/controller/ll_sw/lll.h | Updates function signature for ull_prepare_dequeue_iter to accept void** instead of uint8_t* |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /* Initialize prepare pipeline list */ | ||
| mem_init(pipeline.mem.pool, EVENT_PIPELINE_EVENT_SIZE, | ||
| sizeof(pipeline.mem.pool) / EVENT_PIPELINE_EVENT_SIZE, | ||
| &pipeline.mem.free); |
There was a problem hiding this comment.
Missing initialization of pipeline.head and pipeline.tail to NULL. These pointers should be explicitly initialized to NULL during reset to ensure proper operation of the linked list, especially since ull_prepare_enqueue checks if (pipeline.tail) at line 2153.
| &pipeline.mem.free); | |
| &pipeline.mem.free); | |
| pipeline.head = NULL; | |
| pipeline.tail = NULL; |
| if (!*next) { | ||
| pipeline.tail = next; | ||
| } |
There was a problem hiding this comment.
Potential issue with the insertion logic when placing a new event at the head of the list. At line 2204, the check if (!*next) is intended to handle the case where the list becomes empty after moving to head, but this condition can never be true here. The *next was just set to pipeline.head at line 2174, and if pipeline.head was NULL, the outer condition at line 2153 (if (pipeline.tail)) would have been false. Consider removing this unreachable condition or documenting why it exists.
| if (!*next) { | |
| pipeline.tail = next; | |
| } |
| struct lll_event *next; | ||
| static memq_link_t link; | ||
| static struct mayfly mfy = {0, 0, &link, NULL, lll_resume}; | ||
| uint8_t enqueued_loop = 2U; |
There was a problem hiding this comment.
The variable enqueued_loop is initialized to 2U but its purpose and why it's set to 2 specifically is unclear. This magic number should be documented with a comment explaining why 2 iterations are allowed before setting is_enqueued flag, or defined as a named constant.
| /* Trigger the LLL resume */ | ||
| mfy.param = e; | ||
| ret = mayfly_enqueue(caller_id, TICKER_USER_ID_LLL, 0, &mfy); | ||
| LL_ASSERT(!ret); |
There was a problem hiding this comment.
Inconsistent assertion macro usage: LL_ASSERT is used here (line 2344) while LL_ASSERT_ERR is used elsewhere in the same function (line 2306). Consider using the same assertion macro consistently throughout the function, or document why different macros are needed in different locations.
| LL_ASSERT(!ret); | |
| LL_ASSERT_ERR(!ret); |
| } | ||
| /* Start the preempt timeout for ready event */ | ||
| ret = preempt_ticker_start(ready, NULL, ready); | ||
| LL_ASSERT((ret == TICKER_STATUS_SUCCESS) || |
There was a problem hiding this comment.
Inconsistent assertion macro usage: LL_ASSERT is used here (line 1232) while LL_ASSERT_ERR was used in the previous code (visible in the diff context). Consider using the same assertion macro consistently, or document why different macros are needed.
| LL_ASSERT((ret == TICKER_STATUS_SUCCESS) || | |
| LL_ASSERT_ERR((ret == TICKER_STATUS_SUCCESS) || |
| return MFIFO_DEQUEUE_GET(prep); | ||
| void *e; | ||
|
|
||
| /* peak lll_event */ |
There was a problem hiding this comment.
Typo in comment: "peak" should be "peek". The function is inspecting/peeking at the head element, not finding a peak/maximum.
| return NULL; | ||
| } | ||
|
|
||
| /* Peak lll_event */ |
There was a problem hiding this comment.
Typo in comment: "Peak" should be "Peek". The function is inspecting/peeking at the event, not finding a peak/maximum.
| /* Trigger the LLL resume */ | ||
| mfy.param = e; | ||
| ret = mayfly_enqueue(caller_id, TICKER_USER_ID_LLL, 0, &mfy); | ||
| LL_ASSERT(!ret); |
There was a problem hiding this comment.
Inconsistent assertion macro usage: LL_ASSERT is used here (line 2375) while LL_ASSERT_ERR is used at line 2306. Consider using the same assertion macro consistently throughout the function, or document why different macros are needed in different locations.
| LL_ASSERT(!ret); | |
| LL_ASSERT_ERR(!ret); |
| preempt_abort_resume: | ||
| /* Abort any duplicate non-resume, that they get dequeued */ | ||
| iter_idx = UINT8_MAX; | ||
| /* Abort any duplicates so that they get dequeued */ |
There was a problem hiding this comment.
[nitpick] Comment text update has a minor issue: "Abort any duplicate non-resume" (old) was changed to "Abort any duplicates" (new), but the old comment was grammatically incomplete. The new comment is better but could be more precise: "Abort any duplicate events so that they get dequeued" would be clearer.
| /* Abort any duplicates so that they get dequeued */ | |
| /* Abort any duplicate events so that they get dequeued */ |
| if (!e_curr->is_aborted && !e_curr->is_resume) { | ||
| prev = curr; | ||
| } |
There was a problem hiding this comment.
Critical bug in list insertion ordering: The prev pointer is only updated for non-aborted, non-resume events (lines 2184-2186), but the list should maintain all nodes including aborted ones until they are explicitly dequeued. If the list contains aborted events followed by a valid event that should come after the new event (e.g., [aborted1] -> [aborted2] -> [valid_event]), the loop will skip updating prev while traversing aborted events, leaving prev as NULL. When the loop exits upon finding valid_event, prev is still NULL, causing the new event to be incorrectly inserted at the head (lines 2202-2203) instead of before valid_event. The fix is to unconditionally update prev = curr before advancing curr at line 2188, regardless of the event's state.
| if (!e_curr->is_aborted && !e_curr->is_resume) { | |
| prev = curr; | |
| } | |
| prev = curr; |
|
This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time. |
8c40860 to
92fb0f9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * through the prepare pipeline. | ||
| */ | ||
| idx = UINT8_MAX; | ||
| idx = NULL; |
There was a problem hiding this comment.
Inside the iter_idx traversal loop, the code resets idx = NULL after abort_cb() to restart iteration, but the iterator variable being used is iter_idx. As written, the loop won't actually restart from the head after the pipeline is modified. This looks like it should reset iter_idx instead of idx.
| idx = NULL; | |
| iter_idx = NULL; |
| #define EVENT_PIPELINE_EVENT_SIZE (sizeof(struct lll_event) + sizeof(void *)) | ||
|
|
||
| static struct { | ||
| struct { | ||
| void *free; | ||
| uint8_t pool[EVENT_PIPELINE_MAX * EVENT_PIPELINE_EVENT_SIZE]; | ||
| } mem; |
There was a problem hiding this comment.
EVENT_PIPELINE_EVENT_SIZE and the pipeline node layout don't account for the mem_* allocator's reserved header (next pointer + free-count stored at MROUND(sizeof(void *)), see util/mem.c). With the current layout (lll_event starts at + sizeof(void *)), mem_release() will overwrite the first bytes of struct lll_event, corrupting event contents if the block is released while still referenced (e.g., passed to mayfly). Consider placing struct lll_event after MEM_FREE_RESERVED_SIZE (and aligning as needed) and updating EVENT_PIPELINE_EVENT_SIZE/offset calculations accordingly.
| return NULL; | ||
| } | ||
|
|
||
| e = (void *)((uint8_t *)next + sizeof(void *)); |
There was a problem hiding this comment.
e is derived as next + sizeof(void *), but the mem_* pool uses the bytes immediately after the next pointer to store the free-count (mem_release() writes it at MROUND(sizeof(void *))). If this block is released back to the pool before the consumer is done, e->prepare_param will be corrupted. Adjust the start offset of lll_event to be after the allocator's reserved header (e.g., MEM_FREE_RESERVED_SIZE, possibly rounded up for alignment).
| e = (void *)((uint8_t *)next + sizeof(void *)); | |
| e = (void *)((uint8_t *)next + MEM_FREE_RESERVED_SIZE); |
| /* Trigger the LLL resume */ | ||
| mfy.param = e; | ||
| ret = mayfly_enqueue(caller_id, TICKER_USER_ID_LLL, 0, &mfy); | ||
| LL_ASSERT_ERR(!ret); | ||
|
|
||
| /* Pipeline could be modified, restart at head to remove all aborted | ||
| * prepare. | ||
| */ | ||
| e_prev = NULL; | ||
| e_curr = pipeline.head; | ||
| } | ||
|
|
||
| /* Free event memory */ | ||
| mem_release(head, &pipeline.mem.free); | ||
|
|
There was a problem hiding this comment.
ull_prepare_dequeue() passes e to mayfly_enqueue() (which may defer execution when caller/callee priorities are not equal), but then immediately mem_release(head, ...) frees the backing storage. Since mem_release() overwrites the freed block's header (and free-count), lll_resume can observe corrupted struct lll_event data if the mayfly call is not executed inline. Keep the event storage valid until lll_resume has run (e.g., release in/after lll_resume, or only release when mayfly_enqueue executed inline / assert on inline-only usage).
| /* Release the memory to pool */ | ||
| mem_release(head, &pipeline.mem.free); |
There was a problem hiding this comment.
Same lifetime issue as above for resume prepares: the resume lll_event is passed to mayfly_enqueue() and then immediately mem_release(head, ...) frees/overwrites the memory. If the mayfly call is deferred, lll_resume may dereference invalid/corrupted data. Defer releasing the node until after lll_resume executes.
| /* Release the memory to pool */ | |
| mem_release(head, &pipeline.mem.free); | |
| /* Note: Do not release the resume event memory here. | |
| * The node must remain valid until after lll_resume has executed. | |
| * It will be released as part of the resume/done handling. | |
| */ |
| /* peak lll_event */ | ||
| if (pipeline.head) { |
There was a problem hiding this comment.
Typo in comment: peak lll_event should be peek lll_event.
| /* Peak lll_event */ | ||
| e = (uint8_t *)*idx + sizeof(void *); |
There was a problem hiding this comment.
Typo in comment: Peak lll_event should be Peek lll_event.
…os#79444 Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com>
…ct-rtos#79444 Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com>
…os#79444 Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com>
…ct-rtos#79444 Co-authored-by: cvinayak <6350656+cvinayak@users.noreply.github.com>
Replace prepare pipeline with ordered linked list. Signed-off-by: Vinayak Kariappa Chettimada <vich@nordicsemi.no>
92fb0f9 to
997a1e0
Compare
|



Replace prepare pipeline with ordered linked list.