Kernel and buildscript improvements (#477)

* **New Features**
  * Centralized module management with global symbol resolution
  * Level-aware logging with colored prefixes and millisecond timestamps

* **Breaking Changes**
  * ModuleParent hierarchy and getModuleParent() removed
  * Logging API and adapter model replaced; LogLevel-driven log_generic signature changed

* **Improvements**
  * Unified, simplified module registration across build targets
  * Tests updated to reflect new module lifecycle and global symbol resolution
This commit is contained in:
Ken Van Hoeylandt 2026-02-03 08:35:29 +01:00 committed by GitHub
parent 1a61eac8e0
commit a935410f82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 432 additions and 665 deletions

29
Buildscripts/module.cmake Normal file
View File

@ -0,0 +1,29 @@
if (COMMAND tactility_add_module)
return()
endif()
macro(tactility_add_module NAME)
set(options)
set(oneValueArgs)
set(multiValueArgs SRCS INCLUDE_DIRS PRIV_INCLUDE_DIRS REQUIRES PRIV_REQUIRES)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if (DEFINED ENV{ESP_IDF_VERSION})
idf_component_register(
SRCS ${ARG_SRCS}
INCLUDE_DIRS ${ARG_INCLUDE_DIRS}
PRIV_INCLUDE_DIRS ${ARG_PRIV_INCLUDE_DIRS}
REQUIRES ${ARG_REQUIRES}
PRIV_REQUIRES ${ARG_PRIV_REQUIRES}
)
else()
add_library(${NAME} OBJECT)
target_sources(${NAME} PRIVATE ${ARG_SRCS})
target_include_directories(${NAME}
PRIVATE ${ARG_PRIV_INCLUDE_DIRS}
PUBLIC ${ARG_INCLUDE_DIRS}
)
target_link_libraries(${NAME} PUBLIC ${ARG_REQUIRES})
target_link_libraries(${NAME} PRIVATE ${ARG_PRIV_REQUIRES})
endif()
endmacro()

View File

@ -12,9 +12,13 @@ set(Cyan "${Esc}[36m")
file(READ version.txt TACTILITY_VERSION) file(READ version.txt TACTILITY_VERSION)
add_compile_definitions(TT_VERSION="${TACTILITY_VERSION}") add_compile_definitions(TT_VERSION="${TACTILITY_VERSION}")
# tactility_add_module() macro
include("Buildscripts/module.cmake")
# Determine device identifier and project location # Determine device identifier and project location
include("Buildscripts/device.cmake")
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
include("Buildscripts/device.cmake")
init_tactility_globals("sdkconfig") init_tactility_globals("sdkconfig")
get_property(TACTILITY_DEVICE_PROJECT GLOBAL PROPERTY TACTILITY_DEVICE_PROJECT) get_property(TACTILITY_DEVICE_PROJECT GLOBAL PROPERTY TACTILITY_DEVICE_PROJECT)
get_property(TACTILITY_DEVICE_ID GLOBAL PROPERTY TACTILITY_DEVICE_ID) get_property(TACTILITY_DEVICE_ID GLOBAL PROPERTY TACTILITY_DEVICE_ID)

View File

@ -1,41 +1,21 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "Source/*.c*") file(GLOB_RECURSE SOURCE_FILES "Source/*.c*")
if (DEFINED ENV{ESP_IDF_VERSION}) list(APPEND REQUIRES_LIST
TactilityKernel
list(APPEND REQUIRES_LIST TactilityCore
TactilityKernel TactilityFreeRtos
TactilityCore )
TactilityFreeRtos
)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Include/"
REQUIRES ${REQUIRES_LIST}
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${COMPONENT_LIB} PUBLIC -Wno-unused-variable)
endif ()
else ()
add_library(hal-device-module OBJECT)
target_sources(hal-device-module PRIVATE ${SOURCE_FILES})
target_include_directories(hal-device-module
PUBLIC Include/
)
target_link_libraries(hal-device-module PUBLIC
TactilityFreeRtos
TactilityCore
TactilityKernel
freertos_kernel
)
if (NOT DEFINED ENV{ESP_IDF_VERSION})
list(APPEND REQUIRES_LIST freertos_kernel)
endif () endif ()
tactility_add_module(hal-device-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS Include/
REQUIRES ${REQUIRES_LIST}
)

View File

@ -8,7 +8,7 @@
#include <memory> #include <memory>
#include <utility> #include <utility>
#define TAG LOG_TAG(HalDevice) #define TAG "HalDevice"
struct HalDevicePrivate { struct HalDevicePrivate {
std::shared_ptr<tt::hal::Device> halDevice; std::shared_ptr<tt::hal::Device> halDevice;

View File

@ -1,30 +1,22 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) file(GLOB_RECURSE SOURCE_FILES "Source/*.c*")
list(APPEND REQUIRES_LIST
TactilityKernel
lvgl
)
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
list(APPEND REQUIRES_LIST esp_lvgl_port)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Include/"
REQUIRES TactilityKernel lvgl esp_lvgl_port
)
else () else ()
list(APPEND REQUIRES_LIST freertos_kernel)
add_library(lvgl-module OBJECT)
target_sources(lvgl-module PRIVATE ${SOURCE_FILES})
target_include_directories(lvgl-module
PUBLIC Include/
)
target_link_libraries(lvgl-module PUBLIC
TactilityKernel
freertos_kernel
lvgl
)
endif () endif ()
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
tactility_add_module(lvgl-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS Include/
REQUIRES ${REQUIRES_LIST}
)

View File

@ -2,19 +2,8 @@ cmake_minimum_required(VERSION 3.20)
file(GLOB_RECURSE SOURCES "Source/*.c**") file(GLOB_RECURSE SOURCES "Source/*.c**")
if (DEFINED ENV{ESP_IDF_VERSION}) idf_component_register(
SRCS ${SOURCES}
idf_component_register( INCLUDE_DIRS "Include/"
SRCS ${SOURCES} REQUIRES TactilityKernel driver
INCLUDE_DIRS "Include/" )
REQUIRES TactilityKernel driver
)
else ()
add_library(PlatformEsp32 OBJECT)
target_sources(PlatformEsp32 PRIVATE ${SOURCES})
target_include_directories(PlatformEsp32 PUBLIC Include/)
target_link_libraries(PlatformEsp32 PUBLIC TactilityKernel)
endif ()

View File

@ -9,7 +9,7 @@
#include <tactility/drivers/gpio.h> #include <tactility/drivers/gpio.h>
#include <tactility/drivers/gpio_controller.h> #include <tactility/drivers/gpio_controller.h>
#define TAG LOG_TAG(esp32_gpio) #define TAG "esp32_gpio"
#define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->config) #define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->config)

View File

