diff --git a/TESTS/events/equeue/main.cpp b/TESTS/events/equeue/main.cpp index c5365ee4ee..98288859fb 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) @@ -977,6 +980,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 +1068,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/TESTS/events/queue/main.cpp b/TESTS/events/queue/main.cpp index f52cf70665..8f6b8e45dc 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); diff --git a/UNITTESTS/events/equeue/test_equeue.cpp b/UNITTESTS/events/equeue/test_equeue.cpp index dbc3ffa73f..cb93134b28 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) @@ -994,4 +997,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/UNITTESTS/stubs/EqueuePosix_stub.c b/UNITTESTS/stubs/EqueuePosix_stub.c index 5c0d55d7cc..e3d975e31c 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/EventQueue.h b/events/EventQueue.h index 80b7b117f8..69b97244c2 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 @@ public: /** 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 @@ public: */ 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 @@ public: */ 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 @@ public: 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 @@ public: */ 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 @@ protected: struct context { F f; - context(F f) + constexpr context(F f) : f(f) {} template @@ -1113,7 +1262,7 @@ protected: F f; C0 c0; - context(F f, C0 c0) + constexpr context(F f, C0 c0) : f(f), c0(c0) {} template @@ -1129,7 +1278,7 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 0000000000..71c4b700cb --- /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/equeue.h b/events/equeue.h index 606d792a01..7c3dfbea54 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/mbed_events.h b/events/mbed_events.h index 6243c7ab21..991211cfae 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 3138549538..a64440e39e 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 data buffer + // we can pass (0, NULL) + equeue_create_inplace(&_equeue, 0, NULL); } 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); + } } } diff --git a/events/source/equeue.c b/events/source/equeue.c index 404ab3e494..cd671afa7b 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) ((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 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); diff --git a/events/source/equeue_posix.c b/events/source/equeue_posix.c index 5053223ff5..c1e67b165b 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) diff --git a/events/source/tests/tests.c b/events/source/tests/tests.c index 941d706f92..d5c8e13100 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; }