diff --git a/features/frameworks/greentea-client/greentea-client/greentea_metrics.h b/features/frameworks/greentea-client/greentea-client/greentea_metrics.h new file mode 100644 index 00000000000..ff5a451cb51 --- /dev/null +++ b/features/frameworks/greentea-client/greentea-client/greentea_metrics.h @@ -0,0 +1,14 @@ +#ifndef GREENTEA_METRICS_H +#define GREENTEA_METRICS_H + +/** + * Setup platform specific metrics + */ +void greentea_metrics_setup(void); + +/** + * Report and cleanup platform specifc metrics + */ +void greentea_metrics_report(void); + +#endif diff --git a/features/frameworks/greentea-client/source/greentea_metrics.cpp b/features/frameworks/greentea-client/source/greentea_metrics.cpp new file mode 100644 index 00000000000..0dc700ec089 --- /dev/null +++ b/features/frameworks/greentea-client/source/greentea_metrics.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2013-2016, ARM Limited, All Rights Reserved + * 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. + */ + +#include "mbed.h" +#include "rtos.h" +#include "mbed_stats.h" +#include "cmsis_os.h" +#include "greentea-client/test_env.h" +#include "greentea-client/greentea_metrics.h" +#include "SingletonPtr.h" +#include "CircularBuffer.h" + +#define THREAD_BUF_COUNT 16 + +typedef struct { + uint32_t entry; + uint32_t arg; + uint32_t stack_size; + uint32_t max_stack; +} thread_info_t; + +// Mutex to protect "buf" +SingletonPtr mutex; +static char buf[128]; +static SingletonPtr > queue; + +static void send_heap_info(void); +static void send_stack_info(void); +static void on_thread_terminate(osThreadId id); +static void enqeue_thread_info(osThreadId id); +static void deque_and_print_thread_info(void); + +// sprintf uses a lot of stack so use these instead +static uint32_t print_hex(char *buf, uint32_t value); +static uint32_t print_dec(char *buf, uint32_t value); + +void greentea_metrics_setup() +{ +#if defined(MBED_STACK_STATS_ENABLED) && MBED_STACK_STATS_ENABLED + Thread::attach_terminate_hook(on_thread_terminate); +#endif +} + +void greentea_metrics_report() +{ + send_heap_info(); +#if defined(MBED_STACK_STATS_ENABLED) && MBED_STACK_STATS_ENABLED + send_stack_info(); + Thread::attach_terminate_hook(NULL); +#endif +} + +static void send_heap_info() +{ + mbed_stats_heap_t heap_stats; + mbed_stats_heap_get(&heap_stats); + greentea_send_kv("max_heap_usage",heap_stats.max_size); +} + +MBED_UNUSED static void send_stack_info() +{ + mutex->lock(); + + // Flush any queued stack entries + while (!queue->empty()) { + deque_and_print_thread_info(); + } + + // Print info for all other threads + osThreadEnumId enum_id = _osThreadsEnumStart(); + while (true) { + osThreadId thread_id = _osThreadEnumNext(enum_id); + if (NULL == thread_id) { + // End of enumeration + break; + } + enqeue_thread_info(thread_id); + deque_and_print_thread_info(); + } + _osThreadEnumFree(enum_id); + + mutex->unlock(); +} + +MBED_UNUSED static void on_thread_terminate(osThreadId id) +{ + mutex->lock(); + + // There should always be space in the queue + enqeue_thread_info(id); + + // If queue is full then print out a single entry + if (queue->full()) { + deque_and_print_thread_info(); + } + + mutex->unlock(); +} + +static void enqeue_thread_info(osThreadId id) +{ + osEvent info; + thread_info_t thread_info = {}; + info = _osThreadGetInfo(id, osThreadInfoEntry); + if (info.status != osOK) { + return; + } + thread_info.entry = (uint32_t)info.value.p; + info = _osThreadGetInfo(id, osThreadInfoArg); + if (info.status != osOK) { + return; + } + thread_info.arg = (uint32_t)info.value.p; + info = _osThreadGetInfo(id, osThreadInfoStackSize); + if (info.status != osOK) { + return; + } + thread_info.stack_size = (uint32_t)info.value.v; + info = _osThreadGetInfo(id, osThreadInfoStackMax); + if (info.status != osOK) { + return; + } + thread_info.max_stack = (uint32_t)info.value.v; + queue->push(thread_info); +} + +static void deque_and_print_thread_info() +{ + thread_info_t thread_info; + bool ret = queue->pop(thread_info); + MBED_ASSERT(ret); + uint32_t pos = 0; + buf[pos++] = '\"'; + pos += print_hex(buf + pos, thread_info.entry); + buf[pos++] = '-'; + pos += print_hex(buf + pos, thread_info.arg); + buf[pos++] = '\"'; + buf[pos++] = ','; + pos += print_dec(buf + pos, thread_info.max_stack); + buf[pos++] = ','; + pos += print_dec(buf + pos, thread_info.stack_size); + buf[pos++] = 0; + greentea_send_kv("__thread_info", buf); +} + +static uint32_t print_hex(char *buf, uint32_t value) +{ + uint32_t pos = 0; + buf[pos] = '0'; + pos++; + buf[pos] = 'x'; + pos++; + for (int i = 8; i >= 0; i--) { + uint32_t val = (value >> (4 * i)) & 0xF; + if (val <= 9) { + buf[pos] = '0' + val; + pos++; + } else { + buf[pos] = 'a' + val - 10; + pos++; + } + } + return pos; +} + +static uint32_t print_dec(char *buf, uint32_t value) +{ + uint32_t pos = 0; + + // The value 0 is special case + if (0 == value) { + buf[pos] = '0'; + pos++; + return pos; + } + + // Write out value in reverse order + while (value != 0) { + uint32_t next = value / 10; + buf[pos] = '0' + (value - next * 10); + value = next; + pos++; + } + + // Reverse order + for (uint32_t i = 0; i < pos / 2; i++) { + char temp = buf[i]; + buf[i] = buf[pos - 1 - i]; + buf[pos - 1 - i] = temp; + } + + return pos; +} diff --git a/features/frameworks/greentea-client/source/test_env.cpp b/features/frameworks/greentea-client/source/test_env.cpp index bf994fbfb94..083452a59de 100644 --- a/features/frameworks/greentea-client/source/test_env.cpp +++ b/features/frameworks/greentea-client/source/test_env.cpp @@ -21,6 +21,7 @@ #include "mbed.h" #include "greentea-client/test_env.h" #include "greentea-client/greentea_serial.h" +#include "greentea-client/greentea_metrics.h" /** @@ -65,6 +66,7 @@ static void greentea_notify_version(); * This function is blocking. */ void GREENTEA_SETUP(const int timeout, const char *host_test_name) { + greentea_metrics_setup(); // Key-value protocol handshake function. Waits for {{__sync;...}} message // Sync preamble: "{{__sync;0dad4a9d-59a3-4aec-810d-d5fb09d852c1}}" // Example value of sync_uuid == "0dad4a9d-59a3-4aec-810d-d5fb09d852c1" @@ -451,6 +453,7 @@ static void greentea_notify_completion(const int result) { __gcov_flush(); coverage_report = false; #endif + greentea_metrics_report(); greentea_send_kv(GREENTEA_TEST_ENV_END, val); greentea_send_kv(GREENTEA_TEST_ENV_EXIT, 0); } diff --git a/features/frameworks/utest/source/utest_greentea_handlers.cpp b/features/frameworks/utest/source/utest_greentea_handlers.cpp index bfc6858221d..b55407d098f 100644 --- a/features/frameworks/utest/source/utest_greentea_handlers.cpp +++ b/features/frameworks/utest/source/utest_greentea_handlers.cpp @@ -21,7 +21,6 @@ #include "greentea-client/test_env.h" #include "utest/utest_stack_trace.h" #include "utest/utest_serial.h" -#include "mbed_stats.h" using namespace utest::v1; @@ -106,10 +105,7 @@ utest::v1::status_t utest::v1::greentea_test_setup_handler(const size_t number_o void utest::v1::greentea_test_teardown_handler(const size_t passed, const size_t failed, const failure_t failure) { UTEST_LOG_FUNCTION(); - mbed_stats_heap_t heap_stats; verbose_test_teardown_handler(passed, failed, failure); - mbed_stats_heap_get(&heap_stats); - greentea_send_kv("max_heap_usage",heap_stats.max_size); greentea_send_kv(TEST_ENV_TESTCASE_SUMMARY, passed, failed); int result = !(failed || (failure.reason && !(failure.reason & REASON_IGNORE))); GREENTEA_TESTSUITE_RESULT(result); diff --git a/rtos/rtos/Thread.cpp b/rtos/rtos/Thread.cpp index 91e80afe2b5..0822872f784 100644 --- a/rtos/rtos/Thread.cpp +++ b/rtos/rtos/Thread.cpp @@ -30,6 +30,15 @@ extern "C" P_TCB rt_tid2ptcb(osThreadId thread_id); + +static void (*terminate_hook)(osThreadId id) = 0; +extern "C" void thread_terminate_hook(osThreadId id) +{ + if (terminate_hook != (void (*)(osThreadId))NULL) { + terminate_hook(id); + } +} + namespace rtos { void Thread::constructor(osPriority priority, @@ -74,10 +83,7 @@ osStatus Thread::start(Callback task) { _thread_def.pthread = Thread::_thunk; if (_thread_def.stack_pointer == NULL) { _thread_def.stack_pointer = new uint32_t[_thread_def.stacksize/sizeof(uint32_t)]; - if (_thread_def.stack_pointer == NULL) { - _mutex.unlock(); - return osErrorNoMemory; - } + MBED_ASSERT(_thread_def.stack_pointer != NULL); } //Fill the stack with a magic word for maximum usage checking @@ -88,8 +94,12 @@ osStatus Thread::start(Callback task) { _task = task; _tid = osThreadCreate(&_thread_def, this); if (_tid == NULL) { - if (_dynamic_stack) delete[] (_thread_def.stack_pointer); + if (_dynamic_stack) { + delete[] (_thread_def.stack_pointer); + _thread_def.stack_pointer = (uint32_t*)NULL; + } _mutex.unlock(); + _join_sem.release(); return osErrorResource; } @@ -336,12 +346,17 @@ void Thread::attach_idle_hook(void (*fptr)(void)) { rtos_attach_idle_hook(fptr); } +void Thread::attach_terminate_hook(void (*fptr)(osThreadId id)) { + terminate_hook = fptr; +} + Thread::~Thread() { // terminate is thread safe terminate(); #ifdef __MBED_CMSIS_RTOS_CM if (_dynamic_stack) { delete[] (_thread_def.stack_pointer); + _thread_def.stack_pointer = (uint32_t*)NULL; } #endif } diff --git a/rtos/rtos/Thread.h b/rtos/rtos/Thread.h index f2b5cb9300a..5c6114f355b 100644 --- a/rtos/rtos/Thread.h +++ b/rtos/rtos/Thread.h @@ -320,6 +320,11 @@ class Thread { */ static void attach_idle_hook(void (*fptr)(void)); + /** Attach a function to be called when a task is killed + @param fptr pointer to the function to be called + */ + static void attach_terminate_hook(void (*fptr)(osThreadId id)); + virtual ~Thread(); private: diff --git a/rtos/rtx/TARGET_CORTEX_A/RTX_Conf_CA.c b/rtos/rtx/TARGET_CORTEX_A/RTX_Conf_CA.c index 83b5820e090..7ef9f6ca051 100644 --- a/rtos/rtx/TARGET_CORTEX_A/RTX_Conf_CA.c +++ b/rtos/rtx/TARGET_CORTEX_A/RTX_Conf_CA.c @@ -320,6 +320,14 @@ void os_error (uint32_t err_code) { for (;;); } +/*---------------------------------------------------------------------------- + * RTX Hooks + *---------------------------------------------------------------------------*/ +extern void thread_terminate_hook(osThreadId id); + +void sysThreadTerminate(osThreadId id) { + thread_terminate_hook(id); +} /*---------------------------------------------------------------------------- * RTX Configuration Functions diff --git a/rtos/rtx/TARGET_CORTEX_A/cmsis_os.h b/rtos/rtx/TARGET_CORTEX_A/cmsis_os.h index 5ead34c9932..5535cdaccea 100644 --- a/rtos/rtx/TARGET_CORTEX_A/cmsis_os.h +++ b/rtos/rtx/TARGET_CORTEX_A/cmsis_os.h @@ -160,6 +160,7 @@ used throughout the whole project. #define osFeature_Semaphore 65535 ///< maximum count for \ref osSemaphoreCreate function #define osFeature_Wait 0 ///< osWait function: 1=available, 0=not available #define osFeature_SysTick 1 ///< osKernelSysTick functions: 1=available, 0=not available +#define osFeature_ThreadEnum 1 ///< Thread enumeration available #if defined (__CC_ARM) #define os_InRegs __value_in_regs // Compiler specific: force struct in registers @@ -223,6 +224,16 @@ typedef enum { osTimerPeriodic = 1 ///< repeating timer } os_timer_type; +typedef enum { + osThreadInfoState, + osThreadInfoStackSize, + osThreadInfoStackMax, + osThreadInfoEntry, + osThreadInfoArg, + + osThreadInfo_reserved = 0x7FFFFFFF ///< prevent from enum down-size compiler optimization. +} osThreadInfo; + /// Entry point of a thread. /// \note MUST REMAIN UNCHANGED: \b os_pthread shall be consistent in every CMSIS-RTOS. typedef void (*os_pthread) (void const *argument); @@ -261,6 +272,8 @@ typedef struct os_messageQ_cb *osMessageQId; /// \note CAN BE CHANGED: \b os_mailQ_cb is implementation specific in every CMSIS-RTOS. typedef struct os_mailQ_cb *osMailQId; +/// Thread enumeration ID identifies the enumeration (pointer to a thread enumeration control block). +typedef uint32_t *osThreadEnumId; /// Thread Definition structure contains startup information of a thread. /// \note CAN BE CHANGED: \b os_thread_def is implementation specific in every CMSIS-RTOS. @@ -448,6 +461,13 @@ osPriority osThreadGetPriority (osThreadId thread_id); uint8_t osThreadGetState (osThreadId thread_id); #endif +/// Get into from an active thread. +/// \param[in] thread_id thread ID obtained by \ref osThreadCreate or \ref osThreadGetId. +/// \param[in] info information to read. +/// \return current state of the thread function. +/// \return requested info that includes the status code. +os_InRegs osEvent _osThreadGetInfo(osThreadId thread_id, osThreadInfo info); + // ==== Generic Wait Functions ==== /// Wait for Timeout (Time Delay). @@ -823,6 +843,26 @@ osStatus osMailFree (osMailQId queue_id, void *mail); #endif // Mail Queues available +// ==== Thread Enumeration Functions ==== + +#if (defined (osFeature_ThreadEnum) && (osFeature_ThreadEnum != 0)) // Thread enumeration available + +/// Start a thread enumeration. +/// \return an enumeration ID or NULL on error. +osThreadEnumId _osThreadsEnumStart(void); + +/// Get the next task ID in the enumeration. +/// \return a thread ID or NULL on if the end of the enumeration has been reached. +osThreadId _osThreadEnumNext(osThreadEnumId enum_id); + +/// Free the enumeration structure. +/// \param[in] enum_id pointer to the enumeration ID that was obtained with \ref _osThreadsEnumStart. +/// \return status code that indicates the execution status of the function. +osStatus _osThreadEnumFree(osThreadEnumId enum_id); + +#endif // Thread Enumeration available + + // ==== RTX Extensions ==== /// os_suspend: http://www.keil.com/support/man/docs/rlarm/rlarm_os_suspend.htm diff --git a/rtos/rtx/TARGET_CORTEX_A/rt_CMSIS.c b/rtos/rtx/TARGET_CORTEX_A/rt_CMSIS.c index 97218d0fecb..51093fa8afb 100644 --- a/rtos/rtx/TARGET_CORTEX_A/rt_CMSIS.c +++ b/rtos/rtx/TARGET_CORTEX_A/rt_CMSIS.c @@ -479,6 +479,10 @@ extern osMessageQId osMessageQId_osTimerMessageQ; extern U32 IRQNestLevel; /* Indicates whether inside an ISR, and the depth of nesting. 0 = not in ISR. */ +// Thread creation and destruction +osMutexDef(osThreadMutex); +osMutexId osMutexId_osThreadMutex; +void sysThreadTerminate(osThreadId id); // ==== Helper Functions ==== @@ -596,6 +600,8 @@ osStatus svcKernelInitialize (void) { // Create OS Timers resources (Message Queue & Thread) osMessageQId_osTimerMessageQ = svcMessageCreate (&os_messageQ_def_osTimerMessageQ, NULL); osThreadId_osTimerThread = svcThreadCreate(&os_thread_def_osTimerThread, NULL); + // Initialize thread mutex + osMutexId_osThreadMutex = osMutexCreate(osMutex(osThreadMutex)); } sysThreadError(osOK); @@ -708,6 +714,7 @@ SVC_1_1(svcThreadTerminate, osStatus, osThreadId, RET SVC_0_1(svcThreadYield, osStatus, RET_osStatus) SVC_2_1(svcThreadSetPriority, osStatus, osThreadId, osPriority, RET_osStatus) SVC_1_1(svcThreadGetPriority, osPriority, osThreadId, RET_osPriority) +SVC_2_3(svcThreadGetInfo, os_InRegs osEvent, osThreadId, osThreadInfo, RET_osEvent) // Thread Service Calls @@ -846,6 +853,80 @@ osPriority svcThreadGetPriority (osThreadId thread_id) { return (osPriority)(ptcb->prio - 1 + osPriorityIdle); } +/// Get info from an active thread +os_InRegs osEvent_type svcThreadGetInfo (osThreadId thread_id, osThreadInfo info) { + P_TCB ptcb; + osEvent ret; + ret.status = osOK; + + ptcb = rt_tid2ptcb(thread_id); // Get TCB pointer + if (ptcb == NULL) { + ret.status = osErrorValue; +#if defined (__GNUC__) && defined (__ARM_PCS_VFP) + osEvent_ret_status; + return; +#else + return osEvent_ret_status; +#endif + } + + if (osThreadInfoStackSize == info) { + uint32_t size; + size = ptcb->priv_stack; + if (0 == size) { + // This is an OS task - always a fixed size + size = os_stackinfo & 0x3FFFF; + } + ret.value.v = size; +#if defined (__GNUC__) && defined (__ARM_PCS_VFP) + osEvent_ret_value; + return; +#else + return osEvent_ret_value; +#endif + } + + if (osThreadInfoStackMax == info) { + // Cortex-A RTX does not have stack init so + // the maximum stack usage cannot be obtained. + ret.status = osErrorResource; +#if defined (__GNUC__) && defined (__ARM_PCS_VFP) + osEvent_ret_status; + return; +#else + return osEvent_ret_status; +#endif + } + + if (osThreadInfoEntry == info) { + ret.value.p = (void*)ptcb->ptask; +#if defined (__GNUC__) && defined (__ARM_PCS_VFP) + osEvent_ret_value; + return; +#else + return osEvent_ret_value; +#endif + } + + if (osThreadInfoArg == info) { + ret.value.p = (void*)ptcb->argv; +#if defined (__GNUC__) && defined (__ARM_PCS_VFP) + osEvent_ret_value; + return; +#else + return osEvent_ret_value; +#endif + } + + // Unsupported option so return error + ret.status = osErrorParameter; +#if defined (__GNUC__) && defined (__ARM_PCS_VFP) + osEvent_ret_status; + return; +#else + return osEvent_ret_status; +#endif +} // Thread Public API @@ -856,7 +937,12 @@ osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument) { // Privileged and not running return svcThreadCreate(thread_def, argument); } else { - return __svcThreadCreate(thread_def, argument); + osThreadId id; + osMutexWait(osMutexId_osThreadMutex, osWaitForever); + // Thread mutex must be held when a thread is created or terminated + id = __svcThreadCreate(thread_def, argument); + osMutexRelease(osMutexId_osThreadMutex); + return id; } } @@ -868,8 +954,14 @@ osThreadId osThreadGetId (void) { /// Terminate execution of a thread and remove it from ActiveThreads osStatus osThreadTerminate (osThreadId thread_id) { + osStatus status; if (__exceptional_mode()) return osErrorISR; // Not allowed in ISR - return __svcThreadTerminate(thread_id); + osMutexWait(osMutexId_osThreadMutex, osWaitForever); + sysThreadTerminate(thread_id); + // Thread mutex must be held when a thread is created or terminated + status = __svcThreadTerminate(thread_id); + osMutexRelease(osMutexId_osThreadMutex); + return status; } /// Pass control to next thread that is in state READY @@ -893,7 +985,14 @@ osPriority osThreadGetPriority (osThreadId thread_id) { /// INTERNAL - Not Public /// Auto Terminate Thread on exit (used implicitly when thread exists) __NO_RETURN void osThreadExit (void) { - __svcThreadTerminate(__svcThreadGetId()); + osThreadId id; + // Thread mutex must be held when a thread is created or terminated + // Note - the mutex will be released automatically by the os when + // the thread is terminated + osMutexWait(osMutexId_osThreadMutex, osWaitForever); + id = __svcThreadGetId(); + sysThreadTerminate(id); + __svcThreadTerminate(id); for (;;); // Should never come here } @@ -911,6 +1010,48 @@ uint8_t osThreadGetState (osThreadId thread_id) { } #endif +/// Get the requested info from the specified active thread +os_InRegs osEvent _osThreadGetInfo(osThreadId thread_id, osThreadInfo info) { + osEvent ret; + if (__exceptional_mode()) { + ret.status = osErrorISR; + return ret; // Not allowed in ISR + } + return __svcThreadGetInfo(thread_id, info); +} + +osThreadEnumId _osThreadsEnumStart() { + static uint32_t thread_enum_index; + osMutexWait(osMutexId_osThreadMutex, osWaitForever); + thread_enum_index = 0; + return &thread_enum_index; +} + +osThreadId _osThreadEnumNext(osThreadEnumId enum_id) { + uint32_t i; + osThreadId id = NULL; + uint32_t *index = (uint32_t*)enum_id; + for (i = *index; i < os_maxtaskrun; i++) { + if (os_active_TCB[i] != NULL) { + id = (osThreadId)os_active_TCB[i]; + break; + } + } + if (i == os_maxtaskrun) { + // Include the idle task at the end of the enumeration + id = &os_idle_TCB; + } + *index = i + 1; + return id; +} + +osStatus _osThreadEnumFree(osThreadEnumId enum_id) { + uint32_t *index = (uint32_t*)enum_id; + *index = 0; + osMutexRelease(osMutexId_osThreadMutex); + return osOK; +} + // ==== Generic Wait Functions ==== // Generic Wait Service Calls declarations diff --git a/rtos/rtx/TARGET_CORTEX_A/rt_TypeDef.h b/rtos/rtx/TARGET_CORTEX_A/rt_TypeDef.h index 832f9ad0083..568c4389ef4 100644 --- a/rtos/rtx/TARGET_CORTEX_A/rt_TypeDef.h +++ b/rtos/rtx/TARGET_CORTEX_A/rt_TypeDef.h @@ -85,6 +85,7 @@ typedef struct OS_TCB { /* Task entry point used for uVision debugger */ FUNCP ptask; /* Task entry address */ + void *argv; /* Task argument */ } *P_TCB; #define TCB_TID 3 /* 'task id' offset */ #define TCB_STACKF 37 /* 'stack_frame' offset */ diff --git a/rtos/rtx/TARGET_CORTEX_M/RTX_Conf_CM.c b/rtos/rtx/TARGET_CORTEX_M/RTX_Conf_CM.c index 562f1addd09..36378726577 100755 --- a/rtos/rtx/TARGET_CORTEX_M/RTX_Conf_CM.c +++ b/rtos/rtx/TARGET_CORTEX_M/RTX_Conf_CM.c @@ -135,7 +135,11 @@ // Initialize thread stack with watermark pattern for analyzing stack usage (current/maximum) in System and Thread Viewer. // Enabling this option increases significantly the execution time of osThreadCreate. #ifndef OS_STKINIT -#define OS_STKINIT 0 + #if (defined(MBED_STACK_STATS_ENABLED) && MBED_STACK_STATS_ENABLED) + #define OS_STKINIT 1 + #else + #define OS_STKINIT 0 + #endif #endif // Processor mode for thread execution @@ -407,6 +411,15 @@ void sysThreadError(osStatus status) { } } +/*---------------------------------------------------------------------------- + * RTX Hooks + *---------------------------------------------------------------------------*/ +extern void thread_terminate_hook(osThreadId id); + +void sysThreadTerminate(osThreadId id) { + thread_terminate_hook(id); +} + /*---------------------------------------------------------------------------- * RTX Configuration Functions *---------------------------------------------------------------------------*/ diff --git a/rtos/rtx/TARGET_CORTEX_M/cmsis_os.h b/rtos/rtx/TARGET_CORTEX_M/cmsis_os.h index ac052ff82b8..f399f1431bb 100644 --- a/rtos/rtx/TARGET_CORTEX_M/cmsis_os.h +++ b/rtos/rtx/TARGET_CORTEX_M/cmsis_os.h @@ -99,6 +99,7 @@ #define osFeature_Semaphore 65535 ///< Maximum count for \ref osSemaphoreCreate function #define osFeature_Wait 0 ///< osWait not available #define osFeature_SysTick 1 ///< osKernelSysTick functions available +#define osFeature_ThreadEnum 1 ///< Thread enumeration available #if defined (__CC_ARM) #define os_InRegs __value_in_regs // Compiler specific: force struct in registers @@ -159,6 +160,16 @@ typedef enum { osTimerPeriodic = 1 ///< repeating timer } os_timer_type; +typedef enum { + osThreadInfoState, + osThreadInfoStackSize, + osThreadInfoStackMax, + osThreadInfoEntry, + osThreadInfoArg, + + osThreadInfo_reserved = 0x7FFFFFFF ///< prevent from enum down-size compiler optimization. +} osThreadInfo; + /// Entry point of a thread. typedef void (*os_pthread) (void const *argument); @@ -188,6 +199,8 @@ typedef struct os_messageQ_cb *osMessageQId; /// Mail ID identifies the mail queue (pointer to a mail queue control block). typedef struct os_mailQ_cb *osMailQId; +/// Thread enumeration ID identifies the enumeration (pointer to a thread enumeration control block). +typedef uint32_t *osThreadEnumId; /// Thread Definition structure contains startup information of a thread. typedef struct os_thread_def { @@ -358,6 +371,13 @@ osPriority osThreadGetPriority (osThreadId thread_id); uint8_t osThreadGetState (osThreadId thread_id); #endif +/// Get into from an active thread. +/// \param[in] thread_id thread ID obtained by \ref osThreadCreate or \ref osThreadGetId. +/// \param[in] info information to read. +/// \return current state of the thread function. +/// \return requested info that includes the status code. +os_InRegs osEvent _osThreadGetInfo(osThreadId thread_id, osThreadInfo info); + // ==== Generic Wait Functions ==== /// Wait for Timeout (Time Delay). @@ -680,6 +700,26 @@ osStatus osMailFree (osMailQId queue_id, void *mail); #endif // Mail Queues available +// ==== Thread Enumeration Functions ==== + +#if (defined (osFeature_ThreadEnum) && (osFeature_ThreadEnum != 0)) // Thread enumeration available + +/// Start a thread enumeration. +/// \return an enumeration ID or NULL on error. +osThreadEnumId _osThreadsEnumStart(void); + +/// Get the next task ID in the enumeration. +/// \return a thread ID or NULL on if the end of the enumeration has been reached. +osThreadId _osThreadEnumNext(osThreadEnumId enum_id); + +/// Free the enumeration structure. +/// \param[in] enum_id pointer to the enumeration ID that was obtained with \ref _osThreadsEnumStart. +/// \return status code that indicates the execution status of the function. +osStatus _osThreadEnumFree(osThreadEnumId enum_id); + +#endif // Thread Enumeration available + + // ==== RTX Extensions ==== /// Suspend the RTX task scheduler. diff --git a/rtos/rtx/TARGET_CORTEX_M/rt_CMSIS.c b/rtos/rtx/TARGET_CORTEX_M/rt_CMSIS.c index 7e08e38f6af..bd24bca59e4 100644 --- a/rtos/rtx/TARGET_CORTEX_M/rt_CMSIS.c +++ b/rtos/rtx/TARGET_CORTEX_M/rt_CMSIS.c @@ -392,6 +392,10 @@ extern osThreadId osThreadId_osTimerThread; extern const osMessageQDef_t os_messageQ_def_osTimerMessageQ; extern osMessageQId osMessageQId_osTimerMessageQ; +// Thread creation and destruction +osMutexDef(osThreadMutex); +osMutexId osMutexId_osThreadMutex; +void sysThreadTerminate(osThreadId id); // ==== Helper Functions ==== @@ -490,6 +494,8 @@ osStatus svcKernelInitialize (void) { // Create OS Timers resources (Message Queue & Thread) osMessageQId_osTimerMessageQ = svcMessageCreate (&os_messageQ_def_osTimerMessageQ, NULL); osThreadId_osTimerThread = svcThreadCreate(&os_thread_def_osTimerThread, NULL, NULL); + // Initialize thread mutex + osMutexId_osThreadMutex = osMutexCreate(osMutex(osThreadMutex)); } sysThreadError(osOK); @@ -632,6 +638,7 @@ SVC_1_1(svcThreadTerminate, osStatus, osThreadId, RET SVC_0_1(svcThreadYield, osStatus, RET_osStatus) SVC_2_1(svcThreadSetPriority, osStatus, osThreadId, osPriority, RET_osStatus) SVC_1_1(svcThreadGetPriority, osPriority, osThreadId, RET_osPriority) +SVC_2_3(svcThreadGetInfo, os_InRegs osEvent, osThreadId, osThreadInfo, RET_osEvent) // Thread Service Calls @@ -791,6 +798,67 @@ osPriority svcThreadGetPriority (osThreadId thread_id) { return (osPriority)(ptcb->prio - 1 + osPriorityIdle); } +/// Get info from an active thread +os_InRegs osEvent_type svcThreadGetInfo (osThreadId thread_id, osThreadInfo info) { + P_TCB ptcb; + osEvent ret; + ret.status = osOK; + + ptcb = rt_tid2ptcb(thread_id); // Get TCB pointer + if (ptcb == NULL) { + ret.status = osErrorValue; + return osEvent_ret_status; + } + + if (osThreadInfoStackSize == info) { + uint32_t size; + size = ptcb->priv_stack; + if (0 == size) { + // This is an OS task - always a fixed size + size = os_stackinfo & 0x3FFFF; + } + ret.value.v = size; + return osEvent_ret_value; + } + + if (osThreadInfoStackMax == info) { + uint32_t i; + uint32_t *stack_ptr; + uint32_t stack_size; + if (!(os_stackinfo & (1 << 28))) { + // Stack init must be turned on for max stack usage + ret.status = osErrorResource; + return osEvent_ret_status; + } + stack_ptr = (uint32_t*)ptcb->stack; + stack_size = ptcb->priv_stack; + if (0 == stack_size) { + // This is an OS task - always a fixed size + stack_size = os_stackinfo & 0x3FFFF; + } + for (i = 1; i ptask; + return osEvent_ret_value; + } + + if (osThreadInfoArg == info) { + ret.value.p = (void*)ptcb->argv; + return osEvent_ret_value; + } + + // Unsupported option so return error + ret.status = osErrorParameter; + return osEvent_ret_status; +} // Thread Public API @@ -806,7 +874,12 @@ osThreadId osThreadContextCreate (const osThreadDef_t *thread_def, void *argumen // Privileged and not running return svcThreadCreate(thread_def, argument, context); } else { - return __svcThreadCreate(thread_def, argument, context); + osThreadId id; + osMutexWait(osMutexId_osThreadMutex, osWaitForever); + // Thread mutex must be held when a thread is created or terminated + id = __svcThreadCreate(thread_def, argument, context); + osMutexRelease(osMutexId_osThreadMutex); + return id; } } @@ -820,10 +893,16 @@ osThreadId osThreadGetId (void) { /// Terminate execution of a thread and remove it from ActiveThreads osStatus osThreadTerminate (osThreadId thread_id) { + osStatus status; if (__get_IPSR() != 0U) { return osErrorISR; // Not allowed in ISR } - return __svcThreadTerminate(thread_id); + osMutexWait(osMutexId_osThreadMutex, osWaitForever); + sysThreadTerminate(thread_id); + // Thread mutex must be held when a thread is created or terminated + status = __svcThreadTerminate(thread_id); + osMutexRelease(osMutexId_osThreadMutex); + return status; } /// Pass control to next thread that is in state READY @@ -852,8 +931,15 @@ osPriority osThreadGetPriority (osThreadId thread_id) { /// INTERNAL - Not Public /// Auto Terminate Thread on exit (used implicitly when thread exists) -__NO_RETURN void osThreadExit (void) { - __svcThreadTerminate(__svcThreadGetId()); +__NO_RETURN void osThreadExit (void) { + osThreadId id; + // Thread mutex must be held when a thread is created or terminated + // Note - the mutex will be released automatically by the os when + // the thread is terminated + osMutexWait(osMutexId_osThreadMutex, osWaitForever); + id = __svcThreadGetId(); + sysThreadTerminate(id); + __svcThreadTerminate(id); for (;;); // Should never come here } @@ -871,6 +957,49 @@ uint8_t osThreadGetState (osThreadId thread_id) { } #endif +/// Get the requested info from the specified active thread +os_InRegs osEvent _osThreadGetInfo(osThreadId thread_id, osThreadInfo info) { + osEvent ret; + + if (__get_IPSR() != 0U) { // Not allowed in ISR + ret.status = osErrorISR; + return ret; + } + return __svcThreadGetInfo(thread_id, info); +} + +osThreadEnumId _osThreadsEnumStart() { + static uint32_t thread_enum_index; + osMutexWait(osMutexId_osThreadMutex, osWaitForever); + thread_enum_index = 0; + return &thread_enum_index; +} + +osThreadId _osThreadEnumNext(osThreadEnumId enum_id) { + uint32_t i; + osThreadId id = NULL; + uint32_t *index = (uint32_t*)enum_id; + for (i = *index; i < os_maxtaskrun; i++) { + if (os_active_TCB[i] != NULL) { + id = (osThreadId)os_active_TCB[i]; + break; + } + } + if (i == os_maxtaskrun) { + // Include the idle task at the end of the enumeration + id = &os_idle_TCB; + } + *index = i + 1; + return id; +} + +osStatus _osThreadEnumFree(osThreadEnumId enum_id) { + uint32_t *index = (uint32_t*)enum_id; + *index = 0; + osMutexRelease(osMutexId_osThreadMutex); + return osOK; +} + // ==== Generic Wait Functions ==== // Generic Wait Service Calls declarations diff --git a/rtos/rtx/TARGET_CORTEX_M/rt_Task.c b/rtos/rtx/TARGET_CORTEX_M/rt_Task.c index 3179a5192f4..812978fdc67 100644 --- a/rtos/rtx/TARGET_CORTEX_M/rt_Task.c +++ b/rtos/rtx/TARGET_CORTEX_M/rt_Task.c @@ -247,6 +247,7 @@ OS_TID rt_tsk_create (FUNCP task, U32 prio_stksz, void *stk, void *argv) { task_context->task_id = (U8)i; /* Pass parameter 'argv' to 'rt_init_context' */ task_context->msg = argv; + task_context->argv = argv; /* For 'size == 0' system allocates the user stack from the memory pool. */ rt_init_context (task_context, (U8)(prio_stksz & 0xFFU), task); diff --git a/rtos/rtx/TARGET_CORTEX_M/rt_TypeDef.h b/rtos/rtx/TARGET_CORTEX_M/rt_TypeDef.h index 39cb9d477ed..f16b96ec203 100644 --- a/rtos/rtx/TARGET_CORTEX_M/rt_TypeDef.h +++ b/rtos/rtx/TARGET_CORTEX_M/rt_TypeDef.h @@ -79,6 +79,7 @@ typedef struct OS_TCB { /* Task entry point used for uVision debugger */ FUNCP ptask; /* Task entry address */ + void *argv; /* Task argument */ void *context; /* Pointer to thread context */ } *P_TCB; #define TCB_STACKF 37 /* 'stack_frame' offset */