diff --git a/components/esp_event/CMakeLists.txt b/components/esp_event/CMakeLists.txt index 999e7f2fd..b30234c95 100644 --- a/components/esp_event/CMakeLists.txt +++ b/components/esp_event/CMakeLists.txt @@ -9,4 +9,11 @@ set(COMPONENT_PRIV_INCLUDEDIRS "private_include") set(COMPONENT_REQUIRES log tcpip_adapter) set(COMPONENT_PRIV_REQUIRES ethernet) +set(COMPONENT_ADD_LDFRAGMENTS linker.lf) + register_component() + +if(GCC_NOT_5_2_0 AND CONFIG_EVENT_LOOP_PROFILING) + # uses C11 atomic feature + set_source_files_properties(esp_event.c PROPERTIES COMPILE_FLAGS -std=gnu11) +endif() \ No newline at end of file diff --git a/components/esp_event/Kconfig b/components/esp_event/Kconfig index e14e1e732..f90f792f2 100644 --- a/components/esp_event/Kconfig +++ b/components/esp_event/Kconfig @@ -8,4 +8,18 @@ menu "Event Loop Library" to/recieved by an event loop, number of callbacks involved, number of events dropped to to a full event loop queue, run time of event handlers, and number of times/run time of each event handler. + config POST_EVENTS_FROM_ISR + bool "Support posting events from ISRs" + default y + help + Enable posting events from interrupt handlers. + + config POST_EVENTS_FROM_IRAM_ISR + bool "Support posting events from ISRs placed in IRAM" + default y + depends on POST_EVENTS_FROM_ISR + help + Enable posting events from interrupt handlers placed in IRAM. Enabling this option places API functions + esp_event_post and esp_event_post_to in IRAM. + endmenu diff --git a/components/esp_event/component.mk b/components/esp_event/component.mk index 05135a275..fd3f6fa71 100644 --- a/components/esp_event/component.mk +++ b/components/esp_event/component.mk @@ -3,4 +3,15 @@ # COMPONENT_ADD_INCLUDEDIRS := include COMPONENT_PRIV_INCLUDEDIRS := private_include -COMPONENT_SRCDIRS := . \ No newline at end of file +COMPONENT_SRCDIRS := . + +ifdef CONFIG_EVENT_LOOP_PROFILING + PROFILING_ENABLED := 1 +else + PROFILING_ENABLED := 0 +endif + +ifeq ($(and $(GCC_NOT_5_2_0),$(PROFILING_ENABLED)), 1) +# uses C11 atomic feature +esp_event.o: CFLAGS += -std=gnu11 +endif diff --git a/components/esp_event/default_event_loop.c b/components/esp_event/default_event_loop.c index 91fb78927..3daf5ab8d 100644 --- a/components/esp_event/default_event_loop.c +++ b/components/esp_event/default_event_loop.c @@ -55,6 +55,20 @@ esp_err_t esp_event_post(esp_event_base_t event_base, int32_t event_id, } +#if CONFIG_POST_EVENTS_FROM_ISR +esp_err_t esp_event_isr_post(esp_event_base_t event_base, int32_t event_id, + void* event_data, size_t event_data_size, BaseType_t* task_unblocked) +{ + if (s_default_loop == NULL) { + return ESP_ERR_INVALID_STATE; + } + + return esp_event_isr_post_to(s_default_loop, event_base, event_id, + event_data, event_data_size, task_unblocked); +} +#endif + + esp_err_t esp_event_loop_create_default() { if (s_default_loop) { diff --git a/components/esp_event/esp_event.c b/components/esp_event/esp_event.c index d9f65c4a2..19bf1d8cc 100644 --- a/components/esp_event/esp_event.c +++ b/components/esp_event/esp_event.c @@ -58,6 +58,8 @@ static portMUX_TYPE s_event_loops_spinlock = portMUX_INITIALIZER_UNLOCKED; /* ------------------------- Static Functions ------------------------------- */ #ifdef CONFIG_EVENT_LOOP_PROFILING + + static int esp_event_dump_prepare() { esp_event_loop_instance_t* loop_it; @@ -129,7 +131,11 @@ static void handler_execute(esp_event_loop_instance_t* loop, esp_event_handler_i start = esp_timer_get_time(); #endif // Execute the handler +#if CONFIG_POST_EVENTS_FROM_ISR + (*(handler->handler))(handler->arg, post.base, post.id, post.data_allocd ? post.data.ptr : &post.data.val); +#else (*(handler->handler))(handler->arg, post.base, post.id, post.data); +#endif #ifdef CONFIG_EVENT_LOOP_PROFILING diff = esp_timer_get_time() - start; @@ -371,34 +377,18 @@ static void loop_node_remove_all_handler(esp_event_loop_node_t* loop_node) } } -static esp_err_t post_instance_create(esp_event_base_t event_base, int32_t event_id, void* event_data, int32_t event_data_size, esp_event_post_instance_t* post) +static void inline __attribute__((always_inline)) post_instance_delete(esp_event_post_instance_t* post) { - void* event_data_copy = NULL; - - // Make persistent copy of event data on heap. - if (event_data != NULL && event_data_size != 0) { - event_data_copy = calloc(1, event_data_size); - - if (event_data_copy == NULL) { - ESP_LOGE(TAG, "alloc for post data to event %s:%d failed", event_base, event_id); - return ESP_ERR_NO_MEM; - } - - memcpy(event_data_copy, event_data, event_data_size); +#if CONFIG_POST_EVENTS_FROM_ISR + if (post->data_allocd && post->data.ptr) { + free(post->data.ptr); } - - post->base = event_base; - post->id = event_id; - post->data = event_data_copy; - - ESP_LOGD(TAG, "created post for event %s:%d", event_base, event_id); - - return ESP_OK; -} - -static void post_instance_delete(esp_event_post_instance_t* post) -{ - free(post->data); +#else + if (post->data) { + free(post->data); + } +#endif + memset(post, 0, sizeof(*post)); } /* ---------------------------- Public API --------------------------------- */ @@ -556,6 +546,9 @@ esp_err_t esp_event_loop_run(esp_event_loop_handle_t event_loop, TickType_t tick } } + esp_event_base_t base = post.base; + int32_t id = post.id; + post_instance_delete(&post); if (ticks_to_run != portMAX_DELAY) { @@ -576,7 +569,7 @@ esp_err_t esp_event_loop_run(esp_event_loop_handle_t event_loop, TickType_t tick if (!exec) { // No handlers were registered, not even loop/base level handlers - ESP_LOGW(TAG, "no handlers have been registered for event %s:%d posted to loop %p", post.base, post.id, event_loop); + ESP_LOGW(TAG, "no handlers have been registered for event %s:%d posted to loop %p", base, id, event_loop); } } @@ -618,7 +611,7 @@ esp_err_t esp_event_loop_delete(esp_event_loop_handle_t event_loop) // Drop existing posts on the queue esp_event_post_instance_t post; while(xQueueReceive(loop->queue, &post, 0) == pdTRUE) { - free(post.data); + post_instance_delete(&post); } // Cleanup loop @@ -735,25 +728,38 @@ esp_err_t esp_event_handler_unregister_with(esp_event_loop_handle_t event_loop, return ESP_OK; } - esp_err_t esp_event_post_to(esp_event_loop_handle_t event_loop, esp_event_base_t event_base, int32_t event_id, void* event_data, size_t event_data_size, TickType_t ticks_to_wait) { assert(event_loop); if (event_base == ESP_EVENT_ANY_BASE || event_id == ESP_EVENT_ANY_ID) { - ESP_LOGE(TAG, "posting nonspecific event base or id unsupported"); return ESP_ERR_INVALID_ARG; } esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; esp_event_post_instance_t post; - esp_err_t err = post_instance_create(event_base, event_id, event_data, event_data_size, &post); + memset((void*)(&(post.data)), 0, sizeof(post.data)); - if (err != ESP_OK) { - return err; + if (event_data != NULL && event_data_size != 0) { + // Make persistent copy of event data on heap. + void* event_data_copy = calloc(1, event_data_size); + + if (event_data_copy == NULL) { + return ESP_ERR_NO_MEM; + } + + memcpy(event_data_copy, event_data, event_data_size); +#if CONFIG_POST_EVENTS_FROM_ISR + post.data.ptr = event_data_copy; + post.data_allocd = true; +#else + post.data = event_data_copy; +#endif } + post.base = event_base; + post.id = event_id; BaseType_t result = pdFALSE; @@ -785,24 +791,65 @@ esp_err_t esp_event_post_to(esp_event_loop_handle_t event_loop, esp_event_base_t post_instance_delete(&post); #ifdef CONFIG_EVENT_LOOP_PROFILING - xSemaphoreTake(loop->profiling_mutex, portMAX_DELAY); - loop->events_dropped++; - xSemaphoreGive(loop->profiling_mutex); + atomic_fetch_add(&loop->events_dropped, 1); #endif return ESP_ERR_TIMEOUT; } #ifdef CONFIG_EVENT_LOOP_PROFILING - xSemaphoreTake(loop->profiling_mutex, portMAX_DELAY); - loop->events_recieved++; - xSemaphoreGive(loop->profiling_mutex); + atomic_fetch_add(&loop->events_recieved, 1); #endif - ESP_LOGD(TAG, "posted %s:%d to loop %p", post.base, post.id, event_loop); - return ESP_OK; } +#if CONFIG_POST_EVENTS_FROM_ISR +esp_err_t esp_event_isr_post_to(esp_event_loop_handle_t event_loop, esp_event_base_t event_base, int32_t event_id, + void* event_data, size_t event_data_size, BaseType_t* task_unblocked) +{ + assert(event_loop); + + if (event_base == ESP_EVENT_ANY_BASE || event_id == ESP_EVENT_ANY_ID) { + return ESP_ERR_INVALID_ARG; + } + + esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; + + esp_event_post_instance_t post; + memset((void*)(&(post.data)), 0, sizeof(post.data)); + + if (event_data_size > sizeof(post.data.val)) { + return ESP_ERR_INVALID_ARG; + } + + if (event_data != NULL && event_data_size != 0) { + memcpy((void*)(&(post.data.val)), event_data, event_data_size); + post.data_allocd = false; + } + post.base = event_base; + post.id = event_id; + + BaseType_t result = pdFALSE; + + // Post the event from an ISR, + result = xQueueSendToBackFromISR(loop->queue, &post, task_unblocked); + + if (result != pdTRUE) { + post_instance_delete(&post); + +#ifdef CONFIG_EVENT_LOOP_PROFILING + atomic_fetch_add(&loop->events_dropped, 1); +#endif + return ESP_FAIL; + } + +#ifdef CONFIG_EVENT_LOOP_PROFILING + atomic_fetch_add(&loop->events_recieved, 1); +#endif + + return ESP_OK; +} +#endif esp_err_t esp_event_dump(FILE* file) { @@ -826,8 +873,13 @@ esp_err_t esp_event_dump(FILE* file) portENTER_CRITICAL(&s_event_loops_spinlock); SLIST_FOREACH(loop_it, &s_event_loops, next) { + uint32_t events_recieved, events_dropped; + + events_recieved = atomic_load(&loop_it->events_recieved); + events_dropped = atomic_load(&loop_it->events_dropped); + PRINT_DUMP_INFO(dst, sz, LOOP_DUMP_FORMAT, loop_it, loop_it->task != NULL ? loop_it->name : "none" , - loop_it->events_recieved, loop_it->events_dropped); + events_recieved, events_dropped); int sz_bak = sz; diff --git a/components/esp_event/include/esp_event.h b/components/esp_event/include/esp_event.h index f97deaf8b..824266512 100644 --- a/components/esp_event/include/esp_event.h +++ b/components/esp_event/include/esp_event.h @@ -29,7 +29,6 @@ extern "C" { #endif - /// Configuration for creating event loops typedef struct { int32_t queue_size; /**< size of the event loop queue */ @@ -224,16 +223,15 @@ esp_err_t esp_event_handler_unregister_with(esp_event_loop_handle_t event_loop, * handler recieves is always valid. * * @param[in] event_base the event base that identifies the event - * @param[in] event_id the the event id that identifies the event + * @param[in] event_id the event id that identifies the event * @param[in] event_data the data, specific to the event occurence, that gets passed to the handler * @param[in] event_data_size the size of the event data * @param[in] ticks_to_wait number of ticks to block on a full event queue * - * @note posting events from an ISR is not supported - * * @return * - ESP_OK: Success - * - ESP_ERR_TIMEOUT: Time to wait for event queue to unblock expired + * - ESP_ERR_TIMEOUT: Time to wait for event queue to unblock expired, + * queue full when posting from ISR * - ESP_ERR_INVALID_ARG: Invalid combination of event base and event id * - Others: Fail */ @@ -253,16 +251,15 @@ esp_err_t esp_event_post(esp_event_base_t event_base, * * @param[in] event_loop the event loop to post to * @param[in] event_base the event base that identifies the event - * @param[in] event_id the the event id that identifies the event + * @param[in] event_id the event id that identifies the event * @param[in] event_data the data, specific to the event occurence, that gets passed to the handler * @param[in] event_data_size the size of the event data * @param[in] ticks_to_wait number of ticks to block on a full event queue * - * @note posting events from an ISR is not supported - * * @return * - ESP_OK: Success - * - ESP_ERR_TIMEOUT: Time to wait for event queue to unblock expired + * - ESP_ERR_TIMEOUT: Time to wait for event queue to unblock expired, + * queue full when posting from ISR * - ESP_ERR_INVALID_ARG: Invalid combination of event base and event id * - Others: Fail */ @@ -273,6 +270,65 @@ esp_err_t esp_event_post_to(esp_event_loop_handle_t event_loop, size_t event_data_size, TickType_t ticks_to_wait); +#if CONFIG_POST_EVENTS_FROM_ISR +/** + * @brief Special variant of esp_event_post for posting events from interrupt handlers. + * + * @param[in] event_base the event base that identifies the event + * @param[in] event_id the event id that identifies the event + * @param[in] event_data the data, specific to the event occurence, that gets passed to the handler + * @param[in] event_data_size the size of the event data; max is 4 bytes + * @param[out] task_unblocked an optional parameter (can be NULL) which indicates that an event task with + * higher priority than currently running task has been unblocked by the posted event; + * a context switch should be requested before the interrupt is existed. + * + * @note this function is only available when CONFIG_POST_EVENTS_FROM_ISR is enabled + * @note when this function is called from an interrupt handler placed in IRAM, this function should + * be placed in IRAM as well by enabling CONFIG_POST_EVENTS_FROM_IRAM_ISR + * + * @return + * - ESP_OK: Success + * - ESP_FAIL: Event queue for the default event loop full + * - ESP_ERR_INVALID_ARG: Invalid combination of event base and event id, + * data size of more than 4 bytes + * - Others: Fail + */ +esp_err_t esp_event_isr_post(esp_event_base_t event_base, + int32_t event_id, + void* event_data, + size_t event_data_size, + BaseType_t* task_unblocked); + +/** + * @brief Special variant of esp_event_post_to for posting events from interrupt handlers + * + * @param[in] event_base the event base that identifies the event + * @param[in] event_id the event id that identifies the event + * @param[in] event_data the data, specific to the event occurence, that gets passed to the handler + * @param[in] event_data_size the size of the event data + * @param[out] task_unblocked an optional parameter (can be NULL) which indicates that an event task with + * higher priority than currently running task has been unblocked by the posted event; + * a context switch should be requested before the interrupt is existed. + * + * @note this function is only available when CONFIG_POST_EVENTS_FROM_ISR is enabled + * @note when this function is called from an interrupt handler placed in IRAM, this function should + * be placed in IRAM as well by enabling CONFIG_POST_EVENTS_FROM_IRAM_ISR + * + * @return + * - ESP_OK: Success + * - ESP_FAIL: Event queue for the loop full + * - ESP_ERR_INVALID_ARG: Invalid combination of event base and event id, + * data size of more than 4 bytes + * - Others: Fail + */ +esp_err_t esp_event_isr_post_to(esp_event_loop_handle_t event_loop, + esp_event_base_t event_base, + int32_t event_id, + void* event_data, + size_t event_data_size, + BaseType_t* task_unblocked); +#endif + /** * @brief Dumps statistics of all event loops. * diff --git a/components/esp_event/linker.lf b/components/esp_event/linker.lf new file mode 100644 index 000000000..fb02ff7a9 --- /dev/null +++ b/components/esp_event/linker.lf @@ -0,0 +1,6 @@ +if POST_EVENTS_FROM_IRAM_ISR = y: + [mapping:esp_event] + archive: libesp_event.a + entries: + esp_event:esp_event_isr_post_to (noflash) + default_event_loop:esp_event_isr_post (noflash) \ No newline at end of file diff --git a/components/esp_event/private_include/esp_event_internal.h b/components/esp_event/private_include/esp_event_internal.h index 734def586..4566ae1bf 100644 --- a/components/esp_event/private_include/esp_event_internal.h +++ b/components/esp_event/private_include/esp_event_internal.h @@ -16,6 +16,7 @@ #define ESP_EVENT_INTERNAL_H_ #include "esp_event.h" +#include "stdatomic.h" #ifdef __cplusplus extern "C" { @@ -77,18 +78,30 @@ typedef struct esp_event_loop_instance { esp_event_loop_nodes_t loop_nodes; /**< set of linked lists containing the registered handlers for the loop */ #ifdef CONFIG_EVENT_LOOP_PROFILING - uint32_t events_recieved; /**< number of events successfully posted to the loop */ - uint32_t events_dropped; /**< number of events dropped due to queue being full */ + atomic_uint_least32_t events_recieved; /**< number of events successfully posted to the loop */ + atomic_uint_least32_t events_dropped; /**< number of events dropped due to queue being full */ SemaphoreHandle_t profiling_mutex; /**< mutex used for profiliing */ SLIST_ENTRY(esp_event_loop_instance) next; /**< next event loop in the list */ #endif } esp_event_loop_instance_t; +#if CONFIG_POST_EVENTS_FROM_ISR +typedef union esp_event_post_data { + uint32_t val; + void *ptr; +} esp_event_post_data_t; +#else +typedef void* esp_event_post_data_t; +#endif + /// Event posted to the event queue typedef struct esp_event_post_instance { +#if CONFIG_POST_EVENTS_FROM_ISR + bool data_allocd; /**< indicates whether data is alloc'd */ +#endif esp_event_base_t base; /**< the event base */ int32_t id; /**< the event id */ - void* data; /**< data associated with the event */ + esp_event_post_data_t data; /**< data associated with the event */ } esp_event_post_instance_t; #ifdef __cplusplus diff --git a/components/esp_event/test/CMakeLists.txt b/components/esp_event/test/CMakeLists.txt index 96543b621..287a2ba92 100644 --- a/components/esp_event/test/CMakeLists.txt +++ b/components/esp_event/test/CMakeLists.txt @@ -1,5 +1,5 @@ set(COMPONENT_SRCDIRS ".") set(COMPONENT_PRIV_INCLUDEDIRS "../private_include" ".") -set(COMPONENT_PRIV_REQUIRES unity test_utils esp_event) +set(COMPONENT_PRIV_REQUIRES unity test_utils esp_event driver) register_component() \ No newline at end of file