@ -9,7 +9,7 @@
#include <tactility/error_esp32.h> #include <tactility/error_esp32.h>
#include <tactility/drivers/esp32_i2c.h> #include <tactility/drivers/esp32_i2c.h>
#define TAG LOG_TAG(esp32_i2c) #define TAG "esp32_i2c"
#define ACK_CHECK_EN 1 #define ACK_CHECK_EN 1
struct InternalData { struct InternalData {

View File

@ -2,19 +2,7 @@ cmake_minimum_required(VERSION 3.20)
file(GLOB_RECURSE SOURCES "Source/*.c**") file(GLOB_RECURSE SOURCES "Source/*.c**")
if (DEFINED ENV{ESP_IDF_VERSION}) add_library(PlatformPosix OBJECT)
target_sources(PlatformPosix PRIVATE ${SOURCES})
idf_component_register( #target_include_directories(PlatformPosix PUBLIC Include/)
SRCS ${SOURCES} target_link_libraries(PlatformPosix PUBLIC TactilityKernel)
# INCLUDE_DIRS "Include/"
REQUIRES TactilityKernel driver
)
else ()
add_library(PlatformPosix OBJECT)
target_sources(PlatformPosix PRIVATE ${SOURCES})
# target_include_directories(PlatformPosix PUBLIC Include/)
target_link_libraries(PlatformPosix PUBLIC TactilityKernel)
endif ()

View File

@ -5,7 +5,7 @@
#include <tactility/freertos/task.h> #include <tactility/freertos/task.h>
#include <tactility/log.h> #include <tactility/log.h>
#define TAG LOG_TAG(freertos) #define TAG "freertos"
/** /**
* Assert implementation as defined in the FreeRTOSConfig.h * Assert implementation as defined in the FreeRTOSConfig.h

View File

@ -1,26 +1,32 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
list(APPEND REQUIRES_LIST
TactilityKernel
TactilityCore
TactilityFreeRtos
hal-device-module
lvgl-module
lv_screenshot
minitar
minmea
)
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
list(APPEND REQUIRES_LIST list(APPEND REQUIRES_LIST
TactilityKernel
PlatformEsp32 PlatformEsp32
TactilityCore
TactilityFreeRtos
hal-device-module
lvgl-module
driver driver
elf_loader elf_loader
lv_screenshot
QRCode QRCode
esp_http_server esp_http_server
esp_http_client esp_http_client
esp-tls esp-tls
esp_wifi esp_wifi
json json # Effectively cJSON
minitar
minmea
nvs_flash nvs_flash
spiffs spiffs
vfs vfs
@ -33,13 +39,25 @@ if (DEFINED ENV{ESP_IDF_VERSION})
list(APPEND REQUIRES_LIST esp_tinyusb) list(APPEND REQUIRES_LIST esp_tinyusb)
endif () endif ()
idf_component_register( else ()
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Include/" list(APPEND REQUIRES_LIST
PRIV_INCLUDE_DIRS "Private/" PlatformPosix
REQUIRES ${REQUIRES_LIST} freertos_kernel
cJSON
lvgl
) )
endif ()
tactility_add_module(Tactility
SRCS ${SOURCE_FILES}
INCLUDE_DIRS Include/
PRIV_INCLUDE_DIRS Private/
REQUIRES ${REQUIRES_LIST}
)
if (DEFINED ENV{ESP_IDF_VERSION})
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${COMPONENT_LIB} PUBLIC -Wno-unused-variable) target_compile_options(${COMPONENT_LIB} PUBLIC -Wno-unused-variable)
endif () endif ()
@ -50,31 +68,6 @@ if (DEFINED ENV{ESP_IDF_VERSION})
# Read-write # Read-write
fatfs_create_spiflash_image(data "${CMAKE_CURRENT_SOURCE_DIR}/../Data/data" FLASH_IN_PROJECT PRESERVE_TIME) fatfs_create_spiflash_image(data "${CMAKE_CURRENT_SOURCE_DIR}/../Data/data" FLASH_IN_PROJECT PRESERVE_TIME)
endif () endif ()
else()
file(GLOB_RECURSE SOURCES "Source/*.c*")
add_library(Tactility OBJECT) endif ()
target_sources(Tactility PRIVATE ${SOURCES})
target_include_directories(Tactility
PRIVATE Private/
PUBLIC Include/
)
target_link_libraries(Tactility PUBLIC
cJSON
TactilityFreeRtos
TactilityCore
TactilityKernel
PlatformPosix
hal-device-module
lvgl-module
freertos_kernel
lvgl
lv_screenshot
minmea
minitar
)
endif()

View File

@ -38,8 +38,6 @@ const Configuration* getConfiguration();
*/ */
Dispatcher& getMainDispatcher(); Dispatcher& getMainDispatcher();
ModuleParent& getModuleParent();
namespace hal { namespace hal {
/** While technically this configuration is nullable, it's never null after initHeadless() is called. */ /** While technically this configuration is nullable, it's never null after initHeadless() is called. */

View File

@ -41,15 +41,6 @@ static auto LOGGER = Logger("Tactility");
static const Configuration* config_instance = nullptr; static const Configuration* config_instance = nullptr;
static Dispatcher mainDispatcher; static Dispatcher mainDispatcher;
static struct ModuleParent tactility_module_parent {
"tactility",
nullptr
};
ModuleParent& getModuleParent() {
return tactility_module_parent;
}
// region Default services // region Default services
namespace service { namespace service {
// Primary // Primary
@ -342,10 +333,9 @@ void run(const Configuration& config, Module* platformModule, Module* deviceModu
return; return;
} }
// Module parent
check(module_parent_construct(&tactility_module_parent) == ERROR_NONE);
// hal-device-module // hal-device-module
check(module_set_parent(&hal_device_module, &tactility_module_parent) == ERROR_NONE); check(module_construct(&hal_device_module) == ERROR_NONE);
check(module_add(&hal_device_module) == ERROR_NONE);
check(module_start(&hal_device_module) == ERROR_NONE); check(module_start(&hal_device_module) == ERROR_NONE);
const hal::Configuration& hardware = *config.hardware; const hal::Configuration& hardware = *config.hardware;
@ -375,7 +365,8 @@ void run(const Configuration& config, Module* platformModule, Module* deviceModu
.task_affinity = getCpuAffinityConfiguration().graphics .task_affinity = getCpuAffinityConfiguration().graphics
#endif #endif
}); });
check(module_set_parent(&lvgl_module, &tactility_module_parent) == ERROR_NONE); check(module_construct(&lvgl_module) == ERROR_NONE);
check(module_add(&lvgl_module) == ERROR_NONE);
lvgl::start(); lvgl::start();
registerAndStartSecondaryServices(); registerAndStartSecondaryServices();

View File

@ -1,32 +1,27 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../Buildscripts/module.cmake")
list(APPEND REQUIRES_LIST
lvgl
)
list(APPEND PRIV_REQUIRES_LIST
Tactility
TactilityCore
TactilityKernel
)
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) list(APPEND PRIV_REQUIRES_LIST elf_loader)
endif ()
idf_component_register( file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Include/"
PRIV_INCLUDE_DIRS "Private/"
REQUIRES lvgl
PRIV_REQUIRES Tactility TactilityCore elf_loader TactilityKernel
)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(${COMPONENT_LIB} PUBLIC -Wno-unused-variable)
endif()
else()
file(GLOB_RECURSE SOURCES "Source/*.c**")
add_library(TactilityC OBJECT)
target_sources(TactilityC PRIVATE ${SOURCES})
include_directories(TactilityC PRIVATE Private/)
target_include_directories(TactilityC PUBLIC Include/)
target_link_libraries(TactilityC
PRIVATE Tactility
PRIVATE TactilityCore
PRIVATE TactilityKernel
PUBLIC lvgl
)
endif()
tactility_add_module(TactilityC
SRCS ${SOURCE_FILES}
INCLUDE_DIRS Include/
PRIV_INCLUDE_DIRS Private/
REQUIRES ${REQUIRES_LIST}
PRIV_REQUIRES ${PRIV_REQUIRES_LIST}
)

View File

@ -51,7 +51,6 @@
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
bool module_parent_resolve_symbol(ModuleParent* pParent, const char* name, uintptr_t* pInt);
extern "C" { extern "C" {
extern double __floatsidf(int x); extern double __floatsidf(int x);
@ -375,9 +374,8 @@ uintptr_t tt_symbol_resolver(const char* symbolName) {
} }
} }
auto& module_parent = tt::getModuleParent();
uintptr_t symbol_address; uintptr_t symbol_address;
if (module_parent_resolve_symbol(&module_parent, symbolName, &symbol_address)) { if (module_resolve_symbol_global(symbolName, &symbol_address)) {
return symbol_address; return symbol_address;
} }

