Skip to content

Develop/nn#17

Merged
00lllooolll00 merged 6 commits into
masterfrom
develop/nn
Apr 5, 2026
Merged

Develop/nn#17
00lllooolll00 merged 6 commits into
masterfrom
develop/nn

Conversation

@00lllooolll00

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings April 5, 2026 06:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a cooperative, event-driven task scheduler for non-RTOS builds (ek_evoke) and adds supporting infrastructure in utils (a new SPSC ring buffer variant and list insertion helpers), along with configuration and build-guard updates.

Changes:

  • Added ek_evoke cooperative event scheduler (task/event creation, publish/broadcast, defer, ISR request FIFO, sleep hooks).
  • Added SPSC ring buffer (ek_ringbuf_*_spsc) and updated ring buffer module guards to allow building either variant.
  • Extended doubly-linked list utilities with ek_list_insert_before/after and enabled SPSC ringbuf in ek_conf.h.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
L2_Core/utils/src/ek_ringbuf.c Adds SPSC ring buffer implementation and adjusts compile-time guards/includes.
L2_Core/utils/inc/ek_ringbuf.h Declares SPSC ring buffer types/APIs and updates feature gating.
L2_Core/utils/src/ek_evoke.c New non-RTOS cooperative event loop implementation using SPSC ISR FIFO + deferred event list.
L2_Core/utils/inc/ek_evoke.h New public API for the cooperative scheduler (tasks/events, ISR helpers, weak hooks).
L2_Core/utils/inc/ek_list.h Adds ek_list_insert_before/after helpers used by deferred scheduling.
ek_conf.h Adds/updates module enable macros (notably EK_RINGBUF_SPSC_ENABLE).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +40 to +47
struct ek_ringbuf_spsc_t
{
uint8_t *buffer; /**< 缓冲区指针 */
uint32_t write_idx; /**< 写入位置索引 */
uint32_t read_idx; /**< 读取位置索引 */
size_t cap; /**< 底层槽位数量,实际最大可存元素数为 cap - 1 */
size_t item_size; /**< 单个元素大小(字节) */
};

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ek_ringbuf_spsc_t is used as an ISR->main SPSC queue (see ek_evoke.c), but read_idx/write_idx are plain uint32_t. Without volatile/atomics (and appropriate memory ordering), the compiler is free to cache these fields, which can cause missed updates (e.g., ek_ringbuf_empty_spsc() not observing a new write) or non-terminating loops. Consider making the indices atomic (C11 _Atomic uint32_t) with acquire/release semantics, or at least volatile plus a compiler barrier tailored to the supported toolchains.

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +168
bool ek_ringbuf_full_spsc(const ek_ringbuf_spsc_t *rb)
{
ek_assert_param(rb != NULL);
return _ek_ringbuf_spsc_next_idx(rb, rb->write_idx) == rb->read_idx;
}

bool ek_ringbuf_empty_spsc(const ek_ringbuf_spsc_t *rb)
{
ek_assert_param(rb != NULL);
return rb->read_idx == rb->write_idx;
}

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPSC ring buffer operations are used across ISR and main contexts, but these functions read/write read_idx/write_idx without any atomic/volatile semantics. Even on MCUs where 32-bit loads/stores are atomic, the lack of compiler-visible ordering can break the SPSC contract. Please add atomic/volatile handling in the implementation consistent with the chosen approach in the header.

Copilot uses AI. Check for mistakes.
Comment on lines +354 to +358
ek_list_remove(&tsk->node);
tsk->state = EK_EVOKE_STATE_RUNNING;
tsk->cb(tsk->wait_event, tsk->arg);
ek_list_insert_tail(&tsk->wait_event->wait_list, &tsk->node);
tsk->state = EK_EVOKE_STATE_WAITTING;

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ek_evoke_event_loop() reinserts the task back into tsk->wait_event->wait_list after executing the callback. If the callback destroys the event (or otherwise invalidates tsk->wait_event) this becomes a use-after-free; similarly, if the callback causes the task to be destroyed, tsk becomes invalid. This needs an explicit safety rule (document and enforce with assertions), or a redesign where the scheduler does not touch tsk/evt after calling the user callback unless they are guaranteed to remain alive.

