diff --git a/TESTS/mbedmicro-rtos-mbed/MemoryPool/main.cpp b/TESTS/mbedmicro-rtos-mbed/MemoryPool/main.cpp index c020eac83f..60a5613992 100644 --- a/TESTS/mbedmicro-rtos-mbed/MemoryPool/main.cpp +++ b/TESTS/mbedmicro-rtos-mbed/MemoryPool/main.cpp @@ -20,6 +20,9 @@ using namespace utest::v1; +#define THREAD_STACK_SIZE 512 +#define TEST_TIMEOUT 50 + /* Enum used to select block allocation method. */ typedef enum { ALLOC, CALLOC @@ -450,6 +453,80 @@ void test_mem_pool_free_realloc_first_complex(AllocType atype) } } +/* Test alloc timeout + * + * Given a pool with one slot for int data + * When a thread tries to allocate two blocks with @ TEST_TIMEOUT timeout + * Then first operation succeeds immediately and second fails at the correct time. + */ +void test_mem_pool_timeout() +{ + MemoryPool mem_pool; + + Timer timer; + timer.start(); + + int *item = mem_pool.alloc_for(TEST_TIMEOUT); + TEST_ASSERT_NOT_NULL(item); + TEST_ASSERT_UINT32_WITHIN(TEST_TIMEOUT * 100, 0, timer.read_us()); + + item = mem_pool.alloc_for(TEST_TIMEOUT); + TEST_ASSERT_NULL(item); + TEST_ASSERT_UINT32_WITHIN(TEST_TIMEOUT * 100, TEST_TIMEOUT * 1000, timer.read_us()); + + uint64_t end_time = Kernel::get_ms_count() + TEST_TIMEOUT; + item = mem_pool.alloc_until(end_time); + TEST_ASSERT_NULL(item); + TEST_ASSERT_UINT64_WITHIN(TEST_TIMEOUT * 100, end_time, Kernel::get_ms_count()); +} + +namespace { +struct free_capture { + MemoryPool *pool; + int *item; +}; +} + +static void free_int_item(free_capture *to_free) +{ + ThisThread::sleep_for(TEST_TIMEOUT); + + osStatus status = to_free->pool->free(to_free->item); + TEST_ASSERT_EQUAL(osOK, status); +} + +/** Test alloc wait forever + * + * Given two threads A & B and a pool with one slot for int data + * When thread A allocs a block from the pool and tries to alloc a second one with @a osWaitForever timeout + * Then thread waits for a block to become free in the pool + * When thread B frees the first block from the pool + * Then thread A successfully allocs a block from the pool + */ +void test_mem_pool_waitforever() +{ + Thread t(osPriorityNormal, THREAD_STACK_SIZE); + MemoryPool pool; + + Timer timer; + timer.start(); + + int *item = pool.alloc_for(osWaitForever); + TEST_ASSERT_NOT_NULL(item); + TEST_ASSERT_UINT32_WITHIN(TEST_TIMEOUT * 100, 0, timer.read_us()); + + struct free_capture to_free; + to_free.pool = &pool; + to_free.item = item; + t.start(callback(free_int_item, &to_free)); + + item = pool.alloc_for(osWaitForever); + TEST_ASSERT_EQUAL(item, to_free.item); + TEST_ASSERT_UINT32_WITHIN(TEST_TIMEOUT * 100, TEST_TIMEOUT * 1000, timer.read_us()); + + t.join(); +} + /* Robustness checks for free() function. * Function under test is called with invalid parameters. * @@ -569,6 +646,9 @@ Case cases[] = { Case("Test: fail (out of free blocks).", test_mem_pool_alloc_fail_wrapper), + Case("Test: timeout", test_mem_pool_timeout), + Case("Test: wait forever", test_mem_pool_waitforever), + Case("Test: free() - robust (free called with invalid param - NULL).", free_block_invalid_parameter_null), Case("Test: free() - robust (free called with invalid param).", free_block_invalid_parameter) }; diff --git a/rtos/Mail.h b/rtos/Mail.h index 70f6380d98..dd10509681 100644 --- a/rtos/Mail.h +++ b/rtos/Mail.h @@ -31,6 +31,7 @@ #include "mbed_rtos_storage.h" #include "mbed_rtos1_types.h" +#include "platform/mbed_toolchain.h" #include "platform/NonCopyable.h" #ifndef MBED_NO_GLOBAL_USING_DIRECTIVE @@ -90,32 +91,94 @@ public: return _queue.full(); } - /** Allocate a memory block of type T. + /** Allocate a memory block of type T, without blocking. * - * @param millisec Not used. + * @param millisec Not used (see note). * * @return Pointer to memory block that you can fill with mail or NULL in case error. * * @note You may call this function from ISR context. + * @note If blocking is required, use Mail::alloc_for or Mail::alloc_until */ - T *alloc(uint32_t millisec = 0) + T *alloc(MBED_UNUSED uint32_t millisec = 0) { return _pool.alloc(); } - /** Allocate a memory block of type T, and set memory block to zero. + /** Allocate a memory block of type T, optionally blocking. * - * @param millisec Not used. + * @param millisec Timeout value, or osWaitForever. * * @return Pointer to memory block that you can fill with mail or NULL in case error. * - * @note You may call this function from ISR context. + * @note You may call this function from ISR context if the millisec parameter is set to 0. */ - T *calloc(uint32_t millisec = 0) + T *alloc_for(uint32_t millisec) + { + return _pool.alloc_for(millisec); + } + + /** Allocate a memory block of type T, blocking. + * + * @param millisec Absolute timeout time, referenced to Kernel::get_ms_count(). + * + * @return Pointer to memory block that you can fill with mail or NULL in case error. + * + * @note You cannot call this function from ISR context. + * @note the underlying RTOS may have a limit to the maximum wait time + * due to internal 32-bit computations, but this is guaranteed to work if the + * wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + * the wait will time out earlier than specified. + */ + T *alloc_until(uint64_t millisec) + { + return _pool.alloc_until(millisec); + } + + /** Allocate a memory block of type T, and set memory block to zero. + * + * @param millisec Not used (see note). + * + * @return Pointer to memory block that you can fill with mail or NULL in case error. + * + * @note You may call this function from ISR context if the millisec parameter is set to 0. + * @note If blocking is required, use Mail::calloc_for or Mail::calloc_until + */ + T *calloc(MBED_UNUSED uint32_t millisec = 0) { return _pool.calloc(); } + /** Allocate a memory block of type T, optionally blocking, and set memory block to zero. + * + * @param millisec Timeout value, or osWaitForever. + * + * @return Pointer to memory block that you can fill with mail or NULL in case error. + * + * @note You may call this function from ISR context if the millisec parameter is set to 0. + */ + T *calloc_for(uint32_t millisec) + { + return _pool.calloc_for(millisec); + } + + /** Allocate a memory block of type T, blocking, and set memory block to zero. + * + * @param millisec Absolute timeout time, referenced to Kernel::get_ms_count(). + * + * @return Pointer to memory block that you can fill with mail or NULL in case error. + * + * @note You cannot call this function from ISR context. + * @note the underlying RTOS may have a limit to the maximum wait time + * due to internal 32-bit computations, but this is guaranteed to work if the + * wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + * the wait will time out earlier than specified. + */ + T *calloc_until(uint64_t millisec) + { + return _pool.calloc_until(millisec); + } + /** Put a mail in the queue. * * @param mptr Memory block previously allocated with Mail::alloc or Mail::calloc. diff --git a/rtos/MemoryPool.h b/rtos/MemoryPool.h index 28d9f32244..02c34bdcd4 100644 --- a/rtos/MemoryPool.h +++ b/rtos/MemoryPool.h @@ -75,7 +75,7 @@ public: osMemoryPoolDelete(_id); } - /** Allocate a memory block of type T from a memory pool. + /** Allocate a memory block from a memory pool, without blocking. @return address of the allocated memory block or NULL in case of no memory available. @note You may call this function from ISR context. @@ -85,14 +85,83 @@ public: return (T *)osMemoryPoolAlloc(_id, 0); } - /** Allocate a memory block of type T from a memory pool and set memory block to zero. + /** Allocate a memory block from a memory pool, optionally blocking. + @param millisec timeout value (osWaitForever to wait forever) + @return address of the allocated memory block or NULL in case of no memory available. + + @note You may call this function from ISR context if the millisec parameter is set to 0. + */ + T *alloc_for(uint32_t millisec) + { + return (T *)osMemoryPoolAlloc(_id, millisec); + } + + /** Allocate a memory block from a memory pool, blocking. + @param millisec absolute timeout time, referenced to Kernel::get_ms_count(). + @return address of the allocated memory block or NULL in case of no memory available. + + @note You cannot call this function from ISR context. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the wait will time out earlier than specified. + */ + T *alloc_until(uint64_t millisec) + { + uint64_t now = Kernel::get_ms_count(); + uint32_t delay; + if (now >= millisec) { + delay = 0; + } else if (millisec - now >= osWaitForever) { + delay = osWaitForever - 1; + } else { + delay = millisec - now; + } + return alloc_for(delay); + } + + /** Allocate a memory block from a memory pool, without blocking, and set memory block to zero. @return address of the allocated memory block or NULL in case of no memory available. @note You may call this function from ISR context. */ T *calloc(void) { - T *item = (T *)osMemoryPoolAlloc(_id, 0); + T *item = alloc(); + if (item != NULL) { + memset(item, 0, sizeof(T)); + } + return item; + } + + /** Allocate a memory block from a memory pool, optionally blocking, and set memory block to zero. + @param millisec timeout value (osWaitForever to wait forever) + @return address of the allocated memory block or NULL in case of no memory available. + + @note You may call this function from ISR context if the millisec parameter is set to 0. + */ + T *calloc_for(uint32_t millisec) + { + T *item = alloc_for(millisec); + if (item != NULL) { + memset(item, 0, sizeof(T)); + } + return item; + } + + /** Allocate a memory block from a memory pool, blocking, and set memory block to zero. + @param millisec absolute timeout time, referenced to Kernel::get_ms_count(). + @return address of the allocated memory block or NULL in case of no memory available. + + @note You cannot call this function from ISR context. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the wait will time out earlier than specified. + */ + T *calloc_until(uint64_t millisec) + { + T *item = alloc_until(millisec); if (item != NULL) { memset(item, 0, sizeof(T)); } @@ -109,7 +178,7 @@ public: */ osStatus free(T *block) { - return osMemoryPoolFree(_id, (void *)block); + return osMemoryPoolFree(_id, block); } private: