mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-17 23:55:04 +00:00
Compare commits
9 Commits
f086465294
...
3cb4e56d3c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cb4e56d3c | ||
|
|
840101b878 | ||
|
|
553e51e9cf | ||
|
|
971dc3dcbe | ||
|
|
b3860a2640 | ||
|
|
138ea54a0c | ||
|
|
1d781644de | ||
|
|
fb541795ec | ||
|
|
311c3325ff |
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: "Build Tests"
|
- name: "Build Tests"
|
||||||
run: cmake --build build --target build-tests
|
run: cmake --build build --target build-tests
|
||||||
- name: "Run Tests"
|
- name: "Run Tests"
|
||||||
run: ctest --build-dir build/Tests
|
run: ctest --test-dir build/Tests
|
||||||
DevicetreeTests:
|
DevicetreeTests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@ -282,7 +282,7 @@ def write_device_structs(file, device: Device, parent_device: Device, bindings:
|
|||||||
file.write(f"\t.address = {address_value},\n")
|
file.write(f"\t.address = {address_value},\n")
|
||||||
file.write(f"\t.name = \"{device.node_name}\",\n") # Use original name
|
file.write(f"\t.name = \"{device.node_name}\",\n") # Use original name
|
||||||
file.write(f"\t.config = &{config_variable_name},\n")
|
file.write(f"\t.config = &{config_variable_name},\n")
|
||||||
file.write(f"\t.flags = DEVICE_FLAG_DTS,\n")
|
file.write("\t.flags = DEVICE_FLAG_DTS,\n")
|
||||||
file.write(f"\t.parent = {parent_value},\n")
|
file.write(f"\t.parent = {parent_value},\n")
|
||||||
file.write("\t.internal = NULL\n")
|
file.write("\t.internal = NULL\n")
|
||||||
file.write("};\n\n")
|
file.write("};\n\n")
|
||||||
|
|||||||
@ -13,6 +13,7 @@ static struct Device root = {
|
|||||||
.address = 0,
|
.address = 0,
|
||||||
.name = "/",
|
.name = "/",
|
||||||
.config = &root_config,
|
.config = &root_config,
|
||||||
|
.flags = DEVICE_FLAG_DTS,
|
||||||
.parent = NULL,
|
.parent = NULL,
|
||||||
.internal = NULL
|
.internal = NULL
|
||||||
};
|
};
|
||||||
@ -27,6 +28,7 @@ static struct Device test_device = {
|
|||||||
.address = 0,
|
.address = 0,
|
||||||
.name = "test-device",
|
.name = "test-device",
|
||||||
.config = &test_device_config,
|
.config = &test_device_config,
|
||||||
|
.flags = DEVICE_FLAG_DTS,
|
||||||
.parent = &root,
|
.parent = &root,
|
||||||
.internal = NULL
|
.internal = NULL
|
||||||
};
|
};
|
||||||
@ -43,6 +45,7 @@ static struct Device bool_test_device = {
|
|||||||
.address = 0,
|
.address = 0,
|
||||||
.name = "bool-test-device",
|
.name = "bool-test-device",
|
||||||
.config = &bool_test_device_config,
|
.config = &bool_test_device_config,
|
||||||
|
.flags = DEVICE_FLAG_DTS,
|
||||||
.parent = &root,
|
.parent = &root,
|
||||||
.internal = NULL
|
.internal = NULL
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#include <tactility/bindings/esp32_sdspi.h>
|
#include <tactility/bindings/esp32_sdspi.h>
|
||||||
#include <tactility/bindings/esp32_pwm_ledc.h>
|
#include <tactility/bindings/esp32_pwm_ledc.h>
|
||||||
#include <tactility/bindings/pwm_backlight.h>
|
#include <tactility/bindings/pwm_backlight.h>
|
||||||
|
#include <gps_generic/bindings.h>
|
||||||
#include <bindings/st7796.h>
|
#include <bindings/st7796.h>
|
||||||
#include <bindings/bq27220.h>
|
#include <bindings/bq27220.h>
|
||||||
#include <bindings/tca8418.h>
|
#include <bindings/tca8418.h>
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
# gps-generic-module
|
# gps-generic-module
|
||||||
|
|
||||||
Kernel driver implementing the `GPS_TYPE`/`GpsApi` interface (declared by `Drivers/gps-module`,
|
Kernel driver implementing the `GPS_TYPE`/`GpsApi` interface with for generic UART-connected GPS/GNSS receivers:
|
||||||
Apache-2.0) for generic UART-connected GPS/GNSS receivers: chipset probing, init sequences, and
|
|
||||||
NMEA parsing for MTK, Airoha/AG33xx, ATGM336H/CASIC, Unicore UC6580 and u-blox 6/7/8/9/10 modules.
|
NMEA parsing for MTK, Airoha/AG33xx, ATGM336H/CASIC, Unicore UC6580 and u-blox 6/7/8/9/10 modules.
|
||||||
|
|
||||||
|
It is ported from [Meshtastic Firmware](https://github.com/MeshTastic/firmware), so it has a GPL v3.0 license.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This module is licensed under **GPL-3.0-or-later** (see `LICENSE-GPL-3.0.md`), separately from
|
This module is licensed under **GPL-3.0-or-later** (see `LICENSE-GPL-3.0.md`), separately from
|
||||||
|
|||||||
@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
#include <minmea.h>
|
#include <minmea.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdio>
|
||||||
|
#include <cstdlib> // For calloc() in PC builds
|
||||||
|
|
||||||
constexpr auto* TAG = "gps-generic";
|
constexpr auto* TAG = "gps-generic";
|
||||||
|
|
||||||
@ -315,7 +316,7 @@ static GpsState gps_api_get_state(Device* device) {
|
|||||||
static error_t gps_api_get_model_name(Device* device, char* model_name, size_t buffer_size) {
|
static error_t gps_api_get_model_name(Device* device, char* model_name, size_t buffer_size) {
|
||||||
const auto* config = GET_CONFIG(device);
|
const auto* config = GET_CONFIG(device);
|
||||||
const char* name_to_set = gpsModelToString(config->model);
|
const char* name_to_set = gpsModelToString(config->model);
|
||||||
strncpy(model_name, name_to_set, buffer_size);
|
snprintf(model_name, buffer_size, "%s", name_to_set);
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
LICENSE.md
15
LICENSE.md
@ -7,13 +7,16 @@ These applications are not part of the Tactility operating system's main firmwar
|
|||||||
|
|
||||||
"end-users" refers to people who install and/or use Tactility software on their devices.
|
"end-users" refers to people who install and/or use Tactility software on their devices.
|
||||||
|
|
||||||
## Past & Present
|
## Summary
|
||||||
|
|
||||||
Formerly, there was a mixed usage of [GPL v3.0](Documentation/LICENSE-GPL-3.0.md) for internal subprojects
|
The main firmware projects (`Firmware/`, `Tactility/`) are licensed under [GPL v3.0](Documentation/LICENSE-GPL-3.0.md)
|
||||||
and [Apache License v2.0](Documentation/LICENSE-Apache-2.0.md) for subprojects that would be used in external apps.
|
|
||||||
|
|
||||||
For future subprojects, [Apache License v2.0](Documentation/LICENSE-Apache-2.0.md) will be chosen for internal subproject.
|
Most drivers have an [Apache License v2.0](Documentation/LICENSE-Apache-2.0.md), with exceptions such as `Drivers/gps-generic-module/`.
|
||||||
Existing GPL-licensed projects will retain this license, as it cannot be changed to a more permissive license.
|
Licensing may also differ for subprojects intended for use in external applications.
|
||||||
|
|
||||||
|
Specific aren't generally used directly in external app projects, but if they are, make sure to check their licenses.
|
||||||
|
|
||||||
|
All projects under `Modules/` have an [Apache License v2.0](Documentation/LICENSE-Apache-2.0.md).
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@ -22,13 +25,13 @@ Below is an overview of the licenses of some of the subprojects.
|
|||||||
| Project | License |
|
| Project | License |
|
||||||
|--------------------|-------------------------|
|
|--------------------|-------------------------|
|
||||||
| Tactility | GNU Public License v3.0 |
|
| Tactility | GNU Public License v3.0 |
|
||||||
| TactilityCore | GNU Public License v3.0 |
|
|
||||||
| TactilityC | Apache License v2.0 |
|
| TactilityC | Apache License v2.0 |
|
||||||
| TactilityFreeRTOS | Apache License v2.0 |
|
| TactilityFreeRTOS | Apache License v2.0 |
|
||||||
| TactilityKernel | Apache License v2.0 |
|
| TactilityKernel | Apache License v2.0 |
|
||||||
| Tests | GNU Public License v3.0 |
|
| Tests | GNU Public License v3.0 |
|
||||||
| Devices/* | GNU Public License v3.0 |
|
| Devices/* | GNU Public License v3.0 |
|
||||||
| Drivers/* | (varies) |
|
| Drivers/* | (varies) |
|
||||||
|
| Modules/* | Apache License v2.0 |
|
||||||
| DevicetreeCompiler | Apache License v2.0 |
|
| DevicetreeCompiler | Apache License v2.0 |
|
||||||
| Platforms/* | Apache License v2.0 |
|
| Platforms/* | Apache License v2.0 |
|
||||||
|
|
||||||
|
|||||||
@ -76,10 +76,27 @@ struct GpsSubscription {
|
|||||||
* @brief API for GPS/GNSS receiver drivers.
|
* @brief API for GPS/GNSS receiver drivers.
|
||||||
*/
|
*/
|
||||||
struct GpsApi {
|
struct GpsApi {
|
||||||
|
/**
|
||||||
|
* @brief Registers a subscriber for GPS events (e.g. RMC/GGA sentences).
|
||||||
|
* @param[in] device the GPS device
|
||||||
|
* @param[in,out] sub subscription to register; caller owns the storage and must keep it alive until unsubscribed
|
||||||
|
*/
|
||||||
error_t (*event_subscribe)(struct Device* device, struct GpsSubscription* sub);
|
error_t (*event_subscribe)(struct Device* device, struct GpsSubscription* sub);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes a previously registered subscription.
|
||||||
|
* @param[in] device the GPS device
|
||||||
|
* @param[in] sub subscription to remove, as passed to event_subscribe
|
||||||
|
*/
|
||||||
error_t (*event_unsubscribe)(struct Device* device, struct GpsSubscription* sub);
|
error_t (*event_unsubscribe)(struct Device* device, struct GpsSubscription* sub);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Blocks the calling task until a new event arrives for the subscription, or timeout elapses.
|
||||||
|
* @param[in] device the GPS device
|
||||||
|
* @param[in,out] sub subscription to wait on
|
||||||
|
* @param[in] timeout max ticks to wait
|
||||||
|
* @return ERROR_NONE if an event arrived, ERROR_TIMEOUT if the timeout elapsed
|
||||||
|
*/
|
||||||
error_t (*event_await)(struct Device* device, struct GpsSubscription* sub, TickType_t timeout);
|
error_t (*event_await)(struct Device* device, struct GpsSubscription* sub, TickType_t timeout);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,6 +105,13 @@ struct GpsApi {
|
|||||||
*/
|
*/
|
||||||
enum GpsState (*get_state)(struct Device* device);
|
enum GpsState (*get_state)(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets a human-readable model name for the device, e.g. "UBLOX8".
|
||||||
|
* @param[in] device the GPS device
|
||||||
|
* @param[out] model_name buffer to receive the NUL-terminated name
|
||||||
|
* @param[in] buffer_size size of model_name in bytes
|
||||||
|
* @return ERROR_NONE on success
|
||||||
|
*/
|
||||||
error_t (*get_model_name)(struct Device* device, char* model_name, size_t buffer_size);
|
error_t (*get_model_name)(struct Device* device, char* model_name, size_t buffer_size);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,7 +127,7 @@ error_t gps_event_await(struct Device* device, struct GpsSubscription* sub, Tick
|
|||||||
/** @copydoc GpsApi::get_state */
|
/** @copydoc GpsApi::get_state */
|
||||||
enum GpsState gps_get_state(struct Device* device);
|
enum GpsState gps_get_state(struct Device* device);
|
||||||
|
|
||||||
/** @copydoc GpsApi::get_state */
|
/** @copydoc GpsApi::get_model_name */
|
||||||
error_t gps_get_model_name(struct Device* device, char* model_name, size_t buffer_size);
|
error_t gps_get_model_name(struct Device* device, char* model_name, size_t buffer_size);
|
||||||
|
|
||||||
extern const struct DeviceType GPS_TYPE;
|
extern const struct DeviceType GPS_TYPE;
|
||||||
|
|||||||
@ -14,15 +14,7 @@
|
|||||||
|
|
||||||
constexpr auto* TAG = "gps_ledger";
|
constexpr auto* TAG = "gps_ledger";
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Configuration for a GPS_TYPE device.
|
|
||||||
* @warning Mirrors gps-generic-module's own (GPL) GpsConfig field-for-field - this module can't
|
|
||||||
* include that header (Apache/GPL boundary), so the layout has to be kept in sync by hand. uart is
|
|
||||||
* left NULL here: this path uses device_set_parent() to the UART_CONTROLLER_TYPE device instead,
|
|
||||||
* which the driver falls back to when config->uart is NULL.
|
|
||||||
*/
|
|
||||||
struct GpsConfig {
|
struct GpsConfig {
|
||||||
Device* uart = nullptr;
|
|
||||||
uint32_t baud_rate;
|
uint32_t baud_rate;
|
||||||
enum GpsModel model;
|
enum GpsModel model;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#include <gps/gps_settings.h>
|
#include <gps/gps_settings.h>
|
||||||
#include <gps/private/gps_ledger.h>
|
#include <gps/private/gps_ledger.h>
|
||||||
|
|
||||||
#include <tactility/filesystem/file_lock.h>
|
#include <tactility/filesystem/file_mutex.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
#include <tactility/service/service_paths.h>
|
#include <tactility/service/service_paths.h>
|
||||||
|
|
||||||
@ -44,14 +44,23 @@ static bool get_configuration_path(char* out_path, size_t out_path_size) {
|
|||||||
// Holds the lock (if any) that `path` needs for the lifetime of the guard - see file_find_lock().
|
// Holds the lock (if any) that `path` needs for the lifetime of the guard - see file_find_lock().
|
||||||
class FileLockGuard {
|
class FileLockGuard {
|
||||||
FileMutex mutex;
|
FileMutex mutex;
|
||||||
|
bool locked;
|
||||||
public:
|
public:
|
||||||
explicit FileLockGuard(const char* path) {
|
explicit FileLockGuard(const char* path) {
|
||||||
file_get_mutex(path, &mutex);
|
file_mutex_get(&mutex, path);
|
||||||
file_lock(&mutex);
|
file_mutex_lock(&mutex);
|
||||||
|
locked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
~FileLockGuard() {
|
~FileLockGuard() {
|
||||||
file_unlock(&mutex);
|
unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() {
|
||||||
|
if (locked) {
|
||||||
|
file_mutex_unlock(&mutex);
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -120,6 +129,7 @@ static error_t write_configurations(const std::vector<GpsConfiguration>& configu
|
|||||||
return ERROR_RESOURCE;
|
return ERROR_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
gps_ledger_sync();
|
gps_ledger_sync();
|
||||||
|
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
|
|||||||
@ -39,10 +39,9 @@ static void task_unlock(void) {
|
|||||||
recursive_mutex_unlock(&task_mutex);
|
recursive_mutex_unlock(&task_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lvgl_lock(void) {
|
void lvgl_lock(void) {
|
||||||
if (!lvgl_mutex_initialised) return false;
|
if (!lvgl_mutex_initialised) return;
|
||||||
recursive_mutex_lock(&lvgl_mutex);
|
recursive_mutex_lock(&lvgl_mutex);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lvgl_try_lock(uint32_t timeout) {
|
bool lvgl_try_lock(uint32_t timeout) {
|
||||||
|
|||||||
@ -14,11 +14,11 @@ constexpr TickType_t defaultLockTime = 500 / portTICK_PERIOD_MS;
|
|||||||
* @warning when passing zero, we wait forever, as this is the default behaviour for esp_lvgl_port, and we want it to remain consistent
|
* @warning when passing zero, we wait forever, as this is the default behaviour for esp_lvgl_port, and we want it to remain consistent
|
||||||
* @deprecated Use lvgl_lock() or lvgl_try_lock() from lvgl-module instead.
|
* @deprecated Use lvgl_lock() or lvgl_try_lock() from lvgl-module instead.
|
||||||
*/
|
*/
|
||||||
bool lock(TickType_t timeout = portMAX_DELAY) __attribute__((deprecated("Use file_get_mutex() from TactilityKernel")));
|
bool lock(TickType_t timeout = portMAX_DELAY) __attribute__((deprecated("Use lvgl_lock() from lvgl-module")));
|
||||||
|
|
||||||
/** @deprecated Use lvgl_unlock() from lvgl-module instead. */
|
/** @deprecated Use lvgl_unlock() from lvgl-module instead. */
|
||||||
void unlock() __attribute__((deprecated("Use file_get_mutex() from TactilityKernel")));
|
void unlock() __attribute__((deprecated("Use lvgl_unlock() from lvgl-module")));
|
||||||
|
|
||||||
std::shared_ptr<Lock> getSyncLock() __attribute__((deprecated("Use file_get_mutex() from TactilityKernel")));
|
std::shared_ptr<Lock> getSyncLock() __attribute__((deprecated("Use lvgl locking functions from lvgl-module")));
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace tt::hal::sdcard {
|
|||||||
* Attempt to find an SD card that the specified belongs to,
|
* Attempt to find an SD card that the specified belongs to,
|
||||||
* and returns its lock if the SD card is mounted. Otherwise it returns nullptr.
|
* and returns its lock if the SD card is mounted. Otherwise it returns nullptr.
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param[in] a path on a file system (e.g. file, directory, etc.)
|
* @param[in] path a path on a file system (e.g. file, directory, etc.)
|
||||||
* @return the lock of a mounted SD card or otherwise null
|
* @return the lock of a mounted SD card or otherwise null
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<Lock> findSdCardLock(const std::string& path) __attribute__((deprecated("Use file_get_mutex() from TactilityKernel")));
|
std::shared_ptr<Lock> findSdCardLock(const std::string& path) __attribute__((deprecated("Use file_get_mutex() from TactilityKernel")));
|
||||||
|
|||||||
@ -53,7 +53,7 @@ constexpr auto* TAG = "Tactility";
|
|||||||
|
|
||||||
static DispatcherHandle_t mainDispatcherHandle = dispatcher_alloc();
|
static DispatcherHandle_t mainDispatcherHandle = dispatcher_alloc();
|
||||||
|
|
||||||
void initFileLvglLock();
|
void initFileMutexForLvgl();
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -374,16 +374,9 @@ void run(Module* dtsModules[], DtsDevice dtsDevices[]) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
initFileLvglLock();
|
check(module_ensure_started(&crypt_module) == ERROR_NONE);
|
||||||
|
check(module_ensure_started(&gps_module) == ERROR_NONE);
|
||||||
// crypt-module
|
check(module_ensure_started(&gps_generic_module) == ERROR_NONE);
|
||||||
check(module_construct_add_start(&crypt_module) == ERROR_NONE);
|
|
||||||
|
|
||||||
// gps-module
|
|
||||||
check(module_construct_add_start(&gps_module) == ERROR_NONE);
|
|
||||||
|
|
||||||
// gps-generic-module
|
|
||||||
check(module_construct_add_start(&gps_generic_module) == ERROR_NONE);
|
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
initEsp();
|
initEsp();
|
||||||
@ -402,6 +395,9 @@ void run(Module* dtsModules[], DtsDevice dtsDevices[]) {
|
|||||||
|
|
||||||
registerAndStartPrimaryServices();
|
registerAndStartPrimaryServices();
|
||||||
|
|
||||||
|
// Must start right before LVGL
|
||||||
|
initFileMutexForLvgl();
|
||||||
|
|
||||||
lvgl_module_configure((LvglModuleConfig) {
|
lvgl_module_configure((LvglModuleConfig) {
|
||||||
.on_start = nullptr,
|
.on_start = nullptr,
|
||||||
.on_stop = nullptr,
|
.on_stop = nullptr,
|
||||||
@ -414,8 +410,7 @@ void run(Module* dtsModules[], DtsDevice dtsDevices[]) {
|
|||||||
.task_affinity = getCpuAffinityConfiguration().graphics
|
.task_affinity = getCpuAffinityConfiguration().graphics
|
||||||
#endif
|
#endif
|
||||||
});
|
});
|
||||||
check(module_construct_add_start(&lvgl_module) == ERROR_NONE);
|
check(module_ensure_started(&lvgl_module) == ERROR_NONE);
|
||||||
check(module_construct_add_start(&gps_module) == ERROR_NONE);
|
|
||||||
|
|
||||||
registerAndStartSecondaryServices();
|
registerAndStartSecondaryServices();
|
||||||
|
|
||||||
|
|||||||
@ -274,14 +274,13 @@ public:
|
|||||||
// thread). Take the same lock updateDeviceStates() uses and hold it across the
|
// thread). Take the same lock updateDeviceStates() uses and hold it across the
|
||||||
// free below, so the timer can never observe pendingDeleteDevice as a dangling
|
// free below, so the timer can never observe pendingDeleteDevice as a dangling
|
||||||
// pointer in deviceRows.
|
// pointer in deviceRows.
|
||||||
auto lock = lvgl::getSyncLock()->asScopedLock();
|
lvgl_lock();
|
||||||
lock.lock();
|
|
||||||
|
|
||||||
// Drop the stale row unconditionally (cheap vector op, no LVGL calls) - this is
|
// Drop the stale row unconditionally (cheap vector op, no LVGL calls) - this is
|
||||||
// what keeps the timer safe regardless of whether onShow() has run yet this cycle.
|
// what keeps the timer safe regardless of whether onShow() has run yet this cycle.
|
||||||
std::erase_if(deviceRows, [this](const DeviceRow& row) {
|
std::erase_if(deviceRows, [this](const DeviceRow& row) {
|
||||||
return row.device == pendingDeleteDevice;
|
return row.device == pendingDeleteDevice;
|
||||||
});
|
});
|
||||||
|
lvgl_unlock();
|
||||||
|
|
||||||
// gps_settings_remove_configuration_at() frees the underlying Device synchronously -
|
// gps_settings_remove_configuration_at() frees the underlying Device synchronously -
|
||||||
// do this only after the dangling pointer is already out of deviceRows.
|
// do this only after the dangling pointer is already out of deviceRows.
|
||||||
@ -291,9 +290,11 @@ public:
|
|||||||
// Only safe to touch deviceListWrapper if onShow() already built it for this show
|
// Only safe to touch deviceListWrapper if onShow() already built it for this show
|
||||||
// cycle - it may not have run yet, in which case it'll rebuild fresh (post-deletion,
|
// cycle - it may not have run yet, in which case it'll rebuild fresh (post-deletion,
|
||||||
// deviceRows already correct) when it does.
|
// deviceRows already correct) when it does.
|
||||||
|
lvgl_lock();
|
||||||
if (isShown) {
|
if (isShown) {
|
||||||
rebuildDeviceList();
|
rebuildDeviceList();
|
||||||
}
|
}
|
||||||
|
lvgl_unlock();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/display.h>
|
#include <tactility/drivers/display.h>
|
||||||
#include <tactility/drivers/spi_controller.h>
|
#include <tactility/drivers/spi_controller.h>
|
||||||
#include <tactility/filesystem/file_lock.h>
|
#include <tactility/filesystem/file_mutex.h>
|
||||||
#include <tactility/filesystem/file_system.h>
|
#include <tactility/filesystem/file_system.h>
|
||||||
#include <tactility/lvgl_module.h>
|
#include <tactility/lvgl_module.h>
|
||||||
|
|
||||||
|
constexpr auto* TAG = "file_mutex_lvgl";
|
||||||
|
|
||||||
struct Device;
|
struct Device;
|
||||||
namespace tt {
|
namespace tt {
|
||||||
@ -19,28 +20,36 @@ static const FileMutex lvgl_mutex = {
|
|||||||
* Finds file systems with a device (e.g. sd card) that is owned by a SPI controller.
|
* Finds file systems with a device (e.g. sd card) that is owned by a SPI controller.
|
||||||
* If the SPI controller has a display on the bus, we create an LVGL lock for the file system path.
|
* If the SPI controller has a display on the bus, we create an LVGL lock for the file system path.
|
||||||
*/
|
*/
|
||||||
void initFileLvglLock() {
|
void initFileMutexForLvgl() {
|
||||||
file_system_for_each(nullptr, [](FileSystem* fs, void* context) {
|
file_system_for_each(nullptr, [](FileSystem* fs, void* context) {
|
||||||
char mount_path[64];
|
char mount_path[64];
|
||||||
if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) {
|
if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
LOG_D(TAG, "Mount path %s", mount_path);
|
||||||
|
|
||||||
// We only care about file system with a Device (owner)
|
// We only care about file system with a Device (owner)
|
||||||
auto* owner = file_system_get_owner(fs);
|
auto* owner = file_system_get_owner(fs);
|
||||||
if (owner == nullptr) {
|
if (owner == nullptr) {
|
||||||
|
LOG_D(TAG, "Owner: none");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_D(TAG, "Owner: %s", owner->name);
|
||||||
|
|
||||||
// Ignore devices without a parent (root)
|
// Ignore devices without a parent (root)
|
||||||
auto* parent = device_get_parent(owner);
|
auto* parent = device_get_parent(owner);
|
||||||
if (parent == nullptr) {
|
if (parent == nullptr) {
|
||||||
|
LOG_D(TAG, "Owner: no parent");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_D(TAG, "Owner: parent %s", parent->name);
|
||||||
|
|
||||||
// If the FileSystem is on a SPI bus and there's more than 1 device, we assume the other one is the display.
|
// If the FileSystem is on a SPI bus and there's more than 1 device, we assume the other one is the display.
|
||||||
auto* type = device_get_type(parent);
|
auto* type = device_get_type(parent);
|
||||||
if (type != &SPI_CONTROLLER_TYPE || device_get_child_count(parent) <= 1) {
|
if (type != &SPI_CONTROLLER_TYPE || device_get_child_count(parent) <= 1) {
|
||||||
|
LOG_D(TAG, "Owner parent not SPI controller or not enough children");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,21 +58,23 @@ void initFileLvglLock() {
|
|||||||
};
|
};
|
||||||
Context ctx = { .mountPath = mount_path };
|
Context ctx = { .mountPath = mount_path };
|
||||||
|
|
||||||
device_for_each_child(parent, &ctx, [](Device* child, void* context) {
|
device_for_each_child(parent, &ctx, [](Device* child, void* context) -> bool {
|
||||||
Context* ctx = static_cast<Context*>(context);
|
Context* ctx = static_cast<Context*>(context);
|
||||||
if (device_get_type(child) == &DISPLAY_TYPE) {
|
if (device_get_type(child) == &DISPLAY_TYPE) {
|
||||||
file_register_mutex(
|
LOG_I(TAG, "Adding file mutex for %s as it shares a bus with a display", ctx->mountPath);
|
||||||
ctx->mountPath,
|
file_mutex_register(
|
||||||
&lvgl_mutex
|
&lvgl_mutex,
|
||||||
|
ctx->mountPath
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
LOG_D(TAG, "child of parent, %s: not DISPLAY_TYPE", child->name);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,28 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <tactility/freertos/freertos.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct FileMutex {
|
|
||||||
void (*lock)();
|
|
||||||
bool (*try_lock)(TickType_t timeout);
|
|
||||||
void (*unlock)();
|
|
||||||
};
|
|
||||||
|
|
||||||
void file_register_mutex(const char* path, const FileMutex* mutex);
|
|
||||||
|
|
||||||
void file_get_mutex(const char* path, struct FileMutex* mutex);
|
|
||||||
|
|
||||||
void file_lock(struct FileMutex* mutex);
|
|
||||||
|
|
||||||
bool file_try_lock(struct FileMutex* mutex, TickType_t timeout);
|
|
||||||
|
|
||||||
void file_unlock(struct FileMutex* mutex);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
51
TactilityKernel/include/tactility/filesystem/file_mutex.h
Normal file
51
TactilityKernel/include/tactility/filesystem/file_mutex.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tactility/freertos/freertos.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set of lock/try_lock/unlock callbacks backing a filesystem mount's mutex.
|
||||||
|
* Any field left null is treated as a no-op by file_mutex_lock/try_lock/unlock.
|
||||||
|
*/
|
||||||
|
struct FileMutex {
|
||||||
|
void (*lock)();
|
||||||
|
bool (*try_lock)(uint32_t timeout);
|
||||||
|
void (*unlock)();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Registers a mutex for a mount path (e.g. "/sdcard") and its descendants.
|
||||||
|
* @param[in] mutex callbacks to associate with the path; a copy is stored
|
||||||
|
* @param[in] path mount path this mutex serializes access to
|
||||||
|
* @note No-op if a mutex is already registered for this exact path.
|
||||||
|
*/
|
||||||
|
void file_mutex_register(const struct FileMutex* mutex, const char* path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Looks up the mutex registered for path or one of its ancestor mount paths.
|
||||||
|
* @param[out] mutex receives the matching mutex, or an all-null (no-op) mutex if none matches
|
||||||
|
* @param[in] path file or directory path to look up
|
||||||
|
*/
|
||||||
|
void file_mutex_get(struct FileMutex* mutex, const char* path);
|
||||||
|
|
||||||
|
/** @brief Locks mutex. No-op if mutex->lock is null. */
|
||||||
|
void file_mutex_lock(struct FileMutex* mutex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Attempts to lock mutex within timeout.
|
||||||
|
* @return true if locked (or mutex->try_lock is null), false on timeout
|
||||||
|
*/
|
||||||
|
bool file_mutex_try_lock(struct FileMutex* mutex, TickType_t timeout);
|
||||||
|
|
||||||
|
/** @brief Unlocks mutex. No-op if mutex->unlock is null. */
|
||||||
|
void file_mutex_unlock(struct FileMutex* mutex);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -122,6 +122,22 @@ error_t module_stop(struct Module* module);
|
|||||||
*/
|
*/
|
||||||
error_t module_construct_add_start(struct Module* module);
|
error_t module_construct_add_start(struct Module* module);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tries to ensure the module is in a started state.
|
||||||
|
* Calls module_construct if needed, calls module_start if needed.
|
||||||
|
* @param module the module
|
||||||
|
* @return ERROR_NONE if module is in a started state
|
||||||
|
*/
|
||||||
|
error_t module_ensure_started(struct Module* module);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tries to ensure the module is in a started state.
|
||||||
|
* Calls module_stop if needed, calls module_destruct if needed.
|
||||||
|
* @param module the module
|
||||||
|
* @return ERROR_NONE if module is in a destructed state
|
||||||
|
*/
|
||||||
|
error_t module_ensure_destructed(struct Module* module);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if the module is started.
|
* @brief Check if the module is started.
|
||||||
* Can be used when module isn't constructed yet.
|
* Can be used when module isn't constructed yet.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#include <tactility/filesystem/file_lock.h>
|
#include <tactility/filesystem/file_mutex.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -19,7 +19,8 @@ struct FileMutexEntry {
|
|||||||
static std::vector<FileMutexEntry> mutex_entries;
|
static std::vector<FileMutexEntry> mutex_entries;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
void file_register_mutex(const char* path, const FileMutex* mutex) {
|
|
||||||
|
void file_mutex_register(const FileMutex* mutex, const char* path) {
|
||||||
// Skip if entry for path exists
|
// Skip if entry for path exists
|
||||||
for (auto& entry : mutex_entries) {
|
for (auto& entry : mutex_entries) {
|
||||||
if (entry.path == path) {
|
if (entry.path == path) {
|
||||||
@ -34,31 +35,36 @@ void file_register_mutex(const char* path, const FileMutex* mutex) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void file_get_mutex(const char* path, FileMutex* mutex) {
|
void file_mutex_get(FileMutex* mutex, const char* path) {
|
||||||
|
std::string path_string = path;
|
||||||
for (auto& entry : mutex_entries) {
|
for (auto& entry : mutex_entries) {
|
||||||
if (entry.path.rfind(path) == 0) {
|
// Match the mount path itself, or a descendant (e.g. "/sdcard" registered, "/sdcard/config.json" requested).
|
||||||
memcpy(mutex, &entry.mutex, sizeof(FileMutexEntry));
|
bool is_match = path_string == entry.path ||
|
||||||
|
(entry.path == "/" && !path_string.empty() && path_string[0] == '/') ||
|
||||||
|
(path_string.rfind(entry.path, 0) == 0 && path_string[entry.path.size()] == '/');
|
||||||
|
if (is_match) {
|
||||||
|
memcpy(mutex, &entry.mutex, sizeof(FileMutex));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(mutex, &no_mutex, sizeof(FileMutex));
|
*mutex = no_mutex;
|
||||||
}
|
}
|
||||||
|
|
||||||
void file_lock(FileMutex* mutex) {
|
void file_mutex_lock(FileMutex* mutex) {
|
||||||
if (mutex->lock) {
|
if (mutex->lock) {
|
||||||
mutex->lock();
|
mutex->lock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool file_try_lock(FileMutex* mutex, TickType_t timeout) {
|
bool file_mutex_try_lock(FileMutex* mutex, TickType_t timeout) {
|
||||||
if (mutex->try_lock) {
|
if (mutex->try_lock) {
|
||||||
return mutex->try_lock(timeout);
|
return mutex->try_lock(timeout);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void file_unlock(FileMutex* mutex) {
|
void file_mutex_unlock(FileMutex* mutex) {
|
||||||
if (mutex->unlock) {
|
if (mutex->unlock) {
|
||||||
mutex->unlock();
|
mutex->unlock();
|
||||||
}
|
}
|
||||||
@ -28,22 +28,39 @@ static ModuleLedger ledger;
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
error_t module_construct(Module* module) {
|
error_t module_construct(Module* module) {
|
||||||
|
if (module->internal != nullptr) {
|
||||||
|
LOG_E(TAG, "Module %s was already constructed", module->name);
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
module->internal = new (std::nothrow) ModuleInternal();
|
module->internal = new (std::nothrow) ModuleInternal();
|
||||||
if (module->internal == nullptr) return ERROR_OUT_OF_MEMORY;
|
if (module->internal == nullptr) return ERROR_OUT_OF_MEMORY;
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_t module_destruct(Module* module) {
|
error_t module_destruct(Module* module) {
|
||||||
delete static_cast<ModuleInternal*>(module->internal);
|
if (module->internal == nullptr) {
|
||||||
|
LOG_E(TAG, "Module %s was already destructed", module->name);
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
delete module->internal;
|
||||||
module->internal = nullptr;
|
module->internal = nullptr;
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_t module_add(Module* module) {
|
error_t module_add(Module* module) {
|
||||||
mutex_lock(&ledger.mutex);
|
mutex_lock(&ledger.mutex);
|
||||||
ledger.modules.push_back(module);
|
bool exists = false;
|
||||||
|
for (auto* ledger_module : ledger.modules) {
|
||||||
|
if (ledger_module == module) {
|
||||||
|
exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exists) {
|
||||||
|
ledger.modules.push_back(module);
|
||||||
|
}
|
||||||
mutex_unlock(&ledger.mutex);
|
mutex_unlock(&ledger.mutex);
|
||||||
return ERROR_NONE;
|
return exists ? ERROR_INVALID_STATE : ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_t module_remove(Module* module) {
|
error_t module_remove(Module* module) {
|
||||||
@ -57,8 +74,8 @@ error_t module_start(Module* module) {
|
|||||||
LOG_I(TAG, "start %s", module->name);
|
LOG_I(TAG, "start %s", module->name);
|
||||||
|
|
||||||
auto* internal = module->internal;
|
auto* internal = module->internal;
|
||||||
if (internal == nullptr) return ERROR_INVALID_STATE;
|
if (internal == nullptr) { return ERROR_INVALID_STATE; }
|
||||||
if (internal->started) return ERROR_NONE;
|
if (internal->started) { return ERROR_NONE; }
|
||||||
|
|
||||||
if (module->start != nullptr) {
|
if (module->start != nullptr) {
|
||||||
auto error = module->start();
|
auto error = module->start();
|
||||||
@ -90,8 +107,8 @@ error_t module_stop(Module* module) {
|
|||||||
LOG_I(TAG, "stop %s", module->name);
|
LOG_I(TAG, "stop %s", module->name);
|
||||||
|
|
||||||
auto* internal = module->internal;
|
auto* internal = module->internal;
|
||||||
if (internal == nullptr) return ERROR_INVALID_STATE;
|
if (internal == nullptr) { return ERROR_INVALID_STATE; }
|
||||||
if (!internal->started) return ERROR_NONE;
|
if (!internal->started) { return ERROR_NONE; }
|
||||||
|
|
||||||
if (module->drivers != nullptr && internal->drivers_ready) {
|
if (module->drivers != nullptr && internal->drivers_ready) {
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
@ -117,12 +134,41 @@ error_t module_stop(Module* module) {
|
|||||||
|
|
||||||
error_t module_construct_add_start(Module* module) {
|
error_t module_construct_add_start(Module* module) {
|
||||||
error_t error = module_construct(module);
|
error_t error = module_construct(module);
|
||||||
if (error != ERROR_NONE) return error;
|
if (error != ERROR_NONE) { return error; }
|
||||||
error = module_add(module);
|
error = module_add(module);
|
||||||
if (error != ERROR_NONE) return error;
|
if (error != ERROR_NONE) { return error; }
|
||||||
return module_start(module);
|
return module_start(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error_t module_ensure_started(Module* module) {
|
||||||
|
if (module->internal == nullptr) {
|
||||||
|
error_t result = module_construct(module);
|
||||||
|
if (result != ERROR_NONE) { return result; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!module->internal->started) {
|
||||||
|
error_t result = module_start(module);
|
||||||
|
if (result != ERROR_NONE) { return result; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t module_ensure_destructed(Module* module) {
|
||||||
|
if (module->internal != nullptr) {
|
||||||
|
error_t result;
|
||||||
|
if (module->internal->started) {
|
||||||
|
result = module_stop(module);
|
||||||
|
if (result != ERROR_NONE) { return result; }
|
||||||
|
}
|
||||||
|
|
||||||
|
result = module_destruct(module);
|
||||||
|
if (result != ERROR_NONE) { return result; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
bool module_resolve_symbol(Module* module, const char* symbol_name, uintptr_t* symbol_address) {
|
bool module_resolve_symbol(Module* module, const char* symbol_name, uintptr_t* symbol_address) {
|
||||||
if (!module_is_started(module)) return false;
|
if (!module_is_started(module)) return false;
|
||||||
auto* symbol_ptr = module->symbols;
|
auto* symbol_ptr = module->symbols;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user