Suggested change
ek_list_remove(&tsk->node);
tsk->state = EK_EVOKE_STATE_RUNNING;
tsk->cb(tsk->wait_event, tsk->arg);
ek_list_insert_tail(&tsk->wait_event->wait_list, &tsk->node);
tsk->state = EK_EVOKE_STATE_WAITTING;
ek_evoke_event_t *evt = tsk->wait_event;
void *arg = tsk->arg;
EK_ASSERT(evt != NULL);
EK_ASSERT(tsk->cb != NULL);
ek_list_remove(&tsk->node);
tsk->state = EK_EVOKE_STATE_RUNNING;
/*
* 回调可能销毁任务或事件因此调度器不能在回调返回后继续访问 tsk/evt
* 在调用用户回调前先恢复等待链表和等待状态避免回调后的悬空访问
*/
ek_list_insert_tail(&evt->wait_list, &tsk->node);
tsk->state = EK_EVOKE_STATE_WAITTING;
tsk->cb(evt, arg);

Copilot uses AI. Check for mistakes.
Comment on lines +244 to +257
uint32_t wakeup_tick = delay + _event_tick_base;

req->evt = evt;
req->broadcast = broadcast;
req->payload = payload;
req->wakeup_tick = wakeup_tick;

ek_list_node_t *pos;
ek_list_foreach(pos, &_defer_evt_list)
{
_defer_req_t *pos_req = ek_list_container(pos, _defer_req_t, node);
if (pos_req->wakeup_tick >= wakeup_tick) break;
}
ek_list_insert_before(pos, &req->node);

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred-event scheduling compares absolute uint32_t ticks (wakeup_tick vs other nodes, and later vs _defer_earilest_tick) using plain >= and subtraction. This will break when _event_tick_base overflows (wrap-around), causing misordering and potentially very large computed delays. Consider using a wrap-around-safe time comparison (signed-difference style, e.g., (int32_t)(a-b) >= 0) and computing deltas via subtraction only after such comparisons, or move to a 64-bit tick accumulator.

Copilot uses AI. Check for mistakes.
Comment on lines +266 to +306
_isr_req_t req = {
.type = ISR_REQ_BROADCAST,
.evt = evt,
.payload = payload,
};
ek_ringbuf_write_spsc(_isr_fifo, &req);

ek_evoke_exit_critical();
}

void ek_evoke_event_publish_from_isr(ek_evoke_event_handle_t evt, void *payload)
{
ek_assert_param(evt != NULL);

ek_evoke_enter_critical();

_isr_req_t req = {
.type = ISR_REQ_PUBLISH,
.evt = evt,
.payload = payload,
};
ek_ringbuf_write_spsc(_isr_fifo, &req);

ek_evoke_exit_critical();
}

