diff --git a/components/bt/common/osi/include/osi/thread.h b/components/bt/common/osi/include/osi/thread.h index d5f150f32a..c9be3375dd 100644 --- a/components/bt/common/osi/include/osi/thread.h +++ b/components/bt/common/osi/include/osi/thread.h @@ -28,6 +28,7 @@ #define OSI_THREAD_MAX_TIMEOUT OSI_SEM_MAX_TIMEOUT struct osi_thread; +struct osi_event; typedef struct osi_thread osi_thread_t; @@ -88,4 +89,42 @@ 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 + * 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. + * 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 + */ +struct osi_event *osi_event_create(osi_thread_func_t func, void *context); + +/* + * brief: Bind an osi_event to a specific work queue for an osi_thread. + * After binding is completed, a function call of API osi_thread_post_event will send a work + * to the workqueue of the thread, with specified queue index. + * param func: event: the pointer to osi_event that is created using osi_event_create + * param thread: the pointer to osi_thread that is created using osi_thread_create + * param queue_idx: the index of the workqueue of the specified osi_thread, with range starting from 0 to work_queue_num - 1 + * return: true if osi_event binds to the thread's workqueue successfully, otherwise false + */ +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 + * param event: the pointer to osi_event + */ +void osi_event_delete(struct osi_event* event); + +/* + * brief: try sending a work to the binded thread's workqueue, so that it can be handled by the worker thread + * param event: pointer to osi_event, created by osi_event_create + * param timeout: post timeout, OSI_THREAD_MAX_TIMEOUT means blocking forever, 0 means never blocking, others means block millisecond + * return: true if the message is enqueued to the thread workqueue, otherwise failed + * note: if the return value of function is false, it is the case that the workqueue of the thread is full, and users + * are expected to post the event sometime later to get the work handled. + */ +bool osi_thread_post_event(struct osi_event *event, uint32_t timeout); + #endif /* __THREAD_H__ */ diff --git a/components/bt/common/osi/thread.c b/components/bt/common/osi/thread.c index 5e0317219c..ba39659bd0 100644 --- a/components/bt/common/osi/thread.c +++ b/components/bt/common/osi/thread.c @@ -23,6 +23,7 @@ #include "freertos/queue.h" #include "osi/semaphore.h" #include "osi/thread.h" +#include "osi/mutex.h" struct work_item { osi_thread_func_t func; @@ -50,6 +51,14 @@ struct osi_thread_start_arg { int error; }; +struct osi_event { + struct work_item item; + osi_mutex_t lock; + uint16_t is_queued; + uint16_t queue_idx; + osi_thread_t *thread; +}; + static const size_t DEFAULT_WORK_QUEUE_CAPACITY = 100; static struct work_queue *osi_work_queue_create(size_t capacity) @@ -364,3 +373,81 @@ int osi_thread_queue_wait_size(osi_thread_t *thread, int wq_idx) return (int)(osi_thead_work_queue_len(thread->work_queues[wq_idx])); } + + +struct osi_event *osi_event_create(osi_thread_func_t func, void *context) +{ + struct osi_event *event = osi_calloc(sizeof(struct osi_event)); + if (event != NULL) { + if (osi_mutex_new(&event->lock) == 0) { + event->item.func = func; + event->item.context = context; + return event; + } + osi_free(event); + } + + return NULL; +} + +void osi_event_delete(struct osi_event* event) +{ + if (event != NULL) { + osi_mutex_free(&event->lock); + memset(event, 0, sizeof(struct osi_event)); + osi_free(event); + } +} + +bool osi_event_bind(struct osi_event* event, osi_thread_t *thread, int queue_idx) +{ + if (event == NULL || event->thread != NULL) { + return false; + } + + if (thread == NULL || queue_idx >= thread->work_queue_num) { + return false; + } + + event->thread = thread; + event->queue_idx = queue_idx; + + return true; +} + +static void osi_thread_generic_event_handler(void *context) +{ + struct osi_event *event = (struct osi_event *)context; + if (event != NULL && event->item.func != NULL) { + osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT); + event->is_queued = 0; + osi_mutex_unlock(&event->lock); + event->item.func(event->item.context); + } +} + +bool osi_thread_post_event(struct osi_event *event, uint32_t timeout) +{ + assert(event != NULL && event->thread != NULL); + assert(event->queue_idx >= 0 && event->queue_idx < event->thread->work_queue_num); + bool ret = false; + if (event->is_queued == 0) { + uint16_t acquire_cnt = 0; + osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT); + event->is_queued += 1; + acquire_cnt = event->is_queued; + osi_mutex_unlock(&event->lock); + + if (acquire_cnt == 1) { + ret = osi_thread_post(event->thread, osi_thread_generic_event_handler, event, event->queue_idx, timeout); + if (!ret) { + // clear "is_queued" when post failure, to allow for following event posts + osi_mutex_lock(&event->lock, OSI_MUTEX_MAX_TIMEOUT); + event->is_queued = 0; + osi_mutex_unlock(&event->lock); + } + } + } + + return ret; +}