app-module events & callstack config, crash diagnostics (#630)

- Apps can specify task stack depth and preferred memory placement in their manifests.
- App identifiers are validated against length and character requirements.
- Crash diagnostics now show the crash cause, reason, fault address, call stack, and program-counter details, with logs saved for review.
- App closing is more consistent across built-in screens. There's now a dedicated function, and the old _emit() function is made private.
- Crash diagnostics no longer display a QR code without a call stack.
- Improved memory allocation for unrestricted requests.
This commit is contained in:
Ken Van Hoeylandt
2026-08-27 21:50:12 +02:00
committed by GitHub
parent c656ee9ffd
commit 92ca046681
74 changed files with 654 additions and 381 deletions
+16 -14
View File
@@ -6,6 +6,8 @@
#include <tactility/log.h>
#include <tactility/time.h>
#include <tactility/freertos/task.h>
#include <cstdlib>
#include <cstring>
#include <string>
@@ -23,9 +25,9 @@ struct Thread {
thread_state_callback_t stateCallback = nullptr;
void* stateCallbackContext = nullptr;
std::string name = "unnamed";
enum ThreadPriority priority = THREAD_PRIORITY_NORMAL;
struct Mutex mutex = { 0 };
configSTACK_DEPTH_TYPE stackSize = 4096;
ThreadPriority priority = THREAD_PRIORITY_NORMAL;
Mutex mutex = { 0 };
configSTACK_DEPTH_TYPE stackDepth = 4096;
portBASE_TYPE affinity = -1;
Thread() {
@@ -94,7 +96,7 @@ Thread* thread_alloc(void) {
Thread* thread_alloc_full(
const char* name,
configSTACK_DEPTH_TYPE stack_size,
size_t stack_size,
thread_main_fn_t function,
void* function_context,
portBASE_TYPE affinity
@@ -128,7 +130,8 @@ void thread_set_stack_size(Thread* thread, size_t stack_size) {
thread->lock();
check(stack_size > 0);
check(thread->state == THREAD_STATE_STOPPED);
thread->stackSize = stack_size;
thread->stackDepth = stack_size / sizeof(StackType_t);
thread->unlock();
}
@@ -176,14 +179,13 @@ error_t thread_start(Thread* thread) {
thread->lock();
check(thread->mainFunction != nullptr);
check(thread->state == THREAD_STATE_STOPPED);
check(thread->stackSize);
check(thread->stackDepth);
thread->unlock();
thread_set_state_internal(thread, THREAD_STATE_STARTING);
thread->lock();
uint32_t stack_depth = thread->stackSize / sizeof(StackType_t);
enum ThreadPriority priority = thread->priority;
auto priority = static_cast<UBaseType_t>(thread->priority);
portBASE_TYPE affinity = thread->affinity;
thread->unlock();
@@ -193,9 +195,9 @@ error_t thread_start(Thread* thread) {
result = xTaskCreatePinnedToCore(
thread_main_body,
thread->name.c_str(),
stack_depth,
thread->stackDepth,
thread,
(UBaseType_t)priority,
priority,
&thread->taskHandle,
affinity
);
@@ -203,9 +205,9 @@ error_t thread_start(Thread* thread) {
result = xTaskCreate(
thread_main_body,
thread->name.c_str(),
stack_depth,
thread->stackDepth,
thread,
(UBaseType_t)priority,
priority,
&thread->taskHandle
);
#endif
@@ -213,9 +215,9 @@ error_t thread_start(Thread* thread) {
result = xTaskCreate(
thread_main_body,
thread->name.c_str(),
stack_depth,
thread->stackDepth,
thread,
(UBaseType_t)priority,
priority,
&thread->taskHandle
);
}
+24 -9
View File
@@ -34,20 +34,25 @@ void* memory_alloc_with_policy(size_t size, const struct MemoryPolicy* policy) {
uint32_t required_caps = toHeapCaps(policy->required);
uint32_t desired_caps = toHeapCaps(policy->desired);
// heap_caps matches heaps via (heap->caps[prio] & caps) != 0 - a caps value of 0 (e.g.
// required_caps when policy->required wasn't set) can never match any heap, so the fallback
// must OR in MALLOC_CAP_DEFAULT to actually reach a general-purpose heap, same as ESP-IDF's
// own heap_caps_malloc_default() does.
// heap_caps_match() tests (heap->caps & caps) == caps - a caps value of 0 is trivially true
// for every heap, not none, so an unconstrained request must be steered to MALLOC_CAP_DEFAULT
// explicitly (same as ESP-IDF's own heap_caps_malloc_default()) or it can land on a heap
// that's unsuitable for the caller's actual use (e.g. not valid as a FreeRTOS task stack).
uint32_t combined_caps = required_caps | desired_caps;
if (combined_caps == 0) {
combined_caps = MALLOC_CAP_DEFAULT;
}
void* ptr;
if (policy->alignment > 0) {
ptr = heap_caps_aligned_alloc(policy->alignment, size, required_caps | desired_caps);
ptr = heap_caps_aligned_alloc(policy->alignment, size, combined_caps);
if (ptr == nullptr && desired_caps != 0) {
// Desired caps couldn't be satisfied alongside the required ones - retry with
// required only, since desired is explicitly optional.
ptr = heap_caps_aligned_alloc(policy->alignment, size, required_caps | MALLOC_CAP_DEFAULT);
}
} else {
ptr = heap_caps_malloc(size, required_caps | desired_caps);
ptr = heap_caps_malloc(size, combined_caps);
if (ptr == nullptr && desired_caps != 0) {
ptr = heap_caps_malloc(size, required_caps | MALLOC_CAP_DEFAULT);
}
@@ -59,9 +64,14 @@ void* memory_realloc_with_policy(void* ptr, size_t size, const struct MemoryPoli
uint32_t required_caps = toHeapCaps(policy->required);
uint32_t desired_caps = toHeapCaps(policy->desired);
uint32_t combined_caps = required_caps | desired_caps;
if (combined_caps == 0) {
combined_caps = MALLOC_CAP_DEFAULT;
}
// No aligned-realloc counterpart in the heap_caps API - policy->alignment is only honored
// on fresh allocations (memory_alloc_with_policy/memory_calloc_with_policy).
void* result = heap_caps_realloc(ptr, size, required_caps | desired_caps);
void* result = heap_caps_realloc(ptr, size, combined_caps);
if (result == nullptr && desired_caps != 0) {
result = heap_caps_realloc(ptr, size, required_caps | MALLOC_CAP_DEFAULT);
}
@@ -72,14 +82,19 @@ void* memory_calloc_with_policy(size_t count, size_t size, const struct MemoryPo
uint32_t required_caps = toHeapCaps(policy->required);
uint32_t desired_caps = toHeapCaps(policy->desired);
uint32_t combined_caps = required_caps | desired_caps;
if (combined_caps == 0) {
combined_caps = MALLOC_CAP_DEFAULT;
}
void* ptr;
if (policy->alignment > 0) {
ptr = heap_caps_aligned_calloc(policy->alignment, count, size, required_caps | desired_caps);
ptr = heap_caps_aligned_calloc(policy->alignment, count, size, combined_caps);
if (ptr == nullptr && desired_caps != 0) {
ptr = heap_caps_aligned_calloc(policy->alignment, count, size, required_caps | MALLOC_CAP_DEFAULT);
}
} else {
ptr = heap_caps_calloc(count, size, required_caps | desired_caps);
ptr = heap_caps_calloc(count, size, combined_caps);
if (ptr == nullptr && desired_caps != 0) {
ptr = heap_caps_calloc(count, size, required_caps | MALLOC_CAP_DEFAULT);
}