Merge branch 'feature/post_events_from_isr' into 'master'

Support posting events from ISR

See merge request idf/esp-idf!4283
pull/3317/head
Angus Gratton 2019-04-11 09:15:13 +08:00
commit 0b59b6069e
11 zmienionych plików z 320 dodań i 56 usunięć

Wyświetl plik

@ -1521,6 +1521,13 @@ UT_004_16:
- UT_T1_1
- psram
UT_004_17:
<<: *unit_test_template
tags:
- ESP32_IDF
- UT_T1_1
- psram
UT_005_01:
<<: *unit_test_template
tags:

Wyświetl plik

@ -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()

Wyświetl plik

@ -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

Wyświetl plik

@ -3,4 +3,15 @@
#
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_PRIV_INCLUDEDIRS := private_include
COMPONENT_SRCDIRS := .
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

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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;

Wyświetl plik

@ -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.
*

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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()

Wyświetl plik

@ -7,7 +7,11 @@
#include "freertos/FreeRTOS.h"
#include "esp_event_loop.h"
#include "freertos/task.h"
#include "freertos/portmacro.h"
#include "esp_log.h"
#include "soc/timer_group_struct.h"
#include "driver/periph_ctrl.h"
#include "driver/timer.h"
#include "esp_event.h"
#include "esp_event_private.h"
@ -268,6 +272,48 @@ static void test_teardown()
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete_default());
}
#define TIMER_DIVIDER 16 // Hardware timer clock divider
#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) // convert counter value to seconds
#define TIMER_INTERVAL0_SEC (2.0) // sample test interval for the first timer
#if CONFIG_POST_EVENTS_FROM_ISR
static void test_handler_post_from_isr(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
SemaphoreHandle_t *sem = (SemaphoreHandle_t*) event_handler_arg;
// Event data is just the address value (maybe have been truncated due to casting).
int *data = (int*) event_data;
TEST_ASSERT_EQUAL(*data, (int) (*sem));
xSemaphoreGive(*sem);
}
#endif
#if CONFIG_POST_EVENTS_FROM_ISR
void IRAM_ATTR test_event_on_timer_alarm(void* para)
{
/* Retrieve the interrupt status and the counter value
from the timer that reported the interrupt */
TIMERG0.hw_timer[TIMER_0].update = 1;
uint64_t timer_counter_value =
((uint64_t) TIMERG0.hw_timer[TIMER_0].cnt_high) << 32
| TIMERG0.hw_timer[TIMER_0].cnt_low;
TIMERG0.int_clr_timers.t0 = 1;
timer_counter_value += (uint64_t) (TIMER_INTERVAL0_SEC * TIMER_SCALE);
TIMERG0.hw_timer[TIMER_0].alarm_high = (uint32_t) (timer_counter_value >> 32);
TIMERG0.hw_timer[TIMER_0].alarm_low = (uint32_t) timer_counter_value;
int data = (int) para;
// Posting events with data more than 4 bytes should fail.
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_isr_post(s_test_base1, TEST_EVENT_BASE1_EV1, &data, 5, NULL));
// This should succeedd, as data is int-sized. The handler for the event checks that the passed event data
// is correct.
BaseType_t task_unblocked;
TEST_ASSERT_EQUAL(ESP_OK, esp_event_isr_post(s_test_base1, TEST_EVENT_BASE1_EV1, &data, sizeof(data), &task_unblocked));
if (task_unblocked == pdTRUE) {
portYIELD_FROM_ISR();
}
}
#endif //CONFIG_POST_EVENTS_FROM_ISR
TEST_CASE("can create and delete event loops", "[event]")
{
@ -1102,6 +1148,44 @@ TEST_CASE("events are dispatched in the order they are registered", "[event]")
TEST_TEARDOWN();
}
#if CONFIG_POST_EVENTS_FROM_ISR
TEST_CASE("can post events from interrupt handler", "[event]")
{
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
/* Select and initialize basic parameters of the timer */
timer_config_t config;
config.divider = TIMER_DIVIDER;
config.counter_dir = TIMER_COUNT_UP;
config.counter_en = TIMER_PAUSE;
config.alarm_en = TIMER_ALARM_EN;
config.intr_type = TIMER_INTR_LEVEL;
config.auto_reload = false;
timer_init(TIMER_GROUP_0, TIMER_0, &config);
/* Timer's counter will initially start from value below.
Also, if auto_reload is set, this value will be automatically reload on alarm */
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL);
/* Configure the alarm value and the interrupt on alarm. */
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_INTERVAL0_SEC * TIMER_SCALE);
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
timer_isr_register(TIMER_GROUP_0, TIMER_0, test_event_on_timer_alarm,
(void *) sem, ESP_INTR_FLAG_IRAM, NULL);
timer_start(TIMER_GROUP_0, TIMER_0);
TEST_SETUP();
TEST_ASSERT_EQUAL(ESP_OK, esp_event_handler_register(s_test_base1, TEST_EVENT_BASE1_EV1,
test_handler_post_from_isr, &sem));
xSemaphoreTake(sem, portMAX_DELAY);
TEST_TEARDOWN();
}
#endif
#ifdef CONFIG_EVENT_LOOP_PROFILING
TEST_CASE("can dump event loop profile", "[event]")
{