diff --git a/build/arm-tools.mk b/build/arm-tools.mk index efd43fee65..b059c85346 100644 --- a/build/arm-tools.mk +++ b/build/arm-tools.mk @@ -15,14 +15,14 @@ AR = $(GCC_ARM_PATH)$(GCC_PREFIX)gcc-ar # # C compiler flags -CFLAGS += -g3 -gdwarf-2 -Os -mcpu=cortex-m3 -mthumb +CFLAGS += -g3 -gdwarf-2 -Os -mcpu=cortex-m3 -mthumb -fomit-frame-pointer # C++ specific flags CPPFLAGS += -fno-exceptions -fno-rtti -fcheck-new CONLYFLAGS += -ASFLAGS += -g3 -gdwarf-2 -mcpu=cortex-m3 -mthumb +ASFLAGS += -g3 -gdwarf-2 -mcpu=cortex-m3 -mthumb -fomit-frame-pointer LDFLAGS += -nostartfiles -Xlinker --gc-sections diff --git a/build/arm/linker/stm32f2xx/backup_ram_system.ld b/build/arm/linker/stm32f2xx/backup_ram_system.ld index 5f58a8bff6..f8b4caf577 100644 --- a/build/arm/linker/stm32f2xx/backup_ram_system.ld +++ b/build/arm/linker/stm32f2xx/backup_ram_system.ld @@ -5,6 +5,8 @@ link_global_retained_system_start = .; *(.retained_system*) link_global_retained_system_end = .; + . = ORIGIN(BACKUPSRAM_SYSTEM) + LENGTH(BACKUPSRAM_SYSTEM); + link_global_retained_system_end_section = .; }>BACKUPSRAM_SYSTEM AT> APP_FLASH /* even though we don't initialize the system backup RAM presently, it has to be placed at APP_FLASH or the resulting file ends up being hundreds of megabytes in size */ diff --git a/communication/makefile b/communication/makefile index d5d32ddd60..4c9029ff1e 100644 --- a/communication/makefile +++ b/communication/makefile @@ -7,6 +7,6 @@ TARGET_TYPE = a BUILD_PATH_EXT=$(COMMUNICATION_BUILD_PATH_EXT) -DEPENDENCIES = hal dynalib services wiring crypto +DEPENDENCIES = hal dynalib services wiring crypto platform include ../build/arm-tlm.mk diff --git a/communication/src/build.mk b/communication/src/build.mk index 16edaf4a19..5b887ccc79 100644 --- a/communication/src/build.mk +++ b/communication/src/build.mk @@ -37,11 +37,11 @@ ASRC += CPPFLAGS += -std=gnu++11 ifeq ($(PLATFORM_ID),6) -CFLAGS += -DLOG_COMPILE_TIME_LEVEL=LOG_LEVEL_NONE +CFLAGS += -DLOG_COMPILE_TIME_LEVEL=LOG_LEVEL_ERROR endif ifeq ($(PLATFORM_ID),8) -CFLAGS += -DLOG_COMPILE_TIME_LEVEL=LOG_LEVEL_NONE +CFLAGS += -DLOG_COMPILE_TIME_LEVEL=LOG_LEVEL_ERROR endif LOG_MODULE_CATEGORY = comm diff --git a/hal/inc/concurrent_hal.h b/hal/inc/concurrent_hal.h index dfb41a6ed0..85e2c8c0c7 100644 --- a/hal/inc/concurrent_hal.h +++ b/hal/inc/concurrent_hal.h @@ -42,8 +42,11 @@ extern "C" { */ #include "concurrent_hal_impl.h" - +#ifdef __cplusplus const os_thread_t OS_THREAD_INVALID_HANDLE = NULL; +#else +#define OS_THREAD_INVALID_HANDLE ((os_thread_t)NULL) +#endif /** * The return type from a thread function. @@ -128,6 +131,25 @@ os_result_t os_thread_cleanup(os_thread_t thread); */ os_result_t os_thread_yield(void); +os_unique_id_t os_thread_unique_id(os_thread_t thread); + +os_thread_t os_thread_current(); + +typedef struct { + uint8_t reserved; + + os_thread_t thread; + const char* name; + os_unique_id_t id; + void* stack; + void* stack_start; + void* stack_end; +} os_thread_dump_info_t; + +typedef os_result_t (*os_thread_dump_callback_t)(os_thread_dump_info_t*, void*); + +os_result_t os_thread_dump(os_thread_t thread, os_thread_dump_callback_t callback, void* reserved); + /** * Delays the current task until a specified time to set up periodic tasks @@ -150,7 +172,11 @@ void os_condition_variable_wait(condition_variable_t var, void* lock); void os_condition_variable_notify_one(condition_variable_t var); void os_condition_variable_notify_all(condition_variable_t var); +#ifdef __cplusplus const system_tick_t CONCURRENT_WAIT_FOREVER = (system_tick_t)-1; +#else +#define CONCURRENT_WAIT_FOREVER ((system_tick_t)-1) +#endif int os_queue_create(os_queue_t* queue, size_t item_size, size_t item_count, void* reserved); /** @@ -190,8 +216,13 @@ int os_semaphore_destroy(os_semaphore_t semaphore); int os_semaphore_take(os_semaphore_t semaphore, system_tick_t timeout, bool reserved); int os_semaphore_give(os_semaphore_t semaphore, bool reserved); -#define _GLIBCXX_HAS_GTHREADS +#ifndef _GLIBCXX_HAS_GTHREADS +# define _GLIBCXX_HAS_GTHREADS +#endif // _GLIBCXX_HAS_GTHREADS + +#ifdef __cplusplus #include +#endif /** * Enables/disables pre-emptive context switching diff --git a/hal/inc/core_hal.h b/hal/inc/core_hal.h index 454aa91b57..6819ae3028 100644 --- a/hal/inc/core_hal.h +++ b/hal/inc/core_hal.h @@ -112,6 +112,7 @@ typedef enum System_Reset_Reason #include "watchdog_hal.h" #include "core_subsys_hal.h" #include "interrupts_hal.h" +#include "concurrent_hal.h" #ifdef __cplusplus extern "C" { @@ -265,6 +266,9 @@ typedef void(*HAL_Event_Callback)(int event, int flags, void* data); void HAL_Set_Event_Callback(HAL_Event_Callback callback, void* reserved); +typedef int (*HAL_Stacktrace_Callback)(void* ptr, int idx, uintptr_t addr, os_thread_dump_info_t* info, void* reserved); +int HAL_Core_Generate_Stacktrace(os_thread_dump_info_t* info, HAL_Stacktrace_Callback callback, void* ptr, void* reserved); + #ifdef __cplusplus } #endif diff --git a/hal/src/electron/FreeRTOSConfig.h b/hal/src/electron/FreeRTOSConfig.h index e8d2773eb4..9bb945992f 100644 --- a/hal/src/electron/FreeRTOSConfig.h +++ b/hal/src/electron/FreeRTOSConfig.h @@ -112,7 +112,7 @@ #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 75 * 1024 ) ) #define configMAX_TASK_NAME_LEN ( 16 ) -#define configUSE_TRACE_FACILITY 0 +#define configUSE_TRACE_FACILITY ( 1 ) #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 #define configUSE_MUTEXES 1 @@ -142,6 +142,7 @@ to exclude the API function. */ #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 +#define INCLUDE_vTaskGetStackInfo 1 /* This is the raw value as per the Cortex-M3 NVIC. Values can be 255 (lowest) to 0 (1?) (highest). */ diff --git a/hal/src/electron/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/include/task.h b/hal/src/electron/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/include/task.h index deda89492c..bea329e13b 100644 --- a/hal/src/electron/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/include/task.h +++ b/hal/src/electron/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/include/task.h @@ -1290,7 +1290,7 @@ TaskHandle_t xTaskGetIdleTaskHandle( void ) PRIVILEGED_FUNCTION; } */ -UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, const UBaseType_t uxArraySize, uint32_t * const pulTotalRunTime ) PRIVILEGED_FUNCTION; +UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, const UBaseType_t uxArraySize, uint32_t * const pulTotalRunTime, BaseType_t ( * callback )( TaskStatus_t * const status, void* ptr ), void* ptr ); /** * task. h diff --git a/hal/src/electron/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/tasks.c b/hal/src/electron/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/tasks.c index 2d0c4bb62f..cb82d8249a 100644 --- a/hal/src/electron/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/tasks.c +++ b/hal/src/electron/rtos/FreeRTOSv8.2.2/FreeRTOS/Source/tasks.c @@ -146,7 +146,7 @@ typedef struct tskTaskControlBlock StackType_t *pxStack; /*< Points to the start of the stack. */ char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ - #if ( portSTACK_GROWTH > 0 ) + #if ( portSTACK_GROWTH > 0 || configUSE_TRACE_FACILITY == 1 ) StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. */ #endif @@ -501,7 +501,7 @@ static TCB_t *prvAllocateTCBAndStack( const uint16_t usStackDepth, StackType_t * */ #if ( configUSE_TRACE_FACILITY == 1 ) - static UBaseType_t prvListTaskWithinSingleList( TaskStatus_t *pxTaskStatusArray, List_t *pxList, eTaskState eState ) PRIVILEGED_FUNCTION; + static UBaseType_t prvListTaskWithinSingleList( TaskStatus_t *pxTaskStatusArray, List_t *pxList, eTaskState eState, BaseType_t ( * callback )( TaskStatus_t * const status, void* ptr ), void* ptr ) PRIVILEGED_FUNCTION; #endif @@ -602,6 +602,9 @@ StackType_t *pxTopOfStack; /* Check the alignment of the calculated top of stack is correct. */ configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); + #if ( configUSE_TRACE_FACILITY == 1 ) + pxNewTCB->pxEndOfStack = pxTopOfStack; + #endif /* configUSE_TRACE_FACILITY == 1 */ } #else /* portSTACK_GROWTH */ { @@ -697,6 +700,7 @@ StackType_t *pxTopOfStack; { /* Add a counter into the TCB for tracing only. */ pxNewTCB->uxTCBNumber = uxTaskNumber; + pxNewTCB->uxTCBNumber = pxNewTCB->uxTaskNumber = uxTaskNumber; } #endif /* configUSE_TRACE_FACILITY */ traceTASK_CREATE( pxNewTCB ); @@ -1540,6 +1544,28 @@ StackType_t *pxTopOfStack; #endif /* ( ( INCLUDE_xTaskResumeFromISR == 1 ) && ( INCLUDE_vTaskSuspend == 1 ) ) */ /*-----------------------------------------------------------*/ +#if ( INCLUDE_vTaskGetStackInfo == 1 ) + + void vTaskGetStackInfo( TaskHandle_t pxTask, void** stack_ptr, void** start_stack_ptr, void** end_stack_ptr ) + { + tskTCB* pxTCB; + taskENTER_CRITICAL(); + pxTCB = ( tskTCB * ) pxTask; + *( (portSTACK_TYPE**) stack_ptr) = pxTCB->pxStack; + #if ( portSTACK_GROWTH > 0 || configUSE_TRACE_FACILITY == 1 ) + *( (portSTACK_TYPE**) end_stack_ptr) = pxTCB->pxEndOfStack; + #else /* #if ( portSTACK_GROWTH > 0 ) */ + *( (portSTACK_TYPE**) end_stack_ptr) = NULL; + #endif /* #if ( portSTACK_GROWTH > 0 ) */ + *( (volatile portSTACK_TYPE**) start_stack_ptr) = pxTCB->pxTopOfStack; + + taskEXIT_CRITICAL(); + return; + } + +#endif /* INCLUDE_vTaskGetStackInfo */ +/*-----------------------------------------------------------*/ + void vTaskStartScheduler( void ) { BaseType_t xReturn; @@ -1832,34 +1858,34 @@ UBaseType_t uxTaskGetNumberOfTasks( void ) #if ( configUSE_TRACE_FACILITY == 1 ) - UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, const UBaseType_t uxArraySize, uint32_t * const pulTotalRunTime ) + UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, const UBaseType_t uxArraySize, uint32_t * const pulTotalRunTime, BaseType_t ( * callback )( TaskStatus_t * const status, void* ptr ), void* ptr ) { UBaseType_t uxTask = 0, uxQueue = configMAX_PRIORITIES; vTaskSuspendAll(); { /* Is there a space in the array for each task in the system? */ - if( uxArraySize >= uxCurrentNumberOfTasks ) + if( uxArraySize >= uxCurrentNumberOfTasks || callback != NULL ) { /* Fill in an TaskStatus_t structure with information on each task in the Ready state. */ do { uxQueue--; - uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &( pxReadyTasksLists[ uxQueue ] ), eReady ); + uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &( pxReadyTasksLists[ uxQueue ] ), eReady, callback, ptr ); } while( uxQueue > ( UBaseType_t ) tskIDLE_PRIORITY ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ /* Fill in an TaskStatus_t structure with information on each task in the Blocked state. */ - uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), ( List_t * ) pxDelayedTaskList, eBlocked ); - uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), ( List_t * ) pxOverflowDelayedTaskList, eBlocked ); + uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), ( List_t * ) pxDelayedTaskList, eBlocked, callback, ptr ); + uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), ( List_t * ) pxOverflowDelayedTaskList, eBlocked, callback, ptr ); #if( INCLUDE_vTaskDelete == 1 ) { /* Fill in an TaskStatus_t structure with information on each task that has been deleted but not yet cleaned up. */ - uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &xTasksWaitingTermination, eDeleted ); + uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &xTasksWaitingTermination, eDeleted, callback, ptr ); } #endif @@ -1867,7 +1893,7 @@ UBaseType_t uxTaskGetNumberOfTasks( void ) { /* Fill in an TaskStatus_t structure with information on each task in the Suspended state. */ - uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &xSuspendedTaskList, eSuspended ); + uxTask += prvListTaskWithinSingleList( &( pxTaskStatusArray[ uxTask ] ), &xSuspendedTaskList, eSuspended, callback, ptr ); } #endif @@ -3177,7 +3203,7 @@ TCB_t *pxNewTCB; #if ( configUSE_TRACE_FACILITY == 1 ) - static UBaseType_t prvListTaskWithinSingleList( TaskStatus_t *pxTaskStatusArray, List_t *pxList, eTaskState eState ) + static UBaseType_t prvListTaskWithinSingleList( TaskStatus_t *pxTaskStatusArray, List_t *pxList, eTaskState eState, BaseType_t ( * callback )( TaskStatus_t * const status, void* ptr ), void* ptr ) { volatile TCB_t *pxNextTCB, *pxFirstTCB; UBaseType_t uxTask = 0; @@ -3245,7 +3271,17 @@ TCB_t *pxNewTCB; } #endif - uxTask++; + if (callback == NULL) + { + uxTask++; + } + else + { + if (callback(pxTaskStatusArray, ptr) != 0) + { + break; + } + } } while( pxNextTCB != pxFirstTCB ); } diff --git a/hal/src/gcc/concurrent_hal_impl.h b/hal/src/gcc/concurrent_hal_impl.h new file mode 100644 index 0000000000..532823a2f3 --- /dev/null +++ b/hal/src/gcc/concurrent_hal_impl.h @@ -0,0 +1,14 @@ +#pragma once + +// redefine these for the underlying concurrency primitives available in the RTOS + +typedef int os_result_t; +typedef int os_thread_prio_t; +typedef void* os_thread_t; +typedef void*os_timer_t; +typedef void*os_queue_t; +typedef void*os_mutex_t; +typedef void* condition_variable_t; +typedef void* os_semaphore_t; +typedef void* os_mutex_recursive_t; +typedef uintptr_t os_unique_id_t; diff --git a/hal/src/newhal/concurrent_hal_impl.h b/hal/src/newhal/concurrent_hal_impl.h index 7f05657b12..532823a2f3 100644 --- a/hal/src/newhal/concurrent_hal_impl.h +++ b/hal/src/newhal/concurrent_hal_impl.h @@ -10,4 +10,5 @@ typedef void*os_queue_t; typedef void*os_mutex_t; typedef void* condition_variable_t; typedef void* os_semaphore_t; -typedef void* os_mutex_recursive_t; \ No newline at end of file +typedef void* os_mutex_recursive_t; +typedef uintptr_t os_unique_id_t; diff --git a/hal/src/photon/lib/FreeRTOS/FreeRTOS.a b/hal/src/photon/lib/FreeRTOS/FreeRTOS.a index 79db851862..5ecf4f2457 100644 Binary files a/hal/src/photon/lib/FreeRTOS/FreeRTOS.a and b/hal/src/photon/lib/FreeRTOS/FreeRTOS.a differ diff --git a/hal/src/photon/wiced/RTOS/FreeRTOS/WWD/ARM_CM3/FreeRTOSConfig.h b/hal/src/photon/wiced/RTOS/FreeRTOS/WWD/ARM_CM3/FreeRTOSConfig.h index 76c9673320..d41574163a 100644 --- a/hal/src/photon/wiced/RTOS/FreeRTOS/WWD/ARM_CM3/FreeRTOSConfig.h +++ b/hal/src/photon/wiced/RTOS/FreeRTOS/WWD/ARM_CM3/FreeRTOSConfig.h @@ -57,7 +57,7 @@ extern "C" { #define configMINIMAL_STACK_SIZE ( ( unsigned short ) (250 / sizeof( portSTACK_TYPE )) ) /* size of idle thread stack */ #define configMAX_TASK_NAME_LEN ( 16 ) #ifndef configUSE_TRACE_FACILITY -#define configUSE_TRACE_FACILITY ( 0 ) +#define configUSE_TRACE_FACILITY ( 1 ) #endif /* configUSE_TRACE_FACILITY */ #define configUSE_16_BIT_TICKS ( 0 ) #define configIDLE_SHOULD_YIELD ( 1 ) diff --git a/hal/src/photon/wiced/RTOS/FreeRTOS/ver8.2.1/Source/include/task.h b/hal/src/photon/wiced/RTOS/FreeRTOS/ver8.2.1/Source/include/task.h index 16118afa71..ad5ecf7050 100644 --- a/hal/src/photon/wiced/RTOS/FreeRTOS/ver8.2.1/Source/include/task.h +++ b/hal/src/photon/wiced/RTOS/FreeRTOS/ver8.2.1/Source/include/task.h @@ -1336,7 +1336,7 @@ TaskHandle_t xTaskGetIdleTaskHandle( void ); } */ -UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, const UBaseType_t uxArraySize, uint32_t * const pulTotalRunTime ); +UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, const UBaseType_t uxArraySize, uint32_t * const pulTotalRunTime, BaseType_t ( * callback )( TaskStatus_t * const status, void* ptr ), void* ptr ); /** * task. h diff --git a/hal/src/photon/wiced/platform/MCU/STM32F2xx/GCC/app_no_bootloader.ld b/hal/src/photon/wiced/platform/MCU/STM32F2xx/GCC/app_no_bootloader.ld index 1644ff9be7..77644dfb8a 100644 --- a/hal/src/photon/wiced/platform/MCU/STM32F2xx/GCC/app_no_bootloader.ld +++ b/hal/src/photon/wiced/platform/MCU/STM32F2xx/GCC/app_no_bootloader.ld @@ -167,6 +167,9 @@ SECTIONS INCLUDE backup_ram_user.ld INCLUDE backup_ram_system.ld + link_heap_location = _heap; + link_heap_location_end = _eheap; + /DISCARD/ : { *(.ARM.attributes*) diff --git a/hal/src/stm32/concurrent_hal_impl.h b/hal/src/stm32/concurrent_hal_impl.h index 204824469c..fddd5d60d2 100644 --- a/hal/src/stm32/concurrent_hal_impl.h +++ b/hal/src/stm32/concurrent_hal_impl.h @@ -15,10 +15,17 @@ typedef void* __gthread_t; typedef void* os_thread_t; typedef int32_t os_result_t; typedef uint8_t os_thread_prio_t; + +#ifdef __cplusplus /* Default priority is the same as the application thread */ const os_thread_prio_t OS_THREAD_PRIORITY_DEFAULT = 2; const os_thread_prio_t OS_THREAD_PRIORITY_CRITICAL = 9; const size_t OS_THREAD_STACK_SIZE_DEFAULT = 3*1024; +#else +#define OS_THREAD_PRIORITY_DEFAULT ((os_thread_prio_t)2) +#define OS_THREAD_PRIORITY_CRITICAL ((os_thread_prio_t)2) +#define OS_THREAD_STACK_SIZE_DEFAULT ((size_t)3*1024) +#endif typedef void* os_mutex_t; typedef void* os_mutex_recursive_t; @@ -28,6 +35,8 @@ typedef void* os_timer_t; typedef os_mutex_t __gthread_mutex_t; typedef os_mutex_recursive_t __gthread_recursive_mutex_t; +typedef uint32_t os_unique_id_t; + /** * Alias for a queue handle in FreeRTOS - all handles are pointers. diff --git a/hal/src/stm32f2xx/concurrent_hal.cpp b/hal/src/stm32f2xx/concurrent_hal.cpp index 8ba61fae58..26911b67b3 100644 --- a/hal/src/stm32f2xx/concurrent_hal.cpp +++ b/hal/src/stm32f2xx/concurrent_hal.cpp @@ -40,6 +40,10 @@ #include "logging.h" #include "atomic_flag_mutex.h" #include "service_debug.h" +#include +#include "timer_hal.h" + +extern "C" void vTaskGetStackInfo( TaskHandle_t pxTask, void** stack_ptr, void** start_stack_ptr, void** end_stack_ptr ); #if PLATFORM_ID == 6 || PLATFORM_ID == 8 # include "wwd_rtos_interface.h" @@ -66,6 +70,11 @@ static_assert(sizeof(uint32_t)==sizeof(void*), "Requires uint32_t to be same siz #define _CREATE_NAME_TYPE const signed char #endif +typedef struct { + os_thread_dump_callback_t callback; + os_thread_t thread; + void* data; +} os_thread_dump_helper_t; /** * Creates a new thread. @@ -98,6 +107,41 @@ bool os_thread_is_current(os_thread_t thread) return thread==xTaskGetCurrentTaskHandle(); } +os_unique_id_t os_thread_unique_id(os_thread_t thread) +{ + return (os_unique_id_t)uxTaskGetTaskNumber(thread); +} + +os_thread_t os_thread_current() +{ + return xTaskGetCurrentTaskHandle(); +} + +static BaseType_t os_thread_dump_helper(TaskStatus_t* const status, void* data) +{ + os_thread_dump_helper_t* h = (os_thread_dump_helper_t*)data; + os_thread_dump_info_t info = {0}; + info.thread = status->xHandle; + info.name = status->pcTaskName; + info.id = status->xTaskNumber; + vTaskGetStackInfo(status->xHandle, &info.stack, &info.stack_start, &info.stack_end); + if (h->callback && (h->thread == OS_THREAD_INVALID_HANDLE || h->thread == status->xHandle)) { + return (BaseType_t)h->callback(&info, h->data); + } + + return 0; +} + +os_result_t os_thread_dump(os_thread_t thread, os_thread_dump_callback_t callback, void* ptr) +{ + TaskStatus_t status = {0}; + os_thread_dump_helper_t data = {callback, thread, ptr}; + // vTaskSuspendAll(); + uxTaskGetSystemState(&status, 1, nullptr, os_thread_dump_helper, (void*)&data); + // xTaskResumeAll(); + + return 0; +} os_result_t os_thread_yield(void) { diff --git a/hal/src/stm32f2xx/core_hal_stm32f2xx.c b/hal/src/stm32f2xx/core_hal_stm32f2xx.c index 1f959c7d9e..0b4cf54572 100644 --- a/hal/src/stm32f2xx/core_hal_stm32f2xx.c +++ b/hal/src/stm32f2xx/core_hal_stm32f2xx.c @@ -53,6 +53,7 @@ #include "deviceid_hal.h" #include "pinmap_impl.h" #include "ota_module.h" +#include "platform_diagnostic.h" #if PLATFORM_ID==PLATFORM_P1 #include "wwd_management.h" @@ -94,6 +95,9 @@ __attribute__((externally_visible)) void prvGetRegistersFromStack( uint32_t *pul (void)r0; (void)r1; (void)r2; (void)r3; (void)r12; (void)lr; (void)pc; (void)psr; } + // Better use LR here, as PC usually points to a wrong location + DIAGNOSTIC_CRASH_CHECKPOINT(lr); + if (SCB->CFSR & (1<<25) /* DIVBYZERO */) { // stay consistent with the core and cause 5 flashes UsageFault_Handler(); @@ -1585,6 +1589,57 @@ void HAL_Core_Led_Mirror_Pin(uint8_t led, pin_t pin, uint32_t flags, uint8_t boo LED_Mirror_Persist(led, &bootloader_conf); } +extern const module_bounds_t module_system_part1; +extern const module_bounds_t module_system_part2; +#if PLATFORM_ID == 10 +extern const module_bounds_t module_system_part3; +#endif // PLATFORM_ID == 10 +extern const module_bounds_t module_user; +extern const module_bounds_t module_user_mono; + +int HAL_Core_Generate_Stacktrace(os_thread_dump_info_t* info, HAL_Stacktrace_Callback callback, void* ptr, void* reserved) +{ + if (info->stack_start == NULL || info->stack_end == NULL) { + return 1; + } + + int count = 0; + + for (uint32_t* sp = (uint32_t*)info->stack_start; sp <= (uint32_t*)info->stack_end; sp++) { + bool irq = (HAL_IsISR() && ((*sp == 0xfffffffd) || + (*sp == 0xfffffff9) || + (*sp == 0xfffffff1))); + // First check that the value on stack resembles an address in flash or a switch to interrupt context + if (((*sp & 0xff000000) == 0x08000000) || irq) { + // Then check whether the address belongs to system or user part module bounds +#if defined(MODULAR_FIRMWARE) && MODULAR_FIRMWARE + if (irq || + (*sp >= module_system_part1.start_address && *sp <= module_system_part1.end_address) || + (*sp >= module_system_part2.start_address && *sp <= module_system_part2.end_address) || +# if PLATFORM_ID == 10 + (*sp >= module_system_part3.start_address && *sp <= module_system_part3.end_address) || +# endif // PLATFORM_ID != 10 + (*sp >= module_user.start_address && *sp <= module_user.end_address)) { +#else + if (irq || + (*sp >= module_user_mono.start_address && *sp <= module_user_mono.end_address)) { +#endif // defined(MODULAR_FIRMWARE) && MODULAR_FIRMWARE + uint32_t* lr = (uint32_t*)(*sp - 1) - 1; + if (platform_is_branching_instruction(lr) || irq) { + uint32_t addr = irq ? *sp : *sp - 3; + // Callback + if (callback(ptr, count, (uintptr_t)addr, info, NULL) != 0) { + break; + } + ++count; + } + } + } + } + return count; +} + + #if HAL_PLATFORM_CLOUD_UDP #include "dtls_session_persist.h" diff --git a/modules/electron/system-part1/makefile b/modules/electron/system-part1/makefile index 5ed49c397b..5d88a9b65e 100644 --- a/modules/electron/system-part1/makefile +++ b/modules/electron/system-part1/makefile @@ -6,13 +6,15 @@ HAL_LINK := PLATFORM_DFU = 0x8060000 LIB_DEPENDENCIES = services-dynalib rt-dynalib platform -MAKE_DEPENDENCIES = $(LIB_DEPENDENCIES) +MAKE_DEPENDENCIES = $(LIB_DEPENDENCIES) DEPENDENCIES = $(MAKE_DEPENDENCIES) dynalib services hal # rebuild if the linker specs change for other modules DEPENDENCIES += modules/electron/user-part modules/electron/system-part2 modules/electron/system-part3 - + +GLOBAL_DEFINES += DIAGNOSTIC_USE_CALLBACKS2 + include ../modular.mk include $(PROJECT_ROOT)/build/platform-id.mk LIBS += $(LIB_DEPENDENCIES) diff --git a/modules/electron/system-part1/module_system_part3_export.ld b/modules/electron/system-part1/module_system_part3_export.ld index 690a5ed1df..aa1c61e6bc 100644 --- a/modules/electron/system-part1/module_system_part3_export.ld +++ b/modules/electron/system-part1/module_system_part3_export.ld @@ -1,8 +1,8 @@ system_part3_start = 0x8060000; -system_part3_ram_end = 0x2001D800 /* 0x20200000-10K */; -system_part3_ram_start = 0x2001c000 /* end of SRAM - 16K */; +system_part3_ram_end = 0x2001D800 - 1K /* 0x20200000-10K-1K */; +system_part3_ram_start = 0x2001c000 - 1K /* end of SRAM - 16K - 1K */; system_part3_module_info_size = 24; @@ -27,3 +27,4 @@ PROVIDE ( dynalib_location_hal_usb = system_part3_module_table + 4 ); PROVIDE ( dynalib_location_hal_cellular = system_part3_module_table + 8 ); PROVIDE ( dynalib_location_hal_socket = system_part3_module_table + 12 ); PROVIDE ( dynalib_location_hal_bootloader = system_part3_module_table + 16 ); +PROVIDE ( dynalib_location_services2 = system_part3_module_table + 20 ); diff --git a/modules/electron/system-part1/src/export_services2.c b/modules/electron/system-part1/src/export_services2.c new file mode 100644 index 0000000000..9358a152c5 --- /dev/null +++ b/modules/electron/system-part1/src/export_services2.c @@ -0,0 +1,4 @@ +#define DYNALIB_EXPORT +#define DIAGNOSTIC_SKIP_PLATFORM +#include "diagnostic.h" +#include "services2_dynalib.h" diff --git a/modules/electron/system-part1/src/module_system_part3.cpp b/modules/electron/system-part1/src/module_system_part3.cpp index 2635383be6..51b528dc9d 100644 --- a/modules/electron/system-part1/src/module_system_part3.cpp +++ b/modules/electron/system-part1/src/module_system_part3.cpp @@ -13,6 +13,7 @@ DYNALIB_TABLE_EXTERN(hal_usb); DYNALIB_TABLE_EXTERN(hal_cellular); DYNALIB_TABLE_EXTERN(hal_socket); DYNALIB_TABLE_EXTERN(hal_bootloader); +DYNALIB_TABLE_EXTERN(services2); /** * The order of these declarations MUST MATCH the order of declarations in @@ -23,6 +24,7 @@ extern "C" __attribute__((externally_visible)) const void* const system_part3_mo DYNALIB_TABLE_NAME(hal_usb), DYNALIB_TABLE_NAME(hal_cellular), DYNALIB_TABLE_NAME(hal_socket), - DYNALIB_TABLE_NAME(hal_bootloader) + DYNALIB_TABLE_NAME(hal_bootloader), + DYNALIB_TABLE_NAME(services2) }; diff --git a/modules/electron/system-part1/src/services2.cpp b/modules/electron/system-part1/src/services2.cpp new file mode 100644 index 0000000000..cb2365e0cb --- /dev/null +++ b/modules/electron/system-part1/src/services2.cpp @@ -0,0 +1 @@ +#include "../../../../services/src/diagnostic.cpp" diff --git a/modules/electron/system-part2/makefile b/modules/electron/system-part2/makefile index 45f8d4b827..f1ad20e082 100644 --- a/modules/electron/system-part2/makefile +++ b/modules/electron/system-part2/makefile @@ -12,6 +12,7 @@ include ../modular.mk include $(PROJECT_ROOT)/build/platform-id.mk GLOBAL_DEFINES += CRYPTO_PART1_SIZE_OPTIMIZATIONS +GLOBAL_DEFINES += DIAGNOSTIC_USE_CALLBACKS LIBS += $(MAKE_DEPENDENCIES) LIB_DEPS += $(COMMUNICATION_LIB_DEP) $(HAL_DYNALIB_LIB_DEP) $(SERVICES_LIB_DEP) $(PLATFORM_LIB_DEP) $(CRYPTO_LIB_DEP) diff --git a/modules/electron/system-part3/linker.ld b/modules/electron/system-part3/linker.ld index 7e958284e3..5149357202 100644 --- a/modules/electron/system-part3/linker.ld +++ b/modules/electron/system-part3/linker.ld @@ -13,7 +13,7 @@ MEMORY The value given here is the sum of system_static_ram_size and stack_size */ - SRAM (rwx) : ORIGIN = 0x20020000 - 10K, LENGTH = 10K + SRAM (rwx) : ORIGIN = 0x20020000 - 11K, LENGTH = 11K INCLUDE backup_ram_memory.ld } diff --git a/modules/electron/system-part3/module_system_part2_export.ld b/modules/electron/system-part3/module_system_part2_export.ld index f6477df097..72b4e2eb83 100644 --- a/modules/electron/system-part3/module_system_part2_export.ld +++ b/modules/electron/system-part3/module_system_part2_export.ld @@ -42,7 +42,7 @@ min_heap_size = 16K; * Can be expanded or reduced as necessary. The heap dynamically expands from * it's static base to the heap top, which is the bottom of this module's static ram. */ -system_part2_ram_size = 6K; +system_part2_ram_size = 7K; /* Place system part2 static RAM immediately below stack. */ system_part2_ram_start = ALIGN( stack_start - system_part2_ram_size, 512); diff --git a/modules/electron/system-part3/src/import_services2.c b/modules/electron/system-part3/src/import_services2.c new file mode 100644 index 0000000000..4d2a3aa1e4 --- /dev/null +++ b/modules/electron/system-part3/src/import_services2.c @@ -0,0 +1,2 @@ +#define DYNALIB_IMPORT +#include "services2_dynalib.h" diff --git a/modules/electron/user-part/module_user_memory.ld b/modules/electron/user-part/module_user_memory.ld index 903c27a8bd..943d173908 100644 --- a/modules/electron/user-part/module_user_memory.ld +++ b/modules/electron/user-part/module_user_memory.ld @@ -5,4 +5,4 @@ user_module_app_flash_length = 128K; /* The SRAM Origin is system_part1_module_ram_end, and extends to system_static_ram_start */ user_module_sram_origin = 0x20000400; -user_module_sram_length = 0x20000 - 0x400 - 16K; +user_module_sram_length = 0x20000 - 0x400 - 16K - 1K; diff --git a/modules/photon/system-part1/makefile b/modules/photon/system-part1/makefile index c2e40fc232..c5b9748f7f 100644 --- a/modules/photon/system-part1/makefile +++ b/modules/photon/system-part1/makefile @@ -13,6 +13,7 @@ include ../modular.mk include $(PROJECT_ROOT)/build/platform-id.mk GLOBAL_DEFINES += CRYPTO_PART1_SIZE_OPTIMIZATIONS +GLOBAL_DEFINES += DIAGNOSTIC_USE_CALLBACKS # dependent on hal lib only for the wwd_nvmem_resource, which is managed specifically in build.mk LIBS += $(filter-out hal,$(MAKE_DEPENDENCIES)) diff --git a/platform/MCU/gcc/inc/platform_diagnostic.h b/platform/MCU/gcc/inc/platform_diagnostic.h new file mode 100644 index 0000000000..0183d2f67e --- /dev/null +++ b/platform/MCU/gcc/inc/platform_diagnostic.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef PLATFORM_DIAGNOSTIC_H +#define PLATFORM_DIAGNOSTIC_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +inline void* platform_get_current_pc(void) { + return NULL; +} + +#define __SW_RETURN_ADDRESS(i) case i: return __builtin_return_address(i) + +inline void* platform_get_return_address(int idx) { + switch(idx) { + __SW_RETURN_ADDRESS(0); + __SW_RETURN_ADDRESS(1); + __SW_RETURN_ADDRESS(2); + __SW_RETURN_ADDRESS(3); + }; + return NULL; +} + +#ifdef __cplusplus +} +#endif + +#endif // PLATFORM_DIAGNOSTIC_H diff --git a/platform/MCU/newhal-mcu/inc/platform_diagnostic.h b/platform/MCU/newhal-mcu/inc/platform_diagnostic.h new file mode 100644 index 0000000000..0760037194 --- /dev/null +++ b/platform/MCU/newhal-mcu/inc/platform_diagnostic.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef PLATFORM_DIAGNOSTIC_H +#define PLATFORM_DIAGNOSTIC_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +inline void* platform_get_current_pc(void) { + return NULL; +} + +inline void* platform_get_return_address(int idx) { + return __builtin_return_address(idx); +} + +#ifdef __cplusplus +} +#endif + +#endif // PLATFORM_DIAGNOSTIC_H diff --git a/platform/MCU/shared/STM32/inc/platform_diagnostic.h b/platform/MCU/shared/STM32/inc/platform_diagnostic.h new file mode 100644 index 0000000000..ccf7cee192 --- /dev/null +++ b/platform/MCU/shared/STM32/inc/platform_diagnostic.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef PLATFORM_DIAGNOSTIC_H +#define PLATFORM_DIAGNOSTIC_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +__attribute__((__noinline__)) void* platform_get_current_pc(void); + +inline void* platform_get_return_address(int idx) { + return __builtin_return_address(idx); +} + +#ifdef __cplusplus +} +#endif + +inline bool platform_is_branching_instruction(void* ptr) +{ + uintptr_t p = *((uintptr_t*)ptr); + if ((p & 0xff800000) == 0x47800000 || (p & 0xe0000000) == 0xe0000000) { + return true; + } + + return false; +} + +#if defined(STM32F2XX) + +#define PLATFORM_DIAGNOSTIC_ENABLED 1 + +extern char link_global_retained_system_end; +extern char link_global_retained_system_end_section; + +#define DIAGNOSTIC_LOCATION_BEGIN (&link_global_retained_system_end) +#define DIAGNOSTIC_LOCATION_END (&link_global_retained_system_end_section) +// Due to the fact that we need DIAGNOSTIC_LOCATION_SIZE to be a constexpr, and a +// difference between two pointers is not constexpr, we define DIAGNOSTIC_LOCATION_SIZE manually here +// and verify that we fit using an assert in linker file +#if PLATFORM_ID != 10 +# define DIAGNOSTIC_LOCATION_SIZE 1024 +#else +# define DIAGNOSTIC_LOCATION_SIZE 804 +#endif + +#endif + +#endif // PLATFORM_DIAGNOSTIC_H diff --git a/platform/MCU/shared/STM32/src/platform_diagnostic.c b/platform/MCU/shared/STM32/src/platform_diagnostic.c new file mode 100644 index 0000000000..4c70e34852 --- /dev/null +++ b/platform/MCU/shared/STM32/src/platform_diagnostic.c @@ -0,0 +1,6 @@ +#include "platform_diagnostic.h" + +__attribute__((__noinline__)) void* platform_get_current_pc(void) +{ + return __builtin_return_address(0); +} diff --git a/platform/MCU/shared/STM32/src/sources.mk b/platform/MCU/shared/STM32/src/sources.mk index ffe74dd8cc..18ef99eb3a 100644 --- a/platform/MCU/shared/STM32/src/sources.mk +++ b/platform/MCU/shared/STM32/src/sources.mk @@ -11,7 +11,7 @@ TARGET_SHARED_SRC_PATH = $(PLATFORM_MCU_SHARED_STM32_PATH)/src # C source files included in this directory. CSRC += $(TARGET_SHARED_SRC_PATH)/hw_ticks.c CSRC += $(TARGET_SHARED_SRC_PATH)/hw_system_flags.c - +CSRC += $(TARGET_SHARED_SRC_PATH)/platform_diagnostic.c # C++ source files included in this build. CPPSRC += diff --git a/services/inc/diagnostic.h b/services/inc/diagnostic.h new file mode 100644 index 0000000000..2c69a33ef5 --- /dev/null +++ b/services/inc/diagnostic.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef SERVICES_DIAGNOSTIC_H +#define SERVICES_DIAGNOSTIC_H + +#include +#include +#include "spark_macros.h" + +#ifndef DIAGNOSTIC_SKIP_PLATFORM +#include "platform_diagnostic.h" +#endif + +typedef enum { + CHECKPOINT_TYPE_INVALID = 0, + CHECKPOINT_TYPE_INSTRUCTION_ADDRESS, + CHECKPOINT_TYPE_TEXTUAL +} diagnostic_checkpoint_type_t; + +typedef struct { + uint8_t version; + diagnostic_checkpoint_type_t type; + void* instruction; + const char* text; +} diagnostic_checkpoint_t; + +typedef enum { + DIAGNOSTIC_FLAG_NONE = 0x00, + DIAGNOSTIC_FLAG_DUMP_STACKTRACES = 0x01, + DIAGNOSTIC_FLAG_FREEZE = 0x02 +} diagnostic_flag_t; + + +typedef int (*diagnostic_save_checkpoint_callback_t)(diagnostic_checkpoint_t* chkpt, uint32_t flags, void* reserved); + +typedef struct { + uint8_t version; + void* obj; + diagnostic_save_checkpoint_callback_t diagnostic_save_checkpoint; +} diagnostic_callbacks_t; + +typedef enum { + DIAGNOSTIC_ERROR_NONE = 0, + DIAGNOSTIC_ERROR, + DIAGNOSTIC_ERROR_NO_THREAD_ENTRY, + DIAGNOSTIC_ERROR_FROZEN, + DIAGNOSTIC_ERROR_NO_SPACE +} diagnostic_error_t; + +#ifdef __cplusplus +extern "C" { +#endif + +int diagnostic_set_callbacks(diagnostic_callbacks_t* cb, void* reserved); +int diagnostic_set_callbacks_(diagnostic_callbacks_t* cb, void* reserved); +int diagnostic_save_checkpoint(diagnostic_checkpoint_t* chkpt, uint32_t flags, void* reserved); +int diagnostic_save_checkpoint_(diagnostic_checkpoint_t* chkpt, uint32_t flags, void* reserved); +int diagnostic_save_checkpoint__(diagnostic_checkpoint_t* chkpt, uint32_t flags, void* reserved); +size_t diagnostic_dump_saved(char* buf, size_t bufSize); +size_t diagnostic_dump_current(char* buf, size_t bufSize); + +#ifdef __cplusplus +} +#endif + +#define DIAGNOSTIC_CHECKPOINT_FILE_NAME(X) (__builtin_strrchr(X, '/') ? __builtin_strrchr(X, '/') + 1 : X) + +#if defined(DIAGNOSTIC_USE_CALLBACKS) +#define DIAGNOSTIC_SAVE_CHECKPOINT_FUNC diagnostic_save_checkpoint_ +#elif defined(DIAGNOSTIC_USE_CALLBACKS2) +#define DIAGNOSTIC_SAVE_CHECKPOINT_FUNC diagnostic_save_checkpoint__ +#else +#define DIAGNOSTIC_SAVE_CHECKPOINT_FUNC diagnostic_save_checkpoint +#endif + +#if PLATFORM_ID == 6 || PLATFORM_ID == 8 || PLATFORM_ID == 10 + +#define DIAGNOSTIC_CHECKPOINT_F(type, pc, text, flags) \ + { \ + diagnostic_checkpoint_t chkpt = {0, CHECKPOINT_TYPE_##type, \ + (void*)pc, text}; \ + DIAGNOSTIC_SAVE_CHECKPOINT_FUNC(&chkpt, flags, NULL); \ + } + +#define DIAGNOSTIC_TEXT_CHECKPOINT_C(txt) DIAGNOSTIC_CHECKPOINT_F(TEXTUAL, platform_get_current_pc(), txt, DIAGNOSTIC_FLAG_NONE) +#define DIAGNOSTIC_TEXT_CHECKPOINT() DIAGNOSTIC_TEXT_CHECKPOINT_C(DIAGNOSTIC_CHECKPOINT_FILE_NAME(__FILE__ ":" stringify(__LINE__))) +#define DIAGNOSTIC_INSTRUCTION_CHECKPOINT(pc) DIAGNOSTIC_CHECKPOINT_F(INSTRUCTION_ADDRESS, pc, NULL, DIAGNOSTIC_FLAG_NONE) +#define DIAGNOSTIC_CHECKPOINT() DIAGNOSTIC_CHECKPOINT_F(INSTRUCTION_ADDRESS, platform_get_current_pc(), NULL, DIAGNOSTIC_FLAG_NONE) +#define DIAGNOSTIC_PANIC_CHECKPOINT() DIAGNOSTIC_CHECKPOINT_F(INSTRUCTION_ADDRESS, platform_get_current_pc(), NULL, DIAGNOSTIC_FLAG_DUMP_STACKTRACES | DIAGNOSTIC_FLAG_FREEZE) +#define DIAGNOSTIC_CRASH_CHECKPOINT(pc) DIAGNOSTIC_CHECKPOINT_F(INSTRUCTION_ADDRESS, pc, NULL, DIAGNOSTIC_FLAG_DUMP_STACKTRACES | DIAGNOSTIC_FLAG_FREEZE) +#define DIAGNOSTIC_UPDATE() DIAGNOSTIC_SAVE_CHECKPOINT_FUNC(NULL, DIAGNOSTIC_FLAG_DUMP_STACKTRACES, NULL) + +#else + +#define DIAGNOSTIC_TEXT_CHECKPOINT_C(txt) +#define DIAGNOSTIC_TEXT_CHECKPOINT() +#define DIAGNOSTIC_INSTRUCTION_CHECKPOINT(pc) +#define DIAGNOSTIC_CHECKPOINT() +#define DIAGNOSTIC_PANIC_CHECKPOINT() +#define DIAGNOSTIC_CRASH_CHECKPOINT(pc) +#define DIAGNOSTIC_UPDATE() + +#endif /* PLATFORM_ID == 6 || PLATFORM_ID == 8 || PLATFORM_ID == 10 */ + +#endif // SERVICES_DIAGNOSTIC_H diff --git a/services/inc/logging.h b/services/inc/logging.h index 15e4afa2df..2ff3fc8c13 100644 --- a/services/inc/logging.h +++ b/services/inc/logging.h @@ -125,6 +125,7 @@ #include "panic.h" #include "config.h" #include "preprocessor.h" +#include "diagnostic.h" // NOTE: This header defines various string constants. Ensure identical strings defined in different // translation units get merged during linking (may require enabled optimizations) @@ -336,6 +337,7 @@ static const char* const _log_category = NULL; #define LOG_C(_level, _category, _fmt, ...) \ do { \ if (LOG_LEVEL_##_level >= LOG_COMPILE_TIME_LEVEL) { \ + DIAGNOSTIC_CHECKPOINT(); \ _LOG_ATTR_INIT(_attr); \ log_message(LOG_LEVEL_##_level, _category, &_attr, NULL, _fmt, ##__VA_ARGS__); \ } \ @@ -344,6 +346,7 @@ static const char* const _log_category = NULL; #define LOG_ATTR_C(_level, _category, _attrs, _fmt, ...) \ do { \ if (LOG_LEVEL_##_level >= LOG_COMPILE_TIME_LEVEL) { \ + DIAGNOSTIC_CHECKPOINT(); \ _LOG_ATTR_INIT(_attr); \ PP_FOR_EACH(_LOG_ATTR_SET, _attr, PP_ARGS(_attrs)); \ log_message(LOG_LEVEL_##_level, _category, &_attr, NULL, _fmt, ##__VA_ARGS__); \ @@ -353,6 +356,7 @@ static const char* const _log_category = NULL; #define LOG_WRITE_C(_level, _category, _data, _size) \ do { \ if (LOG_LEVEL_##_level >= LOG_COMPILE_TIME_LEVEL) { \ + DIAGNOSTIC_CHECKPOINT(); \ log_write(LOG_LEVEL_##_level, _category, _data, _size, NULL); \ } \ } while (0) @@ -360,6 +364,7 @@ static const char* const _log_category = NULL; #define LOG_PRINT_C(_level, _category, _str) \ do { \ if (LOG_LEVEL_##_level >= LOG_COMPILE_TIME_LEVEL) { \ + DIAGNOSTIC_CHECKPOINT(); \ const char* const _s = _str; \ log_write(LOG_LEVEL_##_level, _category, _s, strlen(_s), NULL); \ } \ @@ -368,6 +373,7 @@ static const char* const _log_category = NULL; #define LOG_PRINTF_C(_level, _category, _fmt, ...) \ do { \ if (LOG_LEVEL_##_level >= LOG_COMPILE_TIME_LEVEL) { \ + DIAGNOSTIC_CHECKPOINT(); \ log_printf(LOG_LEVEL_##_level, _category, NULL, _fmt, ##__VA_ARGS__); \ } \ } while (0) @@ -432,6 +438,7 @@ static const char* const _log_category = NULL; #define PANIC(_code, _fmt, ...) \ do { \ + DIAGNOSTIC_PANIC_CHECKPOINT(); \ LOG_DEBUG(PANIC, _fmt, ##__VA_ARGS__); \ panic_(_code, NULL, HAL_Delay_Microseconds); \ } while (0) diff --git a/services/inc/services2_dynalib.h b/services/inc/services2_dynalib.h new file mode 100644 index 0000000000..e787b0dd49 --- /dev/null +++ b/services/inc/services2_dynalib.h @@ -0,0 +1,13 @@ +#ifndef SERVICES2_DYNALIB_H +#define SERVICES2_DYNALIB_H + +#include "dynalib.h" + +DYNALIB_BEGIN(services2) + +DYNALIB_FN(0, services2, diagnostic_set_callbacks_, int(diagnostic_callbacks_t*, void*)) +DYNALIB_FN(1, services2, diagnostic_save_checkpoint__, int(diagnostic_checkpoint_t*, uint32_t, void*)) + +DYNALIB_END(services2) + +#endif /* SERVICES2_DYNALIB_H */ diff --git a/services/inc/services_dynalib.h b/services/inc/services_dynalib.h index 55f63e1db8..a7446a75d5 100644 --- a/services/inc/services_dynalib.h +++ b/services/inc/services_dynalib.h @@ -66,6 +66,8 @@ DYNALIB_FN(32, services, led_set_status_active, void(LEDStatusData*, int, void*) DYNALIB_FN(33, services, led_set_update_enabled, void(int, void*)) DYNALIB_FN(34, services, led_update_enabled, int(void*)) DYNALIB_FN(35, services, led_update, void(system_tick_t, LEDStatusData*, void*)) +DYNALIB_FN(36, services, diagnostic_set_callbacks, int(diagnostic_callbacks_t*, void*)) +DYNALIB_FN(37, services, diagnostic_save_checkpoint_, int(diagnostic_checkpoint_t*, uint32_t, void*)) DYNALIB_END(services) diff --git a/services/makefile b/services/makefile index 052e1f3069..1bc847691c 100644 --- a/services/makefile +++ b/services/makefile @@ -5,6 +5,6 @@ SERVICES_MODULE_PATH=. # Target this makefile is building. TARGET_TYPE = a BUILD_PATH_EXT=$(SERVICES_BUILD_PATH_EXT) -DEPENDENCIES = hal wiring dynalib +DEPENDENCIES = hal wiring dynalib platform include ../build/arm-tlm.mk diff --git a/services/src/diagnostic.cpp b/services/src/diagnostic.cpp new file mode 100644 index 0000000000..eeceb33c7c --- /dev/null +++ b/services/src/diagnostic.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#define DIAGNOSTIC_SKIP_PLATFORM +#include "diagnostic.h" + +#if defined(DIAGNOSTIC_USE_CALLBACKS) + +static diagnostic_callbacks_t callbacks = {0}; + +int diagnostic_set_callbacks(diagnostic_callbacks_t* cb, void* reserved) { + if (cb != nullptr) { + callbacks = *cb; + } + return 0; +} + +int diagnostic_save_checkpoint_(diagnostic_checkpoint_t* chkpt, uint32_t flags, void* reserved) { + if (callbacks.diagnostic_save_checkpoint) { + return callbacks.diagnostic_save_checkpoint(chkpt, flags, reserved); + } + + return -1; +} + +#else + +int __attribute__((weak)) diagnostic_set_callbacks(diagnostic_callbacks_t* cb, void* reserved) { + return -1; +} + +#endif + +#if defined(DIAGNOSTIC_USE_CALLBACKS2) + +static diagnostic_callbacks_t callbacks2 = {0}; + +int diagnostic_set_callbacks_(diagnostic_callbacks_t* cb, void* reserved) { + if (cb != nullptr) { + callbacks2 = *cb; + } + return 0; +} + +int diagnostic_save_checkpoint__(diagnostic_checkpoint_t* chkpt, uint32_t flags, void* reserved) { + if (callbacks2.diagnostic_save_checkpoint) { + return callbacks2.diagnostic_save_checkpoint(chkpt, flags, reserved); + } + + return -1; +} + +#else + +int __attribute__((weak)) diagnostic_set_callbacks_(diagnostic_callbacks_t* cb, void* reserved) { + return -1; +} + +#endif diff --git a/services/src/services_dynalib.c b/services/src/services_dynalib.c index efa8492f1a..b194e7ff79 100644 --- a/services/src/services_dynalib.c +++ b/services/src/services_dynalib.c @@ -27,5 +27,6 @@ #include "logging.h" #include "system_error.h" #include "led_service.h" +#define DIAGNOSTIC_SKIP_PLATFORM +#include "diagnostic.h" #include "services_dynalib.h" - diff --git a/system/inc/system_control.h b/system/inc/system_control.h index 1595d5b477..d65a745d0e 100644 --- a/system/inc/system_control.h +++ b/system/inc/system_control.h @@ -56,7 +56,9 @@ typedef enum USBRequestType { USB_REQUEST_SAFE_MODE = 60, USB_REQUEST_LISTENING_MODE = 70, USB_REQUEST_LOG_CONFIG = 80, - USB_REQUEST_MODULE_INFO = 90 + USB_REQUEST_MODULE_INFO = 90, + USB_REQUEST_GET_DIAGNOSTIC = 100, + USB_REQUEST_UPDATE_DIAGNOSTIC = 101 } USBRequestType; typedef enum USBRequestResult { @@ -108,7 +110,7 @@ class SystemControlInterface { uint8_t handleVendorRequest(HAL_USB_SetupRequest* req); - uint8_t enqueueRequest(HAL_USB_SetupRequest* req, DataFormat fmt = DATA_FORMAT_BINARY); + uint8_t enqueueRequest(HAL_USB_SetupRequest* req, DataFormat fmt = DATA_FORMAT_BINARY, bool use_isr = false); uint8_t fetchRequestResult(HAL_USB_SetupRequest* req); static void processSystemRequest(void* data); // Called by SystemThread diff --git a/system/inc/system_dynalib.h b/system/inc/system_dynalib.h index 0cea848de9..fa6d5b6ab2 100644 --- a/system/inc/system_dynalib.h +++ b/system/inc/system_dynalib.h @@ -36,6 +36,7 @@ #include "system_version.h" #include "system_control.h" #include "system_led_signal.h" +#include "diagnostic.h" #endif DYNALIB_BEGIN(system) @@ -84,7 +85,9 @@ DYNALIB_FN(BASE_IDX + 3, system, led_set_signal_theme, int(const LEDSignalThemeD DYNALIB_FN(BASE_IDX + 4, system, led_get_signal_theme, int(LEDSignalThemeData*, int, void*)) DYNALIB_FN(BASE_IDX + 5, system, led_signal_status, const LEDStatusData*(int, void*)) DYNALIB_FN(BASE_IDX + 6, system, led_pattern_period, uint16_t(int, int, void*)) - +DYNALIB_FN(BASE_IDX + 7, system, diagnostic_save_checkpoint, int(diagnostic_checkpoint_t*, uint32_t, void*)) +DYNALIB_FN(BASE_IDX + 8, system, diagnostic_dump_saved, size_t(char*, size_t)) +DYNALIB_FN(BASE_IDX + 9, system, diagnostic_dump_current, size_t(char*, size_t)) DYNALIB_END(system) #undef BASE_IDX diff --git a/system/src/diagnostic.cpp b/system/src/diagnostic.cpp new file mode 100644 index 0000000000..a8fda62297 --- /dev/null +++ b/system/src/diagnostic.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2017 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "concurrent_hal.h" +#include "core_hal.h" +#include +#include "diagnostic.h" + +#if defined(PLATFORM_DIAGNOSTIC_ENABLED) +#include "diagnostic_impl.h" + +using namespace particle::diagnostic; + +struct DiagnosticServiceInitializer { + DiagnosticServiceInitializer() { + auto s = DiagnosticService::lock(true); + + auto diag = DiagnosticService::instance(); + (void)diag; + + diagnostic_callbacks_t cb = {0}; + cb.diagnostic_save_checkpoint = &diagnostic_save_checkpoint; + diagnostic_set_callbacks(&cb, nullptr); +#if defined(MODULE_HAS_SYSTEM_PART3) && MODULE_HAS_SYSTEM_PART3 == 1 + diagnostic_set_callbacks_(&cb, nullptr); +#endif /* defined(MODULE_HAS_SYSTEM_PART3) && MODULE_HAS_SYSTEM_PART3 == 1 */ + + DiagnosticService::unlock(true, s); + } +}; + +static DiagnosticServiceInitializer s_initializer; + +struct diag_save_data_t { + DiagnosticService* diag; + diagnostic_checkpoint_t* chkpt; + os_unique_id_t id; + uint32_t flags; + diagnostic_error_t err; +}; + +int diagnostic_save_checkpoint(diagnostic_checkpoint_t* chkpt, uint32_t flags, void* reserved) +{ + diagnostic_error_t err = DIAGNOSTIC_ERROR_NONE; + os_thread_t thread = os_thread_current(); + os_unique_id_t id = os_thread_unique_id(thread); + + // bool isr = HAL_IsISR(); + bool isr = true; + + uintptr_t st = DiagnosticService::lock(isr); + auto diag = DiagnosticService::instance(); + + if (diag->frozen()) { + DiagnosticService::unlock(isr, st); + return 1; + } + if (flags & DIAGNOSTIC_FLAG_DUMP_STACKTRACES) { + diag->cleanStacktraces(); + diag->unmarkAll(); + + diag_save_data_t d = {diag, chkpt, id, flags, DIAGNOSTIC_ERROR}; + os_thread_dump(OS_THREAD_INVALID_HANDLE, [](os_thread_dump_info_t* info, void* data) -> os_result_t { + diag_save_data_t* d = (diag_save_data_t*)data; + d->err = d->diag->insertCheckpoint(info, info->id == d->id ? d->chkpt : nullptr, true, true); + return 0; + }, &d); + diag->removeUnmarked(); + err = d.err; + } else if (chkpt) { + err = diag->insertCheckpoint(id, chkpt); + if (err == DIAGNOSTIC_ERROR_NO_THREAD_ENTRY) { + diag_save_data_t d = {diag, chkpt, id, flags, DIAGNOSTIC_ERROR}; + os_thread_dump(thread, [](os_thread_dump_info_t* info, void* data) -> os_result_t { + diag_save_data_t* d = (diag_save_data_t*)data; + d->err = d->diag->insertCheckpoint(info, d->chkpt, false); + return 1; + }, &d); + err = d.err; + } + } + diag->updateCrc(); + if (flags & DIAGNOSTIC_FLAG_FREEZE) { + diag->freeze(true); + } + DiagnosticService::unlock(isr, st); + + return 0; +} + +size_t diagnostic_dump_current(char* buf, size_t bufSize) { + // bool isr = HAL_IsISR(); + bool isr = true; + uintptr_t st = DiagnosticService::lock(isr); + auto diag = DiagnosticService::instance(); + size_t sz = diag->dumpCurrent(buf, bufSize); + DiagnosticService::unlock(isr, st); + + return sz; +} + +size_t diagnostic_dump_saved(char* buf, size_t bufSize) { + auto diag = DiagnosticService::instance(); + size_t sz = diag->dumpSaved(buf, bufSize); + + return sz; +} + +#endif /* PLATFORM_DIAGNOSTIC_ENABLED */ diff --git a/system/src/diagnostic_impl.h b/system/src/diagnostic_impl.h new file mode 100644 index 0000000000..9d9c97e05f --- /dev/null +++ b/system/src/diagnostic_impl.h @@ -0,0 +1,705 @@ +/* + * Copyright (c) 2017 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef SYSTEM_DIAGNOSTIC_IMPL_H +#define SYSTEM_DIAGNOSTIC_IMPL_H + +#ifndef DIAGNOSTIC_LOCATION_EXTERNAL_DEFINES +#include "platform_diagnostic.h" +#endif // DIAGNOSTIC_LOCATION_EXTERNAL_DEFINES + +#include +#include +#include "spark_wiring_json.h" + +namespace particle { namespace diagnostic { + +/** + * Default constraints on maximum size + */ +constexpr size_t maxThreadNameLength = 10; +constexpr size_t maxStacktraceSize = 32; +constexpr size_t maxTextualCheckpointLength = 63; + +struct __attribute__((packed)) ThreadEntry { + /** + * Total size of the entry including the structure size + */ + uint16_t size : 15; + + /** + * A marker + */ + uint16_t mark : 1; + + /** + * Unique thread id + */ + uintptr_t id; + + /** + * Textual type checkpoint present flag + */ + uint16_t textual_checkpoint : 1; + + /** + * Address type checkpoint present flag + */ + uint16_t address_checkpoint : 1; + + /** + * A number of uintptr_t entries in the stacktrace + */ + uint16_t count : 7; + + /** + * Thread name length + */ + uint16_t name_length : 7; + + + bool initialized() const { + return (size != 0); + } + + char* name() const { + if (initialized() && name_length > 0) { + return ((char*)this + sizeof(*this)); + } + + return nullptr; + } + + void setName(const char* name) { + uint8_t* ptr = (uint8_t*)this + sizeof(*this); + const size_t len = strnlen(name, maxThreadNameLength); + memcpy(ptr, name, len); + ptr += len; + *ptr = '\0'; + name_length = len + 1; + updateSize(); + } + + uint8_t* checkpoint() const { + if (initialized()) { + return ((uint8_t*)this + sizeof(*this) + name_length); + } + return nullptr; + } + + const char* checkpointText() const { + uint8_t* ptr = checkpoint(); + if (ptr && textual_checkpoint) { + if (address_checkpoint) { + ptr += sizeof(uintptr_t); + } + return (const char*)ptr; + } + return nullptr; + } + + uintptr_t* checkpointAddress() const { + uint8_t* ptr = checkpoint(); + if (ptr && address_checkpoint) { + return (uintptr_t*)ptr; + } + return nullptr; + } + + size_t checkpointSize() const { + size_t sz = 0; + uint8_t* ptr = checkpoint(); + if (initialized() && ptr != nullptr) { + if (address_checkpoint) { + sz += sizeof(uintptr_t); + } + if (textual_checkpoint) { + sz += strlen((const char*)ptr + sizeof(uintptr_t)) + 1; + } + } + + return sz; + } + + uint8_t* stacktrace() const { + size_t chksz = checkpointSize(); + uint8_t* ptr = checkpoint(); + if (ptr != nullptr) { + return (ptr + chksz); + } + + return nullptr; + } + + size_t stacktraceSize() const { + size_t sz = 0; + if (initialized()) { + sz = count * sizeof(uintptr_t); + } + + return sz; + } + + size_t calculateSize() const { + return sizeof(*this) + name_length + checkpointSize() + stacktraceSize(); + } + + void updateSize() { + size = calculateSize(); + } + + void updateCheckpoint(diagnostic_checkpoint_t* chkpt) { + if (chkpt != nullptr) { + address_checkpoint = 1; + textual_checkpoint = chkpt->type == CHECKPOINT_TYPE_TEXTUAL; + uint8_t* ptr = checkpoint(); + *((uintptr_t*)ptr) = (uintptr_t)chkpt->instruction; + ptr += sizeof(uintptr_t); + if (textual_checkpoint) { + const size_t tLen = strnlen(chkpt->text, maxTextualCheckpointLength); + memcpy(ptr, chkpt->text, tLen); + ptr += tLen; + *ptr = '\0'; + } + } else { + address_checkpoint = textual_checkpoint = 0; + } + updateSize(); + } + + void reset() { + memset(this, 0, sizeof(*this)); + } + +}; + +struct ThreadEntryIterator { + ThreadEntryIterator(ThreadEntry* th, const uint8_t* location, size_t sz) + : loc{location}, + locsz{sz}, + val{th} { + } + + ThreadEntryIterator(const ThreadEntryIterator& other) = default; + + ThreadEntry* value() { + if (valid(val)) { + return val; + } + + return nullptr; + } + + bool valid(ThreadEntry* th) const { + if (th && (uint8_t*)th >= ((uint8_t*)loc + sizeof(uint32_t)) && + (uint8_t*)th < ((uint8_t*)(loc + locsz) - sizeof(ThreadEntry)) && + th->initialized()) { + return true; + } + + return false; + } + + ThreadEntryIterator next() const { + if (!valid(val)) { + return ThreadEntryIterator(nullptr, loc, locsz); + } + + uint8_t* ptr = ((uint8_t*)val) + val->size; + if (valid(reinterpret_cast(ptr))) { + return ThreadEntryIterator(reinterpret_cast(ptr), loc, locsz); + } + + return ThreadEntryIterator(nullptr, loc, locsz); + } + + const uint8_t* loc; + size_t locsz; + ThreadEntry* val; +}; + +static_assert(std::is_pod::value, "ThreadEntry should be a POD"); + +class DiagnosticService { +public: + static DiagnosticService* instance(); + + bool isValid() const; + diagnostic_error_t insertCheckpoint(os_thread_t thread, diagnostic_checkpoint_t* chkpt); + diagnostic_error_t insertCheckpoint(os_thread_dump_info_t* info, diagnostic_checkpoint_t* chkpt, bool stacktrace, bool keep = true); + diagnostic_error_t insertCheckpoint(os_unique_id_t thread, diagnostic_checkpoint_t* chkpt); + bool unmarkAll(); + bool removeUnmarked(); + bool cleanStacktraces(); + + static uintptr_t lock(bool irq = false); + static void unlock(bool irq = false, uintptr_t st = 0); + + size_t dumpCurrent(char* out, size_t sz) const; + size_t dumpSaved(char* out, size_t sz) const; + void updateCrc(); + + void freeze(bool val); + bool frozen() const; + +protected: + DiagnosticService(); + DiagnosticService(uint8_t* location, size_t sz); + + static size_t size(diagnostic_checkpoint_t* chkpt); + + void initialize(); + bool valid() const; + uint32_t computeCrc() const; + + size_t dump(uint8_t* location, size_t dsize, char* out, size_t sz) const; + bool allocate(ThreadEntry* th, uint8_t* ptr, size_t available, size_t needed); + + bool setThreadCheckpoint(ThreadEntry* h, diagnostic_checkpoint_t* chkpt); + + ThreadEntry* first() const; + ThreadEntry* last(bool initialized = false) const; + ThreadEntry* next(ThreadEntry* t) const; + ThreadEntry* findThread(os_unique_id_t th) const; + + bool isThreadEntry(uint8_t* ptr) const; + + size_t freeSpace() const; + + uint8_t* location_ = nullptr; + const size_t size_ = 0; + uint8_t savedData_[DIAGNOSTIC_LOCATION_SIZE]; + bool savedAvailable_ = false; + bool frozen_ = false; +}; + +inline DiagnosticService::DiagnosticService() : DiagnosticService((uint8_t*)DIAGNOSTIC_LOCATION_BEGIN, DIAGNOSTIC_LOCATION_SIZE) { +} + +inline DiagnosticService::DiagnosticService(uint8_t* location, size_t sz) + : location_{location}, + size_{sz} { + uintptr_t st = lock(true); + if (valid()) { + savedAvailable_ = true; + memcpy(savedData_, location_, size_); + } + initialize(); + unlock(true, st); +} + +inline DiagnosticService* DiagnosticService::instance() { + static DiagnosticService service; + return &service; +} + +inline bool DiagnosticService::isValid() const { + uintptr_t st = lock(true); + bool v = valid(); + unlock(true, st); + + return v; +} + +inline diagnostic_error_t DiagnosticService::insertCheckpoint(os_thread_t thread, diagnostic_checkpoint_t* chkpt) { + if (frozen_) { + return DIAGNOSTIC_ERROR_FROZEN; + } + os_unique_id_t id = os_thread_unique_id(thread); + return insertCheckpoint(id, chkpt); +} + +inline diagnostic_error_t DiagnosticService::insertCheckpoint(os_unique_id_t thread, diagnostic_checkpoint_t* chkpt) { + if (frozen_) { + return DIAGNOSTIC_ERROR_FROZEN; + } + ThreadEntry* h = findThread(thread); + if (h == nullptr) { + return DIAGNOSTIC_ERROR_NO_THREAD_ENTRY; + } + + bool result = setThreadCheckpoint(h, chkpt); + + return result ? DIAGNOSTIC_ERROR_NONE : DIAGNOSTIC_ERROR; +} + +inline diagnostic_error_t DiagnosticService::insertCheckpoint(os_thread_dump_info_t* info, diagnostic_checkpoint_t* chkpt, bool stacktrace, bool keep) { + if (frozen_) { + return DIAGNOSTIC_ERROR_FROZEN; + } + ThreadEntry* th = findThread(info->id); + bool res = false; + + if (th != nullptr) { + // Update existing + if (chkpt != nullptr || !keep) { + res = setThreadCheckpoint(th, chkpt); + } else { + res = true; + } + } else { + // Insert new at the end + if (freeSpace() < (sizeof(ThreadEntry) + size(chkpt) + strnlen(info->name, maxThreadNameLength) + 1)) { + return DIAGNOSTIC_ERROR_NO_SPACE; + } + + th = last(); + + if (th == nullptr) { + return DIAGNOSTIC_ERROR; + } + + th->id = info->id; + th->setName(info->name); + res = setThreadCheckpoint(th, chkpt); + } + + if (stacktrace) { + // Dry run to get a number of entries + int count = HAL_Core_Generate_Stacktrace(info, + [](void* ptr, int idx, uintptr_t addr, os_thread_dump_info_t* info, void* reserved) -> int { + return 0; + }, + (void*)th, nullptr); + + th->mark = 1; + + size_t available = th->stacktraceSize(); + if (allocate(th, th->stacktrace(), available, sizeof(uintptr_t) * count)) { + th->count = std::min(count, (int)maxStacktraceSize); + HAL_Core_Generate_Stacktrace(info, + [](void* ptr, int idx, uintptr_t addr, os_thread_dump_info_t* info, void* reserved) -> int { + ThreadEntry* th = (ThreadEntry*)ptr; + if (idx < th->count) { + ((uintptr_t*)th->stacktrace())[idx] = addr; + return 0; + } + return 1; + }, + (void*)th, nullptr); + + res = true; + } else { + allocate(th, th->stacktrace(), available, 0); + th->count = 0; + } + } + + th->updateSize(); + + return res ? DIAGNOSTIC_ERROR_NONE : DIAGNOSTIC_ERROR; +} + +inline bool DiagnosticService::unmarkAll() { + if (frozen_) { + return false; + } + for(ThreadEntry* h = first(); h != nullptr && h->initialized(); h = next(h)) { + h->mark = 0; + } + + return true; +} + +inline bool DiagnosticService::removeUnmarked() { + if (frozen_) { + return false; + } + bool modified = false; + for(ThreadEntry* h = first(); h != nullptr && h->initialized();) { + if (h->mark == 0) { + const size_t sz = h->size; + memset(h, 0, sz); + allocate(h, (uint8_t*)h, sz, 0); + modified = true; + } else { + h = next(h); + } + } + + if (modified) { + updateCrc(); + } + + return true; +} + +inline bool DiagnosticService::cleanStacktraces() { + if (frozen_) { + return false; + } + bool modified = false; + for(ThreadEntry* h = first(); h != nullptr && h->initialized(); h = next(h)) { + if (h->stacktraceSize() > 0) { + allocate(h, h->stacktrace(), h->stacktraceSize(), 0); + h->count = 0; + h->updateSize(); + modified = true; + } + } + + if (modified) { + updateCrc(); + } + + return true; +} + +inline uintptr_t DiagnosticService::lock(bool irq) { + if (!HAL_IsISR() && !irq) { + os_thread_scheduling(false, nullptr); + } else { + return static_cast(HAL_disable_irq()); + } + + return 0; +} + +inline void DiagnosticService::unlock(bool irq, uintptr_t st) { + if (!HAL_IsISR() && !irq) { + os_thread_scheduling(true, nullptr); + } else { + HAL_enable_irq(st); + } +} + +inline size_t DiagnosticService::dumpCurrent(char* out, size_t sz) const { + return dump(location_, size_, out, sz); +} + +inline size_t DiagnosticService::dumpSaved(char* out, size_t sz) const { + if (savedAvailable_ == true) { + return dump((uint8_t*)savedData_, sizeof(savedData_), out, sz); + } + return 0; +} + +inline void DiagnosticService::updateCrc() { + if (frozen_) { + return; + } + uint32_t* crc = reinterpret_cast(location_); + *crc = computeCrc(); +} + +inline size_t DiagnosticService::size(diagnostic_checkpoint_t* chkpt) { + size_t sz = 0; + + if (chkpt) { + sz = chkpt->type == CHECKPOINT_TYPE_INSTRUCTION_ADDRESS ? + sizeof(uintptr_t) : + sizeof(uintptr_t) + strnlen(chkpt->text, maxTextualCheckpointLength) + 1; + } + + return sz; +} + +inline void DiagnosticService::freeze(bool val) { + frozen_ = val; +} + +inline bool DiagnosticService::frozen() const { + return frozen_; +} + +inline void DiagnosticService::initialize() { + memset(location_, 0, size_); + updateCrc(); +} + +inline bool DiagnosticService::valid() const { + uint32_t crc = *reinterpret_cast(location_); + return (crc == computeCrc()); +} + +inline uint32_t DiagnosticService::computeCrc() const { + return HAL_Core_Compute_CRC32((const uint8_t*)(location_ + sizeof(uint32_t)), + size_ - sizeof(uint32_t)); +} + +inline size_t DiagnosticService::dump(uint8_t* location, size_t dsize, char* out, size_t sz) const { + char tmp[sizeof(uintptr_t) * 2 + 4] = {0}; + spark::JSONBufferWriter writer(out, sz - 1); + + writer.beginArray(); + for (auto it = ThreadEntryIterator(reinterpret_cast(location + sizeof(uint32_t)), + location, dsize); + it.value() != nullptr; + it = it.next()) { + auto th = it.value(); + writer.beginObject(); + writer.name("t"); + writer.value(th->name()); + writer.name("i"); + writer.value(th->id); + if (th->textual_checkpoint || th->address_checkpoint) { + writer.name("c"); + writer.beginObject(); + if (th->address_checkpoint) { + writer.name("a"); + snprintf(tmp, sizeof(tmp), "0x%" PRIxPTR, *th->checkpointAddress()); + writer.value(tmp); + } + if (th->textual_checkpoint) { + writer.name("x"); + writer.value(th->checkpointText()); + } + writer.endObject(); + } + if (th->count > 0) { + writer.name("s"); + writer.beginArray(); + for (unsigned i = 0; i < th->count; i++) { + uintptr_t val = *(uintptr_t*)(th->stacktrace() + (i * sizeof(uintptr_t))); + snprintf(tmp, sizeof(tmp), "0x%" PRIxPTR, val); + writer.value(tmp); + } + writer.endArray(); + } + writer.endObject(); + } + writer.endArray(); + + out[std::min(sz - 1, writer.dataSize())] = '\0'; + return writer.dataSize(); +} + +inline bool DiagnosticService::allocate(ThreadEntry* th, uint8_t* ptr, size_t available, size_t needed) { + if (available >= needed) { + memmove(ptr + needed, ptr + available, + (uint8_t*)(location_ + size_) - (ptr + available)); + + return true; + } else { + const size_t moveRight = (needed - available); + if (freeSpace() >= moveRight) { + ThreadEntry* lt = last(true); + if (lt == nullptr) { + lt = first(); + } + uint8_t* end = (uint8_t*)lt; + end += lt->size; + memmove(ptr + moveRight, ptr, end - ptr); + return true; + } else { + return false; + } + } + + return false; +} + +inline bool DiagnosticService::setThreadCheckpoint(ThreadEntry* h, diagnostic_checkpoint_t* chkpt) { + if (h == nullptr) { + return false; + } + + const size_t currentSize = h->checkpointSize(); + const size_t newSize = size(chkpt); + + if (allocate(h, h->checkpoint(), currentSize, newSize)) { + h->updateCheckpoint(chkpt); + } else { + // Remove it + allocate(h, h->checkpoint(), currentSize, 0); + h->updateCheckpoint(nullptr); + return false; + } + + + return true; +} + +inline ThreadEntry* DiagnosticService::first() const { + // Never returns null + ThreadEntry* h = (ThreadEntry*)((uint8_t*)(location_) + sizeof(uint32_t)); + return h; +} + +inline ThreadEntry* DiagnosticService::last(bool initialized) const { + ThreadEntry* l = first(); + ThreadEntry* prev = l; + for (ThreadEntry* h = l; h != nullptr; h = next(h)) { + prev = l; + l = h; + if (!h->initialized()) { + break; + } + } + + if (initialized && !l->initialized()) { + return (prev != nullptr && prev->initialized()) ? prev : nullptr; + } + + if (!initialized && l->initialized()) { + return nullptr; + } + + return l; +} + +inline ThreadEntry* DiagnosticService::next(ThreadEntry* t) const { + if (t == nullptr || !t->initialized()) { + return nullptr; + } + + uint8_t* ptr = ((uint8_t*)t) + t->size; + if (isThreadEntry(ptr)) { + return (ThreadEntry*)(ptr); + } + + return nullptr; +} + +inline ThreadEntry* DiagnosticService::findThread(os_unique_id_t th) const { + for(ThreadEntry* h = first(); h != nullptr && h->initialized(); h = next(h)) { + if (h->id == th) { + return h; + } + } + + return nullptr; +} + +inline bool DiagnosticService::isThreadEntry(uint8_t* ptr) const { + if (ptr >= ((uint8_t*)location_ + sizeof(uint32_t)) && + ptr < ((uint8_t*)(location_ + size_) - sizeof(ThreadEntry))) { + + return true; + } + + return false; +} + +inline size_t DiagnosticService::freeSpace() const { + ThreadEntry* t = last(true); + if (t == nullptr) { + t = first(); + } + uint8_t* end = (uint8_t*)t; + end += t->size; + + if (end <= (location_ + size_)) { + return ((uint8_t*)(location_ + size_) - end); + } + + return 0; +} + +} } // namespace particle::diagnostic + +#endif // SYSTEM_DIAGNOSTIC_IMPL_H diff --git a/system/src/system_control.cpp b/system/src/system_control.cpp index 4289a8a301..0a150e9660 100644 --- a/system/src/system_control.cpp +++ b/system/src/system_control.cpp @@ -34,6 +34,7 @@ #include "system_network_internal.h" #include "bytes2hexbuf.h" #include "system_update.h" +#include "diagnostic.h" #ifdef USB_VENDOR_REQUEST_ENABLE @@ -143,10 +144,18 @@ uint8_t SystemControlInterface::handleVendorRequest(HAL_USB_SetupRequest* req) { return enqueueRequest(req, DATA_FORMAT_JSON); } + case USB_REQUEST_GET_DIAGNOSTIC: { + return enqueueRequest(req, DATA_FORMAT_BINARY, true); + } case USB_REQUEST_CUSTOM: { return enqueueRequest(req); } + case USB_REQUEST_UPDATE_DIAGNOSTIC: { + DIAGNOSTIC_UPDATE(); + break; + } + default: { // Unknown request return 1; @@ -193,7 +202,8 @@ uint8_t SystemControlInterface::handleVendorRequest(HAL_USB_SetupRequest* req) { case USB_REQUEST_MODULE_INFO: case USB_REQUEST_LOG_CONFIG: - case USB_REQUEST_CUSTOM: { + case USB_REQUEST_CUSTOM: + case USB_REQUEST_GET_DIAGNOSTIC: { return fetchRequestResult(req); } @@ -207,7 +217,7 @@ uint8_t SystemControlInterface::handleVendorRequest(HAL_USB_SetupRequest* req) { return 0; } -uint8_t SystemControlInterface::enqueueRequest(HAL_USB_SetupRequest* req, DataFormat fmt) { +uint8_t SystemControlInterface::enqueueRequest(HAL_USB_SetupRequest* req, DataFormat fmt, bool use_isr) { SPARK_ASSERT(req->bmRequestTypeDirection == 0); // Host to device if (usbReq_.active && !usbReq_.ready) { return 1; // // There is an active request already @@ -229,9 +239,11 @@ uint8_t SystemControlInterface::enqueueRequest(HAL_USB_SetupRequest* req, DataFo return 0; // OK } } - // Schedule request for processing in the system thread's context - if (!SystemISRTaskQueue.enqueue(processSystemRequest, &usbReq_.req)) { - return 1; + if (!use_isr) { + // Schedule request for processing in the system thread's context + if (!SystemISRTaskQueue.enqueue(processSystemRequest, &usbReq_.req)) { + return 1; + } } usbReq_.req.type = (USBRequestType)req->wIndex; usbReq_.req.value = req->wValue; @@ -240,6 +252,9 @@ uint8_t SystemControlInterface::enqueueRequest(HAL_USB_SetupRequest* req, DataFo usbReq_.req.format = fmt; usbReq_.ready = false; usbReq_.active = true; + if (use_isr) { + processSystemRequest(&usbReq_.req); + } return 0; } @@ -314,6 +329,18 @@ void SystemControlInterface::processSystemRequest(void* data) { break; } + case USB_REQUEST_GET_DIAGNOSTIC: { + if (req->value) { + req->reply_size = diagnostic_dump_current(req->data, USB_REQUEST_BUFFER_SIZE); + } else { + req->reply_size = diagnostic_dump_saved(req->data, USB_REQUEST_BUFFER_SIZE); + } + if (req->reply_size > USB_REQUEST_BUFFER_SIZE) { + req->reply_size = USB_REQUEST_BUFFER_SIZE; + } + break; + } + default: if (usbReqAppHandler) { processAppRequest(data); // Forward request to the application thread diff --git a/user/import.mk b/user/import.mk index 5ae12dacba..a2e063035a 100644 --- a/user/import.mk +++ b/user/import.mk @@ -26,6 +26,8 @@ CFLAGS += -DINCLUDE_PLATFORM=1 # platforms.h ifeq ($(PLATFORM_ID),3) INCLUDE_DIRS += $(PROJECT_ROOT)/platform/shared/inc +INCLUDE_DIRS += $(PROJECT_ROOT)/platform/MCU/gcc/inc +INCLUDE_DIRS += $(PROJECT_ROOT)/hal/src/gcc endif # gcc HAL is different for test driver and test subject diff --git a/user/tests/app/checkpoint/README.md b/user/tests/app/checkpoint/README.md new file mode 100644 index 0000000000..af79547670 --- /dev/null +++ b/user/tests/app/checkpoint/README.md @@ -0,0 +1,20 @@ +Flash checkpoint application to the device: +$ cd ~/firmware/modules +$ make -s all program-dfu PLATFORM=photon TEST=app/checkpoint + +Install cli dependencies: +$ cd ~/firmware/user/tests/app/checkpoint/cli +$ npm install + +Run client: +$ ./cli + +Publish some events that trigger specific scenarios on device: +$ particle publish hardfault --private +$ particle publish panic --private +$ particle publish deadlock --private + +$ Run some commands to get last or current diagnostic info or force a full stacktrace dump of currently running threads: +$ ./cli get +$ ./cli getlast +$ ./cli update diff --git a/user/tests/app/checkpoint/checkpoint.cpp b/user/tests/app/checkpoint/checkpoint.cpp new file mode 100644 index 0000000000..ca23af1f03 --- /dev/null +++ b/user/tests/app/checkpoint/checkpoint.cpp @@ -0,0 +1,137 @@ +/* + ****************************************************************************** + * Copyright (c) 2015 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + ****************************************************************************** + */ + +#include "application.h" + +SYSTEM_THREAD(ENABLED); + +static os_mutex_t s_mutex = {0}; + +void hardfault_event_handler(const char* event, const char* data) { + HAL_USART_Write_NineBitData((HAL_USART_Serial)10, 0x0000); +} + +void panic_event_handler(const char* event, const char* data) { + // Got into panic + PANIC(UsageFault, "UsageFault"); +} + +void deadlock_event_handler(const char* event, const char* data) { + // Lock an already locked mutex + os_mutex_lock(s_mutex); +} + +/* executes once at startup */ +void setup() { + os_mutex_create(&s_mutex); + os_mutex_lock(s_mutex); + + pinMode(D7, OUTPUT); + + Particle.subscribe("hardfault", hardfault_event_handler, MY_DEVICES); + Particle.subscribe("panic", panic_event_handler, MY_DEVICES); + Particle.subscribe("deadlock", deadlock_event_handler, MY_DEVICES); +} + +void somefunc(); + +/* executes continuously after setup() runs */ +void loop() { + delay(1000); + CHECKPOINT(); + digitalWrite(D7, !digitalRead(D7)); + delay(1000); + somefunc(); + digitalWrite(D7, !digitalRead(D7)); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +void somefunc() { + CHECKPOINT(); +} diff --git a/user/tests/app/checkpoint/cli/cli b/user/tests/app/checkpoint/cli/cli new file mode 100755 index 0000000000..5b77290fc3 --- /dev/null +++ b/user/tests/app/checkpoint/cli/cli @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +if [ ! $PLATFORM ]; then + echo 'PLATFORM is not defined, assuming Photon' + export PLATFORM=photon +fi + +this_dir=$(cd $(dirname "$0") && pwd) +test_dir=$(cd "$this_dir/../../../accept" && pwd) + +PATH="$test_dir/tools:$PATH" +source "$test_dir/init_env" + +SCRIPT_DIR="${this_dir}" +export SCRIPT_DIR + +node "$this_dir/main.js" $@ diff --git a/user/tests/app/checkpoint/cli/main.js b/user/tests/app/checkpoint/cli/main.js new file mode 100644 index 0000000000..1b422eecba --- /dev/null +++ b/user/tests/app/checkpoint/cli/main.js @@ -0,0 +1,185 @@ +const parser = require('particle-diagnostic-parser').DiagnosticParser; +const util = require('util'); +const Addr2Line = require('addr2line').Addr2Line; +const Promise = require('bluebird'); +const os = require('os'); +const Table = require('cli-table'); +const exec = require('child_process').exec; + +const checkpointTableHead = ['Addr', 'Text', 'Source', 'Function']; +const stackTraceTableHead = ['Addr', 'Source', 'Function']; + +function formatFileLine(file, line) { + if (file && line) { + return util.format('%s:%d', file, line); + } + return ''; +}; + +function fixupJson(json) { + // We currently have a limit on maximum USB request size set to 512, so JSON data might get truncated. + // We are using this simple function to at least try to fixup the truncated JSON + // Taken from https://gist.github.com/kekscom/10925007 + var chunk = json; + + var m, q = false; + var stack = []; + + while (m = chunk.match(/[^\{\[\]\}"]*([\{\[\]\}"])/)) { + switch (m[1]) { + case '{': + stack.push('}'); + break; + case '[': + stack.push(']'); + break; + + case '}': + case ']': + stack.pop(); + break; + + case '"': + if (!q) { + q = true; + stack.push('"'); + } else { + q = false; + stack.pop(); + } + break; + } + chunk = chunk.substring(m[0].length); + } + + if (chunk[chunk.length-1] === ':') { + json += '""'; + } + + while (stack.length) { + json += stack.pop(); + } + + return json; +}; + +function execPromise(args, opts) { + opts = opts || {}; + return new Promise((resolve, reject) => { + const proc = exec(args, opts, (err, stdout, stderr) => { + if (err) { + return reject(err); + } else { + return resolve({stdout: stdout, stderr: stderr}); + } + }); + }); +}; + +function dump(data) { + return new Promise((resolve, reject) => { + let str = ''; + data.forEach((thread) => { + str += util.format('Thread: %s [%d] %s', thread.thread, thread.id, os.EOL); + if ('checkpoint' in thread) { + str += 'Checkpoint:' + os.EOL; + let table = new Table({ head: checkpointTableHead }); + table.push([thread.checkpoint.address, + thread.checkpoint.text || '', + formatFileLine(thread.checkpoint.filename, thread.checkpoint.line), + thread.checkpoint.function || '']); + str += table.toString() + os.EOL; + } + if ('stacktrace' in thread) { + str += 'Stacktrace:' + os.EOL; + let table = new Table({ head: stackTraceTableHead }); + thread.stacktrace.forEach((sitem) => { + table.push([sitem.address, + formatFileLine(sitem.filename, sitem.line), + sitem.function || '']); + }); + str += table.toString() + os.EOL; + } + str += os.EOL; + }); + resolve(str); + }); +}; + +var elfs = []; + +function generateElfs() { + const platform = process.env.PLATFORM; + const pwd = process.env.SCRIPT_DIR; + const genPath = function(pwd, platform, target, file) { + return pwd + '/../../../../../build/target/' + target + '/' + platform + '/' + file; + }; + switch(platform.toLowerCase()) { + case 'photon': + elfs = [ + genPath(pwd, 'platform-6-m', 'system-part1', 'system-part1.elf'), + genPath(pwd, 'platform-6-m', 'system-part2', 'system-part2.elf'), + genPath(pwd, 'platform-6-m', 'user-part', 'checkpoint.elf') + ]; + case 'p1': + elfs = [ + genPath(pwd, 'platform-8-m', 'system-part1', 'system-part1.elf'), + genPath(pwd, 'platform-8-m', 'system-part2', 'system-part2.elf'), + genPath(pwd, 'platform-8-m', 'user-part', 'checkpoint.elf') + ]; + break; + case 'electron': + elfs = [ + genPath(pwd, 'platform-10-m', 'system-part1', 'system-part1.elf'), + genPath(pwd, 'platform-10-m', 'system-part2', 'system-part2.elf'), + genPath(pwd, 'platform-10-m', 'system-part3', 'system-part3.elf'), + genPath(pwd, 'platform-10-m', 'user-part', 'checkpoint.elf') + ]; + } +}; + +generateElfs(); + +const resolver = new Addr2Line(elfs, {prefix: 'arm-none-eabi-', basenames: true}); +const p = new parser((addr) => { + return resolver.resolve(addr); +}); + +function help() { + console.log('Commands:'); + console.log(' - update: Forces a full diagnostic dump of a running device (includes stacktraces)'); + console.log(' - get: Retrieves current diagnostic info from a running device'); + console.log(' - getlast: Retrieves previous boot diagnostic info from a running device'); + process.exit(1); +}; + +function main() { + const args = process.argv.splice(process.execArgv.length + 2); + let v = 1; + switch(args[0]) { + case 'update': + execPromise('send_usb_req -i 101 -d out', { shell: '/bin/bash' }).then(() => { + console.log('Done'); + process.exit(0); + }); + break; + case 'getlast': + v = 0; + case 'get': + execPromise(util.format('send_usb_req -i 100 -v %d', v), { shell: '/bin/bash' }).then((s) => { + let j = fixupJson(s.stdout.trim()); + p.expand(j).then((res) => { + dump(res).then((res) => { + console.log(res); + process.exit(0); + }); + }); + }); + break; + default: + help(); + return; + } +}; + +main(); diff --git a/user/tests/app/checkpoint/cli/package.json b/user/tests/app/checkpoint/cli/package.json new file mode 100644 index 0000000000..a5d16ed5b9 --- /dev/null +++ b/user/tests/app/checkpoint/cli/package.json @@ -0,0 +1,11 @@ +{ + "name": "cli", + "version": "1.0.0", + "private": true, + "main": "main.js", + "dependencies": { + "particle-diagnostic-parser": "^0.0.1", + "addr2line": "^0.0.3", + "cli-table": "^0.3.1" + } +} diff --git a/user/tests/unit/diagnostic_service.cpp b/user/tests/unit/diagnostic_service.cpp new file mode 100644 index 0000000000..5c3098f453 --- /dev/null +++ b/user/tests/unit/diagnostic_service.cpp @@ -0,0 +1,367 @@ +#include "tools/random.h" +#include "tools/catch.h" +#include +#include + +#include "hippomocks.h" + +#define DIAGNOSTIC_LOCATION_SIZE 1024 +static uint8_t s_diagnostic_area[DIAGNOSTIC_LOCATION_SIZE] = {0}; + +#define DIAGNOSTIC_LOCATION_BEGIN (&s_diagnostic_area[0]) +#define DIAGNOSTIC_LOCATION_END (&s_diagnostic_area[0] + DIAGNOSTIC_LOCATION_SIZE) + +#include "concurrent_hal.h" +#include "core_hal.h" + +#define DIAGNOSTIC_LOCATION_EXTERNAL_DEFINES +#include "diagnostic.h" +#include "diagnostic_impl.h" + +int HAL_disable_irq() +{ + return 0; +} + +void HAL_enable_irq(int is) { +} + +void os_thread_scheduling(bool enable, void* reserved) { +} + +uint32_t HAL_Core_Compute_CRC32(const uint8_t *pBuffer, uint32_t bufferSize) +{ + boost::crc_32_type result; + result.process_bytes(pBuffer, bufferSize); + return result.checksum(); +} + +int HAL_Core_Generate_Stacktrace(os_thread_dump_info_t* info, HAL_Stacktrace_Callback callback, void* ptr, void* reserved) { + return 0; +} + +os_unique_id_t os_thread_unique_id(os_thread_t th) { + return (os_unique_id_t)th; +} + +namespace { + +using namespace particle::diagnostic; + +class DiagnosticTestService : public DiagnosticService { +public: + DiagnosticTestService() + : DiagnosticService(s_diagnostic_area, sizeof(s_diagnostic_area)) { + + } + + bool pallocate(ThreadEntry* th, uint8_t* ptr, size_t available, size_t needed) { + return allocate(th, ptr, available, needed); + } + + ThreadEntry* pfirst() const { + return first(); + } + + ThreadEntry* plast(bool initialized = false) const { + return last(initialized); + } + + ThreadEntry* pnext(ThreadEntry* t) const { + return next(t); + } + + ThreadEntry* pfindThread(os_unique_id_t th) { + return findThread(th); + } + + bool pvalid() const { + return valid(); + } + + uint32_t pcomputeCrc() const { + return computeCrc(); + } + + void pupdateCrc() { + return updateCrc(); + } + + bool pisThreadEntry(uint8_t* ptr) const { + return isThreadEntry(ptr); + } + + void pinitialize() { + return initialize(); + } + + static size_t psize(diagnostic_checkpoint_t* chkpt) { + return size(chkpt); + } + + size_t pfreeSpace() { + return freeSpace(); + } + + bool psavedAvailable() { + return savedAvailable_; + } +}; + +const char* s_thread_names[] = { + "thread0", + "thread1", + "thread2", + "thread3", + "thread4", + "thread5", + "thread6", + "thread7", + "thread8", + "thread9", + "thread10", + "thread11", + "thread12", + "thread13", + "thread14", + "thread15", + "thread16" +}; + +struct ThreadEntryValidator { + ThreadEntryValidator(ThreadEntry* th, os_thread_dump_info_t* info, diagnostic_checkpoint_t* chkpt) { + const size_t checkpointSize = DiagnosticTestService::psize(chkpt); + const size_t stacktraceSize = th->stacktraceSize(); + value = ( + th != nullptr && chkpt != nullptr && + th->initialized() == true && + (th->size == sizeof(*th) + strnlen(info->name, maxThreadNameLength) + 1 + checkpointSize + stacktraceSize) && + (th->checkpointSize() == checkpointSize) && + !strncmp(th->name(), info->name, strlen(th->name())) && + (chkpt->type != CHECKPOINT_TYPE_TEXTUAL || !strncmp(th->checkpointText(), chkpt->text, strlen(th->checkpointText()))) && + th->checkpointAddress() && *(th->checkpointAddress()) == (uintptr_t)chkpt->instruction + ); + } + + operator bool() const { + return value; + } + + bool value; +}; + +struct StacktraceGenerator { + StacktraceGenerator(size_t num) + : maxItems(num) { + } + + int HAL_Core_Generate_Stacktrace(os_thread_dump_info_t* info, HAL_Stacktrace_Callback callback, void* ptr, void* reserved) { + auto it = stacktraces.find(info->thread); + if (it == stacktraces.end()) { + auto& gen = test::randomGenerator(); + std::uniform_int_distribution sdist; + std::uniform_int_distribution maxdist(1, maxItems); + std::vector strace; + const size_t items = maxdist(gen); + for (size_t i = 0; i < items; i++) { + strace.push_back(sdist(gen)); + } + stacktraces.insert({info->thread, std::move(strace)}); + it = stacktraces.find(info->thread); + } + + if (it != stacktraces.end()) { + for (unsigned i = 0; i < it->second.size(); i++) { + if (callback(ptr, i, (it->second)[i], info, nullptr) != 0) { + return i; + } + } + + return it->second.size(); + } + + return 0; + } + + std::unordered_map > stacktraces; + const size_t maxItems; +}; + +class StacktraceMocks { +public: + StacktraceMocks(StacktraceGenerator& gen) + : gen_(gen) { + mocks_.OnCallFunc(HAL_Core_Generate_Stacktrace).Do([&](os_thread_dump_info_t* info, HAL_Stacktrace_Callback callback, void* ptr, void* reserved) -> int { + return gen_.HAL_Core_Generate_Stacktrace(info, callback, ptr, reserved); + }); + } + +private: + StacktraceGenerator& gen_; + MockRepository mocks_; +}; + + +} // namespace + +TEST_CASE("DiagnosticService") { + DiagnosticTestService diag; + + std::vector threads; + for(unsigned i = 0; i < 16; i++) { + os_thread_dump_info_t th = { + 0, + reinterpret_cast(i), + s_thread_names[i], + static_cast(i), + nullptr, + nullptr, + nullptr + }; + threads.push_back(th); + } + + SECTION("constructed") { + REQUIRE(diag.isValid() == true); + REQUIRE(diag.pfreeSpace() == (DIAGNOSTIC_LOCATION_SIZE - sizeof(uint32_t))); + auto f = diag.pfirst(); + REQUIRE((uint8_t*)f == ((uint8_t*)DIAGNOSTIC_LOCATION_BEGIN + sizeof(uint32_t))); + REQUIRE(f->initialized() == false); + REQUIRE(diag.plast() == diag.pfirst()); + REQUIRE(diag.plast(true) == nullptr); + REQUIRE(diag.psavedAvailable() == false); + } + + SECTION("long thread name and long textual checkpoint get truncated") { + threads[0].name = "this a very long thread name that should be truncated"; + diagnostic_checkpoint_t chkpt = {0}; + chkpt.type = CHECKPOINT_TYPE_TEXTUAL; + chkpt.text = "this is a very long checkpoint that should be truncated 12345678901234567890-=1234551235"; + chkpt.instruction = (void*)0xdeadbeef; + auto result = diag.insertCheckpoint(&threads[0], &chkpt, false); + diag.updateCrc(); + REQUIRE(result == DIAGNOSTIC_ERROR_NONE); + auto f = diag.pfirst(); + REQUIRE(ThreadEntryValidator(f, &threads[0], &chkpt) == true); + REQUIRE(strlen(f->name()) == maxThreadNameLength); + REQUIRE(strlen(f->checkpointText()) == maxTextualCheckpointLength); + } + + SECTION("insert thread entry with an instruction checkpoint") { + diagnostic_checkpoint_t chkpt = {0}; + chkpt.type = CHECKPOINT_TYPE_INSTRUCTION_ADDRESS; + chkpt.instruction = (void*)0xdeadbeef; + auto result = diag.insertCheckpoint(&threads[0], &chkpt, false); + diag.updateCrc(); + REQUIRE(result == DIAGNOSTIC_ERROR_NONE); + auto f = diag.pfirst(); + REQUIRE(ThreadEntryValidator(f, &threads[0], &chkpt) == true); + REQUIRE(diag.pfreeSpace() == (DIAGNOSTIC_LOCATION_SIZE - sizeof(uint32_t) - f->size)); + + SECTION("update thread entry with a textual checkpoint") { + diagnostic_checkpoint_t chkpt = {0}; + chkpt.type = CHECKPOINT_TYPE_TEXTUAL; + chkpt.instruction = (void*)0xdeadcafe; + chkpt.text = "a test string"; + auto result = diag.insertCheckpoint(&threads[0], &chkpt, false); + diag.updateCrc(); + REQUIRE(result == DIAGNOSTIC_ERROR_NONE); + auto f = diag.pfirst(); + REQUIRE(ThreadEntryValidator(f, &threads[0], &chkpt) == true); + REQUIRE(diag.pfreeSpace() == (DIAGNOSTIC_LOCATION_SIZE - sizeof(uint32_t) - f->size)); + } + + SECTION("insert maximum number of thread entries") { + diagnostic_checkpoint_t chkpt = {0}; + chkpt.type = CHECKPOINT_TYPE_TEXTUAL; + chkpt.instruction = (void*)0xdeadbeef; + chkpt.text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + bool result = true; + int i = 1; + while (result && i < 16) { + result = diag.insertCheckpoint(&threads[i], &chkpt, false) == DIAGNOSTIC_ERROR_NONE; + if (result) { + i++; + } + } + diag.updateCrc(); + REQUIRE(i > 1); + REQUIRE(diag.pfreeSpace() < diag.psize(&chkpt) + sizeof(ThreadEntry)); + // Count number of threads + int count = 0; + for(ThreadEntry* t = diag.pfirst(); t != nullptr && t->initialized(); t = diag.pnext(t)) { + count++; + } + REQUIRE(count == i); + + SECTION("unmark and remove all entries") { + diag.unmarkAll(); + diag.removeUnmarked(); + REQUIRE(diag.pfreeSpace() == DIAGNOSTIC_LOCATION_SIZE - sizeof(uint32_t)); + } + } + + SECTION("unmark single entry and remove it") { + diag.unmarkAll(); + diag.removeUnmarked(); + REQUIRE(diag.pfreeSpace() == DIAGNOSTIC_LOCATION_SIZE - sizeof(uint32_t)); + } + } + + SECTION("insert all threads with stacktraces") { + StacktraceGenerator gen(5); + StacktraceMocks mocks_(gen); + for (auto& th : threads) { + REQUIRE(diag.insertCheckpoint(&th, nullptr, true) == DIAGNOSTIC_ERROR_NONE); + } + diag.updateCrc(); + REQUIRE(diag.isValid() == true); + + for (auto& th : threads) { + auto f = diag.pfindThread(th.id); + REQUIRE(f != nullptr); + auto stacktrace = gen.stacktraces[th.thread]; + REQUIRE(f->count == stacktrace.size()); + for (unsigned i = 0; i < f->count; i++) { + REQUIRE(*((uintptr_t*)f->stacktrace() + i) == stacktrace[i]); + } + } + } + + SECTION("insert all threads with stacktraces plus a checkpoint") { + StacktraceGenerator gen(5); + StacktraceMocks mocks_(gen); + + diagnostic_checkpoint_t chkpt = {0}; + chkpt.type = CHECKPOINT_TYPE_TEXTUAL; + chkpt.instruction = (void*)0xdeadcafe; + chkpt.text = "a test string"; + + const os_unique_id_t id = 10; + + for (auto& th : threads) { + if (th.id == id) { + REQUIRE(diag.insertCheckpoint(&th, &chkpt, true) == DIAGNOSTIC_ERROR_NONE); + } else { + REQUIRE(diag.insertCheckpoint(&th, nullptr, true) == DIAGNOSTIC_ERROR_NONE); + } + } + diag.updateCrc(); + REQUIRE(diag.isValid() == true); + + for (auto& th : threads) { + auto f = diag.pfindThread(th.id); + REQUIRE(f != nullptr); + auto stacktrace = gen.stacktraces[th.thread]; + REQUIRE(f->count == stacktrace.size()); + for (unsigned i = 0; i < f->count; i++) { + REQUIRE(*((uintptr_t*)f->stacktrace() + i) == stacktrace[i]); + } + + if (th.id == id) { + REQUIRE(ThreadEntryValidator(f, &th, &chkpt) == true); + } + } + } +} + diff --git a/user/tests/unit/makefile b/user/tests/unit/makefile index 12a30c1469..8dea50d6ba 100644 --- a/user/tests/unit/makefile +++ b/user/tests/unit/makefile @@ -80,9 +80,11 @@ INCLUDE_DIRS += $(SRC_PATH)stubs INCLUDE_DIRS += $(LIB_SERVICES)inc INCLUDE_DIRS += $(WIRING)inc INCLUDE_DIRS += $(SYSTEM)inc +INCLUDE_DIRS += $(SYSTEM)src INCLUDE_DIRS += $(HAL)shared INCLUDE_DIRS += $(HAL)inc INCLUDE_DIRS += $(HAL)src/electron +INCLUDE_DIRS += $(HAL)src/gcc INCLUDE_DIRS += $(COMMUNICATION)src INCLUDE_DIRS += dynalib/inc INCLUDE_DIRS += $(PLATFORM)shared/inc diff --git a/wiring/inc/spark_wiring_diagnostic.h b/wiring/inc/spark_wiring_diagnostic.h new file mode 100644 index 0000000000..a8c87ef69e --- /dev/null +++ b/wiring/inc/spark_wiring_diagnostic.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#ifndef SPARK_WIRING_DIAGNOSTIC_H +#define SPARK_WIRING_DIAGNOSTIC_H + +#include "diagnostic.h" + +#define _DIAG_VA_NARGS_IMPL(_1, _2, _3, _4, _5, N, ...) N +#define _DIAG_VA_NARGS(...) _DIAG_VA_NARGS_IMPL(X,##__VA_ARGS__, 4, 3, 2, 1, 0) +#define _DIAG_VARARG_IMPL2(base, count, ...) base##count(__VA_ARGS__) +#define _DIAG_VARARG_IMPL(base, count, ...) _DIAG_VARARG_IMPL2(base, count, __VA_ARGS__) +#define _DIAG_VARARG(base, ...) _DIAG_VARARG_IMPL(base, _DIAG_VA_NARGS(__VA_ARGS__), __VA_ARGS__) + +#if defined(DIAGNOSTIC_ELF_AVAILABLE) && DIAGNOSTIC_ELF_AVAILABLE == 1 +# define CHECKPOINT0() DIAGNOSTIC_CHECKPOINT() +# define _LOG_CHECKPOINT() DIAGNOSTIC_INSTRUCTION_CHECKPOINT(platform_get_return_address(0)) +#else +# define CHECKPOINT0() DIAGNOSTIC_TEXT_CHECKPOINT() +# define _LOG_CHECKPOINT() +#endif /* defined(DIAGNOSTIC_ELF_AVAILABLE) && DIAGNOSTIC_ELF_AVAILABLE == 1 */ + +#define CHECKPOINT1(txt) DIAGNOSTIC_TEXT_CHECKPOINT_C(txt) +#define CHECKPOINT(...) _DIAG_VARARG(CHECKPOINT, __VA_ARGS__) + +#endif /* SPARK_WIRING_DIAGNOSTIC_H */ diff --git a/wiring/inc/spark_wiring_json.h b/wiring/inc/spark_wiring_json.h index 1f993da075..cc54c617d4 100644 --- a/wiring/inc/spark_wiring_json.h +++ b/wiring/inc/spark_wiring_json.h @@ -175,6 +175,8 @@ class JSONWriter { JSONWriter& value(bool val); JSONWriter& value(int val); JSONWriter& value(unsigned val); + JSONWriter& value(long int val); + JSONWriter& value(long unsigned val); JSONWriter& value(double val); JSONWriter& value(const char *val); JSONWriter& value(const char *val, size_t size); diff --git a/wiring/inc/spark_wiring_logging.h b/wiring/inc/spark_wiring_logging.h index f73bb7bffc..5456cacd45 100644 --- a/wiring/inc/spark_wiring_logging.h +++ b/wiring/inc/spark_wiring_logging.h @@ -29,6 +29,7 @@ #include "spark_wiring_thread.h" #include "spark_wiring_vector.h" #include "spark_wiring_platform.h" +#include "spark_wiring_diagnostic.h" #if Wiring_LogConfig #include "system_control.h" @@ -732,6 +733,7 @@ inline spark::Logger::Logger(const char *name) : } inline void spark::Logger::trace(const char *fmt, ...) const { + _LOG_CHECKPOINT(); va_list args; va_start(args, fmt); log(LOG_LEVEL_TRACE, fmt, args); @@ -739,6 +741,7 @@ inline void spark::Logger::trace(const char *fmt, ...) const { } inline void spark::Logger::info(const char *fmt, ...) const { + _LOG_CHECKPOINT(); va_list args; va_start(args, fmt); log(LOG_LEVEL_INFO, fmt, args); @@ -746,6 +749,7 @@ inline void spark::Logger::info(const char *fmt, ...) const { } inline void spark::Logger::warn(const char *fmt, ...) const { + _LOG_CHECKPOINT(); va_list args; va_start(args, fmt); log(LOG_LEVEL_WARN, fmt, args); @@ -753,6 +757,7 @@ inline void spark::Logger::warn(const char *fmt, ...) const { } inline void spark::Logger::error(const char *fmt, ...) const { + _LOG_CHECKPOINT(); va_list args; va_start(args, fmt); log(LOG_LEVEL_ERROR, fmt, args); @@ -760,6 +765,7 @@ inline void spark::Logger::error(const char *fmt, ...) const { } inline void spark::Logger::log(const char *fmt, ...) const { + _LOG_CHECKPOINT(); va_list args; va_start(args, fmt); log(DEFAULT_LEVEL, fmt, args); diff --git a/wiring/src/spark_wiring_json.cpp b/wiring/src/spark_wiring_json.cpp index e261849d43..f103572df8 100644 --- a/wiring/src/spark_wiring_json.cpp +++ b/wiring/src/spark_wiring_json.cpp @@ -462,6 +462,20 @@ spark::JSONWriter& spark::JSONWriter::value(unsigned val) { return *this; } +spark::JSONWriter& spark::JSONWriter::value(long int val) { + writeSeparator(); + printf("%ld", val); + state_ = NEXT; + return *this; +} + +spark::JSONWriter& spark::JSONWriter::value(long unsigned val) { + writeSeparator(); + printf("%lu", val); + state_ = NEXT; + return *this; +} + spark::JSONWriter& spark::JSONWriter::value(double val) { writeSeparator(); printf("%g", val);