diff --git a/Buildscripts/TactilitySDK/CMakeLists.txt b/Buildscripts/TactilitySDK/CMakeLists.txt index 5ee600eb2..3be1ba3ef 100644 --- a/Buildscripts/TactilitySDK/CMakeLists.txt +++ b/Buildscripts/TactilitySDK/CMakeLists.txt @@ -4,6 +4,7 @@ idf_component_register( "Libraries/TactilityKernel/include" "Libraries/TactilityFreeRtos/include" "Libraries/lvgl/include" + "Libraries/minmea/include" "Modules/lvgl-module/include" # DRIVER_INCLUDE_DIRS_PLACEHOLDER REQUIRES esp_timer @@ -13,7 +14,9 @@ idf_component_register( add_prebuilt_library(TactilityC Libraries/TactilityC/binary/libTactilityC.a) add_prebuilt_library(TactilityKernel Libraries/TactilityKernel/binary/libTactilityKernel.a) add_prebuilt_library(lvgl Libraries/lvgl/binary/liblvgl.a) +add_prebuilt_library(minmea Libraries/minmea/binary/libminmea.a) target_link_libraries(${COMPONENT_LIB} INTERFACE TactilityC) target_link_libraries(${COMPONENT_LIB} INTERFACE TactilityKernel) target_link_libraries(${COMPONENT_LIB} INTERFACE lvgl) +target_link_libraries(${COMPONENT_LIB} INTERFACE minmea) diff --git a/Buildscripts/release-sdk.py b/Buildscripts/release-sdk.py index 54b16b58d..3c39cbdc5 100644 --- a/Buildscripts/release-sdk.py +++ b/Buildscripts/release-sdk.py @@ -185,6 +185,13 @@ def main(): # elf_loader {'src': 'Libraries/elf_loader/elf_loader.cmake', 'dst': 'Libraries/elf_loader/'}, {'src': 'Libraries/elf_loader/license.txt', 'dst': 'Libraries/elf_loader/'}, + # minmea + {'src': 'build/esp-idf/minmea/libminmea.a', 'dst': 'Libraries/minmea/binary/'}, + {'src': 'Libraries/minmea/Include/**', 'dst': 'Libraries/minmea/include/'}, + {'src': 'Libraries/minmea/CMakeLists.txt', 'dst': 'Libraries/minmea/'}, + {'src': 'Libraries/minmea/README.md', 'dst': 'Libraries/minmea/'}, + {'src': 'Libraries/minmea/LICENSE*.*', 'dst': 'Libraries/minmea/'}, + {'src': 'Libraries/minmea/COPYING', 'dst': 'Libraries/minmea/'}, ] map_copy(mappings, target_path) @@ -192,6 +199,7 @@ def main(): # Modules add_module(target_path, "lvgl-module") add_module(target_path, "crypt-module") + add_module(target_path, "gps-module") # Drivers - only ones actually built for this target (chip-restricted drivers like # sc2356-module won't have a .a outside ESP32-P4) diff --git a/Modules/lvgl-module/source/devices/devices.c b/Modules/lvgl-module/source/devices/devices.c index f7766f2ce..a119b61e4 100644 --- a/Modules/lvgl-module/source/devices/devices.c +++ b/Modules/lvgl-module/source/devices/devices.c @@ -111,7 +111,7 @@ void lvgl_devices_detach() { lv_disp_t* display = lv_disp_get_next(NULL); while (display != NULL) { - lv_display_delete(display); + lvgl_display_remove(display); display = lv_disp_get_next(NULL); } diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 3ed13adbb..a3ff036e9 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -6,26 +6,33 @@ #include #include - -#include +#include #include #include #include #include #include #include +#include +#include #include +#include #include #include #include #include +#ifdef ESP_PLATFORM +#include +#endif + #include #include #include #include #include + #include #include #include @@ -36,15 +43,7 @@ #include #include #include - -#ifdef ESP_PLATFORM -#include -#endif - -#include "Tactility/Paths.h" -#include "Tactility/hal/SdCard.h" - -#include +#include namespace tt { @@ -371,6 +370,8 @@ static void onLvglStarted() { #if TT_FEATURE_SCREENSHOT_ENABLED addService(service::screenshot::manifest); #endif + + memory_trace(); } static void onLvglStopped() { @@ -386,6 +387,8 @@ static void onLvglStopped() { check(service::removeService(service::memorychecker::manifest.id)); check(service::removeService(service::statusbar::manifest.id)); check(service::removeService(service::gui::manifest.id)); + + memory_trace(); } void run(Module* dtsModules[], DtsDevice dtsDevices[]) { diff --git a/Tactility/Source/app/ElfApp.cpp b/Tactility/Source/app/ElfApp.cpp index 1354c19b2..42d3f0ba3 100644 --- a/Tactility/Source/app/ElfApp.cpp +++ b/Tactility/Source/app/ElfApp.cpp @@ -95,6 +95,7 @@ private: // Note: the result code maps to values from cstdlib's errno.h lastError = getErrorCodeString(-relocate_result); LOG_E(TAG, "Application failed to load: %s", lastError.c_str()); + esp_elf_deinit(&elf); elfFileData = nullptr; return false; } diff --git a/Tactility/Source/network/HttpdReq.cpp b/Tactility/Source/network/HttpdReq.cpp index 9af41cd14..9d9c86324 100644 --- a/Tactility/Source/network/HttpdReq.cpp +++ b/Tactility/Source/network/HttpdReq.cpp @@ -200,10 +200,10 @@ size_t receiveFile(httpd_req_t* request, size_t length, const std::string& fileP auto expected_chunk_size = std::min(BUFFER_SIZE, length - bytes_received); size_t receive_chunk_size = httpd_req_recv(request, buffer, expected_chunk_size); if (receive_chunk_size <= 0) { - LOG_E(TAG, "Receive failed"); + LOG_E(TAG, "Receive failed, got 0 bytes but expected %zu more", length - bytes_received); break; } - if (fwrite(buffer, 1, receive_chunk_size, file) != (size_t)receive_chunk_size) { + if (fwrite(buffer, 1, receive_chunk_size, file) != receive_chunk_size) { LOG_E(TAG, "Failed to write all bytes"); break; } diff --git a/Tactility/Source/service/ServiceRegistration.cpp b/Tactility/Source/service/ServiceRegistration.cpp index 6e0af05e2..ea9aeeccc 100644 --- a/Tactility/Source/service/ServiceRegistration.cpp +++ b/Tactility/Source/service/ServiceRegistration.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -9,11 +10,35 @@ #include #include +#include namespace tt::service { constexpr auto* TAG = "ServiceRegistration"; +namespace { + +// Tracks the heap allocations addService() makes per registered id, so removeService() +// can free them once the kernel confirms the manifest is unregistered. The kernel only +// ever stores the raw pointer it's handed (see service_manager_add/_remove) - it never +// takes ownership - so the registering side (us) is responsible for the lifetime. +struct AllocatedManifest { + std::shared_ptr* persistentManifest; + ::ServiceManifest* cManifest; +}; + +Mutex& allocatedManifestsMutex() { + static Mutex mutex; + return mutex; +} + +std::unordered_map& allocatedManifests() { + static std::unordered_map map; + return map; +} + +} // namespace + // Bridges the kernel's C ServiceManifest/Service callbacks to the C++ Service // instances they wrap. Declared extern "C" to match the linkage of the C // function-pointer types they're assigned to (see e.g. gpio_controller.cpp). @@ -48,9 +73,8 @@ void addService(std::shared_ptr manifest, bool autoStart) return; } - // Intentionally never freed: removeService() only unregisters the manifest - // from the kernel, it doesn't own this allocation. Keeps id's backing - // string alive for cManifest.id below. + // Freed by removeService() once the kernel confirms the manifest is unregistered. + // Keeps id's backing string alive for cManifest.id below in the meantime. auto* persistentManifest = new std::shared_ptr(manifest); auto* cManifest = new ::ServiceManifest { .id = (*persistentManifest)->id.c_str(), @@ -60,6 +84,12 @@ void addService(std::shared_ptr manifest, bool autoStart) .on_stop = cppOnStopTrampoline, }; + { + auto lock = allocatedManifestsMutex().asScopedLock(); + lock.lock(); + allocatedManifests()[id] = AllocatedManifest { persistentManifest, cManifest }; + } + error_t error = service_manager_add(cManifest, autoStart); if (error != ERROR_NONE) { LOG_E(TAG, "Failed to add service %s: %s", id.c_str(), error_to_string(error)); @@ -85,6 +115,20 @@ bool removeService(const std::string& id) { LOG_E(TAG, "Failed to remove service %s: %s", id.c_str(), error_to_string(error)); return false; } + + // The kernel has confirmed the manifest is unregistered, so id (which points into + // persistentManifest's string) is no longer needed by anything - safe to free now. + { + auto lock = allocatedManifestsMutex().asScopedLock(); + lock.lock(); + auto iterator = allocatedManifests().find(id); + if (iterator != allocatedManifests().end()) { + delete iterator->second.cManifest; + delete iterator->second.persistentManifest; + allocatedManifests().erase(iterator); + } + } + LOG_I(TAG, "Removed %s", id.c_str()); return true; } diff --git a/Tactility/Source/service/development/DevelopmentService.cpp b/Tactility/Source/service/development/DevelopmentService.cpp index 1ba0855f5..f59c7fd07 100644 --- a/Tactility/Source/service/development/DevelopmentService.cpp +++ b/Tactility/Source/service/development/DevelopmentService.cpp @@ -157,13 +157,20 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) { // Create tmp directory const std::string tmp_path = getTempPath(); if (!file::findOrCreateDirectory(tmp_path, 0777)) { - httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file"); + httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create temp path"); return ESP_FAIL; } - auto file_path = std::format("{}/{}", tmp_path, filename_entry->second); + std::string safe_name = file::getLastPathSegment(filename_entry->second); + if (safe_name.empty() || safe_name.find("..") != std::string::npos || + safe_name.find('/') != std::string::npos || safe_name.find('\\') != std::string::npos) { + httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "invalid filename"); + return ESP_FAIL; + } + auto file_path = std::format("{}/{}", tmp_path, safe_name); if (network::receiveFile(request, file_size, file_path) != file_size) { - httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file"); + file::deleteFile(file_path); + httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file"); return ESP_FAIL; } diff --git a/Tactility/Source/service/gui/GuiService.cpp b/Tactility/Source/service/gui/GuiService.cpp index 77871340a..b1029693e 100644 --- a/Tactility/Source/service/gui/GuiService.cpp +++ b/Tactility/Source/service/gui/GuiService.cpp @@ -200,6 +200,13 @@ void GuiService::redraw() { // Create a default group which adds all objects automatically, // and assign all indevs to it. // This enables navigation with limited input, such as encoder wheels. + // The previous default group (if any) is no longer referenced by anything + // after lv_obj_clean() above, so it must be freed here or it leaks. + auto* previous_group = lv_group_get_default(); + if (previous_group != nullptr) { + lv_group_delete(previous_group); + } + lv_group_t* group = lv_group_create(); auto* indev = lv_indev_get_next(nullptr); while (indev) { @@ -279,6 +286,17 @@ void GuiService::onStop(ServiceContext& service) { lv_group_delete(keyboardGroup); keyboardGroup = nullptr; } + + auto* default_group = lv_group_get_default(); + if (default_group != nullptr) { + lv_group_delete(default_group); + lv_group_set_default(nullptr); + } + + auto* screen_root = lv_screen_active(); + if (screen_root != nullptr) { + lv_obj_clean(screen_root); + } lvgl_unlock(); delete thread; diff --git a/Tactility/Source/service/loader/Loader.cpp b/Tactility/Source/service/loader/Loader.cpp index 8275dc361..078fbc5d4 100644 --- a/Tactility/Source/service/loader/Loader.cpp +++ b/Tactility/Source/service/loader/Loader.cpp @@ -10,12 +10,8 @@ #include -#ifdef ESP_PLATFORM -#include -#include -#endif - #include +#include namespace tt::service::loader { @@ -72,6 +68,8 @@ void LoaderService::onStartAppMessage(const std::string& id, app::LaunchId launc appStack.push_back(new_app); transitionAppToState(new_app, app::State::Created); transitionAppToState(new_app, app::State::Showing); + + memory_trace(); } void LoaderService::onStopTopAppMessage(const std::string& id) { @@ -125,10 +123,6 @@ void LoaderService::onStopTopAppMessage(const std::string& id) { LOG_W(TAG, "Memory leak: Stopped %s, but use count is %d", app_to_stop->getManifest().appId.c_str(), (int)(app_to_stop->getApp().use_count() - 2)); } -#ifdef ESP_PLATFORM - LOG_I(TAG, "Free heap: %d", (int)heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); -#endif - std::shared_ptr instance_to_resume; // If there's a previous app, resume it if (!appStack.empty()) { @@ -167,6 +161,8 @@ void LoaderService::onStopTopAppMessage(const std::string& id) { ); } } + + memory_trace(); } int LoaderService::findAppInStack(const std::string& id) const { diff --git a/TactilityKernel/include/tactility/memory.h b/TactilityKernel/include/tactility/memory.h new file mode 100644 index 000000000..c067640ae --- /dev/null +++ b/TactilityKernel/include/tactility/memory.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void memory_trace(); + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/source/memory.cpp b/TactilityKernel/source/memory.cpp new file mode 100644 index 000000000..d35b9e97d --- /dev/null +++ b/TactilityKernel/source/memory.cpp @@ -0,0 +1,23 @@ +#include +#include + +#ifdef ESP_PLATFORM +#include +#endif + +constexpr auto* TAG = "memory"; + +extern "C" { + +void memory_trace() { +#ifdef ESP_PLATFORM + size_t heap_free = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); + size_t heap_total = heap_caps_get_total_size(MALLOC_CAP_INTERNAL); + LOG_I(TAG, "Heap: %zu / %zu available", heap_free, heap_total); + size_t ext_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); + size_t ext_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM); + LOG_I(TAG, "External: %zu / %zu available", ext_free, ext_total); +#endif +} + +}