View File

@ -1,31 +1,23 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
list(APPEND REQUIRES_LIST
TactilityFreeRtos
TactilityKernel
mbedtls
)
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
file(GLOB_RECURSE SOURCE_FILES Source/*.c*) list(APPEND REQUIRES_LIST
nvs_flash esp_rom
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Include/"
REQUIRES TactilityFreeRtos TactilityKernel mbedtls nvs_flash esp_rom
) )
endif ()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") tactility_add_module(TactilityCore
target_compile_options(${COMPONENT_LIB} PUBLIC -Wno-unused-variable) SRCS ${SOURCE_FILES}
endif() INCLUDE_DIRS "Include/"
else() REQUIRES ${REQUIRES_LIST}
file(GLOB_RECURSE SOURCES "Source/*.c*") )
add_library(TactilityCore OBJECT)
target_sources(TactilityCore
PRIVATE ${SOURCES}
)
target_include_directories(TactilityCore PUBLIC Include/)
target_link_libraries(TactilityCore PUBLIC
TactilityFreeRtos
TactilityKernel
mbedtls
)
endif()

View File

@ -1,72 +1,70 @@
#pragma once #pragma once
#include "LoggerAdapter.h" #include <tactility/log.h>
#include "LoggerSettings.h"
#ifdef ESP_PLATFORM
#include "LoggerAdapterEsp.h"
#else
#include "LoggerAdapterGeneric.h"
#endif
#include <format> #include <format>
namespace tt { namespace tt {
#ifdef ESP_PLATFORM
static LoggerAdapter defaultLoggerAdapter = espLoggerAdapter;
#else
static LoggerAdapter defaultLoggerAdapter = genericLoggerAdapter;
#endif
class Logger { class Logger {
const char* tag; const char* tag;
LogLevel level = LOG_LEVEL_INFO;
public: public:
explicit Logger(const char* tag) : tag(tag) {} explicit Logger(const char* tag) : tag(tag) {}
template <typename... Args>
void log(LogLevel level, std::format_string<Args...> format, Args&&... args) const {
std::string message = std::format(format, std::forward<Args>(args)...);
defaultLoggerAdapter(level, tag, message.c_str());
}
template <typename... Args> template <typename... Args>
void verbose(std::format_string<Args...> format, Args&&... args) const { void verbose(std::format_string<Args...> format, Args&&... args) const {
log(LogLevel::Verbose, format, std::forward<Args>(args)...); std::string message = std::format(format, std::forward<Args>(args)...);
LOG_V(tag, "%s", message.c_str());
} }
template <typename... Args> template <typename... Args>
void debug(std::format_string<Args...> format, Args&&... args) const { void debug(std::format_string<Args...> format, Args&&... args) const {
log(LogLevel::Debug, format, std::forward<Args>(args)...); std::string message = std::format(format, std::forward<Args>(args)...);
LOG_D(tag, "%s", message.c_str());
} }
template <typename... Args> template <typename... Args>
void info(std::format_string<Args...> format, Args&&... args) const { void info(std::format_string<Args...> format, Args&&... args) const {
log(LogLevel::Info, format, std::forward<Args>(args)...); std::string message = std::format(format, std::forward<Args>(args)...);
LOG_I(tag, "%s", message.c_str());
} }
template <typename... Args> template <typename... Args>
void warn(std::format_string<Args...> format, Args&&... args) const { void warn(std::format_string<Args...> format, Args&&... args) const {
log(LogLevel::Warning, format, std::forward<Args>(args)...); std::string message = std::format(format, std::forward<Args>(args)...);
LOG_W(tag, "%s", message.c_str());
} }
template <typename... Args> template <typename... Args>
void error(std::format_string<Args...> format, Args&&... args) const { void error(std::format_string<Args...> format, Args&&... args) const {
log(LogLevel::Error, format, std::forward<Args>(args)...); std::string message = std::format(format, std::forward<Args>(args)...);
LOG_E(tag, "%s", message.c_str());
} }
bool isLoggingVerbose() const { return LogLevel::Verbose <= LOG_LEVEL; } bool isLoggingVerbose() const {
return LOG_LEVEL_VERBOSE <= level;
}
bool isLoggingDebug() const { return LogLevel::Debug <= LOG_LEVEL; } bool isLoggingDebug() const {
return LOG_LEVEL_DEBUG <= level;
}
bool isLoggingInfo() const { return LogLevel::Info <= LOG_LEVEL; } bool isLoggingInfo() const {
return LOG_LEVEL_INFO <= level;
}
bool isLoggingWarning() const { return LogLevel::Warning <= LOG_LEVEL; } bool isLoggingWarning() const {
return LOG_LEVEL_WARNING <= level;
}
bool isLoggingError() const { return LogLevel::Error <= LOG_LEVEL; } bool isLoggingError() const {
return LOG_LEVEL_ERROR <= level;
}
}; };
} }

View File

@ -1,10 +0,0 @@
#pragma once
#include "LoggerCommon.h"
#include <functional>
namespace tt {
typedef std::function<void(LogLevel level, const char* tag, const char* message)> LoggerAdapter;
}

View File

@ -1,35 +0,0 @@
#pragma once
#include "LoggerAdapter.h"
#include "LoggerAdapterShared.h"
#include <esp_log.h>
#include <sstream>
namespace tt {
inline esp_log_level_t toEspLogLevel(LogLevel level) {
switch (level) {
case LogLevel::Error:
return ESP_LOG_ERROR;
case LogLevel::Warning:
return ESP_LOG_WARN;
case LogLevel::Info:
return ESP_LOG_INFO;
case LogLevel::Debug:
return ESP_LOG_DEBUG;
case LogLevel::Verbose:
default:
return ESP_LOG_VERBOSE;
}
}
static const LoggerAdapter espLoggerAdapter = [](LogLevel level, const char* tag, const char* message) {
constexpr auto COLOR_RESET = "\033[0m";
constexpr auto COLOR_GREY = "\033[37m";
std::stringstream buffer;
buffer << COLOR_GREY << esp_log_timestamp() << ' ' << toTagColour(level) << toPrefix(level) << COLOR_GREY << " [" << COLOR_RESET << tag << COLOR_GREY << "] " << toMessageColour(level) << message << COLOR_RESET << std::endl;
esp_log_write(toEspLogLevel(level), tag, "%s", buffer.str().c_str());
};
}

View File

@ -1,35 +0,0 @@
#pragma once
#include "LoggerAdapter.h"
#include "LoggerAdapterShared.h"
#include <cstdint>
#include <mutex>
#include <sstream>
#include <sys/time.h>
namespace tt {
static uint64_t getLogTimestamp() {
static uint64_t base = 0U;
static std::once_flag init_flag;
std::call_once(init_flag, []() {
timeval time {};
gettimeofday(&time, nullptr);
base = ((uint64_t)time.tv_sec * 1000U) + (time.tv_usec / 1000U);
});
timeval time {};
gettimeofday(&time, nullptr);
uint64_t now = ((uint64_t)time.tv_sec * 1000U) + (time.tv_usec / 1000U);
return now - base;
}
static const LoggerAdapter genericLoggerAdapter = [](LogLevel level, const char* tag, const char* message) {
constexpr auto COLOR_RESET = "\033[0m";
constexpr auto COLOR_GREY = "\033[37m";
std::stringstream buffer;
buffer << COLOR_GREY << getLogTimestamp() << ' ' << toTagColour(level) << toPrefix(level) << COLOR_GREY << " [" << COLOR_RESET << tag << COLOR_GREY << "] " << toMessageColour(level) << message << COLOR_RESET << std::endl;
printf("%s", buffer.str().c_str());
};
}

View File

@ -1,58 +0,0 @@
#pragma once
#include "LoggerCommon.h"
namespace tt {
inline const char* toTagColour(LogLevel level) {
using enum LogLevel;
switch (level) {
case Error:
return "\033[1;31m";
case Warning:
return "\033[1;33m";
case Info:
return "\033[32m";
case Debug:
return "\033[36m";
case Verbose:
return "\033[37m";
default:
return "";
}
}
inline const char* toMessageColour(LogLevel level) {
using enum LogLevel;
switch (level) {
case Error:
return "\033[1;31m";
case Warning:
return "\033[1;33m";
case Info:
case Debug:
case Verbose:
return "\033[0m";
default:
return "";
}
}
inline char toPrefix(LogLevel level) {
using enum LogLevel;
switch (level) {
case Error:
return 'E';
case Warning:
return 'W';
case Info:
return 'I';
case Debug:
return 'D';
case Verbose:
default:
return 'V';
}
}
}

View File

@ -1,14 +0,0 @@
#pragma once
namespace tt {
/** Used for log output filtering */
enum class LogLevel : int {
Error, /*!< Critical errors, software module can not recover on its own */
Warning, /*!< Error conditions from which recovery measures have been taken */
Info, /*!< Information messages which describe normal flow of events */
Debug, /*!< Extra information which is not necessary for normal use (values, pointers, sizes, etc). */
Verbose /*!< Bigger chunks of debugging information, or frequent messages which can potentially flood the output. */
};
}

