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
38 changed files with 432 additions and 665 deletions
+3 -13
View File
@@ -7,21 +7,15 @@
#include <tactility/kernel_init.h>
#include <tactility/hal_device_module.h>
typedef struct {
int argc;
char** argv;
int result;
} TestTaskData;
extern "C" {
// From the relevant platform
extern struct Module platform_module;
}
struct ModuleParent tactility_tests_module_parent {
"tactility-tests",
nullptr
};
extern "C" struct Module platform_module;
void test_task(void* parameter) {
auto* data = (TestTaskData*)parameter;
@@ -33,11 +27,7 @@ void test_task(void* parameter) {
// overrides
context.setOption("no-breaks", true); // don't break in the debugger when assertions fail
check(kernel_init(&platform_module, nullptr, 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);
check(kernel_init(&platform_module, &hal_device_module, nullptr) == ERROR_NONE);
data->result = context.run();
+54 -82
View File
@@ -1,6 +1,8 @@
#include "doctest.h"
#include <tactility/module.h>
static void symbol_test_function() { /* NO-OP */ }
static error_t test_start_result = ERROR_NONE;
static bool start_called = false;
static error_t test_start() {
@@ -15,84 +17,40 @@ static error_t test_stop() {
return test_stop_result;
}
TEST_CASE("ModuleParent construction and destruction") {
struct ModuleParent parent = { "test_parent", nullptr };
TEST_CASE("Module construction and destruction") {
struct Module module = {
.name = "test",
.start = test_start,
.stop = test_stop,
.symbols = nullptr,
.internal = {.started = false}
};
// Test successful construction
CHECK_EQ(module_parent_construct(&parent), ERROR_NONE);
CHECK_NE(parent.module_parent_private, nullptr);
CHECK_EQ(module_construct(&module), ERROR_NONE);
CHECK_EQ(module.internal.started, false);
// Test successful destruction
CHECK_EQ(module_parent_destruct(&parent), ERROR_NONE);
CHECK_EQ(parent.module_parent_private, nullptr);
CHECK_EQ(module_destruct(&module), ERROR_NONE);
}
TEST_CASE("ModuleParent destruction with children") {
struct ModuleParent parent = { "parent", nullptr };
REQUIRE_EQ(module_parent_construct(&parent), ERROR_NONE);
TEST_CASE("Module registration") {
struct Module module = {
.name = "test",
.start = test_start,
.stop = test_stop,
.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
CHECK_EQ(module_parent_destruct(&parent), ERROR_INVALID_STATE);
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);
// module_remove should succeed
CHECK_EQ(module_remove(&module), ERROR_NONE);
}
TEST_CASE("Module lifecycle") {
struct ModuleParent parent = { "parent", nullptr };
REQUIRE_EQ(module_parent_construct(&parent), ERROR_NONE);
start_called = false;
stop_called = false;
test_start_result = ERROR_NONE;
@@ -103,48 +61,37 @@ TEST_CASE("Module lifecycle") {
.start = test_start,
.stop = test_stop,
.symbols = nullptr,
.internal = {.started = false, .parent = nullptr}
.internal = {.started = false}
};
// 1. Cannot start without parent
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
// 1. Successful start (no parent required anymore)
CHECK_EQ(module_start(&module), ERROR_NONE);
CHECK_EQ(module_is_started(&module), 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;
CHECK_EQ(module_start(&module), ERROR_NONE);
CHECK_EQ(start_called, false); // start() function should NOT be called again
// 4. Cannot change parent while started
CHECK_EQ(module_set_parent(&module, nullptr), ERROR_INVALID_STATE);
// 5. Successful stop
// Stop successful
CHECK_EQ(module_stop(&module), ERROR_NONE);
CHECK_EQ(module_is_started(&module), false);
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;
CHECK_EQ(module_stop(&module), ERROR_NONE);
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;
start_called = false;
CHECK_EQ(module_start(&module), ERROR_NOT_FOUND);
CHECK_EQ(module_is_started(&module), false);
CHECK_EQ(start_called, true);
// 8. Test failed stop
CHECK_EQ(module_set_parent(&module, &parent), ERROR_NONE);
// Test failed stop
test_start_result = 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
test_stop_result = 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);
}