mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'bugfix/fix_osi_event_bug' into 'master'
Bugfix/fix osi event bug See merge request espressif/esp-idf!51916
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
|
||||
struct osi_thread;
|
||||
struct osi_event;
|
||||
struct osi_dynamic_event;
|
||||
|
||||
typedef struct osi_thread osi_thread_t;
|
||||
|
||||
@@ -81,10 +82,12 @@ const char *osi_thread_name(osi_thread_t *thread);
|
||||
int osi_thread_queue_wait_size(osi_thread_t *thread, int wq_idx);
|
||||
|
||||
/*
|
||||
* brief: Create an osi_event struct and register the handler function and its argument
|
||||
* brief: Create a session-stable osi_event and register its handler and argument.
|
||||
* An osi_event is a kind of work that can be posted to the workqueue of osi_thread to process,
|
||||
* but the work can have at most one instance the thread workqueue before it is processed. This
|
||||
* allows the "single post, multiple data processing" jobs.
|
||||
* Delete is logical: storage remains valid until osi_thread_event_deinit(), allowing stale
|
||||
* posts during session teardown to be rejected without a global alive-list lock.
|
||||
* param func: the handler to process the job
|
||||
* param context: the argument to be passed to the handler function when the job is being processed
|
||||
* return: NULL if no memory, otherwise a valid struct pointer
|
||||
@@ -103,7 +106,7 @@ struct osi_event *osi_event_create(osi_thread_func_t func, void *context);
|
||||
bool osi_event_bind(struct osi_event* event, osi_thread_t *thread, int queue_idx);
|
||||
|
||||
/*
|
||||
* brief: Destroy the osi_event struct created by osi_event_create and free the allocated memory
|
||||
* brief: Logically delete an osi_event. Its memory is reclaimed by osi_thread_event_deinit().
|
||||
* param event: the pointer to osi_event
|
||||
*/
|
||||
void osi_event_delete(struct osi_event* event);
|
||||
@@ -118,6 +121,18 @@ void osi_event_delete(struct osi_event* event);
|
||||
*/
|
||||
bool osi_thread_post_event(struct osi_event *event, uint32_t timeout);
|
||||
|
||||
/*
|
||||
* Dynamic events may be created and destroyed repeatedly during one
|
||||
* osi_thread event-subsystem session. Unlike session-stable osi_event objects,
|
||||
* their storage can be released by delete, so all operations use a separate
|
||||
* API whose post/bind entry points validate the opaque pointer without first
|
||||
* dereferencing it.
|
||||
*/
|
||||
struct osi_dynamic_event *osi_dynamic_event_create(osi_thread_func_t func, void *context);
|
||||
bool osi_dynamic_event_bind(struct osi_dynamic_event *event, osi_thread_t *thread, int queue_idx);
|
||||
bool osi_dynamic_event_post(struct osi_dynamic_event *event, uint32_t timeout);
|
||||
void osi_dynamic_event_delete(struct osi_dynamic_event *event);
|
||||
|
||||
int osi_thread_event_init(void);
|
||||
void osi_thread_event_deinit(void);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ struct osi_thread_start_arg {
|
||||
int error;
|
||||
};
|
||||
|
||||
struct osi_event {
|
||||
struct osi_event_core {
|
||||
struct work_item item;
|
||||
osi_mutex_t lock;
|
||||
osi_thread_t *thread;
|
||||
@@ -65,17 +65,25 @@ struct osi_event {
|
||||
uint8_t queue_idx;
|
||||
};
|
||||
|
||||
struct osi_event {
|
||||
struct osi_event_core core;
|
||||
};
|
||||
|
||||
struct osi_dynamic_event {
|
||||
struct osi_event_core core;
|
||||
};
|
||||
|
||||
#define OSI_EVENT_FLAG_QUEUED (1U << 0)
|
||||
#define OSI_EVENT_FLAG_POSTING (1U << 1)
|
||||
#define OSI_EVENT_FLAG_DELETING (1U << 2)
|
||||
#define OSI_EVENT_FLAG_RUNNING (1U << 3)
|
||||
#define OSI_EVENT_FLAG_DELETING (1U << 1)
|
||||
#define OSI_EVENT_FLAG_RUNNING (1U << 2)
|
||||
|
||||
#define OSI_EVENT_HAS_FLAG(event, flag) (((event)->flags & (flag)) != 0)
|
||||
#define OSI_EVENT_SET_FLAG(event, flag) ((event)->flags |= (uint8_t)(flag))
|
||||
#define OSI_EVENT_CLEAR_FLAG(event, flag) ((event)->flags &= (uint8_t)(~(flag)))
|
||||
|
||||
static const size_t DEFAULT_WORK_QUEUE_CAPACITY = 100;
|
||||
static list_t *s_osi_event_list;
|
||||
static list_t *s_osi_session_event_list;
|
||||
static list_t *s_osi_dynamic_event_list;
|
||||
static osi_mutex_t s_osi_event_lock;
|
||||
|
||||
#if OSI_THREAD_DEBUG
|
||||
@@ -494,35 +502,47 @@ int osi_thread_queue_wait_size(osi_thread_t *thread, int wq_idx)
|
||||
}
|
||||
|
||||
|
||||
static bool osi_event_add_alive_locked(struct osi_event *event)
|
||||
static struct osi_event_core *osi_event_core_new(size_t size, osi_thread_func_t func, void *context)
|
||||
{
|
||||
assert(s_osi_event_list != NULL);
|
||||
return list_append(s_osi_event_list, event);
|
||||
struct osi_event_core *event = osi_calloc(size);
|
||||
|
||||
if (event == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (osi_mutex_new(&event->lock) != 0) {
|
||||
osi_free(event);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
event->item.func = func;
|
||||
event->item.context = context;
|
||||
/* Session events hold a registry pin; dynamic events hold an owner ref. */
|
||||
event->ref_count = 1;
|
||||
return event;
|
||||
}
|
||||
|
||||
static void osi_event_free(struct osi_event *event)
|
||||
static void osi_event_core_free(struct osi_event_core *event)
|
||||
{
|
||||
if (event != NULL) {
|
||||
osi_mutex_free(&event->lock);
|
||||
memset(event, 0, sizeof(struct osi_event));
|
||||
memset(event, 0, sizeof(*event));
|
||||
osi_free(event);
|
||||
}
|
||||
}
|
||||
|
||||
static bool osi_event_is_idle(const struct osi_event *event)
|
||||
static bool osi_event_is_idle(const struct osi_event_core *event)
|
||||
{
|
||||
return !OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_QUEUED) &&
|
||||
!OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_POSTING) &&
|
||||
!OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_RUNNING);
|
||||
}
|
||||
|
||||
static bool osi_event_should_free(const struct osi_event *event)
|
||||
static bool osi_event_should_free(const struct osi_event_core *event)
|
||||
{
|
||||
return OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_DELETING) &&
|
||||
osi_event_is_idle(event);
|
||||
}
|
||||
|
||||
static bool osi_event_can_bind_locked(const struct osi_event *event, osi_thread_t *thread, int queue_idx)
|
||||
static bool osi_event_can_bind_locked(const struct osi_event_core *event, osi_thread_t *thread, int queue_idx)
|
||||
{
|
||||
return !OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_DELETING) &&
|
||||
event->thread == NULL &&
|
||||
@@ -531,7 +551,7 @@ static bool osi_event_can_bind_locked(const struct osi_event *event, osi_thread_
|
||||
queue_idx < thread->work_queue_num;
|
||||
}
|
||||
|
||||
static bool osi_event_can_post_locked(const struct osi_event *event)
|
||||
static bool osi_event_can_post_locked(const struct osi_event_core *event)
|
||||
{
|
||||
if (event->thread == NULL || event->queue_idx >= event->thread->work_queue_num) {
|
||||
OSI_TRACE_EVENT("%s deny ev=%p flags=0x%x qidx=%u",
|
||||
@@ -549,173 +569,188 @@ static bool osi_event_can_post_locked(const struct osi_event *event)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Do NOT gate on OSI_EVENT_FLAG_POSTING here. POSTING marks the window in
|
||||
* osi_thread_post_event() between osi_thread_post() (enqueue) and the
|
||||
* poster clearing the flag. During that window the generic event handler
|
||||
* may already have run and cleared QUEUED. A concurrent post that arrives
|
||||
* after QUEUED is cleared is a legitimate re-post (new work arrived while
|
||||
* the handler was draining) and must be accepted; rejecting it causes a
|
||||
* lost wakeup. QUEUED alone prevents genuine double-queueing. POSTING is
|
||||
* retained only for osi_event_is_idle()/osi_event_should_free(). */
|
||||
/* QUEUED alone prevents double-queueing. Do not gate on RUNNING: the
|
||||
* generic handler clears QUEUED before invoking the user callback, and a
|
||||
* concurrent post after that is a legitimate re-post of work that arrived
|
||||
* while the handler was draining. */
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool osi_event_is_alive_locked(const struct osi_event *event)
|
||||
/* Caller holds event->lock. Drops one reference. Returns true if the caller
|
||||
* must free the event AFTER unlocking; never destroy the mutex while held. */
|
||||
static bool osi_event_release_locked(struct osi_event_core *event)
|
||||
{
|
||||
/* Do not dereference event here: callers may pass a stale pointer racing
|
||||
* with osi_event_delete(). The alive list is the ownership boundary. */
|
||||
return s_osi_event_list != NULL && list_contains(s_osi_event_list, event);
|
||||
}
|
||||
|
||||
static bool osi_event_remove_alive_locked(struct osi_event *event)
|
||||
{
|
||||
bool removed = false;
|
||||
|
||||
if (s_osi_event_list == NULL) {
|
||||
assert(event->ref_count > 0);
|
||||
event->ref_count--;
|
||||
if (event->ref_count != 0) {
|
||||
return false;
|
||||
}
|
||||
return osi_event_should_free(event);
|
||||
}
|
||||
|
||||
removed = list_delete(s_osi_event_list, event);
|
||||
return removed;
|
||||
static void osi_event_unlock_and_maybe_free(struct osi_event_core *event, bool should_free)
|
||||
{
|
||||
osi_mutex_unlock(&event->lock);
|
||||
if (should_free) {
|
||||
osi_event_core_free(event);
|
||||
}
|
||||
}
|
||||
|
||||
static void osi_event_mark_deleting_locked(struct osi_event_core *event)
|
||||
{
|
||||
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_DELETING);
|
||||
event->item.func = NULL;
|
||||
event->item.context = NULL;
|
||||
}
|
||||
|
||||
struct osi_event *osi_event_create(osi_thread_func_t func, void *context)
|
||||
{
|
||||
struct osi_event *event = (struct osi_event *)osi_event_core_new(sizeof(*event), func, context);
|
||||
bool added = false;
|
||||
struct osi_event *event = osi_calloc(sizeof(struct osi_event));
|
||||
|
||||
if (event == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (osi_mutex_new(&event->lock) != 0) {
|
||||
osi_free(event);
|
||||
osi_event_lock();
|
||||
if (s_osi_session_event_list != NULL) {
|
||||
added = list_append(s_osi_session_event_list, event);
|
||||
}
|
||||
osi_event_unlock();
|
||||
if (!added) {
|
||||
osi_event_core_free(&event->core);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
event->item.func = func;
|
||||
event->item.context = context;
|
||||
event->ref_count = 1;
|
||||
|
||||
osi_event_lock();
|
||||
added = osi_event_add_alive_locked(event);
|
||||
osi_event_unlock();
|
||||
if (added) {
|
||||
return event;
|
||||
}
|
||||
|
||||
osi_mutex_free(&event->lock);
|
||||
osi_free(event);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ref_count is protected by the per-event lock (event->lock), NOT the global
|
||||
* s_osi_event_lock. This keeps the reference-release path independent of the
|
||||
* global event subsystem: it must stay valid even after
|
||||
* osi_thread_event_deinit() has freed s_osi_event_lock (e.g. when a thread is
|
||||
* freed on a shutdown path that tears the event subsystem down first). The
|
||||
* global lock is only used to gate the alive-list membership that decides
|
||||
* whether a new reference may be acquired. */
|
||||
static bool osi_event_acquire(struct osi_event *event)
|
||||
{
|
||||
bool acquired = false;
|
||||
|
||||
if (event == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Hold the global lock so the event cannot be removed from the alive list
|
||||
* (and thus cannot be freed) while we take a fresh reference. Nesting is
|
||||
* always global-lock-outer, event->lock-inner; no path takes them in the
|
||||
* reverse order, so this cannot deadlock. */
|
||||
osi_event_lock();
|
||||
if (osi_event_is_alive_locked(event)) {
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
assert(event->ref_count > 0);
|
||||
event->ref_count++;
|
||||
osi_mutex_unlock(&event->lock);
|
||||
acquired = true;
|
||||
}
|
||||
osi_event_unlock();
|
||||
|
||||
return acquired;
|
||||
}
|
||||
|
||||
static void osi_event_retain(struct osi_event *event)
|
||||
{
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
assert(event->ref_count > 0);
|
||||
event->ref_count++;
|
||||
osi_mutex_unlock(&event->lock);
|
||||
}
|
||||
|
||||
static void osi_event_release(struct osi_event *event)
|
||||
{
|
||||
bool should_free = false;
|
||||
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
assert(event->ref_count > 0);
|
||||
event->ref_count--;
|
||||
if (event->ref_count == 0) {
|
||||
should_free = osi_event_should_free(event);
|
||||
}
|
||||
osi_mutex_unlock(&event->lock);
|
||||
|
||||
if (should_free) {
|
||||
osi_event_free(event);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
void osi_event_delete(struct osi_event *event)
|
||||
{
|
||||
bool removed = false;
|
||||
|
||||
if (event == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
osi_event_lock();
|
||||
removed = osi_event_remove_alive_locked(event);
|
||||
osi_event_unlock();
|
||||
if (!removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_DELETING);
|
||||
event->item.func = NULL;
|
||||
event->item.context = NULL;
|
||||
osi_mutex_unlock(&event->lock);
|
||||
|
||||
osi_event_release(event);
|
||||
/* The registry pin keeps session-event storage valid until subsystem
|
||||
* deinit, so delete is only a logical, idempotent operation. */
|
||||
osi_mutex_lock(&event->core.lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
osi_event_mark_deleting_locked(&event->core);
|
||||
osi_mutex_unlock(&event->core.lock);
|
||||
}
|
||||
|
||||
bool osi_event_bind(struct osi_event *event, osi_thread_t *thread, int queue_idx)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (!osi_event_acquire(event)) {
|
||||
if (event == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
if (osi_event_can_bind_locked(event, thread, queue_idx)) {
|
||||
event->thread = thread;
|
||||
event->queue_idx = queue_idx;
|
||||
osi_mutex_lock(&event->core.lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
if (osi_event_can_bind_locked(&event->core, thread, queue_idx)) {
|
||||
event->core.thread = thread;
|
||||
event->core.queue_idx = (uint8_t)queue_idx;
|
||||
ret = true;
|
||||
}
|
||||
osi_mutex_unlock(&event->lock);
|
||||
|
||||
osi_event_release(event);
|
||||
|
||||
osi_mutex_unlock(&event->core.lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct osi_dynamic_event *osi_dynamic_event_create(osi_thread_func_t func, void *context)
|
||||
{
|
||||
struct osi_dynamic_event *event =
|
||||
(struct osi_dynamic_event *)osi_event_core_new(sizeof(*event), func, context);
|
||||
bool added = false;
|
||||
|
||||
if (event == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
osi_event_lock();
|
||||
if (s_osi_dynamic_event_list != NULL) {
|
||||
added = list_append(s_osi_dynamic_event_list, event);
|
||||
}
|
||||
osi_event_unlock();
|
||||
if (!added) {
|
||||
osi_event_core_free(&event->core);
|
||||
return NULL;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
/* The global alive list is the ownership boundary for dynamic events. Never
|
||||
* dereference event until list membership is confirmed under the global lock.
|
||||
* Lock nesting is always global-lock-outer, event-lock-inner.
|
||||
* On success: drops the global lock and returns holding event->core.lock with
|
||||
* an extra reference; the caller must unlock (and maybe free) via
|
||||
* osi_event_unlock_and_maybe_free after osi_event_release_locked. */
|
||||
static bool osi_dynamic_event_acquire_locked(struct osi_dynamic_event *event)
|
||||
{
|
||||
if (event == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
osi_event_lock();
|
||||
if (s_osi_dynamic_event_list == NULL ||
|
||||
!list_contains(s_osi_dynamic_event_list, event)) {
|
||||
osi_event_unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
osi_mutex_lock(&event->core.lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
assert(event->core.ref_count > 0);
|
||||
event->core.ref_count++;
|
||||
osi_event_unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool osi_dynamic_event_bind(struct osi_dynamic_event *event, osi_thread_t *thread, int queue_idx)
|
||||
{
|
||||
bool ret = false;
|
||||
bool should_free;
|
||||
|
||||
if (!osi_dynamic_event_acquire_locked(event)) {
|
||||
return false;
|
||||
}
|
||||
if (osi_event_can_bind_locked(&event->core, thread, queue_idx)) {
|
||||
event->core.thread = thread;
|
||||
event->core.queue_idx = (uint8_t)queue_idx;
|
||||
ret = true;
|
||||
}
|
||||
should_free = osi_event_release_locked(&event->core);
|
||||
osi_event_unlock_and_maybe_free(&event->core, should_free);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void osi_dynamic_event_delete(struct osi_dynamic_event *event)
|
||||
{
|
||||
bool removed = false;
|
||||
bool should_free;
|
||||
|
||||
if (event == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
osi_event_lock();
|
||||
if (s_osi_dynamic_event_list != NULL) {
|
||||
removed = list_delete(s_osi_dynamic_event_list, event);
|
||||
}
|
||||
osi_event_unlock();
|
||||
if (!removed) {
|
||||
return;
|
||||
}
|
||||
|
||||
osi_mutex_lock(&event->core.lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
osi_event_mark_deleting_locked(&event->core);
|
||||
should_free = osi_event_release_locked(&event->core);
|
||||
osi_event_unlock_and_maybe_free(&event->core, should_free);
|
||||
}
|
||||
|
||||
static void osi_thread_generic_event_handler(void *context)
|
||||
{
|
||||
struct osi_event *event = (struct osi_event *)context;
|
||||
struct osi_event_core *event = (struct osi_event_core *)context;
|
||||
osi_thread_func_t func = NULL;
|
||||
void *func_context = NULL;
|
||||
bool should_free = false;
|
||||
|
||||
if (event == NULL) {
|
||||
return;
|
||||
@@ -724,8 +759,8 @@ static void osi_thread_generic_event_handler(void *context)
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_QUEUED);
|
||||
if (OSI_EVENT_HAS_FLAG(event, OSI_EVENT_FLAG_DELETING)) {
|
||||
osi_mutex_unlock(&event->lock);
|
||||
osi_event_release(event);
|
||||
should_free = osi_event_release_locked(event);
|
||||
osi_event_unlock_and_maybe_free(event, should_free);
|
||||
return;
|
||||
}
|
||||
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_RUNNING);
|
||||
@@ -741,20 +776,17 @@ static void osi_thread_generic_event_handler(void *context)
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_RUNNING);
|
||||
OSI_TRACE_DEBUG("%s exit ev=%p flags=0x%x", __func__, event, event->flags);
|
||||
osi_mutex_unlock(&event->lock);
|
||||
|
||||
osi_event_release(event);
|
||||
should_free = osi_event_release_locked(event);
|
||||
osi_event_unlock_and_maybe_free(event, should_free);
|
||||
}
|
||||
|
||||
/* Reclaim a queued event work item during thread teardown WITHOUT invoking the
|
||||
* user callback. It only clears the QUEUED flag and drops the reference the
|
||||
* queued item owns (osi_thread_post_event() retained it); this frees the event
|
||||
* if it was already deleted, and leaves a still-live event untouched. Unlike
|
||||
* osi_thread_generic_event_handler(), it never dispatches into stack state that
|
||||
* may already have been freed on the shutdown path. */
|
||||
* user callback. The release path only uses the per-event lock and remains
|
||||
* valid after the global event subsystem has been deinitialized. */
|
||||
static void osi_thread_generic_event_drain(void *context)
|
||||
{
|
||||
struct osi_event *event = (struct osi_event *)context;
|
||||
struct osi_event_core *event = (struct osi_event_core *)context;
|
||||
bool should_free = false;
|
||||
|
||||
if (event == NULL) {
|
||||
return;
|
||||
@@ -762,86 +794,150 @@ static void osi_thread_generic_event_drain(void *context)
|
||||
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_QUEUED);
|
||||
osi_mutex_unlock(&event->lock);
|
||||
|
||||
osi_event_release(event);
|
||||
should_free = osi_event_release_locked(event);
|
||||
osi_event_unlock_and_maybe_free(event, should_free);
|
||||
}
|
||||
|
||||
bool osi_thread_post_event(struct osi_event *event, uint32_t timeout)
|
||||
{
|
||||
bool ret = false;
|
||||
osi_thread_t *thread = NULL;
|
||||
uint8_t queue_idx = 0;
|
||||
struct osi_event_core *core;
|
||||
osi_thread_t *thread;
|
||||
uint8_t queue_idx;
|
||||
bool ret;
|
||||
bool should_free = false;
|
||||
|
||||
if (!osi_event_acquire(event)) {
|
||||
OSI_TRACE_EVENT("%s acquire fail ev=%p", __func__, event);
|
||||
if (event == NULL) {
|
||||
return false;
|
||||
}
|
||||
core = &event->core;
|
||||
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
if (!osi_event_can_post_locked(event)) {
|
||||
osi_mutex_unlock(&event->lock);
|
||||
osi_event_release(event);
|
||||
/* Session-event storage is pinned until subsystem deinit, so the hot path
|
||||
* needs only the per-event lock and a queue reference. */
|
||||
osi_mutex_lock(&core->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
if (!osi_event_can_post_locked(core)) {
|
||||
OSI_TRACE_EVENT("%s post fail ev=%p qidx=%u wq_len=%d", __func__, event, core->queue_idx,
|
||||
core->thread ? osi_thread_queue_wait_size(core->thread, core->queue_idx) : -1);
|
||||
osi_mutex_unlock(&core->lock);
|
||||
return false;
|
||||
}
|
||||
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_QUEUED);
|
||||
OSI_EVENT_SET_FLAG(event, OSI_EVENT_FLAG_POSTING);
|
||||
thread = event->thread;
|
||||
queue_idx = event->queue_idx;
|
||||
osi_mutex_unlock(&event->lock);
|
||||
OSI_EVENT_SET_FLAG(core, OSI_EVENT_FLAG_QUEUED);
|
||||
core->ref_count++;
|
||||
thread = core->thread;
|
||||
queue_idx = core->queue_idx;
|
||||
osi_mutex_unlock(&core->lock);
|
||||
|
||||
/* The queued work item owns a reference until the generic handler drains it. */
|
||||
osi_event_retain(event);
|
||||
ret = osi_thread_post(thread, osi_thread_generic_event_handler, event, queue_idx, timeout);
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_POSTING);
|
||||
if (!ret) {
|
||||
// clear "is_queued" when post failure, to allow for following event posts
|
||||
OSI_EVENT_CLEAR_FLAG(event, OSI_EVENT_FLAG_QUEUED);
|
||||
}
|
||||
osi_mutex_unlock(&event->lock);
|
||||
ret = osi_thread_post(thread, osi_thread_generic_event_handler, core, queue_idx, timeout);
|
||||
|
||||
osi_mutex_lock(&core->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
if (!ret) {
|
||||
OSI_TRACE_EVENT("%s enqueue fail ev=%p qidx=%u wq_len=%d",
|
||||
__func__, event, queue_idx,
|
||||
OSI_TRACE_EVENT("%s enqueue fail ev=%p qidx=%u wq_len=%d", __func__, event, queue_idx,
|
||||
osi_thread_queue_wait_size(thread, queue_idx));
|
||||
osi_event_release(event);
|
||||
/* Clear QUEUED on post failure so a later post may enqueue. */
|
||||
OSI_EVENT_CLEAR_FLAG(core, OSI_EVENT_FLAG_QUEUED);
|
||||
should_free = osi_event_release_locked(core);
|
||||
}
|
||||
osi_event_release(event);
|
||||
osi_event_unlock_and_maybe_free(core, should_free);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool osi_dynamic_event_post(struct osi_dynamic_event *event, uint32_t timeout)
|
||||
{
|
||||
struct osi_event_core *core;
|
||||
osi_thread_t *thread;
|
||||
uint8_t queue_idx;
|
||||
bool ret;
|
||||
bool should_free = false;
|
||||
|
||||
if (!osi_dynamic_event_acquire_locked(event)) {
|
||||
return false;
|
||||
}
|
||||
core = &event->core;
|
||||
|
||||
if (!osi_event_can_post_locked(core)) {
|
||||
OSI_TRACE_EVENT("%s post fail ev=%p qidx=%u wq_len=%d", __func__, event, core->queue_idx,
|
||||
core->thread ? osi_thread_queue_wait_size(core->thread, core->queue_idx) : -1);
|
||||
should_free = osi_event_release_locked(core);
|
||||
osi_event_unlock_and_maybe_free(core, should_free);
|
||||
return false;
|
||||
}
|
||||
|
||||
OSI_EVENT_SET_FLAG(core, OSI_EVENT_FLAG_QUEUED);
|
||||
core->ref_count++;
|
||||
thread = core->thread;
|
||||
queue_idx = core->queue_idx;
|
||||
osi_mutex_unlock(&core->lock);
|
||||
|
||||
ret = osi_thread_post(thread, osi_thread_generic_event_handler, core, queue_idx, timeout);
|
||||
|
||||
osi_mutex_lock(&core->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
if (!ret) {
|
||||
OSI_TRACE_EVENT("%s enqueue fail ev=%p qidx=%u wq_len=%d", __func__, event, queue_idx,
|
||||
osi_thread_queue_wait_size(thread, queue_idx));
|
||||
/* Drop the queue ownership taken above; handler will never run. */
|
||||
OSI_EVENT_CLEAR_FLAG(core, OSI_EVENT_FLAG_QUEUED);
|
||||
should_free = osi_event_release_locked(core);
|
||||
}
|
||||
/* Always drop the acquire reference from osi_dynamic_event_acquire_locked. */
|
||||
if (osi_event_release_locked(core)) {
|
||||
should_free = true;
|
||||
}
|
||||
osi_event_unlock_and_maybe_free(core, should_free);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void osi_event_retire(struct osi_event_core *event)
|
||||
{
|
||||
bool should_free;
|
||||
|
||||
osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT);
|
||||
osi_event_mark_deleting_locked(event);
|
||||
should_free = osi_event_release_locked(event);
|
||||
osi_event_unlock_and_maybe_free(event, should_free);
|
||||
}
|
||||
|
||||
int osi_thread_event_init(void)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
do {
|
||||
if (osi_mutex_new(&s_osi_event_lock) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
s_osi_event_list = list_new(NULL);
|
||||
if (s_osi_event_list == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
} while (0);
|
||||
|
||||
if (ret != 0) {
|
||||
osi_thread_event_deinit();
|
||||
if (osi_mutex_new(&s_osi_event_lock) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
s_osi_session_event_list = list_new(NULL);
|
||||
s_osi_dynamic_event_list = list_new(NULL);
|
||||
if (s_osi_session_event_list == NULL || s_osi_dynamic_event_list == NULL) {
|
||||
list_free(s_osi_session_event_list);
|
||||
list_free(s_osi_dynamic_event_list);
|
||||
s_osi_session_event_list = NULL;
|
||||
s_osi_dynamic_event_list = NULL;
|
||||
osi_mutex_free(&s_osi_event_lock);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void osi_thread_event_deinit(void)
|
||||
{
|
||||
if (s_osi_event_list != NULL) {
|
||||
list_free(s_osi_event_list);
|
||||
s_osi_event_list = NULL;
|
||||
if (s_osi_event_lock == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
osi_event_lock();
|
||||
while (s_osi_session_event_list != NULL && !list_is_empty(s_osi_session_event_list)) {
|
||||
struct osi_event *event = (struct osi_event *)list_front(s_osi_session_event_list);
|
||||
list_delete(s_osi_session_event_list, event);
|
||||
osi_event_retire(&event->core);
|
||||
}
|
||||
while (s_osi_dynamic_event_list != NULL && !list_is_empty(s_osi_dynamic_event_list)) {
|
||||
struct osi_dynamic_event *event =
|
||||
(struct osi_dynamic_event *)list_front(s_osi_dynamic_event_list);
|
||||
list_delete(s_osi_dynamic_event_list, event);
|
||||
osi_event_retire(&event->core);
|
||||
}
|
||||
list_free(s_osi_session_event_list);
|
||||
list_free(s_osi_dynamic_event_list);
|
||||
s_osi_session_event_list = NULL;
|
||||
s_osi_dynamic_event_list = NULL;
|
||||
osi_event_unlock();
|
||||
osi_mutex_free(&s_osi_event_lock);
|
||||
}
|
||||
|
||||
|
||||
@@ -1305,7 +1305,7 @@ static void btc_hh_cb_arg_deep_free(btc_msg_t *msg)
|
||||
static void btc_hh_data_path_deinit_inner(btc_hh_device_t *p_dev)
|
||||
{
|
||||
struct pkt_queue *queue;
|
||||
struct osi_event *event;
|
||||
struct osi_dynamic_event *event;
|
||||
|
||||
if (p_dev == NULL) {
|
||||
return;
|
||||
@@ -1319,7 +1319,7 @@ static void btc_hh_data_path_deinit_inner(btc_hh_device_t *p_dev)
|
||||
osi_mutex_unlock(&p_dev->lock);
|
||||
|
||||
if (event != NULL) {
|
||||
osi_event_delete(event);
|
||||
osi_dynamic_event_delete(event);
|
||||
}
|
||||
|
||||
if (queue != NULL) {
|
||||
@@ -1332,7 +1332,7 @@ bool btc_hh_data_enqueue_linked_pkt(pkt_linked_item_t *linked_pkt)
|
||||
tBTA_HH_DATA_PKT *pkt = linked_pkt != NULL ? (tBTA_HH_DATA_PKT *)linked_pkt->data : NULL;
|
||||
btc_hh_device_t *p_dev = pkt != NULL ? btc_hh_find_connected_dev_by_handle(pkt->dev_handle) : NULL;
|
||||
struct pkt_queue *data_queue;
|
||||
struct osi_event *data_ready;
|
||||
struct osi_dynamic_event *data_ready;
|
||||
pkt_linked_item_t *old = NULL;
|
||||
bool enqueue_ok;
|
||||
|
||||
@@ -1357,7 +1357,7 @@ bool btc_hh_data_enqueue_linked_pkt(pkt_linked_item_t *linked_pkt)
|
||||
|
||||
enqueue_ok = pkt_queue_enqueue(data_queue, linked_pkt);
|
||||
if (enqueue_ok && data_ready != NULL) {
|
||||
osi_thread_post_event(data_ready, 0);
|
||||
osi_dynamic_event_post(data_ready, 0);
|
||||
}
|
||||
osi_mutex_unlock(&p_dev->lock);
|
||||
|
||||
@@ -1423,7 +1423,7 @@ static bool btc_hh_data_path_init_inner(btc_hh_device_t *p_dev)
|
||||
{
|
||||
bool result = false;
|
||||
struct pkt_queue *data_queue = NULL;
|
||||
struct osi_event *data_ready = NULL;
|
||||
struct osi_dynamic_event *data_ready = NULL;
|
||||
|
||||
do {
|
||||
if (p_dev == NULL) {
|
||||
@@ -1446,13 +1446,13 @@ static bool btc_hh_data_path_init_inner(btc_hh_device_t *p_dev)
|
||||
break;
|
||||
}
|
||||
|
||||
data_ready = osi_event_create(btc_hh_data_pkt_handler, p_dev);
|
||||
data_ready = osi_dynamic_event_create(btc_hh_data_pkt_handler, p_dev);
|
||||
if (data_ready == NULL) {
|
||||
BTC_TRACE_ERROR("%s: osi_event_create failed", __func__);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!osi_event_bind(data_ready, btc_get_current_thread(), BTC_HH_DATA_QUEUE_IDX)) {
|
||||
if (!osi_dynamic_event_bind(data_ready, btc_get_current_thread(), BTC_HH_DATA_QUEUE_IDX)) {
|
||||
BTC_TRACE_ERROR("%s: osi_event_bind failed", __func__);
|
||||
break;
|
||||
}
|
||||
@@ -1470,7 +1470,7 @@ static bool btc_hh_data_path_init_inner(btc_hh_device_t *p_dev)
|
||||
if (!result) {
|
||||
btc_hh_data_path_deinit_inner(p_dev);
|
||||
if (data_ready != NULL) {
|
||||
osi_event_delete(data_ready);
|
||||
osi_dynamic_event_delete(data_ready);
|
||||
}
|
||||
if (data_queue != NULL) {
|
||||
pkt_queue_destroy(data_queue, bta_hh_co_data_linked_pkt_free);
|
||||
@@ -1550,7 +1550,7 @@ static void btc_hh_data_pkt_handler(void *arg)
|
||||
}
|
||||
|
||||
if (p_dev->data_ready != NULL && !pkt_queue_is_empty(p_dev->data_queue)) {
|
||||
osi_thread_post_event(p_dev->data_ready, 0);
|
||||
osi_dynamic_event_post(p_dev->data_ready, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ typedef struct {
|
||||
uint32_t drop_pkt_cnt;
|
||||
osi_mutex_t lock;
|
||||
struct pkt_queue *data_queue;
|
||||
struct osi_event *data_ready;
|
||||
struct osi_dynamic_event *data_ready;
|
||||
osi_alarm_t *vup_timer;
|
||||
} btc_hh_device_t;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ idf_component_register(SRCS "test_bt_main.c"
|
||||
"test_smp.c"
|
||||
"test_tinycrypt_ecc.c"
|
||||
"test_osal.c"
|
||||
"test_osi_event.c"
|
||||
"test_prf_task.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity bt
|
||||
|
||||
535
components/bt/test_apps/basic_unit_test/main/test_osi_event.c
Normal file
535
components/bt/test_apps/basic_unit_test/main/test_osi_event.c
Normal file
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Unit tests for osi_event (components/bt/common/osi/thread.c): coalesce,
|
||||
* delete-while-queued, drain without user callbacks, stale post after delete,
|
||||
* and re-post after QUEUED is cleared (lost-wakeup regression).
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "osi/thread.h"
|
||||
|
||||
#define TEST_WORKER_STACK 3072
|
||||
#define TEST_HELPER_STACK 2560
|
||||
#define TEST_WAIT_MS 1000
|
||||
#define TEST_NO_RUN_MS 50
|
||||
#define TEST_WORKER_PRIO_HIGH (configMAX_PRIORITIES - 2)
|
||||
#define TEST_WORKER_PRIO_LOW 2
|
||||
|
||||
static SemaphoreHandle_t s_done;
|
||||
static SemaphoreHandle_t s_gate;
|
||||
static SemaphoreHandle_t s_join;
|
||||
static volatile uint32_t s_run_count;
|
||||
static volatile uint32_t s_dummy_count;
|
||||
static struct osi_event *s_event;
|
||||
static volatile bool s_stop_posters;
|
||||
|
||||
static void handler_count(void *context)
|
||||
{
|
||||
(void)context;
|
||||
s_run_count++;
|
||||
xSemaphoreGive(s_done);
|
||||
}
|
||||
|
||||
static void handler_gated(void *context)
|
||||
{
|
||||
(void)context;
|
||||
s_run_count++;
|
||||
xSemaphoreGive(s_done);
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_gate, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
}
|
||||
|
||||
static void handler_repost(void *context)
|
||||
{
|
||||
(void)context;
|
||||
uint32_t n = ++s_run_count;
|
||||
if (n < 3) {
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(s_event, 0));
|
||||
}
|
||||
if (n == 3) {
|
||||
xSemaphoreGive(s_done);
|
||||
}
|
||||
}
|
||||
|
||||
static void handler_self_delete(void *context)
|
||||
{
|
||||
(void)context;
|
||||
s_run_count++;
|
||||
osi_event_delete(s_event);
|
||||
s_event = NULL;
|
||||
xSemaphoreGive(s_done);
|
||||
}
|
||||
|
||||
static void dummy_work(void *context)
|
||||
{
|
||||
(void)context;
|
||||
s_dummy_count++;
|
||||
}
|
||||
|
||||
static osi_thread_t *test_thread_create(int priority, size_t queue_len)
|
||||
{
|
||||
const size_t workqueue_len[] = {queue_len};
|
||||
/* Pin to core 0: OSI_THREAD_CORE_AFFINITY (== 2) is not a valid FreeRTOS
|
||||
* core id / tskNO_AFFINITY and trips xTaskCreatePinnedToCore on ESP32. */
|
||||
osi_thread_t *thread = osi_thread_create("osi_ev_test", TEST_WORKER_STACK, priority,
|
||||
OSI_THREAD_CORE_0, 1, workqueue_len, false);
|
||||
TEST_ASSERT_NOT_NULL(thread);
|
||||
return thread;
|
||||
}
|
||||
|
||||
static struct osi_event *test_event_bind(osi_thread_t *thread, osi_thread_func_t func)
|
||||
{
|
||||
struct osi_event *event = osi_event_create(func, NULL);
|
||||
TEST_ASSERT_NOT_NULL(event);
|
||||
TEST_ASSERT_TRUE(osi_event_bind(event, thread, 0));
|
||||
return event;
|
||||
}
|
||||
|
||||
static struct osi_dynamic_event *test_dynamic_event_bind(osi_thread_t *thread,
|
||||
osi_thread_func_t func)
|
||||
{
|
||||
struct osi_dynamic_event *event = osi_dynamic_event_create(func, NULL);
|
||||
TEST_ASSERT_NOT_NULL(event);
|
||||
TEST_ASSERT_TRUE(osi_dynamic_event_bind(event, thread, 0));
|
||||
return event;
|
||||
}
|
||||
|
||||
static void osi_event_test_begin(void)
|
||||
{
|
||||
s_run_count = 0;
|
||||
s_dummy_count = 0;
|
||||
s_event = NULL;
|
||||
s_stop_posters = false;
|
||||
|
||||
s_done = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(s_done);
|
||||
s_gate = xSemaphoreCreateBinary();
|
||||
TEST_ASSERT_NOT_NULL(s_gate);
|
||||
s_join = xSemaphoreCreateCounting(8, 0);
|
||||
TEST_ASSERT_NOT_NULL(s_join);
|
||||
|
||||
TEST_ASSERT_EQUAL(0, osi_thread_event_init());
|
||||
}
|
||||
|
||||
static void osi_event_test_end(osi_thread_t *thread, struct osi_event *event)
|
||||
{
|
||||
if (event != NULL) {
|
||||
osi_event_delete(event);
|
||||
}
|
||||
if (thread != NULL) {
|
||||
osi_thread_free(thread);
|
||||
}
|
||||
osi_thread_event_deinit();
|
||||
|
||||
vSemaphoreDelete(s_done);
|
||||
s_done = NULL;
|
||||
vSemaphoreDelete(s_gate);
|
||||
s_gate = NULL;
|
||||
vSemaphoreDelete(s_join);
|
||||
s_join = NULL;
|
||||
|
||||
/* Idle task reclaims deleted worker TCBs asynchronously. */
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event duplicate post coalesces until handler runs", "[osi_event]")
|
||||
{
|
||||
UBaseType_t saved_prio = uxTaskPriorityGet(NULL);
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
vTaskPrioritySet(NULL, TEST_WORKER_PRIO_HIGH);
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_LOW, 8);
|
||||
event = test_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(event, 0));
|
||||
TEST_ASSERT_FALSE(osi_thread_post_event(event, 0));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
|
||||
|
||||
vTaskPrioritySet(NULL, saved_prio);
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
|
||||
TEST_ASSERT_EQUAL(pdFALSE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_NO_RUN_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
|
||||
|
||||
osi_event_test_end(thread, event);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event re-post while handler is running is accepted", "[osi_event]")
|
||||
{
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_HIGH, 8);
|
||||
event = test_event_bind(thread, handler_gated);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(event, OSI_THREAD_MAX_TIMEOUT));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
|
||||
|
||||
/* QUEUED is already clear; POSTING must not reject this re-post. */
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(event, 0));
|
||||
xSemaphoreGive(s_gate);
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(2, s_run_count);
|
||||
xSemaphoreGive(s_gate);
|
||||
|
||||
osi_event_test_end(thread, event);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event callback may self-repost", "[osi_event]")
|
||||
{
|
||||
osi_thread_t *thread;
|
||||
|
||||
osi_event_test_begin();
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_HIGH, 8);
|
||||
s_event = test_event_bind(thread, handler_repost);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(s_event, 0));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(3, s_run_count);
|
||||
|
||||
osi_event_test_end(thread, s_event);
|
||||
s_event = NULL;
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event callback may self-delete", "[osi_event]")
|
||||
{
|
||||
osi_thread_t *thread;
|
||||
|
||||
osi_event_test_begin();
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_HIGH, 8);
|
||||
s_event = test_event_bind(thread, handler_self_delete);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(s_event, 0));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
|
||||
TEST_ASSERT_NULL(s_event);
|
||||
|
||||
osi_event_test_end(thread, NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event delete then stale post is rejected", "[osi_event]")
|
||||
{
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_HIGH, 8);
|
||||
event = test_event_bind(thread, handler_count);
|
||||
|
||||
osi_event_delete(event);
|
||||
TEST_ASSERT_FALSE(osi_thread_post_event(event, 0));
|
||||
TEST_ASSERT_FALSE(osi_thread_post_event(NULL, 0));
|
||||
osi_event_delete(event);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
|
||||
|
||||
osi_event_test_end(thread, NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event delete while queued skips callback and drains refs", "[osi_event]")
|
||||
{
|
||||
UBaseType_t saved_prio = uxTaskPriorityGet(NULL);
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
vTaskPrioritySet(NULL, TEST_WORKER_PRIO_HIGH);
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_LOW, 8);
|
||||
event = test_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(event, 0));
|
||||
osi_event_delete(event);
|
||||
vTaskPrioritySet(NULL, saved_prio);
|
||||
|
||||
TEST_ASSERT_EQUAL(pdFALSE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_NO_RUN_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
|
||||
|
||||
osi_event_test_end(thread, NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event thread free drains pending event without callback", "[osi_event]")
|
||||
{
|
||||
UBaseType_t saved_prio = uxTaskPriorityGet(NULL);
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
vTaskPrioritySet(NULL, TEST_WORKER_PRIO_HIGH);
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_LOW, 8);
|
||||
event = test_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(event, 0));
|
||||
osi_thread_free(thread);
|
||||
vTaskPrioritySet(NULL, saved_prio);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
|
||||
osi_event_delete(event);
|
||||
osi_thread_event_deinit();
|
||||
|
||||
vSemaphoreDelete(s_done);
|
||||
s_done = NULL;
|
||||
vSemaphoreDelete(s_gate);
|
||||
s_gate = NULL;
|
||||
vSemaphoreDelete(s_join);
|
||||
s_join = NULL;
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event drain after event subsystem deinit", "[osi_event]")
|
||||
{
|
||||
UBaseType_t saved_prio = uxTaskPriorityGet(NULL);
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
vTaskPrioritySet(NULL, TEST_WORKER_PRIO_HIGH);
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_LOW, 8);
|
||||
event = test_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(event, 0));
|
||||
osi_event_delete(event);
|
||||
osi_thread_event_deinit();
|
||||
osi_thread_free(thread);
|
||||
vTaskPrioritySet(NULL, saved_prio);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
|
||||
|
||||
vSemaphoreDelete(s_done);
|
||||
s_done = NULL;
|
||||
vSemaphoreDelete(s_gate);
|
||||
s_gate = NULL;
|
||||
vSemaphoreDelete(s_join);
|
||||
s_join = NULL;
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event post timeout 0 fails when work queue is full", "[osi_event]")
|
||||
{
|
||||
UBaseType_t saved_prio = uxTaskPriorityGet(NULL);
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *event;
|
||||
const size_t queue_len = 2;
|
||||
|
||||
osi_event_test_begin();
|
||||
vTaskPrioritySet(NULL, TEST_WORKER_PRIO_HIGH);
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_LOW, queue_len);
|
||||
event = test_event_bind(thread, handler_count);
|
||||
|
||||
for (size_t i = 0; i < queue_len; i++) {
|
||||
TEST_ASSERT_TRUE(osi_thread_post(thread, dummy_work, NULL, 0, 0));
|
||||
}
|
||||
TEST_ASSERT_FALSE(osi_thread_post_event(event, 0));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
|
||||
|
||||
/* Queue reservation was rolled back; a later post can succeed once space exists. */
|
||||
vTaskPrioritySet(NULL, saved_prio);
|
||||
TEST_ASSERT_EQUAL(pdFALSE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_NO_RUN_MS)));
|
||||
|
||||
vTaskPrioritySet(NULL, TEST_WORKER_PRIO_HIGH);
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(event, 0));
|
||||
vTaskPrioritySet(NULL, saved_prio);
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
|
||||
|
||||
osi_event_test_end(thread, event);
|
||||
}
|
||||
|
||||
static void poster_task(void *arg)
|
||||
{
|
||||
struct osi_event *event = (struct osi_event *)arg;
|
||||
|
||||
while (!s_stop_posters) {
|
||||
osi_thread_post_event(event, 0);
|
||||
/* Must block, not only yield: two ready posters can pin both CPUs and
|
||||
* starve IDLE*, which trips the task WDT. */
|
||||
vTaskDelay(1);
|
||||
}
|
||||
xSemaphoreGive(s_join);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event concurrent post and delete", "[osi_event]")
|
||||
{
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_HIGH, 16);
|
||||
event = test_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(poster_task, "osi_ev_p1", TEST_HELPER_STACK,
|
||||
event, TEST_WORKER_PRIO_LOW, NULL));
|
||||
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(poster_task, "osi_ev_p2", TEST_HELPER_STACK,
|
||||
event, TEST_WORKER_PRIO_LOW, NULL));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(30));
|
||||
osi_event_delete(event);
|
||||
s_stop_posters = true;
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_join, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_join, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
|
||||
TEST_ASSERT_FALSE(osi_thread_post_event(event, 0));
|
||||
|
||||
osi_event_test_end(thread, NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event create bind post delete stop cycles", "[osi_event]")
|
||||
{
|
||||
for (int cycle = 0; cycle < 8; cycle++) {
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_HIGH, 8);
|
||||
event = test_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(event, OSI_THREAD_MAX_TIMEOUT));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(1, s_run_count);
|
||||
|
||||
osi_event_test_end(thread, event);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("osi_dynamic_event delete then stale post is rejected", "[osi_event]")
|
||||
{
|
||||
osi_thread_t *thread;
|
||||
struct osi_dynamic_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_HIGH, 8);
|
||||
event = test_dynamic_event_bind(thread, handler_count);
|
||||
|
||||
osi_dynamic_event_delete(event);
|
||||
TEST_ASSERT_FALSE(osi_dynamic_event_post(event, 0));
|
||||
TEST_ASSERT_FALSE(osi_dynamic_event_post(NULL, 0));
|
||||
osi_dynamic_event_delete(event);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
|
||||
|
||||
osi_event_test_end(thread, NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_dynamic_event delete while queued skips callback", "[osi_event]")
|
||||
{
|
||||
UBaseType_t saved_prio = uxTaskPriorityGet(NULL);
|
||||
osi_thread_t *thread;
|
||||
struct osi_dynamic_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
vTaskPrioritySet(NULL, TEST_WORKER_PRIO_HIGH);
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_LOW, 8);
|
||||
event = test_dynamic_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_dynamic_event_post(event, 0));
|
||||
osi_dynamic_event_delete(event);
|
||||
vTaskPrioritySet(NULL, saved_prio);
|
||||
|
||||
TEST_ASSERT_EQUAL(pdFALSE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_NO_RUN_MS)));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
|
||||
|
||||
osi_event_test_end(thread, NULL);
|
||||
}
|
||||
|
||||
static void dynamic_poster_task(void *arg)
|
||||
{
|
||||
struct osi_dynamic_event *event = (struct osi_dynamic_event *)arg;
|
||||
|
||||
while (!s_stop_posters) {
|
||||
osi_dynamic_event_post(event, 0);
|
||||
vTaskDelay(1);
|
||||
}
|
||||
xSemaphoreGive(s_join);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_dynamic_event concurrent post and delete", "[osi_event]")
|
||||
{
|
||||
osi_thread_t *thread;
|
||||
struct osi_dynamic_event *event;
|
||||
|
||||
osi_event_test_begin();
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_HIGH, 16);
|
||||
event = test_dynamic_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(dynamic_poster_task, "osi_dev_p1", TEST_HELPER_STACK,
|
||||
event, TEST_WORKER_PRIO_LOW, NULL));
|
||||
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(dynamic_poster_task, "osi_dev_p2", TEST_HELPER_STACK,
|
||||
event, TEST_WORKER_PRIO_LOW, NULL));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(30));
|
||||
osi_dynamic_event_delete(event);
|
||||
s_stop_posters = true;
|
||||
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_join, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_join, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
TEST_ASSERT_FALSE(osi_dynamic_event_post(event, 0));
|
||||
|
||||
osi_event_test_end(thread, NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_dynamic_event create post delete cycles in one session", "[osi_event]")
|
||||
{
|
||||
osi_thread_t *thread;
|
||||
|
||||
osi_event_test_begin();
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_HIGH, 8);
|
||||
|
||||
for (int cycle = 0; cycle < 16; cycle++) {
|
||||
struct osi_dynamic_event *event = test_dynamic_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_dynamic_event_post(event, OSI_THREAD_MAX_TIMEOUT));
|
||||
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done, pdMS_TO_TICKS(TEST_WAIT_MS)));
|
||||
osi_dynamic_event_delete(event);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_UINT32(16, s_run_count);
|
||||
|
||||
osi_event_test_end(thread, NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("osi_event deinit retires queued session and dynamic events", "[osi_event]")
|
||||
{
|
||||
UBaseType_t saved_prio = uxTaskPriorityGet(NULL);
|
||||
osi_thread_t *thread;
|
||||
struct osi_event *session_event;
|
||||
struct osi_dynamic_event *dynamic_event;
|
||||
|
||||
osi_event_test_begin();
|
||||
vTaskPrioritySet(NULL, TEST_WORKER_PRIO_HIGH);
|
||||
thread = test_thread_create(TEST_WORKER_PRIO_LOW, 8);
|
||||
session_event = test_event_bind(thread, handler_count);
|
||||
dynamic_event = test_dynamic_event_bind(thread, handler_count);
|
||||
|
||||
TEST_ASSERT_TRUE(osi_thread_post_event(session_event, 0));
|
||||
TEST_ASSERT_TRUE(osi_dynamic_event_post(dynamic_event, 0));
|
||||
osi_thread_event_deinit();
|
||||
osi_thread_free(thread);
|
||||
vTaskPrioritySet(NULL, saved_prio);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(0, s_run_count);
|
||||
|
||||
vSemaphoreDelete(s_done);
|
||||
s_done = NULL;
|
||||
vSemaphoreDelete(s_gate);
|
||||
s_gate = NULL;
|
||||
vSemaphoreDelete(s_join);
|
||||
s_join = NULL;
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
Reference in New Issue
Block a user