View File

@ -1,9 +0,0 @@
#pragma once
#include "LoggerCommon.h"
namespace tt {
constexpr auto LOG_LEVEL = LogLevel::Info;
}

View File

@ -1,20 +1,20 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
file(GLOB_RECURSE SOURCES "Source/*.c**") include("${CMAKE_CURRENT_LIST_DIR}/../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "Source/*.c**")
list(APPEND REQUIRES_LIST)
if (DEFINED ENV{ESP_IDF_VERSION}) if (DEFINED ENV{ESP_IDF_VERSION})
# TODO move the related logic for esp_time in Tactility/time.h into the Platform/ subproject
idf_component_register( list(APPEND REQUIRES_LIST esp_timer)
SRCS ${SOURCES}
INCLUDE_DIRS "Include/"
# TODO move the related logic for esp_time in Tactility/time.h into the Platform/ subproject
REQUIRES esp_timer
)
else () else ()
list(APPEND REQUIRES_LIST freertos_kernel)
add_library(TactilityKernel OBJECT ${SOURCES})
target_include_directories(TactilityKernel PUBLIC Include/)
target_link_libraries(TactilityKernel PUBLIC freertos_kernel)
endif () endif ()
tactility_add_module(TactilityKernel
SRCS ${SOURCE_FILES}
INCLUDE_DIRS Include/
REQUIRES ${REQUIRES_LIST}
)

View File

@ -10,25 +10,32 @@
extern "C" { extern "C" {
#endif #endif
#define LOG_TAG(x) "\033[37m"#x"\033[0m" /** Used for log output filtering */
enum LogLevel {
LOG_LEVEL_ERROR, /*!< Critical errors, software module can not recover on its own */
LOG_LEVEL_WARNING, /*!< Error conditions from which recovery measures have been taken */
LOG_LEVEL_INFO, /*!< Information messages which describe normal flow of events */
LOG_LEVEL_DEBUG, /*!< Extra information which is not necessary for normal use (values, pointers, sizes, etc). */
LOG_LEVEL_VERBOSE /*!< Bigger chunks of debugging information, or frequent messages which can potentially flood the output. */
};
#ifndef ESP_PLATFORM #ifndef ESP_PLATFORM
void log_generic(const char* tag, const char* format, ...); void log_generic(enum LogLevel level, const char* tag, const char* format, ...);
#define LOG_E(x, ...) log_generic(x, ##__VA_ARGS__) #define LOG_E(tag, ...) log_generic(LOG_LEVEL_ERROR, tag, ##__VA_ARGS__)
#define LOG_W(x, ...) log_generic(x, ##__VA_ARGS__) #define LOG_W(tag, ...) log_generic(LOG_LEVEL_WARNING, tag, ##__VA_ARGS__)
#define LOG_I(x, ...) log_generic(x, ##__VA_ARGS__) #define LOG_I(tag, ...) log_generic(LOG_LEVEL_INFO, tag, ##__VA_ARGS__)
#define LOG_D(x, ...) log_generic(x, ##__VA_ARGS__) #define LOG_D(tag, ...) log_generic(LOG_LEVEL_DEBUG, tag, ##__VA_ARGS__)
#define LOG_V(x, ...) log_generic(x, ##__VA_ARGS__) #define LOG_V(tag, ...) log_generic(LOG_LEVEL_VERBOSE, tag, ##__VA_ARGS__)
#else #else
#define LOG_E(x, ...) ESP_LOGE(x, ##__VA_ARGS__) #define LOG_E(tag, ...) ESP_LOGE(tag, ##__VA_ARGS__)
#define LOG_W(x, ...) ESP_LOGW(x, ##__VA_ARGS__) #define LOG_W(tag, ...) ESP_LOGW(tag, ##__VA_ARGS__)
#define LOG_I(x, ...) ESP_LOGI(x, ##__VA_ARGS__) #define LOG_I(tag, ...) ESP_LOGI(tag, ##__VA_ARGS__)
#define LOG_D(x, ...) ESP_LOGD(x, ##__VA_ARGS__) #define LOG_D(tag, ...) ESP_LOGD(tag, ##__VA_ARGS__)
#define LOG_V(x, ...) ESP_LOGV(x, ##__VA_ARGS__) #define LOG_V(tag, ...) ESP_LOGV(tag, ##__VA_ARGS__)
#endif #endif

View File

@ -6,15 +6,17 @@
#include <stdint.h> #include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { #define MODULE_SYMBOL_TERMINATOR { nullptr, nullptr }
#else
#define MODULE_SYMBOL_TERMINATOR { NULL, NULL }
#endif #endif
struct ModuleParent;
struct ModuleParentPrivate;
#define MODULE_SYMBOL_TERMINATOR { NULL, NULL }
#define DEFINE_MODULE_SYMBOL(symbol) { #symbol, (void*)&symbol } #define DEFINE_MODULE_SYMBOL(symbol) { #symbol, (void*)&symbol }
#ifdef __cplusplus
extern "C" {
#endif
/** A binary symbol like a function or a variable. */ /** A binary symbol like a function or a variable. */
struct ModuleSymbol { struct ModuleSymbol {
/** The name of the symbol. */ /** The name of the symbol. */
@ -57,60 +59,45 @@ struct Module {
*/ */
const struct ModuleSymbol* symbols; const struct ModuleSymbol* symbols;
struct { struct {
bool started; bool started;
struct ModuleParent* parent;
} internal; } internal;
}; };
/** /**
* A module parent is a collection of modules that can be loaded and unloaded at runtime. * @brief Construct a module instance.
* @param module module instance to construct
* @return ERROR_NONE if successful
*/ */
struct ModuleParent { error_t module_construct(struct Module* module);
/** The name of the parent module, for logging/debugging purposes */
const char* name;
struct ModuleParentPrivate* module_parent_private;
};
/** /**
* @brief Initialize the module parent. * @brief Destruct a module instance.
* @warn This function does no validation on input or state. * @param module module instance to destruct
* @param parent parent module * @return ERROR_NONE if successful
* @return ERROR_NONE if successful, ERROR_OUT_OF_MEMORY if allocation fails
*/ */
error_t module_parent_construct(struct ModuleParent* parent); error_t module_destruct(struct Module* module);
/** /**
* @brief Deinitialize the module parent. Must have no children when calling this. * @brief Add a module to the system.
* @warn This function does no validation on input. * @warning Only call this once. This function does not check if it was added before.
* @param parent parent module * @param module module to add
* @return ERROR_NONE if successful or ERROR_INVALID_STATE if the parent has children * @return ERROR_NONE if successful
*/ */
error_t module_parent_destruct(struct ModuleParent* parent); error_t module_add(struct Module* module);
/** /**
* @brief Resolve a symbol from the module parent. * @brief Remove a module from the system.
* @details This function iterates through all started modules in the parent and attempts to resolve the symbol. * @param module module to remove
* @param parent parent module * @return ERROR_NONE if successful
* @param symbol_name name of the symbol to resolve
* @param symbol_address pointer to store the address of the resolved symbol
* @return true if the symbol was found, false otherwise
*/ */
bool module_parent_resolve_symbol(struct ModuleParent* parent, const char* symbol_name, uintptr_t* symbol_address); error_t module_remove(struct Module* module);
/**
* @brief Set the parent of the module.
* @warning must call before module_start()
* @param module module
* @param parent nullable parent module
* @return ERROR_NONE if successful, ERROR_INVALID_STATE if the module is already started
*/
error_t module_set_parent(struct Module* module, struct ModuleParent* parent);
/** /**
* @brief Start the module. * @brief Start the module.
* @param module module * @param module module
* @return ERROR_NONE if already started, ERROR_INVALID_STATE if the module doesn't have a parent, or otherwise it returns the result of the module's start function * @return ERROR_NONE if already started, or otherwise it returns the result of the module's start function
*/ */
error_t module_start(struct Module* module); error_t module_start(struct Module* module);
@ -138,6 +125,15 @@ error_t module_stop(struct Module* module);
*/ */
bool module_resolve_symbol(struct Module* module, const char* symbol_name, uintptr_t* symbol_address); bool module_resolve_symbol(struct Module* module, const char* symbol_name, uintptr_t* symbol_address);
/**
* @brief Resolve a symbol from any module
* @details This function iterates through all started modules in the parent and attempts to resolve the symbol.
* @param symbol_name name of the symbol to resolve
* @param symbol_address pointer to store the address of the resolved symbol
* @return true if the symbol was found, false otherwise
*/
bool module_resolve_symbol_global(const char* symbol_name, uintptr_t* symbol_address);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -11,7 +11,7 @@
#include <tactility/log.h> #include <tactility/log.h>
#include <atomic> #include <atomic>
#define TAG LOG_TAG(Dispatcher) #define TAG "Dispatcher"
static constexpr EventBits_t BACKPRESSURE_WARNING_COUNT = 100U; static constexpr EventBits_t BACKPRESSURE_WARNING_COUNT = 100U;
static constexpr EventBits_t WAIT_FLAG = 1U; static constexpr EventBits_t WAIT_FLAG = 1U;

View File

@ -11,7 +11,7 @@
#include <string> #include <string>
static const size_t LOCAL_STORAGE_SELF_POINTER_INDEX = 0; static const size_t LOCAL_STORAGE_SELF_POINTER_INDEX = 0;
static const char* TAG = LOG_TAG(Thread); static const char* TAG = "Thread";
struct Thread { struct Thread {
TaskHandle_t taskHandle = nullptr; TaskHandle_t taskHandle = nullptr;

View File

@ -2,7 +2,7 @@
#include <tactility/freertos/task.h> #include <tactility/freertos/task.h>
#include <tactility/log.h> #include <tactility/log.h>
static const auto* TAG = LOG_TAG(Kernel); static const auto* TAG = "Kernel";
static void log_memory_info() { static void log_memory_info() {
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM

View File

@ -11,7 +11,7 @@
#include <sys/errno.h> #include <sys/errno.h>
#include <vector> #include <vector>
#define TAG LOG_TAG(device) #define TAG "device"
struct DevicePrivate { struct DevicePrivate {
std::vector<Device*> children; std::vector<Device*> children;

View File

@ -10,7 +10,7 @@
#include <tactility/error.h> #include <tactility/error.h>
#include <tactility/log.h> #include <tactility/log.h>
#define TAG LOG_TAG(driver) #define TAG "driver"
struct DriverPrivate { struct DriverPrivate {
Mutex mutex { 0 }; Mutex mutex { 0 };
@ -30,21 +30,11 @@ struct DriverLedger {
std::vector<Driver*> drivers; std::vector<Driver*> drivers;
Mutex mutex { 0 }; Mutex mutex { 0 };
DriverLedger() { DriverLedger() { mutex_construct(&mutex); }
mutex_construct(&mutex); ~DriverLedger() { mutex_destruct(&mutex); }
}
~DriverLedger() { void lock() { mutex_lock(&mutex); }
mutex_destruct(&mutex); void unlock() { mutex_unlock(&mutex); }
}
void lock() {
mutex_lock(&mutex);
}
void unlock() {
mutex_unlock(&mutex);
}
}; };
static DriverLedger& get_ledger() { static DriverLedger& get_ledger() {
@ -99,6 +89,8 @@ error_t driver_add(Driver* driver) {
error_t driver_remove(Driver* driver) { error_t driver_remove(Driver* driver) {
LOG_I(TAG, "remove %s", driver->name); LOG_I(TAG, "remove %s", driver->name);
if (driver->owner == nullptr) return ERROR_NOT_ALLOWED;
ledger.lock(); ledger.lock();
const auto iterator = std::ranges::find(ledger.drivers, driver); const auto iterator = std::ranges::find(ledger.drivers, driver);
if (iterator == ledger.drivers.end()) { if (iterator == ledger.drivers.end()) {

View File

@ -12,8 +12,7 @@ Driver root_driver = {
.stop_device = nullptr, .stop_device = nullptr,
.api = nullptr, .api = nullptr,
.device_type = nullptr, .device_type = nullptr,
.owner = nullptr, .owner = nullptr
.driver_private = nullptr
}; };
} }

View File

@ -5,12 +5,7 @@
extern "C" { extern "C" {
#endif #endif
#define TAG LOG_TAG(kernel) #define TAG "kernel"
struct ModuleParent kernel_module_parent = {
"kernel",
nullptr
};
static error_t init_kernel_drivers() { static error_t init_kernel_drivers() {
extern Driver root_driver; extern Driver root_driver;
@ -21,24 +16,37 @@ static error_t init_kernel_drivers() {
error_t kernel_init(struct Module* platform_module, struct Module* device_module, struct CompatibleDevice devicetree_devices[]) { error_t kernel_init(struct Module* platform_module, struct Module* device_module, struct CompatibleDevice devicetree_devices[]) {
LOG_I(TAG, "init"); LOG_I(TAG, "init");
if (module_parent_construct(&kernel_module_parent) != ERROR_NONE) {
LOG_E(TAG, "init failed to create kernel module parent");
return ERROR_RESOURCE;
}
if (init_kernel_drivers() != ERROR_NONE) { if (init_kernel_drivers() != ERROR_NONE) {
LOG_E(TAG, "init failed to init kernel drivers"); LOG_E(TAG, "init failed to init kernel drivers");
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
module_set_parent(platform_module, &kernel_module_parent); if (module_construct(platform_module) != ERROR_NONE) {
LOG_E(TAG, "init failed to construct platform module");
return ERROR_RESOURCE;
}
if (module_add(platform_module) != ERROR_NONE) {
LOG_E(TAG, "init failed to add platform module");
return ERROR_RESOURCE;
}
if (module_start(platform_module) != ERROR_NONE) { if (module_start(platform_module) != ERROR_NONE) {
LOG_E(TAG, "init failed to start platform module"); LOG_E(TAG, "init failed to start platform module");
return ERROR_RESOURCE; return ERROR_RESOURCE;
} }
if (device_module != nullptr) { if (device_module != nullptr) {
module_set_parent(device_module, &kernel_module_parent); if (module_construct(device_module) != ERROR_NONE) {
LOG_E(TAG, "init failed to construct device module");
return ERROR_RESOURCE;
}
if (module_add(device_module) != ERROR_NONE) {
LOG_E(TAG, "init failed to add device module");
return ERROR_RESOURCE;
}
if (module_start(device_module) != ERROR_NONE) { if (module_start(device_module) != ERROR_NONE) {
LOG_E(TAG, "init failed to start device module"); LOG_E(TAG, "init failed to start device module");
return ERROR_RESOURCE; return ERROR_RESOURCE;
@ -58,7 +66,7 @@ error_t kernel_init(struct Module* platform_module, struct Module* device_module
LOG_I(TAG, "init done"); LOG_I(TAG, "init done");
return ERROR_NONE; return ERROR_NONE;
}; }
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -4,17 +4,71 @@
#include <tactility/log.h> #include <tactility/log.h>
#include <mutex>
#include <inttypes.h>
#include <stdio.h> #include <stdio.h>
#include <stdint.h>
#include <stdarg.h> #include <stdarg.h>
#include <sys/time.h>
static const char* get_log_color(LogLevel level) {
using enum LogLevel;
switch (level) {
case LOG_LEVEL_ERROR:
return "\033[1;31m";
case LOG_LEVEL_WARNING:
return "\033[1;33m";
case LOG_LEVEL_INFO:
return "\033[32m";
case LOG_LEVEL_DEBUG:
return "\033[36m";
case LOG_LEVEL_VERBOSE:
return "\033[37m";
default:
return "";
}
}
static inline char get_log_prefix(LogLevel level) {
using enum LogLevel;
switch (level) {
case LOG_LEVEL_ERROR:
return 'E';
case LOG_LEVEL_WARNING:
return 'W';
case LOG_LEVEL_INFO:
return 'I';
case LOG_LEVEL_DEBUG:
return 'D';
case LOG_LEVEL_VERBOSE:
return 'V';
default:
return '?';
}
}
static uint64_t get_log_timestamp() {
static uint64_t base = 0U;
static std::once_flag init_flag;
std::call_once(init_flag, []() {
timeval time {};
gettimeofday(&time, nullptr);
base = ((uint64_t)time.tv_sec * 1000U) + (time.tv_usec / 1000U);
});
timeval time {};
gettimeofday(&time, nullptr);
uint64_t now = ((uint64_t)time.tv_sec * 1000U) + (time.tv_usec / 1000U);
return now - base;
}
extern "C" { extern "C" {
void log_generic(const char* tag, const char* format, ...) { void log_generic(enum LogLevel level, const char* tag, const char* format, ...) {
va_list args; va_list args;
va_start(args, format); va_start(args, format);
printf("%s ", tag); printf("%s %c (%" PRIu64 ") %s ", get_log_color(level), get_log_prefix(level), get_log_timestamp(), tag);
vprintf(format, args); vprintf(format, args);
printf("\n"); printf("\033[0m\n");
va_end(args); va_end(args);
} }

View File

@ -4,88 +4,40 @@
#include <tactility/concurrent/mutex.h> #include <tactility/concurrent/mutex.h>
#include <tactility/module.h> #include <tactility/module.h>
#define TAG LOG_TAG(module) #define TAG "module"
struct ModuleParentPrivate { struct ModuleLedger {
std::vector<struct Module*> modules; std::vector<struct Module*> modules;
struct Mutex mutex = { 0 }; struct Mutex mutex = { 0 };
ModuleLedger() { mutex_construct(&mutex); }
~ModuleLedger() { mutex_destruct(&mutex); }
}; };
static ModuleLedger ledger;
extern "C" { extern "C" {
#pragma region module_parent error_t module_construct(struct Module* module) {
module->internal.started = false;
error_t module_parent_construct(struct ModuleParent* parent) {
parent->module_parent_private = new (std::nothrow) ModuleParentPrivate();
if (!parent->module_parent_private) return ERROR_OUT_OF_MEMORY;
auto* data = static_cast<ModuleParentPrivate*>(parent->module_parent_private);
mutex_construct(&data->mutex);
return ERROR_NONE; return ERROR_NONE;
} }
error_t module_parent_destruct(struct ModuleParent* parent) { error_t module_destruct(struct Module* module) {
auto* data = static_cast<ModuleParentPrivate*>(parent->module_parent_private);
if (data == nullptr) return ERROR_NONE;
mutex_lock(&data->mutex);
if (!data->modules.empty()) {
mutex_unlock(&data->mutex);
return ERROR_INVALID_STATE;
}
mutex_unlock(&data->mutex);
mutex_destruct(&data->mutex);
delete data;
parent->module_parent_private = nullptr;
return ERROR_NONE; return ERROR_NONE;
} }
bool module_parent_resolve_symbol(ModuleParent* parent, const char* symbol_name, uintptr_t* symbol_address) { error_t module_add(struct Module* module) {
auto* data = static_cast<ModuleParentPrivate*>(parent->module_parent_private); mutex_lock(&ledger.mutex);
mutex_lock(&data->mutex); ledger.modules.push_back(module);
for (auto* module : data->modules) { mutex_unlock(&ledger.mutex);
if (!module_is_started(module)) return ERROR_NONE;
continue;
if (module_resolve_symbol(module, symbol_name, symbol_address)) {
mutex_unlock(&data->mutex);
return true;
}
}
mutex_unlock(&data->mutex);
return false;
} }
#pragma endregion error_t module_remove(struct Module* module) {
mutex_lock(&ledger.mutex);
#pragma region module ledger.modules.erase(std::remove(ledger.modules.begin(), ledger.modules.end(), module), ledger.modules.end());
mutex_unlock(&ledger.mutex);
error_t module_set_parent(struct Module* module, struct ModuleParent* parent) {
if (module->internal.started) return ERROR_INVALID_STATE;
if (module->internal.parent == parent) return ERROR_NONE;
// Remove from old parent
if (module->internal.parent && module->internal.parent->module_parent_private) {
auto* old_data = static_cast<ModuleParentPrivate*>(module->internal.parent->module_parent_private);
mutex_lock(&old_data->mutex);
auto it = std::find(old_data->modules.begin(), old_data->modules.end(), module);
if (it != old_data->modules.end()) {
old_data->modules.erase(it);
}
mutex_unlock(&old_data->mutex);
}
module->internal.parent = parent;
// Add to new parent
if (parent && parent->module_parent_private) {
auto* new_data = static_cast<ModuleParentPrivate*>(parent->module_parent_private);
mutex_lock(&new_data->mutex);
new_data->modules.push_back(module);
mutex_unlock(&new_data->mutex);
}
return ERROR_NONE; return ERROR_NONE;
} }
@ -93,7 +45,6 @@ error_t module_start(struct Module* module) {
LOG_I(TAG, "start %s", module->name); LOG_I(TAG, "start %s", module->name);
if (module->internal.started) return ERROR_NONE; if (module->internal.started) return ERROR_NONE;
if (!module->internal.parent) return ERROR_INVALID_STATE;
error_t error = module->start(); error_t error = module->start();
module->internal.started = (error == ERROR_NONE); module->internal.started = (error == ERROR_NONE);
@ -132,7 +83,19 @@ bool module_resolve_symbol(Module* module, const char* symbol_name, uintptr_t* s
return false; return false;
} }
#pragma endregion bool module_resolve_symbol_global(const char* symbol_name, uintptr_t* symbol_address) {
mutex_lock(&ledger.mutex);
for (auto* module : ledger.modules) {
if (!module_is_started(module))
continue;
if (module_resolve_symbol(module, symbol_name, symbol_address)) {
mutex_unlock(&ledger.mutex);
return true;
}
}
mutex_unlock(&ledger.mutex);
return false;
}
} }

View File

@ -7,21 +7,15 @@
#include <tactility/kernel_init.h> #include <tactility/kernel_init.h>
#include <tactility/hal_device_module.h> #include <tactility/hal_device_module.h>
typedef struct { typedef struct {
int argc; int argc;
char** argv; char** argv;
int result; int result;
} TestTaskData; } TestTaskData;
extern "C" {
// From the relevant platform // From the relevant platform
extern struct Module platform_module; extern "C" struct Module platform_module;
}
struct ModuleParent tactility_tests_module_parent {
"tactility-tests",
nullptr
};
void test_task(void* parameter) { void test_task(void* parameter) {
auto* data = (TestTaskData*)parameter; auto* data = (TestTaskData*)parameter;
@ -33,11 +27,7 @@ void test_task(void* parameter) {
// overrides // overrides
context.setOption("no-breaks", true); // don't break in the debugger when assertions fail context.setOption("no-breaks", true); // don't break in the debugger when assertions fail
check(kernel_init(&platform_module, nullptr, nullptr) == ERROR_NONE); check(kernel_init(&platform_module, &hal_device_module, nullptr) == ERROR_NONE);
// HAL compatibility module: it creates kernel driver wrappers for tt::hal::Device
check(module_parent_construct(&tactility_tests_module_parent) == ERROR_NONE);
check(module_set_parent(&hal_device_module, &tactility_tests_module_parent) == ERROR_NONE);
check(module_start(&hal_device_module) == ERROR_NONE);
data->result = context.run(); data->result = context.run();

View File

@ -1,6 +1,8 @@
#include "doctest.h" #include "doctest.h"
#include <tactility/module.h> #include <tactility/module.h>
static void symbol_test_function() { /* NO-OP */ }
static error_t test_start_result = ERROR_NONE; static error_t test_start_result = ERROR_NONE;
static bool start_called = false; static bool start_called = false;
static error_t test_start() { static error_t test_start() {
@ -15,84 +17,40 @@ static error_t test_stop() {
return test_stop_result; return test_stop_result;
} }
TEST_CASE("ModuleParent construction and destruction") { TEST_CASE("Module construction and destruction") {
struct ModuleParent parent = { "test_parent", nullptr }; struct Module module = {
.name = "test",
.start = test_start,
.stop = test_stop,
.symbols = nullptr,
.internal = {.started = false}
};
// Test successful construction // Test successful construction
CHECK_EQ(module_parent_construct(&parent), ERROR_NONE); CHECK_EQ(module_construct(&module), ERROR_NONE);
CHECK_NE(parent.module_parent_private, nullptr); CHECK_EQ(module.internal.started, false);
// Test successful destruction // Test successful destruction
CHECK_EQ(module_parent_destruct(&parent), ERROR_NONE); CHECK_EQ(module_destruct(&module), ERROR_NONE);
CHECK_EQ(parent.module_parent_private, nullptr);
} }
TEST_CASE("ModuleParent destruction with children") { TEST_CASE("Module registration") {
struct ModuleParent parent = { "parent", nullptr };
REQUIRE_EQ(module_parent_construct(&parent), ERROR_NONE);
struct Module module = { struct Module module = {
.name = "test", .name = "test",
.start = test_start, .start = test_start,
.stop = test_stop, .stop = test_stop,
.symbols = nullptr, .symbols = nullptr,
.internal = {.started = false, .parent = nullptr} .internal = {.started = false}
}; };
REQUIRE_EQ(module_set_parent(&module, &parent), ERROR_NONE); // module_add should succeed
CHECK_EQ(module_add(&module), ERROR_NONE);
// Should fail to destruct because it has a child // module_remove should succeed
CHECK_EQ(module_parent_destruct(&parent), ERROR_INVALID_STATE); CHECK_EQ(module_remove(&module), ERROR_NONE);
CHECK_NE(parent.module_parent_private, nullptr);
// Remove child
REQUIRE_EQ(module_set_parent(&module, nullptr), ERROR_NONE);
// Now it should succeed
CHECK_EQ(module_parent_destruct(&parent), ERROR_NONE);
CHECK_EQ(parent.module_parent_private, nullptr);
}
TEST_CASE("Module parent management") {
struct ModuleParent parent1 = { "parent1", nullptr };
struct ModuleParent parent2 = { "parent2", nullptr };
REQUIRE_EQ(module_parent_construct(&parent1), ERROR_NONE);
REQUIRE_EQ(module_parent_construct(&parent2), ERROR_NONE);
struct Module module = {
.name = "test",
.start = test_start,
.stop = test_stop,
.symbols = nullptr,
.internal = {.started = false, .parent = nullptr}
};
// Set parent
CHECK_EQ(module_set_parent(&module, &parent1), ERROR_NONE);
CHECK_EQ(module.internal.parent, &parent1);
// Change parent
CHECK_EQ(module_set_parent(&module, &parent2), ERROR_NONE);
CHECK_EQ(module.internal.parent, &parent2);
// Clear parent
CHECK_EQ(module_set_parent(&module, nullptr), ERROR_NONE);
CHECK_EQ(module.internal.parent, nullptr);
// Set same parent (should be NOOP and return ERROR_NONE)
CHECK_EQ(module_set_parent(&module, &parent1), ERROR_NONE);
CHECK_EQ(module_set_parent(&module, &parent1), ERROR_NONE);
CHECK_EQ(module.internal.parent, &parent1);
CHECK_EQ(module_set_parent(&module, nullptr), ERROR_NONE);
CHECK_EQ(module_parent_destruct(&parent1), ERROR_NONE);
CHECK_EQ(module_parent_destruct(&parent2), ERROR_NONE);
} }
TEST_CASE("Module lifecycle") { TEST_CASE("Module lifecycle") {
struct ModuleParent parent = { "parent", nullptr };
REQUIRE_EQ(module_parent_construct(&parent), ERROR_NONE);
start_called = false; start_called = false;
stop_called = false; stop_called = false;
test_start_result = ERROR_NONE; test_start_result = ERROR_NONE;
@ -103,48 +61,37 @@ TEST_CASE("Module lifecycle") {
.start = test_start, .start = test_start,
.stop = test_stop, .stop = test_stop,
.symbols = nullptr, .symbols = nullptr,
.internal = {.started = false, .parent = nullptr} .internal = {.started = false}
}; };
// 1. Cannot start without parent // 1. Successful start (no parent required anymore)
CHECK_EQ(module_start(&module), ERROR_INVALID_STATE);
CHECK_EQ(module_is_started(&module), false);
CHECK_EQ(start_called, false);
CHECK_EQ(module_set_parent(&module, &parent), ERROR_NONE);
// 2. Successful start
CHECK_EQ(module_start(&module), ERROR_NONE); CHECK_EQ(module_start(&module), ERROR_NONE);
CHECK_EQ(module_is_started(&module), true); CHECK_EQ(module_is_started(&module), true);
CHECK_EQ(start_called, true); CHECK_EQ(start_called, true);
// 3. Start when already started (should return ERROR_NONE) // Start when already started (should return ERROR_NONE)
start_called = false; start_called = false;
CHECK_EQ(module_start(&module), ERROR_NONE); CHECK_EQ(module_start(&module), ERROR_NONE);
CHECK_EQ(start_called, false); // start() function should NOT be called again CHECK_EQ(start_called, false); // start() function should NOT be called again
// 4. Cannot change parent while started // Stop successful
CHECK_EQ(module_set_parent(&module, nullptr), ERROR_INVALID_STATE);
// 5. Successful stop
CHECK_EQ(module_stop(&module), ERROR_NONE); CHECK_EQ(module_stop(&module), ERROR_NONE);
CHECK_EQ(module_is_started(&module), false); CHECK_EQ(module_is_started(&module), false);
CHECK_EQ(stop_called, true); CHECK_EQ(stop_called, true);
// 6. Stop when already stopped (should return ERROR_NONE) // Stop when already stopped (should return ERROR_NONE)
stop_called = false; stop_called = false;
CHECK_EQ(module_stop(&module), ERROR_NONE); CHECK_EQ(module_stop(&module), ERROR_NONE);
CHECK_EQ(stop_called, false); // stop() function should NOT be called again CHECK_EQ(stop_called, false); // stop() function should NOT be called again
// 7. Test failed start // Test failed start
test_start_result = ERROR_NOT_FOUND; test_start_result = ERROR_NOT_FOUND;
start_called = false; start_called = false;
CHECK_EQ(module_start(&module), ERROR_NOT_FOUND); CHECK_EQ(module_start(&module), ERROR_NOT_FOUND);
CHECK_EQ(module_is_started(&module), false); CHECK_EQ(module_is_started(&module), false);
CHECK_EQ(start_called, true); CHECK_EQ(start_called, true);
// 8. Test failed stop // Test failed stop
CHECK_EQ(module_set_parent(&module, &parent), ERROR_NONE);
test_start_result = ERROR_NONE; test_start_result = ERROR_NONE;
CHECK_EQ(module_start(&module), ERROR_NONE); CHECK_EQ(module_start(&module), ERROR_NONE);
@ -157,7 +104,32 @@ TEST_CASE("Module lifecycle") {
// Clean up: fix stop result so we can stop it // Clean up: fix stop result so we can stop it
test_stop_result = ERROR_NONE; test_stop_result = ERROR_NONE;
CHECK_EQ(module_stop(&module), ERROR_NONE); CHECK_EQ(module_stop(&module), ERROR_NONE);
}
CHECK_EQ(module_set_parent(&module, nullptr), ERROR_NONE);
CHECK_EQ(module_parent_destruct(&parent), ERROR_NONE); TEST_CASE("Global symbol resolution") {
static const struct ModuleSymbol test_symbols[] = {
DEFINE_MODULE_SYMBOL(symbol_test_function),
MODULE_SYMBOL_TERMINATOR
};
struct Module module = {
.name = "test_sym",
.start = test_start,
.stop = test_stop,
.symbols = test_symbols,
.internal = {.started = false}
};
uintptr_t addr;
// Should fail as it is not added or started
CHECK_EQ(module_resolve_symbol_global("symbol_test_function", &addr), false);
REQUIRE_EQ(module_add(&module), ERROR_NONE);
CHECK_EQ(module_resolve_symbol_global("symbol_test_function", &addr), false);
REQUIRE_EQ(module_start(&module), ERROR_NONE);
// Still fails as symbols are null
CHECK_EQ(module_resolve_symbol_global("symbol_test_function", &addr), true);
// Cleanup
CHECK_EQ(module_remove(&module), ERROR_NONE);
CHECK_EQ(module_destruct(&module), ERROR_NONE);
} }