void ek_evoke_event_defer_from_isr(ek_evoke_event_handle_t evt, void *payload, uint32_t delay, bool broadcast)
{
ek_assert_param(evt != NULL);

ek_evoke_enter_critical();

_isr_req_t req = {
.type = (broadcast == true) ? (ISR_REQ_PUBLISH_DEALY | ISR_REQ_BROADCAST)
: (ISR_REQ_PUBLISH_DEALY | ISR_REQ_PUBLISH),
.evt = evt,
.payload = payload,
.delay = delay,
};
ek_ringbuf_write_spsc(_isr_fifo, &req);

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ISR enqueue helpers (*_from_isr) ignore the return value of ek_ringbuf_write_spsc(). When the ISR FIFO is full, requests will be silently dropped, potentially losing events with no visibility. Please handle the false case (e.g., increment a dropped counter, set an overflow flag processed in the main loop, or assert in debug builds).

Copilot uses AI. Check for mistakes.
static volatile uint32_t _sleep_lock;
static volatile uint32_t _event_tick_base;
static volatile uint32_t _event_tick_diff;
static volatile uint32_t _defer_earilest_tick;

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in identifier: _defer_earilest_tick should be _defer_earliest_tick (and update related uses).

Suggested change
static volatile uint32_t _defer_earilest_tick;
static volatile uint32_t _defer_earliest_tick;

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +76
typedef enum
{
EK_EVOKE_STATE_IDLE = 0, /**< 空闲状态 */
EK_EVOKE_STATE_DELAY, /**< 延迟状态 */
EK_EVOKE_STATE_WAITTING, /**< 等待状态 */
EK_EVOKE_STATE_READY, /**< 就绪状态 */
EK_EVOKE_STATE_RUNNING, /**< 运行状态 */

EK_EVOKE_STATE_MAX, /**< 状态最大值 */
} ek_evoke_state_t;

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in enum value: EK_EVOKE_STATE_WAITTING should be EK_EVOKE_STATE_WAITING. Since this is a public header, it’s best to correct before release; if you need compatibility, you can keep the old name as a deprecated alias to the corrected one.

Copilot uses AI. Check for mistakes.
Comment on lines +170 to +256
ek_ringbuf_spsc_t *ek_ringbuf_create_spsc(size_t item_size, uint32_t item_amount)
{
ek_assert_param(item_amount > 1U);
ek_assert_param(item_size != 0U);

ek_ringbuf_spsc_t *rb = (ek_ringbuf_spsc_t *)ek_malloc(sizeof(ek_ringbuf_spsc_t));
if (rb == NULL)
{
return NULL;
}

rb->buffer = (uint8_t *)ek_malloc(item_amount * item_size);
if (rb->buffer == NULL)
{
ek_free(rb);
return NULL;
}

rb->cap = item_amount;
rb->item_size = item_size;
rb->read_idx = 0U;
rb->write_idx = 0U;

return rb;
}

void ek_ringbuf_destroy_spsc(ek_ringbuf_spsc_t *rb)
{
ek_assert_param(rb != NULL);

ek_free(rb->buffer);
ek_free(rb);
}

bool ek_ringbuf_write_spsc(ek_ringbuf_spsc_t *rb, const void *item)
{
ek_assert_param(rb != NULL);
ek_assert_param(item != NULL);

uint32_t next_idx = _ek_ringbuf_spsc_next_idx(rb, rb->write_idx);
if (next_idx == rb->read_idx)
{
return false;
}

uint8_t *target = rb->buffer + (rb->write_idx * rb->item_size);
memcpy(target, item, rb->item_size);
rb->write_idx = next_idx;

return true;
}

bool ek_ringbuf_read_spsc(ek_ringbuf_spsc_t *rb, void *item)
{
ek_assert_param(rb != NULL);

if (rb->read_idx == rb->write_idx)
{
return false;
}

if (item != NULL)
{
const uint8_t *source = rb->buffer + (rb->read_idx * rb->item_size);
memcpy(item, source, rb->item_size);
}

rb->read_idx = _ek_ringbuf_spsc_next_idx(rb, rb->read_idx);

return true;
}

bool ek_ringbuf_peek_spsc(ek_ringbuf_spsc_t *rb, void *item)
{
ek_assert_param(rb != NULL);
ek_assert_param(item != NULL);

if (rb->read_idx == rb->write_idx)
{
return false;
}

const uint8_t *source = rb->buffer + (rb->read_idx * rb->item_size);
memcpy(item, source, rb->item_size);

return true;
}

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New SPSC ring buffer API has no corresponding tests, while the existing generic ring buffer has coverage in Test/ringbuf_test.c. Please add unit/functional tests for SPSC behavior (empty/full boundaries, wrap-around, overwrite prevention, and basic ISR-producer/main-consumer simulation where possible) to prevent regressions.

Copilot uses AI. Check for mistakes.
# define ek_ringbuf_destroy_safely(rb_ptr) \
do \
{ \
ek_ringbuf_destroy(rb_ptr); \

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Macro arg isn’t parenthesized in the destroy call: ek_ringbuf_destroy(rb_ptr) vs the project’s existing pattern ek_stack_destroy((sk_ptr)) (ek_stack.h:105). Please wrap rb_ptr in parentheses in both ek_ringbuf_destroy_safely and ek_ringbuf_destroy_safely_spsc to avoid surprises if a non-trivial expression is ever passed.

Suggested change
ek_ringbuf_destroy(rb_ptr); \
ek_ringbuf_destroy((rb_ptr)); \

Copilot uses AI. Check for mistakes.
# define ek_ringbuf_destroy_safely_spsc(rb_ptr) \
do \
{ \
ek_ringbuf_destroy_spsc(rb_ptr); \

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same macro-parenthesization issue exists for ek_ringbuf_destroy_safely_spsc: wrap rb_ptr in parentheses when passing to ek_ringbuf_destroy_spsc() to match existing macros and avoid surprises if an expression is passed.

Suggested change
ek_ringbuf_destroy_spsc(rb_ptr); \
ek_ringbuf_destroy_spsc((rb_ptr)); \

Copilot uses AI. Check for mistakes.
@00lllooolll00 00lllooolll00 merged commit c743daa into master Apr 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants