From 66da6946253aa3dc3e9873e450c0250e7f14361a Mon Sep 17 00:00:00 2001 From: Maciej Bocianski Date: Mon, 12 Aug 2019 15:56:17 +0200 Subject: [PATCH 1/8] equeue: add user allocated event support Allow posting events allocated outside queue memory --- events/equeue.h | 34 +++++++++++++ events/source/equeue.c | 111 ++++++++++++++++++++++++++++++++--------- 2 files changed, 121 insertions(+), 24 deletions(-) diff --git a/events/equeue.h b/events/equeue.h index 606d792a010..7c3dfbea54b 100644 --- a/events/equeue.h +++ b/events/equeue.h @@ -176,6 +176,17 @@ void equeue_event_dtor(void *event, void (*dtor)(void *)); // be passed to equeue_cancel. int equeue_post(equeue_t *queue, void (*cb)(void *), void *event); +// Post an user allocated event onto the event queue +// +// The equeue_post_user_allocated function takes a callback and a pointer +// to an event allocated by user. The specified callback will be executed +// in the context of the event queue's dispatch loop with the allocated +// event as its argument. +// +// The equeue_post_user_allocated function is irq safe and can act as +// a mechanism for moving events out of irq contexts. +void equeue_post_user_allocated(equeue_t *queue, void (*cb)(void *), void *event); + // Cancel an in-flight event // // Attempts to cancel an event referenced by the unique id returned from @@ -191,6 +202,20 @@ int equeue_post(equeue_t *queue, void (*cb)(void *), void *event); // Returning false if invalid id or already started executing. bool equeue_cancel(equeue_t *queue, int id); +// Cancel an in-flight user allocated event +// +// Attempts to cancel an event referenced by its address. +// It is safe to call equeue_cancel_user_allocated after an event +// has already been dispatched. +// +// The equeue_cancel_user_allocated function is irq safe. +// +// If called while the event queue's dispatch loop is active, +// equeue_cancel_user_allocated does not guarantee that the event +// will not not execute after it returns as the event may have +// already begun executing. +bool equeue_cancel_user_allocated(equeue_t *queue, void *event); + // Query how much time is left for delayed event // // If event is delayed, this function can be used to query how much time @@ -200,6 +225,15 @@ bool equeue_cancel(equeue_t *queue, int id); // int equeue_timeleft(equeue_t *q, int id); +// Query how much time is left for delayed user allocated event +// +// If event is delayed, this function can be used to query how much time +// is left until the event is due to be dispatched. +// +// This function is irq safe. +// +int equeue_timeleft_user_allocated(equeue_t *q, void *event); + // Background an event queue onto a single-shot timer // // The provided update function will be called to indicate when the queue diff --git a/events/source/equeue.c b/events/source/equeue.c index 404ab3e494c..f73513269ad 100644 --- a/events/source/equeue.c +++ b/events/source/equeue.c @@ -21,6 +21,9 @@ #include #include +// check if the event is allocaded by user - event address is outside queues internal buffer address range +#define EQUEUE_IS_USER_ALLOCATED_EVENT(e) (((uintptr_t)(e) < (uintptr_t)q->buffer) || ((uintptr_t)(e) > ((uintptr_t)q->slab.data))) + // calculate the relative-difference between absolute times while // correctly handling overflow conditions static inline int equeue_tickdiff(unsigned a, unsigned b) @@ -64,9 +67,15 @@ int equeue_create_inplace(equeue_t *q, size_t size, void *buffer) { // setup queue around provided buffer // ensure buffer and size are aligned - q->buffer = (void *)(((uintptr_t) buffer + sizeof(void *) -1) & ~(sizeof(void *) -1)); - size -= (char *) q->buffer - (char *) buffer; - size &= ~(sizeof(void *) -1); + if (size >= sizeof(void *)) { + q->buffer = (void *)(((uintptr_t) buffer + sizeof(void *) -1) & ~(sizeof(void *) -1)); + size -= (char *) q->buffer - (char *) buffer; + size &= ~(sizeof(void *) -1); + } else { + // don't align when size less then pointer size + // e.g. static queue (size == 1) + q->buffer = buffer; + } q->allocated = 0; @@ -220,15 +229,13 @@ void equeue_dealloc(equeue_t *q, void *p) e->dtor(e + 1); } - equeue_mem_dealloc(q, e); + if (!EQUEUE_IS_USER_ALLOCATED_EVENT(e)) { + equeue_mem_dealloc(q, e); + } } - -// equeue scheduling functions -static int equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned tick) +void equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned tick) { - // setup event and hash local id with buffer offset for unique id - int id = (e->id << q->npw2) | ((unsigned char *)e - q->buffer); e->target = tick + equeue_clampdiff(e->target, tick); e->generation = q->generation; @@ -254,7 +261,6 @@ static int equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned tick) if (e->next) { e->next->ref = &e->next; } - e->sibling = 0; } @@ -267,24 +273,19 @@ static int equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned tick) q->background.update(q->background.timer, equeue_clampdiff(e->target, tick)); } - equeue_mutex_unlock(&q->queuelock); - - return id; } -static struct equeue_event *equeue_unqueue(equeue_t *q, int id) +// equeue scheduling functions +static int equeue_event_id(equeue_t *q, struct equeue_event *e) { - // decode event from unique id and check that the local id matches - struct equeue_event *e = (struct equeue_event *) - &q->buffer[id & ((1 << q->npw2) - 1)]; + // setup event and hash local id with buffer offset for unique id + return ((e->id << q->npw2) | ((unsigned char *)e - q->buffer)); +} +static struct equeue_event *equeue_unqueue_by_address(equeue_t *q, struct equeue_event *e) +{ equeue_mutex_lock(&q->queuelock); - if (e->id != id >> q->npw2) { - equeue_mutex_unlock(&q->queuelock); - return 0; - } - // clear the event and check if already in-flight e->cb = 0; e->period = -1; @@ -310,6 +311,26 @@ static struct equeue_event *equeue_unqueue(equeue_t *q, int id) e->next->ref = e->ref; } } + equeue_mutex_unlock(&q->queuelock); + return e; +} + +static struct equeue_event *equeue_unqueue_by_id(equeue_t *q, int id) +{ + // decode event from unique id and check that the local id matches + struct equeue_event *e = (struct equeue_event *) + &q->buffer[id & ((1 << q->npw2) - 1)]; + + equeue_mutex_lock(&q->queuelock); + if (e->id != id >> q->npw2) { + equeue_mutex_unlock(&q->queuelock); + return 0; + } + + if (0 == equeue_unqueue_by_address(q, e)) { + equeue_mutex_unlock(&q->queuelock); + return 0; + } equeue_incid(q, e); equeue_mutex_unlock(&q->queuelock); @@ -369,18 +390,30 @@ int equeue_post(equeue_t *q, void (*cb)(void *), void *p) e->cb = cb; e->target = tick + e->target; - int id = equeue_enqueue(q, e, tick); + equeue_enqueue(q, e, tick); + int id = equeue_event_id(q, e); equeue_sema_signal(&q->eventsema); return id; } +void equeue_post_user_allocated(equeue_t *q, void (*cb)(void *), void *p) +{ + struct equeue_event *e = (struct equeue_event *)p; + unsigned tick = equeue_tick(); + e->cb = cb; + e->target = tick + e->target; + + equeue_enqueue(q, e, tick); + equeue_sema_signal(&q->eventsema); +} + bool equeue_cancel(equeue_t *q, int id) { if (!id) { return false; } - struct equeue_event *e = equeue_unqueue(q, id); + struct equeue_event *e = equeue_unqueue_by_id(q, id); if (e) { equeue_dealloc(q, e + 1); return true; @@ -389,6 +422,21 @@ bool equeue_cancel(equeue_t *q, int id) } } +bool equeue_cancel_user_allocated(equeue_t *q, void *e) +{ + if (!e) { + return false; + } + + struct equeue_event *_e = equeue_unqueue_by_address(q, e); + if (_e) { + equeue_dealloc(q, _e + 1); + return true; + } else { + return false; + } +} + int equeue_timeleft(equeue_t *q, int id) { int ret = -1; @@ -409,6 +457,21 @@ int equeue_timeleft(equeue_t *q, int id) return ret; } +int equeue_timeleft_user_allocated(equeue_t *q, void *e) +{ + int ret = -1; + + if (!e) { + return -1; + } + + struct equeue_event *_e = (struct equeue_event *)e; + equeue_mutex_lock(&q->queuelock); + ret = equeue_clampdiff(_e->target, equeue_tick()); + equeue_mutex_unlock(&q->queuelock); + return ret; +} + void equeue_break(equeue_t *q) { equeue_mutex_lock(&q->queuelock); From b336f73e7702bd602bf104beace404375b2070e8 Mon Sep 17 00:00:00 2001 From: Maciej Bocianski Date: Thu, 29 Aug 2019 12:31:11 +0200 Subject: [PATCH 2/8] equeue posix impl: make queue mutex reentrant --- UNITTESTS/stubs/EqueuePosix_stub.c | 5 ++++- events/source/equeue_posix.c | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/UNITTESTS/stubs/EqueuePosix_stub.c b/UNITTESTS/stubs/EqueuePosix_stub.c index 5c0d55d7cc8..e3d975e31c4 100644 --- a/UNITTESTS/stubs/EqueuePosix_stub.c +++ b/UNITTESTS/stubs/EqueuePosix_stub.c @@ -40,7 +40,10 @@ unsigned equeue_tick(void) // Mutex operations int equeue_mutex_create(equeue_mutex_t *m) { - return pthread_mutex_init(m, 0); + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + return pthread_mutex_init(m, &attr); } void equeue_mutex_destroy(equeue_mutex_t *m) diff --git a/events/source/equeue_posix.c b/events/source/equeue_posix.c index 5053223ff51..c1e67b165b4 100644 --- a/events/source/equeue_posix.c +++ b/events/source/equeue_posix.c @@ -40,7 +40,10 @@ unsigned equeue_tick(void) // Mutex operations int equeue_mutex_create(equeue_mutex_t *m) { - return pthread_mutex_init(m, 0); + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + return pthread_mutex_init(m, &attr); } void equeue_mutex_destroy(equeue_mutex_t *m) From e7e5cd2aa48f15d84d74ef621846ae39a358c7d4 Mon Sep 17 00:00:00 2001 From: Maciej Bocianski Date: Mon, 26 Aug 2019 13:32:19 +0200 Subject: [PATCH 3/8] equeue tests: add user allocated events tests --- TESTS/events/equeue/main.cpp | 62 ++++++++++++++++++++++++- UNITTESTS/events/equeue/test_equeue.cpp | 61 +++++++++++++++++++++++- events/source/tests/tests.c | 51 ++++++++++++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-) diff --git a/TESTS/events/equeue/main.cpp b/TESTS/events/equeue/main.cpp index c5365ee4eed..79ed14caa5f 100644 --- a/TESTS/events/equeue/main.cpp +++ b/TESTS/events/equeue/main.cpp @@ -977,6 +977,65 @@ static void test_equeue_sibling() equeue_destroy(&q); } +struct user_allocated_event { + struct equeue_event e; + uint8_t touched; +}; + +/** Test that equeue executes user allocated events passed by equeue_post. + * + * Given queue is initialized and its size is set to store one event at max in its internal memory. + * When post events allocated in queues internal memory (what is done by calling equeue_call). + * Then only one event can be posted due to queue memory size. + * When post user allocated events. + * Then number of posted events is not limited by queue memory size. + * When both queue allocaded and user allocated events are posted and equeue_dispatch is called. + * Then both types of events are executed properly. + */ +static void test_equeue_user_allocated_event_post() +{ + equeue_t q; + int err = equeue_create(&q, EQUEUE_EVENT_SIZE); + TEST_ASSERT_EQUAL_INT(0, err); + + uint8_t touched = 0; + user_allocated_event e1 = { { 0, 0, 0, NULL, NULL, NULL, 0, -1, NULL, NULL }, 0 }; + user_allocated_event e2 = { { 0, 0, 0, NULL, NULL, NULL, 1, -1, NULL, NULL }, 0 }; + user_allocated_event e3 = { { 0, 0, 0, NULL, NULL, NULL, 1, -1, NULL, NULL }, 0 }; + user_allocated_event e4 = { { 0, 0, 0, NULL, NULL, NULL, 1, -1, NULL, NULL }, 0 }; + user_allocated_event e5 = { { 0, 0, 0, NULL, NULL, NULL, 0, -1, NULL, NULL }, 0 }; + + TEST_ASSERT_NOT_EQUAL(0, equeue_call(&q, simple_func, &touched)); + TEST_ASSERT_EQUAL_INT(0, equeue_call(&q, simple_func, &touched)); + TEST_ASSERT_EQUAL_INT(0, equeue_call(&q, simple_func, &touched)); + + equeue_post_user_allocated(&q, simple_func, &e1.e); + equeue_post_user_allocated(&q, simple_func, &e2.e); + equeue_post_user_allocated(&q, simple_func, &e3.e); + equeue_post_user_allocated(&q, simple_func, &e4.e); + equeue_post_user_allocated(&q, simple_func, &e5.e); + equeue_cancel_user_allocated(&q, &e3.e); + + equeue_dispatch(&q, 1); + + TEST_ASSERT_EQUAL_UINT8(1, touched); + TEST_ASSERT_EQUAL_UINT8(1, e1.touched); + TEST_ASSERT_EQUAL_UINT8(1, e2.touched); + TEST_ASSERT_EQUAL_UINT8(0, e3.touched); + TEST_ASSERT_EQUAL_UINT8(1, e4.touched); + TEST_ASSERT_EQUAL_UINT8(1, e5.touched); + + equeue_dispatch(&q, 10); + + TEST_ASSERT_EQUAL_UINT8(1, touched); + TEST_ASSERT_EQUAL_UINT8(1, e1.touched); + TEST_ASSERT_EQUAL_UINT8(1, e2.touched); + TEST_ASSERT_EQUAL_UINT8(0, e3.touched); + TEST_ASSERT_EQUAL_UINT8(1, e4.touched); + TEST_ASSERT_EQUAL_UINT8(1, e5.touched); + + equeue_destroy(&q); +} Case cases[] = { Case("simple call test", test_equeue_simple_call), @@ -1006,7 +1065,8 @@ Case cases[] = { Case("fragmenting barrage test", test_equeue_fragmenting_barrage<10>), Case("multithreaded barrage test", test_equeue_multithreaded_barrage<10>), Case("break request cleared on timeout test", test_equeue_break_request_cleared_on_timeout), - Case("sibling test", test_equeue_sibling) + Case("sibling test", test_equeue_sibling), + Case("user allocated event test", test_equeue_user_allocated_event_post) }; diff --git a/UNITTESTS/events/equeue/test_equeue.cpp b/UNITTESTS/events/equeue/test_equeue.cpp index dbc3ffa73f5..0fab6e94195 100644 --- a/UNITTESTS/events/equeue/test_equeue.cpp +++ b/UNITTESTS/events/equeue/test_equeue.cpp @@ -994,4 +994,63 @@ TEST_F(TestEqueue, test_equeue_sibling) equeue_cancel(&q, id1); equeue_cancel(&q, id2); equeue_destroy(&q); -} \ No newline at end of file +} + +/** Test that equeue executes user allocated events passed by equeue_post. + * + * Given queue is initialized and its size is set to store one event at max in its internal memory. + * When post events allocated in queues internal memory (what is done by calling equeue_call). + * Then only one event can be posted due to queue memory size. + * When post user allocated events. + * Then number of posted events is not limited by queue memory size. + * When both queue allocaded and user allocated events are posted and equeue_dispatch is called. + * Then both types of events are executed properly. + */ +TEST_F(TestEqueue, test_equeue_user_allocated_event_post) +{ + struct user_allocated_event { + struct equeue_event e; + uint8_t touched; + }; + equeue_t q; + int err = equeue_create(&q, EQUEUE_EVENT_SIZE); + ASSERT_EQ(0, err); + + uint8_t touched = 0; + user_allocated_event e1 = { { 0, 0, 0, NULL, NULL, NULL, 0, -1, NULL, NULL }, 0 }; + user_allocated_event e2 = { { 0, 0, 0, NULL, NULL, NULL, 1, -1, NULL, NULL }, 0 }; + user_allocated_event e3 = { { 0, 0, 0, NULL, NULL, NULL, 1, -1, NULL, NULL }, 0 }; + user_allocated_event e4 = { { 0, 0, 0, NULL, NULL, NULL, 1, -1, NULL, NULL }, 0 }; + user_allocated_event e5 = { { 0, 0, 0, NULL, NULL, NULL, 0, -1, NULL, NULL }, 0 }; + + EXPECT_NE(0, equeue_call(&q, simple_func, &touched)); + EXPECT_EQ(0, equeue_call(&q, simple_func, &touched)); + EXPECT_EQ(0, equeue_call(&q, simple_func, &touched)); + + equeue_post_user_allocated(&q, simple_func, &e1.e); + equeue_post_user_allocated(&q, simple_func, &e2.e); + equeue_post_user_allocated(&q, simple_func, &e3.e); + equeue_post_user_allocated(&q, simple_func, &e4.e); + equeue_post_user_allocated(&q, simple_func, &e5.e); + equeue_cancel_user_allocated(&q, &e3.e); + + equeue_dispatch(&q, 1); + + EXPECT_EQ(1, touched); + EXPECT_EQ(1, e1.touched); + EXPECT_EQ(1, e2.touched); + EXPECT_EQ(0, e3.touched); + EXPECT_EQ(1, e4.touched); + EXPECT_EQ(1, e5.touched); + + equeue_dispatch(&q, 10); + + EXPECT_EQ(1, touched); + EXPECT_EQ(1, e1.touched); + EXPECT_EQ(1, e2.touched); + EXPECT_EQ(0, e3.touched); + EXPECT_EQ(1, e4.touched); + EXPECT_EQ(1, e5.touched); + + equeue_destroy(&q); +} diff --git a/events/source/tests/tests.c b/events/source/tests/tests.c index 941d706f921..d5c8e131008 100644 --- a/events/source/tests/tests.c +++ b/events/source/tests/tests.c @@ -802,6 +802,56 @@ void sibling_test(void) equeue_destroy(&q); } +struct user_allocated_event { + struct equeue_event e; + bool touched; +}; + +void user_allocated_event_test() +{ + equeue_t q; + int err = equeue_create(&q, EQUEUE_EVENT_SIZE); + test_assert(!err); + + bool touched = false; + struct user_allocated_event e1 = { { 0, 0, 0, NULL, NULL, NULL, 0, -1, NULL, NULL }, 0 }; + struct user_allocated_event e2 = { { 0, 0, 0, NULL, NULL, NULL, 1, -1, NULL, NULL }, 0 }; + struct user_allocated_event e3 = { { 0, 0, 0, NULL, NULL, NULL, 1, -1, NULL, NULL }, 0 }; + struct user_allocated_event e4 = { { 0, 0, 0, NULL, NULL, NULL, 1, -1, NULL, NULL }, 0 }; + struct user_allocated_event e5 = { { 0, 0, 0, NULL, NULL, NULL, 0, -1, NULL, NULL }, 0 }; + + test_assert(0 != equeue_call(&q, simple_func, &touched)); + test_assert(0 == equeue_call(&q, simple_func, &touched)); + test_assert(0 == equeue_call(&q, simple_func, &touched)); + + equeue_post_user_allocated(&q, simple_func, &e1.e); + equeue_post_user_allocated(&q, simple_func, &e2.e); + equeue_post_user_allocated(&q, simple_func, &e3.e); + equeue_post_user_allocated(&q, simple_func, &e4.e); + equeue_post_user_allocated(&q, simple_func, &e5.e); + equeue_cancel_user_allocated(&q, &e3.e); + + equeue_dispatch(&q, 1); + + test_assert(true == touched); + test_assert(true == e1.touched); + test_assert(true == e2.touched); + test_assert(false == e3.touched); + test_assert(true == e4.touched); + test_assert(true == e5.touched); + + equeue_dispatch(&q, 10); + + test_assert(true == touched); + test_assert(true == e1.touched); + test_assert(true == e2.touched); + test_assert(false == e3.touched); + test_assert(true == e4.touched); + test_assert(true == e5.touched); + + equeue_destroy(&q); +} + int main() { printf("beginning tests...\n"); @@ -830,6 +880,7 @@ int main() test_run(multithreaded_barrage_test, 20); test_run(break_request_cleared_on_timeout); test_run(sibling_test); + test_run(user_allocated_event_test); printf("done!\n"); return test_failure; } From 19e33ba44b215750f1ee4cfaa04b659bef88c545 Mon Sep 17 00:00:00 2001 From: Maciej Bocianski Date: Tue, 27 Aug 2019 09:34:32 +0200 Subject: [PATCH 4/8] UserAllocatedEvent implementation UserAllocatedEvent provides mechanism for event posting and dispatching without utilization of queue internal memory. UserAllocatedEvent embeds all underlying event data and doesn't require any memory allocation while posting and dispatching. All of these makes it cannot fail due to memory exhaustion while posting. --- events/EventQueue.h | 163 ++++++++++++- events/UserAllocatedEvent.h | 460 +++++++++++++++++++++++++++++++++++ events/mbed_events.h | 1 + events/source/EventQueue.cpp | 14 +- 4 files changed, 627 insertions(+), 11 deletions(-) create mode 100644 events/UserAllocatedEvent.h diff --git a/events/EventQueue.h b/events/EventQueue.h index 80b7b117f87..9ddacc4ea4d 100644 --- a/events/EventQueue.h +++ b/events/EventQueue.h @@ -45,6 +45,8 @@ namespace events { // Predeclared classes template class Event; +template +class UserAllocatedEvent; /** * \defgroup events_EventQueue EventQueue class @@ -60,10 +62,17 @@ class EventQueue : private mbed::NonCopyable { /** Create an EventQueue * * Create an event queue. The event queue either allocates a buffer of - * the specified size with malloc or uses the user provided buffer. + * the specified size with malloc or uses the user provided buffer or + * uses 1B dummy buffer if 0 size passed. + * + * 0 size queue is a special purpose queue to dispatch static events + * only (see UserAllocatedEvent). Such a queue gives the guarantee + * that no dynamic memory allocation will take place while queue + * creation and events posting & dispatching. * * @param size Size of buffer to use for events in bytes * (default to EVENTS_QUEUE_SIZE) + * If 0 provided then 1B dummy buffer is used * @param buffer Pointer to buffer to use for events * (default to NULL) */ @@ -139,6 +148,34 @@ class EventQueue : private mbed::NonCopyable { */ bool cancel(int id); + /** Cancel an in-flight user allocated event + * + * Attempts to cancel an UserAllocatedEvent referenced by its address + * It is safe to call cancel after an event has already been dispatched. + * + * Event must be valid i.e. event must have not finished executing + * and must have been bound to this queue. + * + * The cancel function is IRQ safe. + * + * If called while the event queue's dispatch loop is active in another thread, + * the cancel function does not guarantee that the event will not execute after it + * returns, as the event may have already begun executing. A call made from + * the same thread as the dispatch loop will always succeed with a valid id. + * + * @param event Address of the event + * @return true if event was successfully cancelled + * false if event was not cancelled (invalid queue or executing already begun) + */ + template + bool cancel(UserAllocatedEvent *event) + { + if (event->_equeue != &_equeue) { + return false; + } + return equeue_cancel_user_allocated(&_equeue, event); + } + /** Query how much time is left for delayed event * * If the event is delayed, this function can be used to query how much time @@ -158,6 +195,33 @@ class EventQueue : private mbed::NonCopyable { */ int time_left(int id); + /** Query how much time is left for delayed UserAllocatedEvent + * + * If the event is delayed, this function can be used to query how much time + * is left until the event is due to be dispatched. + * + * Event must be valid i.e. event must have not finished executing + * and must have been bound to this queue. + * + * This function is IRQ safe. + * + * @param event Address of the event + * + * @return Remaining time in milliseconds or + * 0 if event is already due to be dispatched or + * is currently executing. + * Undefined if id is invalid. + * + */ + template + int time_left(UserAllocatedEvent *event) + { + if (event && event->_equeue != &_equeue) { + return -1; + } + return equeue_timeleft_user_allocated(&_equeue, &event->_e); + } + /** Background an event queue onto a single-shot timer-interrupt * * When updated, the event queue will call the provided update function @@ -597,6 +661,53 @@ class EventQueue : private mbed::NonCopyable { template Event event(mbed::Callback cb, ContextArgs ...context_args); + /** Creates an user allocated event bound to the event queue + * + * Constructs an user allocated event bound to the specified event queue. + * The specified callback acts as the target for the event and is executed + * in the context of the event queue's dispatch loop once posted. + * + * @code + * #include "mbed.h" + * + * void handler(int data) { ... } + * + * class Device { + * public: + * void handler(int data) { ... } + * }; + * + * Device dev; + * + * // queue with not internal storage for dynamic events + * // accepts only user allocated events + * static EventQueue queue(0); + * // Create events + * static auto e1 = make_user_allocated_event(&dev, Device::handler, 2); + * static auto e2 = queue.make_user_allocated_event(handler, 3); + * + * int main() + * { + * e1.call_on(&queue); + * e2.call(); + * + * queue.dispatch(1); + * } + * @endcode + * + * @param f Function to execute when the event is dispatched + * @return Event that will dispatch on the specific queue + */ + template + UserAllocatedEvent make_user_allocated_event(F f, ArgTs... args); + + /** Creates an user allocated event bound to the event queue + * @see EventQueue::make_user_allocated_event + */ + template + UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(T *obj, R(T::*method)(ArgTs... args), ArgTs... args); + + #else /** Calls an event on the queue @@ -1068,12 +1179,50 @@ class EventQueue : private mbed::NonCopyable { */ template Event event(mbed::Callback cb, C0 c0, C1 c1, C2 c2, C3 c3, C4 c4); + + /** Creates an user allocated event bound to the event queue + * + * Constructs an user allocated event bound to the specified event queue. + * The specified callback acts as the target for the event and is executed + * in the context of the event queue's dispatch loop once posted. + * + * @param f Function to execute when the event is dispatched + * @return Event that will dispatch on the specific queue + */ + template + UserAllocatedEvent make_user_allocated_event(F f, ArgTs... args); + + /** Creates an user allocated event bound to the event queue + * @see EventQueue::make_user_allocated_event + */ + template + UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(T *obj, R(T::*method)(ArgTs... args), ArgTs... args); + + /** Creates an user allocated event bound to the event queue + * @see EventQueue::make_user_allocated_event + */ + template + UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(const T *obj, R(T::*method)(ArgTs... args) const, ArgTs... args); + + /** Creates an user allocated event bound to the event queue + * @see EventQueue::make_user_allocated_event + */ + template + UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(volatile T *obj, R(T::*method)(ArgTs... args) volatile, ArgTs... args); + + /** Creates an user allocated event bound to the event queue + * @see EventQueue::make_user_allocated_event + */ + template + UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(const volatile T *obj, R(T::*method)(ArgTs... args) const volatile, ArgTs... args); #endif protected: #if !defined(DOXYGEN_ONLY) template friend class Event; + template + friend class UserAllocatedEvent; struct equeue _equeue; mbed::Callback _update; @@ -1098,7 +1247,7 @@ class EventQueue : private mbed::NonCopyable { struct context { F f; - context(F f) + constexpr context(F f) : f(f) {} template @@ -1113,7 +1262,7 @@ class EventQueue : private mbed::NonCopyable { F f; C0 c0; - context(F f, C0 c0) + constexpr context(F f, C0 c0) : f(f), c0(c0) {} template @@ -1129,7 +1278,7 @@ class EventQueue : private mbed::NonCopyable { C0 c0; C1 c1; - context(F f, C0 c0, C1 c1) + constexpr context(F f, C0 c0, C1 c1) : f(f), c0(c0), c1(c1) {} template @@ -1146,7 +1295,7 @@ class EventQueue : private mbed::NonCopyable { C1 c1; C2 c2; - context(F f, C0 c0, C1 c1, C2 c2) + constexpr context(F f, C0 c0, C1 c1, C2 c2) : f(f), c0(c0), c1(c1), c2(c2) {} template @@ -1164,7 +1313,7 @@ class EventQueue : private mbed::NonCopyable { C2 c2; C3 c3; - context(F f, C0 c0, C1 c1, C2 c2, C3 c3) + constexpr context(F f, C0 c0, C1 c1, C2 c2, C3 c3) : f(f), c0(c0), c1(c1), c2(c2), c3(c3) {} template @@ -1183,7 +1332,7 @@ class EventQueue : private mbed::NonCopyable { C3 c3; C4 c4; - context(F f, C0 c0, C1 c1, C2 c2, C3 c3, C4 c4) + constexpr context(F f, C0 c0, C1 c1, C2 c2, C3 c3, C4 c4) : f(f), c0(c0), c1(c1), c2(c2), c3(c3), c4(c4) {} template diff --git a/events/UserAllocatedEvent.h b/events/UserAllocatedEvent.h new file mode 100644 index 00000000000..71c4b700cbf --- /dev/null +++ b/events/UserAllocatedEvent.h @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2019 ARM Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef USER_ALLOCATED_EVENT_H +#define USER_ALLOCATED_EVENT_H + +#include "events/EventQueue.h" +#include "platform/mbed_assert.h" +#include "platform/mbed_atomic.h" + +namespace events { +/** + * \addtogroup events-public-api Events + * \ingroup mbed-os-public + * @{ + */ +template +class UserAllocatedEvent; + +/** + * \defgroup events_Event UserAllocatedEvent class + * @{ + */ + +/** UserAllocatedEvent + * + * Representation of an static event for fine-grain dispatch control. + * + * UserAllocatedEvent provides mechanism for event posting and dispatching + * without utilization of queue internal memory. It embeds all underlying + * event data and doesn't require any memory allocation while posting and dispatching. + * All of these makes it cannot fail due to memory exhaustion while posting + * + * Usage: + * @code + * #include "mbed.h" + * + * void handler(int data) { ... } + * + * class Device { + * public: + * void handler(int data) { ... } + * }; + * + * Device dev; + * + * // queue with not internal storage for dynamic events + * // accepts only user allocated events + * static EventQueue queue(0); + * // Create events + * static auto e1 = make_user_allocated_event(&dev, Device::handler, 2); + * static auto e2 = queue.make_user_allocated_event(handler, 3); + * + * int main() + * { + * e1.call_on(&queue); + * e2.call(); + * + * queue.dispatch(1); + * } + * @endcode + */ +template +class UserAllocatedEvent { +public: + typedef EventQueue::context C; + + /** Create an event + * + * Constructs an event. The specified callback acts as the target + * for the event and is executed in the context of the + * event queue's dispatch loop once posted. + * + * @param f Function to execute when the event is dispatched + * @param args Arguments to bind to the callback + */ + constexpr UserAllocatedEvent(F f, ArgTs... args) : _e(get_default_equeue_event()), _c(f, args...), _equeue(), _post_ref() + { + } + + /** Create an event + * + * Constructs an event. The specified callback acts as the target + * for the event and is executed in the context of the + * event queue's dispatch loop once posted. + * + * @param queue Event queue to dispatch on + * @param f Function to execute when the event is dispatched + * @param args Arguments to bind to the callback + */ + constexpr UserAllocatedEvent(EventQueue *queue, F f, ArgTs... args) : _e(get_default_equeue_event()), _c(f, args...), _equeue(&queue->_equeue), _post_ref() + { + } + + /** Destructor for events + */ +#if !defined(NDEBUG) + // Remove the user provided destructor in release to allow constexpr optimization + // constexpr requires destructor to be trivial and only default one is treated as trivial + ~UserAllocatedEvent() + { + MBED_ASSERT(!_post_ref); + } +#endif + + /** Posts an event onto the underlying event queue, returning void + * + * The event is posted to the underlying queue and is executed in the + * context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * The post function is IRQ safe and can act as a mechanism for moving + * events out of IRQ contexts. + * + */ + void call() + { + MBED_ASSERT(!_post_ref); + MBED_ASSERT(_equeue); + MBED_UNUSED bool status = post(); + MBED_ASSERT(status); + } + + /** Posts an event onto the event queue passed as argument, returning void + * + * The event is posted to the event queue passed as argument + * and is executed in the context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * The post function is IRQ safe and can act as a mechanism for moving + * events out of IRQ contexts. + * + * @param queue Event queue to dispatch on. Will replace earlier bound EventQueue. + * + */ + void call_on(EventQueue *queue) + { + MBED_ASSERT(!_post_ref); + MBED_UNUSED bool status = post_on(queue); + MBED_ASSERT(status); + } + + /** Posts an event onto the underlying event queue + * + * The event is posted to the event queue passed as argument + * and is executed in the context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * @return False if the event was already posted + * true otherwise + * + */ + bool try_call() + { + return post(); + } + + /** Posts an event onto the event queue passed as argument, + * + * The event is posted to the underlying queue and is executed in the + * context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * @param queue Event queue to dispatch on. Will replace earlier bound EventQueue. + * @return False if the event was already posted + * true otherwise + * + */ + bool try_call_on(EventQueue *queue) + { + return post_on(queue); + } + + /** Posts an event onto the underlying event queue, returning void + * + * The event is posted to the underlying queue and is executed in the + * context of the event queue's dispatch loop. + * + * This call cannot fail due queue memory exhaustion + * because it doesn't allocate any memory + * + * The post function is IRQ safe and can act as a mechanism for moving + * events out of IRQ contexts. + * + */ + void operator()() + { + return call(); + } + + /** Configure the delay of an event + * + * @param delay Millisecond delay before dispatching the event + */ + void delay(int delay) + { + equeue_event_delay(&_e + 1, delay); + } + + /** Configure the period of an event + * + * @param period Millisecond period for repeatedly dispatching an event + */ + void period(int period) + { + equeue_event_period(&_e + 1, period); + } + + /** Cancels posted event + * + * Attempts to cancel posted event. It is safe to call + * cancel after an event has already been dispatched. + * + * The cancel function is IRQ safe. + * + * If called while the event queue's dispatch loop is active, the cancel + * function does not guarantee that the event will not execute after it + * returns, as the event may have already begun executing. + * + * @return true if event was successfully cancelled + */ + bool cancel() + { + return equeue_cancel_user_allocated(_equeue, &_e); + } + + +private: + friend class EventQueue; + struct equeue_event _e; + C _c; + struct equeue *_equeue; + uint8_t _post_ref; + + bool post() + { + if (_post_ref) { + return false; + } + core_util_atomic_incr_u8(&_post_ref, 1); + equeue_post_user_allocated(_equeue, &EventQueue::function_call, &_e); + return true; + } + + bool post_on(EventQueue *queue) + { + if (_post_ref) { + return false; + } + _equeue = &(queue->_equeue); + core_util_atomic_incr_u8(&_post_ref, 1); + equeue_post_user_allocated(_equeue, &EventQueue::function_call, &_e); + return true; + } + + static void event_dtor(void *p) + { + UserAllocatedEvent *instance = (UserAllocatedEvent *)(((equeue_event *)p) - 1); + core_util_atomic_decr_u8(&instance->_post_ref, 1); + MBED_ASSERT(!instance->_post_ref); + } + + constexpr static equeue_event get_default_equeue_event() + { + return equeue_event{ 0, 0, 0, NULL, NULL, NULL, 0, -1, &UserAllocatedEvent::event_dtor, NULL }; + } + +public: + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(T *obj, R(T::*method)(ArgTs...), ArgTs... args) : + UserAllocatedEvent(mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(EventQueue *q, T *obj, R(T::*method)(ArgTs...), ArgTs... args) : + UserAllocatedEvent(q, mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(const T *obj, R(T::*method)(ArgTs...) const, ArgTs... args) : + UserAllocatedEvent(mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(EventQueue *q, const T *obj, R(T::*method)(ArgTs...) const, ArgTs... args) : + UserAllocatedEvent(q, mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(volatile T *obj, R(T::*method)(ArgTs...) volatile, ArgTs... args) : + UserAllocatedEvent(mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(EventQueue *q, volatile T *obj, R(T::*method)(ArgTs...) volatile, ArgTs... args) : + UserAllocatedEvent(q, mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(const volatile T *obj, R(T::*method)(ArgTs...) const volatile, ArgTs... args) : + UserAllocatedEvent(mbed::callback(obj, method), args...) { } + + /** Create an event + * @see UserAllocatedEvent::UserAllocatedEvent + */ + template + constexpr UserAllocatedEvent(EventQueue *q, const volatile T *obj, R(T::*method)(ArgTs...) const volatile, ArgTs... args) : + UserAllocatedEvent(q, mbed::callback(obj, method), args...) { } +}; + +// Convenience functions declared here to avoid cyclic +// dependency between Event and EventQueue +template +UserAllocatedEvent EventQueue::make_user_allocated_event(F f, ArgTs... args) +{ + return UserAllocatedEvent(this, f, args...); +} + +template +UserAllocatedEvent, void(ArgTs...)> EventQueue::make_user_allocated_event(T *obj, R(T::*method)(ArgTs... args), ArgTs... args) +{ + return UserAllocatedEvent, void(ArgTs...)>(this, mbed::callback(obj, method), args...); +} + +template +UserAllocatedEvent, void(ArgTs...)> EventQueue::make_user_allocated_event(const T *obj, R(T::*method)(ArgTs... args) const, ArgTs... args) +{ + return UserAllocatedEvent, void(ArgTs...)>(this, mbed::callback(obj, method), args...); +} + +template +UserAllocatedEvent, void(ArgTs...)> EventQueue::make_user_allocated_event(volatile T *obj, R(T::*method)(ArgTs... args) volatile, ArgTs... args) +{ + return UserAllocatedEvent, void(ArgTs...)>(this, mbed::callback(obj, method), args...); +} + +template +UserAllocatedEvent, void(ArgTs...)> EventQueue::make_user_allocated_event(const volatile T *obj, R(T::*method)(ArgTs... args) const volatile, ArgTs... args) +{ + return UserAllocatedEvent, void(ArgTs...)>(this, mbed::callback(obj, method), args...); +} + + +/** Creates a UserAllocatedEvent object, deducing the target type from the types of arguments + * + * UserAllocatedEvent doesn't utilize EventQueue internal memory, + * therefore it can be posted on the queue without being afraid + * of post fail due to queue memory exhaustion + * + * @return UserAllocatedEvent object instance + * + */ +template +constexpr UserAllocatedEvent make_user_allocated_event(F f, ArgTs... args) +{ + return UserAllocatedEvent(f, args...); +} + +/** Creates a UserAllocatedEvent object, deducing the target type from the types of arguments + * + * UserAllocatedEvent doesn't utilize EventQueue internal memory, + * therefore it can be posted on the queue without being afraid + * of post fail due to queue memory exhaustion + * + * @return UserAllocatedEvent object instance + * + */ +template +constexpr UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(T *obj, R(T::*method)(ArgTs... args), ArgTs... args) +{ + return UserAllocatedEvent, void(ArgTs...)>(mbed::callback(obj, method), args...); +} + +/** Creates a UserAllocatedEvent object, deducing the target type from the types of arguments + * + * UserAllocatedEvent doesn't utilize EventQueue internal memory, + * therefore it can be posted on the queue without being afraid + * of post fail due to queue memory exhaustion + * + * @return UserAllocatedEvent object instance + * + */ +template +constexpr UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(const T *obj, R(T::*method)(ArgTs... args) const, ArgTs... args) +{ + return UserAllocatedEvent, void(ArgTs...)>(mbed::callback(obj, method), args...); +} + +/** Creates a UserAllocatedEvent object, deducing the target type from the types of arguments + * + * UserAllocatedEvent doesn't utilize EventQueue internal memory, + * therefore it can be posted on the queue without being afraid + * of post fail due to queue memory exhaustion + * + * @return UserAllocatedEvent object instance + * + */ +template +constexpr UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(volatile T *obj, R(T::*method)(ArgTs... args) volatile, ArgTs... args) +{ + return UserAllocatedEvent, void(ArgTs...)>(mbed::callback(obj, method), args...); +} + +/** Creates a UserAllocatedEvent object, deducing the target type from the types of arguments + * + * UserAllocatedEvent doesn't utilize EventQueue internal memory, + * therefore it can be posted on the queue without being afraid + * of post fail due to queue memory exhaustion + * + * @return UserAllocatedEvent object instance + * + */ +template +constexpr UserAllocatedEvent, void(ArgTs...)> make_user_allocated_event(const volatile T *obj, R(T::*method)(ArgTs... args) const volatile, ArgTs... args) +{ + return UserAllocatedEvent, void(ArgTs...)>(mbed::callback(obj, method), args...); +} + +/** @}*/ + +/** @}*/ +} +#endif diff --git a/events/mbed_events.h b/events/mbed_events.h index 6243c7ab21d..991211cfae6 100644 --- a/events/mbed_events.h +++ b/events/mbed_events.h @@ -20,6 +20,7 @@ #include "events/EventQueue.h" #include "events/Event.h" +#include "events/UserAllocatedEvent.h" #include "events/mbed_shared_queues.h" diff --git a/events/source/EventQueue.cpp b/events/source/EventQueue.cpp index 3138549538d..89ae0bfc0b3 100644 --- a/events/source/EventQueue.cpp +++ b/events/source/EventQueue.cpp @@ -1,5 +1,5 @@ /* events - * Copyright (c) 2016 ARM Limited + * Copyright (c) 2016-2019 ARM Limited * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,10 +23,16 @@ namespace events { EventQueue::EventQueue(unsigned event_size, unsigned char *event_pointer) { - if (!event_pointer) { - equeue_create(&_equeue, event_size); + if (event_size == 0) { + // As static queue (EventQueue(0)) won't perform any access to its dummy buffer + // set 1B dummy buffer as pointer to itself + equeue_create_inplace(&_equeue, 1, this); } else { - equeue_create_inplace(&_equeue, event_size, event_pointer); + if (!event_pointer) { + equeue_create(&_equeue, event_size); + } else { + equeue_create_inplace(&_equeue, event_size, event_pointer); + } } } From 6440aea8437dd4e0cbc5fad59d60570855bc1fb1 Mon Sep 17 00:00:00 2001 From: Maciej Bocianski Date: Tue, 27 Aug 2019 09:36:59 +0200 Subject: [PATCH 5/8] queue test: add user allocated events test --- TESTS/events/queue/main.cpp | 144 +++++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/TESTS/events/queue/main.cpp b/TESTS/events/queue/main.cpp index f52cf70665a..8f6b8e45dc0 100644 --- a/TESTS/events/queue/main.cpp +++ b/TESTS/events/queue/main.cpp @@ -1,5 +1,5 @@ /* mbed Microcontroller Library - * Copyright (c) 2017 ARM Limited + * Copyright (c) 2017-2019 ARM Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -322,6 +322,145 @@ void time_left_test() TEST_ASSERT_EQUAL(-1, queue.time_left(0)); } +void f5(int a1, int a2, int a3, int a4, int a5) +{ + touched = true; +} + +class EventTest { +public: + EventTest() : counter() {} + void f0() + { + counter++; + } + void f1(int a) + { + counter += a; + } + void f5(int a, int b, int c, int d, int e) + { + counter += a + b + c + d + e; + } + uint32_t counter; +}; + +/** Test that queue executes both dynamic and user allocated events. + * + * Given queue is initialized and its size is set to store three Event at max in its internal memory. + * When post queue allocated event. + * Then only three event can be posted due to queue memory size. + * When post user allocated evens. + * Then number of posted events is not limited by queue memory size. + * When both Event and UserAllocatedEvent are posted and queue dispatch is called. + * Then both types of events are executed properly. + * + */ +void mixed_dynamic_static_events_queue_test() +{ + { + EventQueue queue(9 * EVENTS_EVENT_SIZE); + + EventTest e1_test; + Event e1 = queue.event(&e1_test, &EventTest::f0); + int id1 = e1.post(); + TEST_ASSERT_NOT_EQUAL(0, id1); + EventTest e2_test; + Event e2 = queue.event(&e2_test, &EventTest::f1, 3); + int id2 = e2.post(); + TEST_ASSERT_NOT_EQUAL(0, id2); + EventTest e3_test; + Event e3 = queue.event(&e3_test, &EventTest::f5, 1, 2, 3, 4, 5); + int id3 = e3.post(); + TEST_ASSERT_NOT_EQUAL(0, id3); + + + auto ue0 = make_user_allocated_event(func0); + EventTest ue1_test; + auto ue1 = make_user_allocated_event(&ue1_test, &EventTest::f0); + EventTest ue2_test; + auto ue2 = make_user_allocated_event(&ue2_test, &EventTest::f1, 3); + EventTest ue3_test; + auto ue3 = make_user_allocated_event(&ue3_test, &EventTest::f5, 1, 2, 3, 4, 5); + EventTest ue4_test; + auto ue4 = make_user_allocated_event(&ue4_test, &EventTest::f5, 1, 2, 3, 4, 5); + + touched = false; + + ue0.call_on(&queue); + TEST_ASSERT_EQUAL(false, ue0.try_call()); + ue1.call_on(&queue); + TEST_ASSERT_EQUAL(false, ue1.try_call()); + ue2.call_on(&queue); + TEST_ASSERT_EQUAL(false, ue2.try_call()); + ue3.call_on(&queue); + TEST_ASSERT_EQUAL(false, ue3.try_call()); + ue4.call_on(&queue); + ue4.cancel(); + TEST_ASSERT_EQUAL(true, ue4.try_call()); + ue4.cancel(); + e2.cancel(); + + queue.dispatch(1); + + TEST_ASSERT_EQUAL(true, touched); + TEST_ASSERT_EQUAL(1, ue1_test.counter); + TEST_ASSERT_EQUAL(3, ue2_test.counter); + TEST_ASSERT_EQUAL(15, ue3_test.counter); + TEST_ASSERT_EQUAL(0, ue4_test.counter); + TEST_ASSERT_EQUAL(1, e1_test.counter); + TEST_ASSERT_EQUAL(0, e2_test.counter); + TEST_ASSERT_EQUAL(15, e3_test.counter); + } +} + + +static EventQueue g_queue(0); + +/** Test that static queue executes user allocated events. + * + * Given static queue is initialized + * When post user allocated evens. + * Then UserAllocatedEvent are posted and dispatched without any error. + */ +void static_events_queue_test() +{ + // check that no dynamic event can be posted + Event e0 = g_queue.event(func0); + TEST_ASSERT_EQUAL(0, e0.post()); + + auto ue0 = g_queue.make_user_allocated_event(func0); + EventTest test1; + auto ue1 = make_user_allocated_event(&test1, &EventTest::f0); + EventTest test2; + auto ue2 = g_queue.make_user_allocated_event(&test2, &EventTest::f1, 3); + EventTest test3; + auto ue3 = make_user_allocated_event(&test3, &EventTest::f5, 1, 2, 3, 4, 5); + EventTest test4; + auto ue4 = g_queue.make_user_allocated_event(&test4, &EventTest::f5, 1, 2, 3, 4, 5); + + ue0.call(); + TEST_ASSERT_EQUAL(false, ue0.try_call()); + ue1.call_on(&g_queue); + TEST_ASSERT_EQUAL(false, ue1.try_call()); + ue2(); + TEST_ASSERT_EQUAL(false, ue2.try_call()); + ue3.call_on(&g_queue); + TEST_ASSERT_EQUAL(false, ue3.try_call()); + ue4.call(); + ue4.cancel(); + TEST_ASSERT_EQUAL(true, ue4.try_call()); + g_queue.cancel(&ue4); + + g_queue.dispatch(1); + + TEST_ASSERT_EQUAL(1, test1.counter); + TEST_ASSERT_EQUAL(3, test2.counter); + TEST_ASSERT_EQUAL(15, test3.counter); + TEST_ASSERT_EQUAL(0, test4.counter); + +} + // Test setup utest::v1::status_t test_setup(const size_t number_of_cases) { @@ -348,6 +487,9 @@ const Case cases[] = { Case("Testing the event inference", event_inference_test), Case("Testing time_left", time_left_test), + Case("Testing mixed dynamic & static events queue", mixed_dynamic_static_events_queue_test), + Case("Testing static events queue", static_events_queue_test) + }; Specification specification(test_setup, cases); From b45d6d6a769cf488ffd360d3666f471bf575fc0b Mon Sep 17 00:00:00 2001 From: Maciej Bocianski Date: Fri, 30 Aug 2019 09:36:37 +0200 Subject: [PATCH 6/8] queue test: add counter overflow protect without this fix test_equeue_break_no_windup was failing on IAR --- TESTS/events/equeue/main.cpp | 5 ++++- UNITTESTS/events/equeue/test_equeue.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/TESTS/events/equeue/main.cpp b/TESTS/events/equeue/main.cpp index 79ed14caa5f..98288859fbf 100644 --- a/TESTS/events/equeue/main.cpp +++ b/TESTS/events/equeue/main.cpp @@ -39,7 +39,10 @@ static void pass_func(void *eh) static void simple_func(void *p) { - (*(reinterpret_cast(p)))++; + uint8_t *d = reinterpret_cast(p); + if (*d < 255) { + (*d)++; + } } static void sloth_func(void *p) diff --git a/UNITTESTS/events/equeue/test_equeue.cpp b/UNITTESTS/events/equeue/test_equeue.cpp index 0fab6e94195..cb93134b281 100644 --- a/UNITTESTS/events/equeue/test_equeue.cpp +++ b/UNITTESTS/events/equeue/test_equeue.cpp @@ -45,7 +45,10 @@ static void pass_func(void *eh) static void simple_func(void *p) { - (*(reinterpret_cast(p)))++; + uint8_t *d = reinterpret_cast(p); + if (*d < 255) { + (*d)++; + } } static void sloth_func(void *p) From 4c3889dca150c4bb03bcde381246776f7d695a9e Mon Sep 17 00:00:00 2001 From: Maciej Bocianski Date: Fri, 30 Aug 2019 13:20:12 +0200 Subject: [PATCH 7/8] EventQueue: allow passing (0, NULL) on static queue creation --- events/source/EventQueue.cpp | 6 +++--- events/source/equeue.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/events/source/EventQueue.cpp b/events/source/EventQueue.cpp index 89ae0bfc0b3..a64440e39e2 100644 --- a/events/source/EventQueue.cpp +++ b/events/source/EventQueue.cpp @@ -24,9 +24,9 @@ namespace events { EventQueue::EventQueue(unsigned event_size, unsigned char *event_pointer) { if (event_size == 0) { - // As static queue (EventQueue(0)) won't perform any access to its dummy buffer - // set 1B dummy buffer as pointer to itself - equeue_create_inplace(&_equeue, 1, this); + // As static queue (EventQueue(0)) won't perform any access to its data buffer + // we can pass (0, NULL) + equeue_create_inplace(&_equeue, 0, NULL); } else { if (!event_pointer) { equeue_create(&_equeue, event_size); diff --git a/events/source/equeue.c b/events/source/equeue.c index f73513269ad..cd671afa7b2 100644 --- a/events/source/equeue.c +++ b/events/source/equeue.c @@ -22,7 +22,7 @@ #include // check if the event is allocaded by user - event address is outside queues internal buffer address range -#define EQUEUE_IS_USER_ALLOCATED_EVENT(e) (((uintptr_t)(e) < (uintptr_t)q->buffer) || ((uintptr_t)(e) > ((uintptr_t)q->slab.data))) +#define EQUEUE_IS_USER_ALLOCATED_EVENT(e) ((q->buffer == NULL) || ((uintptr_t)(e) < (uintptr_t)q->buffer) || ((uintptr_t)(e) > ((uintptr_t)q->slab.data))) // calculate the relative-difference between absolute times while // correctly handling overflow conditions From b637da37c59cd04f705ed4b5eb6ffa7720e81fba Mon Sep 17 00:00:00 2001 From: Maciej Bocianski Date: Fri, 30 Aug 2019 15:16:18 +0200 Subject: [PATCH 8/8] fix astyle --- events/EventQueue.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events/EventQueue.h b/events/EventQueue.h index 9ddacc4ea4d..69b97244c2f 100644 --- a/events/EventQueue.h +++ b/events/EventQueue.h @@ -66,7 +66,7 @@ class EventQueue : private mbed::NonCopyable { * uses 1B dummy buffer if 0 size passed. * * 0 size queue is a special purpose queue to dispatch static events - * only (see UserAllocatedEvent). Such a queue gives the guarantee + * only (see UserAllocatedEvent). Such a queue gives the guarantee * that no dynamic memory allocation will take place while queue * creation and events posting & dispatching. *