Fixes and improvements (#642)
This commit is contained in:
@@ -2,17 +2,26 @@
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(ESP_PLATFORM) && defined(CONFIG_IDF_TARGET_ARCH_XTENSA)
|
||||
#if defined(ESP_PLATFORM)
|
||||
|
||||
#include <Tactility/PanicHandler.h>
|
||||
|
||||
#include <esp_attr.h>
|
||||
#include <esp_memory_utils.h>
|
||||
#include <esp_private/panic_internal.h>
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ARCH_XTENSA)
|
||||
#include <esp_cpu.h>
|
||||
#include <esp_cpu_utils.h>
|
||||
#include <esp_debug_helpers.h>
|
||||
#include <esp_memory_utils.h>
|
||||
#include <esp_private/panic_internal.h>
|
||||
#include <xtensa/xtruntime.h>
|
||||
#elif defined(CONFIG_IDF_TARGET_ARCH_RISCV)
|
||||
#include <riscv/rvruntime-frames.h>
|
||||
|
||||
// The walker below reads s0 as a frame pointer. GCC only guarantees this with
|
||||
// -fno-omit-frame-pointer (set project-wide, excluding the bootloader, in the top-level
|
||||
// CMakeLists.txt).
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -28,13 +37,6 @@ void __real_esp_panic_handler(void* info);
|
||||
|
||||
void __wrap_esp_panic_handler(void* info) {
|
||||
|
||||
esp_backtrace_frame_t frame = {
|
||||
.pc = 0,
|
||||
.sp = 0,
|
||||
.next_pc = 0,
|
||||
.exc_frame = nullptr
|
||||
};
|
||||
|
||||
const auto* panic_info = static_cast<const panic_info_t*>(info);
|
||||
|
||||
switch (panic_info->exception) {
|
||||
@@ -51,6 +53,7 @@ void __wrap_esp_panic_handler(void* info) {
|
||||
}
|
||||
|
||||
crashData.callstackLength = 0;
|
||||
crashData.callstackCorrupted = false;
|
||||
crashData.faultAddress = reinterpret_cast<uint32_t>(panic_info->addr);
|
||||
|
||||
// g_panic_abort_details carries the actual assert()/abort() message when present; panic_info->reason
|
||||
@@ -64,6 +67,16 @@ void __wrap_esp_panic_handler(void* info) {
|
||||
crashData.reason[sizeof(crashData.reason) - 1] = '\0';
|
||||
}
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ARCH_XTENSA)
|
||||
// Xtensa's register-windowing hardware lets ESP-IDF walk the stack via
|
||||
// esp_backtrace_get_start()/esp_backtrace_get_next_frame().
|
||||
esp_backtrace_frame_t frame = {
|
||||
.pc = 0,
|
||||
.sp = 0,
|
||||
.next_pc = 0,
|
||||
.exc_frame = nullptr
|
||||
};
|
||||
|
||||
esp_backtrace_get_start(&frame.pc, &frame.sp, &frame.next_pc);
|
||||
crashData.callstack[0].pc = frame.pc;
|
||||
#if CRASH_DATA_INCLUDES_SP
|
||||
@@ -105,6 +118,70 @@ void __wrap_esp_panic_handler(void* info) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif defined(CONFIG_IDF_TARGET_ARCH_RISCV)
|
||||
// RISC-V has no register-windowing hardware, so the stack has to be walked by hand via the
|
||||
// frame-pointer (s0) chain. Algorithm ported from esp-rs/esp-hal's esp-backtrace crate
|
||||
// (Apache-2.0): https://github.com/esp-rs/esp-hal/blob/main/esp-backtrace/src/riscv.rs
|
||||
//
|
||||
// s0 for a frame points just past that frame's saved {ra, s0} pair: the caller's return
|
||||
// address is at fp-4, the caller's own frame pointer at fp-8.
|
||||
const auto* exc_frame = static_cast<const RvExcFrame*>(panic_info->frame);
|
||||
|
||||
// mepc is the exact faulting instruction, not a return address, so it's used directly rather
|
||||
// than read from the stack like the rest of the walk.
|
||||
crashData.callstack[0].pc = exc_frame->mepc;
|
||||
#if CRASH_DATA_INCLUDES_SP
|
||||
crashData.callstack[0].sp = exc_frame->sp;
|
||||
#endif
|
||||
crashData.callstackLength++;
|
||||
|
||||
uint32_t fp = exc_frame->s0;
|
||||
|
||||
crashData.callstackCorrupted = !(esp_stack_ptr_is_sane(exc_frame->sp) && esp_ptr_executable(reinterpret_cast<void*>(exc_frame->mepc)));
|
||||
|
||||
while (
|
||||
!crashData.callstackCorrupted
|
||||
&& crashData.callstackLength < CRASH_DATA_CALLSTACK_LIMIT
|
||||
) {
|
||||
// esp_stack_ptr_is_sane() also requires 16-byte alignment, a property of sp at call
|
||||
// boundaries but not of a frame pointer (fp only needs word alignment). esp_ptr_in_dram()
|
||||
// is the same range check without that assumption.
|
||||
//
|
||||
// Checked against fp-8, not just fp: the record about to be read is [fp-8, fp), and an fp
|
||||
// near the very start of the DRAM range can itself pass esp_ptr_in_dram() while fp-8
|
||||
// underflows below it, so validate the whole record before dereferencing any of it.
|
||||
//
|
||||
// Every task's root frame is vPortTaskWrapper() (FreeRTOS-Kernel/portable/riscv/port.c),
|
||||
// which marks itself `.cfi_undefined ra`: no valid frame exists below it, so an invalid fp
|
||||
// here is the expected end of the walk once at least one real frame has been captured, not
|
||||
// corruption. An invalid fp on the very first iteration is a real problem.
|
||||
if (!esp_ptr_in_dram(reinterpret_cast<void*>(fp - 8)) || !esp_ptr_in_dram(reinterpret_cast<void*>(fp)) || (fp & 0x3) != 0) {
|
||||
crashData.callstackCorrupted = (crashData.callstackLength <= 1);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t ra = *reinterpret_cast<const uint32_t*>(fp - 4);
|
||||
uint32_t prev_fp = *reinterpret_cast<const uint32_t*>(fp - 8);
|
||||
|
||||
// A zero return address marks the outermost frame (startup code zero-initialises it).
|
||||
if (ra == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!esp_ptr_executable(reinterpret_cast<void*>(ra))) {
|
||||
crashData.callstackCorrupted = (crashData.callstackLength <= 1);
|
||||
break;
|
||||
}
|
||||
|
||||
crashData.callstack[crashData.callstackLength].pc = ra;
|
||||
#if CRASH_DATA_INCLUDES_SP
|
||||
crashData.callstack[crashData.callstackLength].sp = fp;
|
||||
#endif
|
||||
crashData.callstackLength++;
|
||||
|
||||
fp = prev_fp;
|
||||
}
|
||||
#endif // CONFIG_IDF_TARGET_ARCH_XTENSA / CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
|
||||
// TODO: Handle corrupted logic
|
||||
|
||||
@@ -115,15 +192,4 @@ void __wrap_esp_panic_handler(void* info) {
|
||||
|
||||
const CrashData& getRtcCrashData() { return crashData; }
|
||||
|
||||
#elif defined(ESP_PLATFORM)
|
||||
|
||||
// Stub implementation for RISC-V and other architectures
|
||||
// TODO: Implement crash data collection for RISC-V using frame pointer or EH frame
|
||||
|
||||
#include <Tactility/PanicHandler.h>
|
||||
|
||||
static CrashData emptyCrashData = {};
|
||||
|
||||
const CrashData& getRtcCrashData() { return emptyCrashData; }
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
#include <Tactility/service/audio/Audio.h>
|
||||
#include <Tactility/settings/DisplaySettings.h>
|
||||
#include <Tactility/settings/TimePrivate.h>
|
||||
#include <Tactility/settings/TouchCalibrationSettings.h>
|
||||
|
||||
@@ -415,6 +416,18 @@ static void applySavedTouchCalibration() {
|
||||
#endif // CONFIG_TT_TOUCH_CALIBRATION_SUPPORTED
|
||||
|
||||
static void onLvglStarted() {
|
||||
// lv_display_create() (inside lvgl_devices_attach(), which already ran by this point) always
|
||||
// resets rotation to LV_DISPLAY_ROTATION_0. The only other code that ever applies a saved
|
||||
// orientation is the display settings app's dropdown change handler, so without this, every
|
||||
// LVGL restart (not just first boot) silently drops back to unrotated. Must run before
|
||||
// window_manager_start() below builds the window tree against the display's current size.
|
||||
lvgl_lock();
|
||||
if (auto* display = lv_display_get_default(); display != nullptr) {
|
||||
auto displaySettings = settings::display::loadOrGetDefault();
|
||||
lv_display_set_rotation(display, settings::display::toLvglDisplayRotation(displaySettings.orientation));
|
||||
}
|
||||
lvgl_unlock();
|
||||
|
||||
window_manager_configure(windowManagerScreenInit);
|
||||
check(module_ensure_started(&lvgl_window_manager_module) == ERROR_NONE);
|
||||
|
||||
@@ -461,7 +474,13 @@ static void onLvglStopped() {
|
||||
check(service::removeService(service::statusbar::manifest.id));
|
||||
|
||||
if (softwareKeyboard.object != nullptr) {
|
||||
// lv_obj_delete() walks/mutates the object graph (event lists, group membership,
|
||||
// parent/child links). Without the LVGL lock this can race the LVGL port task's own
|
||||
// concurrent traversal (input dispatch, timers, animations), producing an intermittent
|
||||
// double-free/use-after-free inside lv_obj_destructor/lv_event_mark_deleted.
|
||||
lvgl_lock();
|
||||
lvgl_software_keyboard_destruct(&softwareKeyboard);
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
module_stop(&lvgl_window_manager_module);
|
||||
|
||||
@@ -53,17 +53,18 @@ void collectManifest(const ::AppManifest* manifest, void* context) {
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
auto* ctx = static_cast<Context*>(userData);
|
||||
|
||||
// Flex column + flex_grow so LVGL recomputes the toolbar/list split on every layout pass,
|
||||
// rather than a fixed height computed once from lv_obj_get_content_height(parent) that would
|
||||
// go stale after a later display resolution/rotation change.
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* toolbar = lvgl_toolbar_create(parent, "Apps");
|
||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t* list = lv_list_create(parent);
|
||||
lv_obj_set_width(list, LV_PCT(100));
|
||||
lv_obj_align_to(list, toolbar, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
|
||||
|
||||
auto toolbar_height = lv_obj_get_height(toolbar);
|
||||
auto parent_content_height = lv_obj_get_content_height(parent);
|
||||
lv_obj_set_height(list, parent_content_height - toolbar_height);
|
||||
lv_obj_set_flex_grow(list, 1);
|
||||
|
||||
std::vector<const ::AppManifest*> manifests;
|
||||
app_manager_for_each_manifest(collectManifest, &manifests);
|
||||
|
||||
@@ -55,18 +55,18 @@ void collectManifest(const ::AppManifest* manifest, void* context) {
|
||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
auto* ctx = static_cast<Context*>(userData);
|
||||
|
||||
// Flex column + flex_grow; see AppList.cpp's createWidgets() for why a fixed height computed
|
||||
// once from lv_obj_get_content_height(parent) goes stale.
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* toolbar = lvgl_toolbar_create(parent, "Installed Apps");
|
||||
// The global toolbar nav callback only knows how to stop old-model apps.
|
||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t* list = lv_list_create(parent);
|
||||
lv_obj_set_width(list, LV_PCT(100));
|
||||
lv_obj_align_to(list, toolbar, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
|
||||
|
||||
auto toolbar_height = lv_obj_get_height(toolbar);
|
||||
auto parent_content_height = lv_obj_get_content_height(parent);
|
||||
lv_obj_set_height(list, parent_content_height - toolbar_height);
|
||||
lv_obj_set_flex_grow(list, 1);
|
||||
|
||||
std::vector<const ::AppManifest*> manifests;
|
||||
app_manager_for_each_manifest(collectManifest, &manifests);
|
||||
@@ -83,9 +83,20 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
}
|
||||
|
||||
if (app_count == 0) {
|
||||
auto* no_apps_label = lv_label_create(parent);
|
||||
// lv_obj_align() is ignored for children of a flex-managed parent, so the empty-state
|
||||
// label needs its own flex-growing wrapper to center within; the (empty) list is hidden
|
||||
// rather than deleted so the wrapper can just take its place in the flex flow.
|
||||
lv_obj_add_flag(list, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_set_flex_grow(list, 0);
|
||||
|
||||
auto* empty_wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_width(empty_wrapper, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(empty_wrapper, 1);
|
||||
lv_obj_set_flex_align(empty_wrapper, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_border_width(empty_wrapper, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* no_apps_label = lv_label_create(empty_wrapper);
|
||||
lv_label_set_text(no_apps_label, "No apps installed");
|
||||
lv_obj_align(no_apps_label, LV_ALIGN_CENTER, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include <sdkconfig.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
@@ -178,12 +179,11 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
int32_t available_height = parent_height - top_label_height - bottom_label_height;
|
||||
int32_t available_width = lv_display_get_horizontal_resolution(display);
|
||||
int32_t smallest_size = std::min(available_height, available_width);
|
||||
int32_t pixel_size;
|
||||
if (qrcode.size * 2 <= smallest_size) {
|
||||
pixel_size = 2;
|
||||
} else if (qrcode.size <= smallest_size) {
|
||||
pixel_size = 1;
|
||||
} else {
|
||||
// Target ~60% of the available space so the code scales with screen size but keeps a margin
|
||||
// from the labels/screen edges.
|
||||
int32_t target_size = smallest_size * 6 / 10;
|
||||
int32_t pixel_size = std::max<int32_t>(1, target_size / qrcode.size);
|
||||
if (pixel_size * qrcode.size > smallest_size) {
|
||||
LOG_E(TAG, "QR code won't fit screen");
|
||||
ctx->hasFatalError = true;
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user