Rename furi to tactility-core (#10)

* renamed module

* renamed code

* more renames

* cleanup
This commit is contained in:
Ken Van Hoeylandt
2024-01-13 22:12:40 +01:00
committed by GitHub
parent 64a01df750
commit 069416eee5
106 changed files with 2992 additions and 3347 deletions
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "core.h"
typedef EventFlag* ApiLock;
#define TT_API_LOCK_EVENT (1U << 0)
#define tt_api_lock_alloc_locked() tt_event_flag_alloc()
#define tt_api_lock_wait_unlock(_lock) \
tt_event_flag_wait(_lock, TT_API_LOCK_EVENT, TtFlagWaitAny, TtWaitForever)
#define tt_api_lock_free(_lock) tt_event_flag_free(_lock)
#define tt_api_lock_unlock(_lock) tt_event_flag_set(_lock, TT_API_LOCK_EVENT)
#define tt_api_lock_wait_unlock_and_free(_lock) \
tt_api_lock_wait_unlock(_lock); \
tt_api_lock_free(_lock);
+128
View File
@@ -0,0 +1,128 @@
#include "app_i.h"
#include <stdio.h>
static AppFlags tt_app_get_flags_default(AppType type);
// region Alloc/free
App tt_app_alloc(const AppManifest* manifest, Bundle* _Nullable parameters) {
AppData* data = malloc(sizeof(AppData));
*data = (AppData) {
.mutex = tt_mutex_alloc(MutexTypeRecursive),
.state = APP_STATE_INITIAL,
.flags = tt_app_get_flags_default(manifest->type),
.manifest = manifest,
.parameters = parameters,
.data = NULL
};
return (App*)data;
}
void tt_app_free(App app) {
AppData* data = (AppData*)app;
if (data->parameters) {
tt_bundle_free(data->parameters);
}
tt_mutex_free(data->mutex);
free(data);
}
// endregion
// region Internal
static void tt_app_lock(AppData* data) {
tt_mutex_acquire(data->mutex, MutexTypeRecursive);
}
static void tt_app_unlock(AppData* data) {
tt_mutex_release(data->mutex);
}
static AppFlags tt_app_get_flags_default(AppType type) {
static const AppFlags DEFAULT_DESKTOP_FLAGS = {
.show_toolbar = false,
.show_statusbar = true
};
static const AppFlags DEFAULT_APP_FLAGS = {
.show_toolbar = true,
.show_statusbar = true
};
return type == AppTypeDesktop
? DEFAULT_DESKTOP_FLAGS
: DEFAULT_APP_FLAGS;
}
// endregion Internal
// region Public getters & setters
void tt_app_set_state(App app, AppState state) {
AppData* data = (AppData*)app;
tt_app_lock(data);
data->state = state;
tt_app_unlock(data);
}
AppState tt_app_get_state(App app) {
AppData* data = (AppData*)app;
tt_app_lock(data);
AppState state = data->state;
tt_app_unlock(data);
return state;
}
const AppManifest* tt_app_get_manifest(App app) {
AppData* data = (AppData*)app;
// No need to lock const data;
return data->manifest;
}
AppFlags tt_app_get_flags(App app) {
AppData* data = (AppData*)app;
tt_app_lock(data);
AppFlags flags = data->flags;
tt_app_unlock(data);
return flags;
}
void tt_app_set_flags(App app, AppFlags flags) {
AppData* data = (AppData*)app;
tt_app_lock(data);
data->flags = flags;
tt_app_unlock(data);
}
void* tt_app_get_data(App app) {
AppData* data = (AppData*)app;
tt_app_lock(data);
void* value = data->data;
tt_app_unlock(data);
return value;
}
void tt_app_set_data(App app, void* value) {
AppData* data = (AppData*)app;
tt_app_lock(data);
data->data = value;
tt_app_unlock(data);
}
/** TODO: Make this thread-safe.
* In practice, the bundle is writeable, so someone could be writing to it
* while it is being accessed from another thread.
* Consider creating MutableBundle vs Bundle.
* Consider not exposing bundle, but expose `app_get_bundle_int(key)` methods with locking in it.
*/
Bundle* _Nullable tt_app_get_parameters(App app) {
AppData* data = (AppData*)app;
tt_app_lock(data);
Bundle* bundle = data->parameters;
tt_app_unlock(data);
return bundle;
}
// endregion Public getters & setters
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include "app_manifest.h"
#include "bundle.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
APP_STATE_INITIAL, // App is being activated in loader
APP_STATE_STARTED, // App is in memory
APP_STATE_SHOWING, // App view is created
APP_STATE_HIDING, // App view is destroyed
APP_STATE_STOPPED // App is not in memory
} AppState;
typedef union {
struct {
bool show_statusbar : 1;
bool show_toolbar : 1;
};
unsigned char flags;
} AppFlags;
typedef void* App;
/** @brief Create an app
* @param manifest
* @param parameters optional bundle. memory ownership is transferred to App
* @return
*/
App tt_app_alloc(const AppManifest* manifest, Bundle* _Nullable parameters);
void tt_app_free(App app);
void tt_app_set_state(App app, AppState state);
AppState tt_app_get_state(App app);
const AppManifest* tt_app_get_manifest(App app);
AppFlags tt_app_get_flags(App app);
void tt_app_set_flags(App app, AppFlags flags);
void* _Nullable tt_app_get_data(App app);
void tt_app_set_data(App app, void* data);
Bundle* _Nullable tt_app_get_parameters(App app);
#ifdef __cplusplus
}
#endif
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include "app.h"
#include "app_manifest.h"
#include "mutex.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
Mutex* mutex;
const AppManifest* manifest;
AppState state;
AppFlags flags;
/** @brief Optional parameters to start the app with
* When these are stored in the app struct, the struct takes ownership.
* Do not mutate after app creation.
*/
Bundle* _Nullable parameters;
/** @brief @brief Contextual data related to the running app's instance
* The app can attach its data to this.
* The lifecycle is determined by the on_start and on_stop methods in the AppManifest.
* These manifest methods can optionally allocate/free data that is attached here.
*/
void* _Nullable data;
} AppData;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,69 @@
#pragma once
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
// Forward declarations
typedef struct _lv_obj_t lv_obj_t;
typedef void* App;
typedef enum {
AppTypeDesktop,
AppTypeSystem,
AppTypeSettings,
AppTypeUser
} AppType;
typedef void (*AppOnStart)(App app);
typedef void (*AppOnStop)(App app);
typedef void (*AppOnShow)(App app, lv_obj_t* parent);
typedef void (*AppOnHide)(App app);
typedef struct {
/**
* The identifier by which the app is launched by the system and other apps.
*/
const char* id;
/**
* The user-readable name of the app. Used in UI.
*/
const char* name;
/**
* Optional icon.
*/
const char* _Nullable icon;
/**
* App type affects launch behaviour.
*/
const AppType type;
/**
* Non-blocking method to call when app is started.
*/
const AppOnStart _Nullable on_start;
/**
* Non-blocking method to call when app is stopped.
*/
const AppOnStop _Nullable on_stop;
/**
* Non-blocking method to create the GUI
*/
const AppOnShow _Nullable on_show;
/**
* Non-blocking method, called before gui is destroyed
*/
const AppOnHide _Nullable on_hide;
} AppManifest;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,76 @@
#include "app_manifest_registry.h"
#include "m-dict.h"
#include "m_cstr_dup.h"
#include "mutex.h"
#include "tactility_core.h"
#define TAG "app_registry"
DICT_DEF2(AppManifestDict, const char*, M_CSTR_DUP_OPLIST, const AppManifest*, M_PTR_OPLIST)
#define APP_REGISTRY_FOR_EACH(manifest_var_name, code_to_execute) \
{ \
app_registry_lock(); \
AppManifestDict_it_t it; \
for (AppManifestDict_it(it, app_manifest_dict); !AppManifestDict_end_p(it); AppManifestDict_next(it)) { \
const AppManifest*(manifest_var_name) = AppManifestDict_cref(it)->value; \
code_to_execute; \
} \
app_registry_unlock(); \
}
AppManifestDict_t app_manifest_dict;
Mutex* mutex = NULL;
void tt_app_manifest_registry_init() {
tt_assert(mutex == NULL);
mutex = tt_mutex_alloc(MutexTypeNormal);
AppManifestDict_init(app_manifest_dict);
}
void app_registry_lock() {
tt_assert(mutex != NULL);
tt_mutex_acquire(mutex, TtWaitForever);
}
void app_registry_unlock() {
tt_assert(mutex != NULL);
tt_mutex_release(mutex);
}
void tt_app_manifest_registry_add(const AppManifest _Nonnull* manifest) {
TT_LOG_I(TAG, "adding %s", manifest->id);
app_registry_lock();
AppManifestDict_set_at(app_manifest_dict, manifest->id, manifest);
app_registry_unlock();
}
void tt_app_manifest_registry_remove(const AppManifest _Nonnull* manifest) {
TT_LOG_I(TAG, "removing %s", manifest->id);
app_registry_lock();
AppManifestDict_erase(app_manifest_dict, manifest->id);
app_registry_unlock();
}
const AppManifest _Nullable* tt_app_manifest_registry_find_by_id(const char* id) {
app_registry_lock();
const AppManifest _Nullable** manifest = AppManifestDict_get(app_manifest_dict, id);
app_registry_unlock();
return (manifest != NULL) ? *manifest : NULL;
}
void tt_app_manifest_registry_for_each_of_type(AppType type, void* _Nullable context, AppManifestCallback callback) {
APP_REGISTRY_FOR_EACH(manifest, {
if (manifest->type == type) {
callback(manifest, context);
}
});
}
void tt_app_manifest_registry_for_each(AppManifestCallback callback, void* _Nullable context) {
APP_REGISTRY_FOR_EACH(manifest, {
callback(manifest, context);
});
}
@@ -0,0 +1,20 @@
#pragma once
#include "app_manifest.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*AppManifestCallback)(const AppManifest*, void* context);
void tt_app_manifest_registry_init();
void tt_app_manifest_registry_add(const AppManifest _Nonnull* manifest);
void tt_app_manifest_registry_remove(const AppManifest _Nonnull* manifest);
const AppManifest _Nullable* tt_app_manifest_registry_find_by_id(const char* id);
void tt_app_manifest_registry_for_each(AppManifestCallback callback, void* _Nullable context);
void tt_app_manifest_registry_for_each_of_type(AppType type, void* _Nullable context, AppManifestCallback callback);
#ifdef __cplusplus
}
#endif
+205
View File
@@ -0,0 +1,205 @@
#include "bundle.h"
#include "m-dict.h"
#include "m_cstr_dup.h"
#include "check.h"
// region BundleEntry
typedef enum {
BUNDLE_ENTRY_TYPE_BOOL,
BUNDLE_ENTRY_TYPE_INT,
BUNDLE_ENTRY_TYPE_STRING,
} BundleEntryType;
typedef struct {
BundleEntryType type;
union {
bool bool_value;
int int_value;
char* string_ptr;
};
} BundleEntry;
BundleEntry* bundle_entry_alloc_bool(bool value) {
BundleEntry* entry = malloc(sizeof(BundleEntry));
entry->type = BUNDLE_ENTRY_TYPE_BOOL;
entry->bool_value = value;
return entry;
}
BundleEntry* bundle_entry_alloc_int(int value) {
BundleEntry* entry = malloc(sizeof(BundleEntry));
entry->type = BUNDLE_ENTRY_TYPE_INT;
entry->int_value = value;
return entry;
}
BundleEntry* bundle_entry_alloc_string(const char* text) {
BundleEntry* entry = malloc(sizeof(BundleEntry));
entry->type = BUNDLE_ENTRY_TYPE_STRING;
entry->string_ptr = malloc(strlen(text) + 1);
strcpy(entry->string_ptr, text);
return entry;
}
BundleEntry* bundle_entry_alloc_copy(BundleEntry* source) {
BundleEntry* entry = malloc(sizeof(BundleEntry));
entry->type = source->type;
if (source->type == BUNDLE_ENTRY_TYPE_STRING) {
entry->string_ptr = malloc(strlen(source->string_ptr) + 1);
strcpy(entry->string_ptr, source->string_ptr);
} else {
entry->int_value = source->int_value;
}
return entry;
}
void bundle_entry_free(BundleEntry* entry) {
if (entry->type == BUNDLE_ENTRY_TYPE_STRING) {
free(entry->string_ptr);
}
free(entry);
}
// endregion BundleEntry
// region Bundle
DICT_DEF2(BundleDict, const char*, M_CSTR_DUP_OPLIST, BundleEntry*, M_PTR_OPLIST)
typedef struct {
BundleDict_t dict;
} BundleData;
Bundle tt_bundle_alloc() {
BundleData* bundle = malloc(sizeof(BundleData));
BundleDict_init(bundle->dict);
return bundle;
}
Bundle tt_bundle_alloc_copy(Bundle source) {
BundleData* source_data = (BundleData*)source;
BundleData* target_data = tt_bundle_alloc();
BundleDict_it_t it;
for (BundleDict_it(it, source_data->dict); !BundleDict_end_p(it); BundleDict_next(it)) {
const char* key = BundleDict_cref(it)->key;
BundleEntry* entry = BundleDict_cref(it)->value;
BundleEntry* entry_copy = bundle_entry_alloc_copy(entry);
BundleDict_set_at(target_data->dict, key, entry_copy);
}
return target_data;
}
void tt_bundle_free(Bundle bundle) {
BundleData* data = (BundleData*)bundle;
BundleDict_it_t it;
for (BundleDict_it(it, data->dict); !BundleDict_end_p(it); BundleDict_next(it)) {
bundle_entry_free(BundleDict_cref(it)->value);
}
BundleDict_clear(data->dict);
free(data);
}
bool tt_bundle_get_bool(Bundle bundle, const char* key) {
BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key);
tt_check(entry != NULL);
return (*entry)->bool_value;
}
int tt_bundle_get_int(Bundle bundle, const char* key) {
BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key);
tt_check(entry != NULL);
return (*entry)->int_value;
}
const char* tt_bundle_get_string(Bundle bundle, const char* key) {
BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key);
tt_check(entry != NULL);
return (*entry)->string_ptr;
}
bool tt_bundle_opt_bool(Bundle bundle, const char* key, bool* out) {
BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key);
if (entry != NULL) {
*out = (*entry)->bool_value;
return true;
} else {
return false;
}
}
bool tt_bundle_opt_int(Bundle bundle, const char* key, int* out) {
BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key);
if (entry != NULL) {
*out = (*entry)->int_value;
return true;
} else {
return false;
}
}
bool tt_bundle_opt_string(Bundle bundle, const char* key, char** out) {
BundleData* data = (BundleData*)bundle;
BundleEntry** entry = BundleDict_get(data->dict, key);
if (entry != NULL) {
*out = (*entry)->string_ptr;
return true;
} else {
return false;
}
}
void tt_bundle_put_bool(Bundle bundle, const char* key, bool value) {
BundleData* data = (BundleData*)bundle;
BundleEntry** entry_handle = BundleDict_get(data->dict, key);
if (entry_handle != NULL) {
BundleEntry* entry = *entry_handle;
tt_assert(entry->type == BUNDLE_ENTRY_TYPE_BOOL);
entry->bool_value = value;
} else {
BundleEntry* entry = bundle_entry_alloc_bool(value);
BundleDict_set_at(data->dict, key, entry);
}
}
void tt_bundle_put_int(Bundle bundle, const char* key, int value) {
BundleData* data = (BundleData*)bundle;
BundleEntry** entry_handle = BundleDict_get(data->dict, key);
if (entry_handle != NULL) {
BundleEntry* entry = *entry_handle;
tt_assert(entry->type == BUNDLE_ENTRY_TYPE_INT);
entry->int_value = value;
} else {
BundleEntry* entry = bundle_entry_alloc_int(value);
BundleDict_set_at(data->dict, key, entry);
}
}
void tt_bundle_put_string(Bundle bundle, const char* key, const char* value) {
BundleData* data = (BundleData*)bundle;
BundleEntry** entry_handle = BundleDict_get(data->dict, key);
if (entry_handle != NULL) {
BundleEntry* entry = *entry_handle;
tt_assert(entry->type == BUNDLE_ENTRY_TYPE_STRING);
if (entry->string_ptr != NULL) {
free(entry->string_ptr);
}
entry->string_ptr = malloc(strlen(value) + 1);
strcpy(entry->string_ptr, value);
} else {
BundleEntry* entry = bundle_entry_alloc_string(value);
BundleDict_set_at(data->dict, key, entry);
}
}
// endregion Bundle
+34
View File
@@ -0,0 +1,34 @@
/**
* @brief key-value storage for general purpose.
* Maps strings on a fixed set of data types.
*/
#pragma once
#include <stdio.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void* Bundle;
Bundle tt_bundle_alloc();
Bundle tt_bundle_alloc_copy(Bundle source);
void tt_bundle_free(Bundle bundle);
bool tt_bundle_get_bool(Bundle bundle, const char* key);
int tt_bundle_get_int(Bundle bundle, const char* key);
const char* tt_bundle_get_string(Bundle bundle, const char* key);
bool tt_bundle_opt_bool(Bundle bundle, const char* key, bool* out);
bool tt_bundle_opt_int(Bundle bundle, const char* key, int* out);
bool tt_bundle_opt_string(Bundle bundle, const char* key, char** out);
void tt_bundle_put_bool(Bundle bundle, const char* key, bool value);
void tt_bundle_put_int(Bundle bundle, const char* key, int value);
void tt_bundle_put_string(Bundle bundle, const char* key, const char* value);
#ifdef __cplusplus
}
#endif
+38
View File
@@ -0,0 +1,38 @@
#include "check.h"
#include "core_defines.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "log.h"
#define TAG "kernel"
static void tt_print_memory_info() {
TT_LOG_E(TAG, "default caps:");
TT_LOG_E(TAG, " total: %u", heap_caps_get_total_size(MALLOC_CAP_DEFAULT));
TT_LOG_E(TAG, " free: %u", heap_caps_get_free_size(MALLOC_CAP_DEFAULT));
TT_LOG_E(TAG, " min free: %u", heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT));
TT_LOG_E(TAG, "internal caps:");
TT_LOG_E(TAG, " total: %u", heap_caps_get_total_size(MALLOC_CAP_INTERNAL));
TT_LOG_E(TAG, " free: %u", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
TT_LOG_E(TAG, " min free: %u", heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
}
static void tt_print_task_info() {
if (TT_IS_IRQ_MODE()) {
TT_LOG_E(TAG, "Task: ISR %lu", __get_IPSR());
} else {
const char* name = pcTaskGetName(NULL);
const char* safe_name = name ? name : "main";
TT_LOG_E(TAG, "Task: %s", safe_name);
TT_LOG_E(TAG, "Stack watermark: %u", uxTaskGetStackHighWaterMark(NULL) * 4);
}
}
TT_NORETURN void tt_crash_implementation() {
tt_print_task_info();
tt_print_memory_info();
// TODO: Add breakpoint when debugger is attached.
esp_system_abort("System halted. Connect debugger for more info.");
__builtin_unreachable();
}
+103
View File
@@ -0,0 +1,103 @@
/**
* @file check.h
*
* Tactility crash and assert functions.
*
* The main problem with crashing is that you can't do anything without disturbing registers,
* and if you disturb registers, you won't be able to see the correct register values in the debugger.
*
* Current solution works around it by passing the message through r12 and doing some magic with registers in crash function.
* r0-r10 are stored in the ram2 on crash routine start and restored at the end.
* The only register that is going to be lost is r11.
*
*/
#pragma once
#include <esp_log.h>
#include <m-core.h>
#ifdef __cplusplus
extern "C" {
#define TT_NORETURN [[noreturn]]
#else
#include <stdnoreturn.h>
#define TT_NORETURN noreturn
#endif
/** Crash system */
TT_NORETURN void tt_crash_implementation();
/** Crash system with message. */
#define __tt_crash(message) \
do { \
ESP_LOGE("crash", "%s\n\tat %s:%d", ((message) ? (message) : ""), __FILE__, __LINE__); \
tt_crash_implementation(); \
} while (0)
/** Crash system
*
* @param optional message (const char*)
*/
#define tt_crash(...) M_APPLY(__tt_crash, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__)))
/** Halt system
*
* @param optional message (const char*)
*/
#define tt_halt(...) M_APPLY(__tt_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__)))
/** Check condition and crash if check failed */
#define __tt_check(__e, __m) \
do { \
if (!(__e)) { \
ESP_LOGE("check", "%s", #__e); \
if (__m) { \
__tt_crash(#__m); \
} else { \
__tt_crash(""); \
} \
} \
} while (0)
/** Check condition and crash if failed
*
* @param condition to check
* @param optional message (const char*)
*/
#define tt_check(...) \
M_APPLY(__tt_check, M_DEFAULT_ARGS(2, NULL, __VA_ARGS__))
/** Only in debug build: Assert condition and crash if assert failed */
#ifdef TT_DEBUG
#define __tt_assert(__e, __m) \
do { \
if (!(__e)) { \
ESP_LOGE("assert", "%s", #__e); \
if (__m) { \
__tt_crash(#__m); \
} else { \
__tt_crash(""); \
} \
} \
} while (0)
#else
#define __tt_assert(__e, __m) \
do { \
((void)(__e)); \
((void)(__m)); \
} while (0)
#endif
/** Assert condition and crash if failed
*
* @warning only will do check if firmware compiled in debug mode
*
* @param condition to check
* @param optional message (const char*)
*/
#define tt_assert(...) \
M_APPLY(__tt_assert, M_DEFAULT_ARGS(2, NULL, __VA_ARGS__))
#ifdef __cplusplus
}
#endif
+22
View File
@@ -0,0 +1,22 @@
#include "core.h"
#include "app_manifest_registry.h"
#include "service_registry.h"
#define TAG "tactility"
void tt_core_init() {
TT_LOG_I(TAG, "core init start");
tt_assert(!tt_kernel_is_irq());
#if defined(__ARM_ARCH_7A__) && (__ARM_ARCH_7A__ == 0U)
/* Service Call interrupt might be configured before kernel start */
/* and when its priority is lower or equal to BASEPRI, svc instruction */
/* causes a Hard Fault. */
NVIC_SetPriority(SVCall_IRQn, 0U);
#endif
tt_service_registry_init();
tt_app_manifest_registry_init();
TT_LOG_I(TAG, "core init complete");
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <stdlib.h>
#include "tactility_core.h"
#include "event_flag.h"
#include "kernel.h"
#include "message_queue.h"
#include "mutex.h"
#include "pubsub.h"
#include "semaphore.h"
#include "stream_buffer.h"
#include "string.h"
#include "thread.h"
#include "timer.h"
#include "tt_string.h"
#ifdef __cplusplus
extern "C" {
#endif
void tt_core_init();
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,42 @@
#pragma once
#include "core_extra_defines.h"
#include "freertos/portmacro.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <cmsis_compiler.h>
#define TT_RETURNS_NONNULL __attribute__((returns_nonnull))
#ifndef TT_WARN_UNUSED
#define TT_WARN_UNUSED __attribute__((warn_unused_result))
#endif
#ifndef TT_WEAK
#define TT_WEAK __attribute__((weak))
#endif
#ifndef TT_PACKED
#define TT_PACKED __attribute__((packed))
#endif
// Used by portENABLE_INTERRUPTS and portDISABLE_INTERRUPTS?
#ifndef TT_IS_IRQ_MODE
#define TT_IS_IRQ_MODE() (xPortInIsrContext() == pdTRUE)
#endif
#ifndef TT_IS_ISR
#define TT_IS_ISR() (TT_IS_IRQ_MODE())
#endif
#ifndef TT_CHECK_RETURN
#define TT_CHECK_RETURN __attribute__((__warn_unused_result__))
#endif
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,114 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#ifndef MAX
#define MAX(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a > _b ? _a : _b; \
})
#endif
#ifndef MIN
#define MIN(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a < _b ? _a : _b; \
})
#endif
#ifndef ABS
#define ABS(a) ({ (a) < 0 ? -(a) : (a); })
#endif
#ifndef ROUND_UP_TO
#define ROUND_UP_TO(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a / _b + !!(_a % _b); \
})
#endif
#ifndef CLAMP
#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower)))
#endif
#ifndef COUNT_OF
#define COUNT_OF(x) (sizeof(x) / sizeof(x[0]))
#endif
#ifndef TT_SWAP
#define TT_SWAP(x, y) \
do { \
typeof(x) SWAP = x; \
x = y; \
y = SWAP; \
} while (0)
#endif
#ifndef PLACE_IN_SECTION
#define PLACE_IN_SECTION(x) __attribute__((section(x)))
#endif
#ifndef ALIGN
#define ALIGN(n) __attribute__((aligned(n)))
#endif
#ifndef __weak
#define __weak __attribute__((weak))
#endif
#ifndef UNUSED
#define UNUSED(X) (void)(X)
#endif
#ifndef STRINGIFY
#define STRINGIFY(x) #x
#endif
#ifndef TOSTRING
#define TOSTRING(x) STRINGIFY(x)
#endif
#ifndef CONCATENATE
#define CONCATENATE(a, b) CONCATENATE_(a, b)
#define CONCATENATE_(a, b) a##b
#endif
#ifndef REVERSE_BYTES_U32
#define REVERSE_BYTES_U32(x) \
((((x) & 0x000000FF) << 24) | (((x) & 0x0000FF00) << 8) | (((x) & 0x00FF0000) >> 8) | \
(((x) & 0xFF000000) >> 24))
#endif
#ifndef TT_BIT
#define TT_BIT(x, n) (((x) >> (n)) & 1)
#endif
#ifndef TT_BIT_SET
#define TT_BIT_SET(x, n) \
({ \
__typeof__(x) _x = (1); \
(x) |= (_x << (n)); \
})
#endif
#ifndef TT_BIT_CLEAR
#define TT_BIT_CLEAR(x, n) \
({ \
__typeof__(x) _x = (1); \
(x) &= ~(_x << (n)); \
})
#endif
#define TT_SW_MEMBARRIER() asm volatile("" : : : "memory")
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,44 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <tactility_core_config.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
TtWaitForever = 0xFFFFFFFFU,
} TtWait;
typedef enum {
TtFlagWaitAny = 0x00000000U, ///< Wait for any flag (default).
TtFlagWaitAll = 0x00000001U, ///< Wait for all flags.
TtFlagNoClear = 0x00000002U, ///< Do not clear flags which have been specified to wait for.
TtFlagError = 0x80000000U, ///< Error indicator.
TtFlagErrorUnknown = 0xFFFFFFFFU, ///< TtStatusError (-1).
TtFlagErrorTimeout = 0xFFFFFFFEU, ///< TtStatusErrorTimeout (-2).
TtFlagErrorResource = 0xFFFFFFFDU, ///< TtStatusErrorResource (-3).
TtFlagErrorParameter = 0xFFFFFFFCU, ///< TtStatusErrorParameter (-4).
TtFlagErrorISR = 0xFFFFFFFAU, ///< TtStatusErrorISR (-6).
} TtFlag;
typedef enum {
TtStatusOk = 0, ///< Operation completed successfully.
TtStatusError =
-1, ///< Unspecified RTOS error: run-time error but no other error message fits.
TtStatusErrorTimeout = -2, ///< Operation not completed within the timeout period.
TtStatusErrorResource = -3, ///< Resource not available.
TtStatusErrorParameter = -4, ///< Parameter error.
TtStatusErrorNoMemory =
-5, ///< System is out of memory: it was impossible to allocate or reserve memory for the operation.
TtStatusErrorISR =
-6, ///< Not allowed in ISR context: the function cannot be called from interrupt service routines.
TtStatusReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization.
} TtStatus;
#ifdef __cplusplus
}
#endif
+35
View File
@@ -0,0 +1,35 @@
#include "critical.h"
#include "core_defines.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static portMUX_TYPE critical_mutex;
__TtCriticalInfo __tt_critical_enter(void) {
__TtCriticalInfo info;
info.isrm = 0;
info.from_isr = TT_IS_ISR();
info.kernel_running = (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING);
if (info.from_isr) {
info.isrm = taskENTER_CRITICAL_FROM_ISR();
} else if (info.kernel_running) {
taskENTER_CRITICAL(&critical_mutex);
} else {
__disable_irq();
}
return info;
}
void __tt_critical_exit(__TtCriticalInfo info) {
if (info.from_isr) {
taskEXIT_CRITICAL_FROM_ISR(info.isrm);
} else if (info.kernel_running) {
taskEXIT_CRITICAL(&critical_mutex);
} else {
__enable_irq();
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <stdio.h>
#include <stdbool.h>
#ifndef TT_CRITICAL_ENTER
#define TT_CRITICAL_ENTER() __TtCriticalInfo __tt_critical_info = __tt_critical_enter();
#endif
#ifndef TT_CRITICAL_EXIT
#define TT_CRITICAL_EXIT() __tt_critical_exit(__tt_critical_info);
#endif
typedef struct {
uint32_t isrm;
bool from_isr;
bool kernel_running;
} __TtCriticalInfo;
__TtCriticalInfo __tt_critical_enter(void);
void __tt_critical_exit(__TtCriticalInfo info);
+146
View File
@@ -0,0 +1,146 @@
#include "event_flag.h"
#include "check.h"
#include "core_defines.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#define TT_EVENT_FLAG_MAX_BITS_EVENT_GROUPS 24U
#define TT_EVENT_FLAG_INVALID_BITS (~((1UL << TT_EVENT_FLAG_MAX_BITS_EVENT_GROUPS) - 1U))
EventFlag* tt_event_flag_alloc() {
tt_assert(!TT_IS_IRQ_MODE());
EventGroupHandle_t handle = xEventGroupCreate();
tt_check(handle);
return ((EventFlag*)handle);
}
void tt_event_flag_free(EventFlag* instance) {
tt_assert(!TT_IS_IRQ_MODE());
vEventGroupDelete((EventGroupHandle_t)instance);
}
uint32_t tt_event_flag_set(EventFlag* instance, uint32_t flags) {
tt_assert(instance);
tt_assert((flags & TT_EVENT_FLAG_INVALID_BITS) == 0U);
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
uint32_t rflags;
BaseType_t yield;
if (TT_IS_IRQ_MODE()) {
yield = pdFALSE;
if (xEventGroupSetBitsFromISR(hEventGroup, (EventBits_t)flags, &yield) == pdFAIL) {
rflags = (uint32_t)TtFlagErrorResource;
} else {
rflags = flags;
portYIELD_FROM_ISR(yield);
}
} else {
rflags = xEventGroupSetBits(hEventGroup, (EventBits_t)flags);
}
/* Return event flags after setting */
return (rflags);
}
uint32_t tt_event_flag_clear(EventFlag* instance, uint32_t flags) {
tt_assert(instance);
tt_assert((flags & TT_EVENT_FLAG_INVALID_BITS) == 0U);
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
uint32_t rflags;
if (TT_IS_IRQ_MODE()) {
rflags = xEventGroupGetBitsFromISR(hEventGroup);
if (xEventGroupClearBitsFromISR(hEventGroup, (EventBits_t)flags) == pdFAIL) {
rflags = (uint32_t)TtStatusErrorResource;
} else {
/* xEventGroupClearBitsFromISR only registers clear operation in the timer command queue. */
/* Yield is required here otherwise clear operation might not execute in the right order. */
/* See https://github.com/FreeRTOS/FreeRTOS-Kernel/issues/93 for more info. */
portYIELD_FROM_ISR(pdTRUE);
}
} else {
rflags = xEventGroupClearBits(hEventGroup, (EventBits_t)flags);
}
/* Return event flags before clearing */
return (rflags);
}
uint32_t tt_event_flag_get(EventFlag* instance) {
tt_assert(instance);
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
uint32_t rflags;
if (TT_IS_IRQ_MODE()) {
rflags = xEventGroupGetBitsFromISR(hEventGroup);
} else {
rflags = xEventGroupGetBits(hEventGroup);
}
/* Return current event flags */
return (rflags);
}
uint32_t tt_event_flag_wait(
EventFlag* instance,
uint32_t flags,
uint32_t options,
uint32_t timeout
) {
tt_assert(!TT_IS_IRQ_MODE());
tt_assert(instance);
tt_assert((flags & TT_EVENT_FLAG_INVALID_BITS) == 0U);
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
BaseType_t wait_all;
BaseType_t exit_clr;
uint32_t rflags;
if (options & TtFlagWaitAll) {
wait_all = pdTRUE;
} else {
wait_all = pdFAIL;
}
if (options & TtFlagNoClear) {
exit_clr = pdFAIL;
} else {
exit_clr = pdTRUE;
}
rflags = xEventGroupWaitBits(
hEventGroup,
(EventBits_t)flags,
exit_clr,
wait_all,
(TickType_t)timeout
);
if (options & TtFlagWaitAll) {
if ((flags & rflags) != flags) {
if (timeout > 0U) {
rflags = (uint32_t)TtStatusErrorTimeout;
} else {
rflags = (uint32_t)TtStatusErrorResource;
}
}
} else {
if ((flags & rflags) == 0U) {
if (timeout > 0U) {
rflags = (uint32_t)TtStatusErrorTimeout;
} else {
rflags = (uint32_t)TtStatusErrorResource;
}
}
}
/* Return event flags before clearing */
return (rflags);
}
@@ -0,0 +1,67 @@
#pragma once
#include "core_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void EventFlag;
/** Allocate EventFlag
*
* @return pointer to EventFlag
*/
EventFlag* tt_event_flag_alloc();
/** Deallocate EventFlag
*
* @param instance pointer to EventFlag
*/
void tt_event_flag_free(EventFlag* instance);
/** Set flags
*
* @param instance pointer to EventFlag
* @param[in] flags The flags
*
* @return Resulting flags or error (TtStatus)
*/
uint32_t tt_event_flag_set(EventFlag* instance, uint32_t flags);
/** Clear flags
*
* @param instance pointer to EventFlag
* @param[in] flags The flags
*
* @return Resulting flags or error (TtStatus)
*/
uint32_t tt_event_flag_clear(EventFlag* instance, uint32_t flags);
/** Get flags
*
* @param instance pointer to EventFlag
*
* @return Resulting flags
*/
uint32_t tt_event_flag_get(EventFlag* instance);
/** Wait flags
*
* @param instance pointer to EventFlag
* @param[in] flags The flags
* @param[in] options The option flags
* @param[in] timeout The timeout
*
* @return Resulting flags or error (TtStatus)
*/
uint32_t tt_event_flag_wait(
EventFlag* instance,
uint32_t flags,
uint32_t options,
uint32_t timeout
);
#ifdef __cplusplus
}
#endif
+182
View File
@@ -0,0 +1,182 @@
#include "kernel.h"
#include "check.h"
#include "core_defines.h"
#include "core_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <rom/ets_sys.h>
bool tt_kernel_is_irq() {
return TT_IS_IRQ_MODE();
}
bool tt_kernel_is_running() {
return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING;
}
int32_t tt_kernel_lock() {
tt_assert(!tt_kernel_is_irq());
int32_t lock;
switch (xTaskGetSchedulerState()) {
case taskSCHEDULER_SUSPENDED:
lock = 1;
break;
case taskSCHEDULER_RUNNING:
vTaskSuspendAll();
lock = 0;
break;
case taskSCHEDULER_NOT_STARTED:
default:
lock = (int32_t)TtStatusError;
break;
}
/* Return previous lock state */
return (lock);
}
int32_t tt_kernel_unlock() {
tt_assert(!tt_kernel_is_irq());
int32_t lock;
switch (xTaskGetSchedulerState()) {
case taskSCHEDULER_SUSPENDED:
lock = 1;
if (xTaskResumeAll() != pdTRUE) {
if (xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) {
lock = (int32_t)TtStatusError;
}
}
break;
case taskSCHEDULER_RUNNING:
lock = 0;
break;
case taskSCHEDULER_NOT_STARTED:
default:
lock = (int32_t)TtStatusError;
break;
}
/* Return previous lock state */
return (lock);
}
int32_t tt_kernel_restore_lock(int32_t lock) {
tt_assert(!tt_kernel_is_irq());
switch (xTaskGetSchedulerState()) {
case taskSCHEDULER_SUSPENDED:
case taskSCHEDULER_RUNNING:
if (lock == 1) {
vTaskSuspendAll();
} else {
if (lock != 0) {
lock = (int32_t)TtStatusError;
} else {
if (xTaskResumeAll() != pdTRUE) {
if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) {
lock = (int32_t)TtStatusError;
}
}
}
}
break;
case taskSCHEDULER_NOT_STARTED:
default:
lock = (int32_t)TtStatusError;
break;
}
/* Return new lock state */
return (lock);
}
uint32_t tt_kernel_get_tick_frequency() {
/* Return frequency in hertz */
return (configTICK_RATE_HZ_RAW);
}
void tt_delay_tick(uint32_t ticks) {
tt_assert(!tt_kernel_is_irq());
if (ticks == 0U) {
taskYIELD();
} else {
vTaskDelay(ticks);
}
}
TtStatus tt_delay_until_tick(uint32_t tick) {
tt_assert(!tt_kernel_is_irq());
TickType_t tcnt, delay;
TtStatus stat;
stat = TtStatusOk;
tcnt = xTaskGetTickCount();
/* Determine remaining number of tick to delay */
delay = (TickType_t)tick - tcnt;
/* Check if target tick has not expired */
if ((delay != 0U) && (0 == (delay >> (8 * sizeof(TickType_t) - 1)))) {
if (xTaskDelayUntil(&tcnt, delay) == pdFALSE) {
/* Did not delay */
stat = TtStatusError;
}
} else {
/* No delay or already expired */
stat = TtStatusErrorParameter;
}
/* Return execution status */
return (stat);
}
uint32_t tt_get_tick() {
TickType_t ticks;
if (tt_kernel_is_irq() != 0U) {
ticks = xTaskGetTickCountFromISR();
} else {
ticks = xTaskGetTickCount();
}
return ticks;
}
uint32_t tt_ms_to_ticks(uint32_t milliseconds) {
#if configTICK_RATE_HZ_RAW == 1000
return milliseconds;
#else
return (uint32_t)((float)configTICK_RATE_HZ_RAW) / 1000.0f * (float)milliseconds;
#endif
}
void tt_delay_ms(uint32_t milliseconds) {
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
if (milliseconds > 0 && milliseconds < portMAX_DELAY - 1) {
milliseconds += 1;
}
#if configTICK_RATE_HZ_RAW == 1000
tt_delay_tick(milliseconds);
#else
tt_delay_tick(tt_ms_to_ticks(milliseconds));
#endif
} else if (milliseconds > 0) {
tt_delay_us(milliseconds * 1000);
}
}
void tt_delay_us(uint32_t microseconds) {
ets_delay_us(microseconds);
}
+124
View File
@@ -0,0 +1,124 @@
#pragma once
#include "core_types.h"
#define configTICK_RATE_HZ_RAW CONFIG_FREERTOS_HZ
#ifdef __cplusplus
extern "C" {
#endif
/** Check if CPU is in IRQ or kernel running and IRQ is masked
*
* Originally this primitive was born as a workaround for FreeRTOS kernel primitives shenanigans with PRIMASK.
*
* Meaningful use cases are:
*
* - When kernel is started and you want to ensure that you are not in IRQ or IRQ is not masked(like in critical section)
* - When kernel is not started and you want to make sure that you are not in IRQ mode, ignoring PRIMASK.
*
* As you can see there will be edge case when kernel is not started and PRIMASK is not 0 that may cause some funky behavior.
* Most likely it will happen after kernel primitives being used, but control not yet passed to kernel.
* It's up to you to figure out if it is safe for your code or not.
*
* @return true if CPU is in IRQ or kernel running and IRQ is masked
*/
bool tt_kernel_is_irq();
/** Check if kernel is running
*
* @return true if running, false otherwise
*/
bool tt_kernel_is_running();
/** Lock kernel, pause process scheduling
*
* @warning This should never be called in interrupt request context.
*
* @return previous lock state(0 - unlocked, 1 - locked)
*/
int32_t tt_kernel_lock();
/** Unlock kernel, resume process scheduling
*
* @warning This should never be called in interrupt request context.
*
* @return previous lock state(0 - unlocked, 1 - locked)
*/
int32_t tt_kernel_unlock();
/** Restore kernel lock state
*
* @warning This should never be called in interrupt request context.
*
* @param[in] lock The lock state
*
* @return new lock state or error
*/
int32_t tt_kernel_restore_lock(int32_t lock);
/** Get kernel systick frequency
*
* @return systick counts per second
*/
uint32_t tt_kernel_get_tick_frequency();
/** Delay execution
*
* @warning This should never be called in interrupt request context.
*
* Also keep in mind delay is aliased to scheduler timer intervals.
*
* @param[in] ticks The ticks count to pause
*/
void tt_delay_tick(uint32_t ticks);
/** Delay until tick
*
* @warning This should never be called in interrupt request context.
*
* @param[in] ticks The tick until which kerel should delay task execution
*
* @return The status.
*/
TtStatus tt_delay_until_tick(uint32_t tick);
/** Get current tick counter
*
* System uptime, may overflow.
*
* @return Current ticks in milliseconds
*/
uint32_t tt_get_tick(void);
/** Convert milliseconds to ticks
*
* @param[in] milliseconds time in milliseconds
* @return time in ticks
*/
uint32_t tt_ms_to_ticks(uint32_t milliseconds);
/** Delay in milliseconds
*
* This method uses kernel ticks on the inside, which causes delay to be aliased to scheduler timer intervals.
* Real wait time will be between X+ milliseconds.
* Special value: 0, will cause task yield.
* Also if used when kernel is not running will fall back to `tt_delay_us`.
*
* @warning Cannot be used from ISR
*
* @param[in] milliseconds milliseconds to wait
*/
void tt_delay_ms(uint32_t milliseconds);
/** Delay in microseconds
*
* Implemented using Cortex DWT counter. Blocking and non aliased.
*
* @param[in] microseconds microseconds to wait
*/
void tt_delay_us(uint32_t microseconds);
#ifdef __cplusplus
}
#endif
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "esp_log.h"
#ifdef __cplusplus
extern "C" {
#endif
#define TT_LOG_E(tag, format, ...) \
ESP_LOGE(tag, format, ##__VA_ARGS__)
#define TT_LOG_W(tag, format, ...) \
ESP_LOGW(tag, format, ##__VA_ARGS__)
#define TT_LOG_I(tag, format, ...) \
ESP_LOGI(tag, format, ##__VA_ARGS__)
#define TT_LOG_D(tag, format, ...) \
ESP_LOGD(tag, format, ##__VA_ARGS__)
#define TT_LOG_T(tag, format, ...) \
ESP_LOGT(tag, format, ##__VA_ARGS__)
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,25 @@
#pragma once
#include <m-core.h>
#ifdef __cplusplus
extern "C" {
#endif
#define M_INIT_DUP(a) ((a) = strdup(""))
#define M_INIT_SET_DUP(a, b) ((a) = strdup(b))
#define M_SET_DUP(a, b) (free((void*)a), (a) = strdup(b))
#define M_CLEAR_DUP(a) (free((void*)a))
#define M_CSTR_DUP_OPLIST \
(INIT(M_INIT_DUP), \
INIT_SET(M_INIT_SET_DUP), \
SET(M_SET_DUP), \
CLEAR(M_CLEAR_DUP), \
HASH(m_core_cstr_hash), \
EQUAL(M_CSTR_EQUAL), \
CMP(strcmp), \
TYPE(const char*))
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,181 @@
#include "message_queue.h"
#include "check.h"
#include "kernel.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
MessageQueue* tt_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) {
tt_assert((tt_kernel_is_irq() == 0U) && (msg_count > 0U) && (msg_size > 0U));
QueueHandle_t handle = xQueueCreate(msg_count, msg_size);
tt_check(handle);
return ((MessageQueue*)handle);
}
void tt_message_queue_free(MessageQueue* instance) {
tt_assert(tt_kernel_is_irq() == 0U);
tt_assert(instance);
vQueueDelete((QueueHandle_t)instance);
}
TtStatus tt_message_queue_put(MessageQueue* instance, const void* msg_ptr, uint32_t timeout) {
QueueHandle_t hQueue = (QueueHandle_t)instance;
TtStatus stat;
BaseType_t yield;
stat = TtStatusOk;
if (tt_kernel_is_irq() != 0U) {
if ((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
stat = TtStatusErrorParameter;
} else {
yield = pdFALSE;
if (xQueueSendToBackFromISR(hQueue, msg_ptr, &yield) != pdTRUE) {
stat = TtStatusErrorResource;
} else {
portYIELD_FROM_ISR(yield);
}
}
} else {
if ((hQueue == NULL) || (msg_ptr == NULL)) {
stat = TtStatusErrorParameter;
} else {
if (xQueueSendToBack(hQueue, msg_ptr, (TickType_t)timeout) != pdPASS) {
if (timeout != 0U) {
stat = TtStatusErrorTimeout;
} else {
stat = TtStatusErrorResource;
}
}
}
}
/* Return execution status */
return (stat);
}
TtStatus tt_message_queue_get(MessageQueue* instance, void* msg_ptr, uint32_t timeout_ticks) {
QueueHandle_t hQueue = (QueueHandle_t)instance;
TtStatus stat;
BaseType_t yield;
stat = TtStatusOk;
if (tt_kernel_is_irq() != 0U) {
if ((hQueue == NULL) || (msg_ptr == NULL) || (timeout_ticks != 0U)) {
stat = TtStatusErrorParameter;
} else {
yield = pdFALSE;
if (xQueueReceiveFromISR(hQueue, msg_ptr, &yield) != pdPASS) {
stat = TtStatusErrorResource;
} else {
portYIELD_FROM_ISR(yield);
}
}
} else {
if ((hQueue == NULL) || (msg_ptr == NULL)) {
stat = TtStatusErrorParameter;
} else {
if (xQueueReceive(hQueue, msg_ptr, (TickType_t)timeout_ticks) != pdPASS) {
if (timeout_ticks != 0U) {
stat = TtStatusErrorTimeout;
} else {
stat = TtStatusErrorResource;
}
}
}
}
/* Return execution status */
return (stat);
}
uint32_t tt_message_queue_get_capacity(MessageQueue* instance) {
StaticQueue_t* mq = (StaticQueue_t*)instance;
uint32_t capacity;
if (mq == NULL) {
capacity = 0U;
} else {
/* capacity = pxQueue->uxLength */
capacity = mq->uxDummy4[1];
}
/* Return maximum number of messages */
return (capacity);
}
uint32_t tt_message_queue_get_message_size(MessageQueue* instance) {
StaticQueue_t* mq = (StaticQueue_t*)instance;
uint32_t size;
if (mq == NULL) {
size = 0U;
} else {
/* size = pxQueue->uxItemSize */
size = mq->uxDummy4[2];
}
/* Return maximum message size */
return (size);
}
uint32_t tt_message_queue_get_count(MessageQueue* instance) {
QueueHandle_t hQueue = (QueueHandle_t)instance;
UBaseType_t count;
if (hQueue == NULL) {
count = 0U;
} else if (tt_kernel_is_irq() != 0U) {
count = uxQueueMessagesWaitingFromISR(hQueue);
} else {
count = uxQueueMessagesWaiting(hQueue);
}
/* Return number of queued messages */
return ((uint32_t)count);
}
uint32_t tt_message_queue_get_space(MessageQueue* instance) {
StaticQueue_t* mq = (StaticQueue_t*)instance;
uint32_t space;
uint32_t isrm;
if (mq == NULL) {
space = 0U;
} else if (tt_kernel_is_irq() != 0U) {
isrm = taskENTER_CRITICAL_FROM_ISR();
/* space = pxQueue->uxLength - pxQueue->uxMessagesWaiting; */
space = mq->uxDummy4[1] - mq->uxDummy4[0];
taskEXIT_CRITICAL_FROM_ISR(isrm);
} else {
space = (uint32_t)uxQueueSpacesAvailable((QueueHandle_t)mq);
}
/* Return number of available slots */
return (space);
}
TtStatus tt_message_queue_reset(MessageQueue* instance) {
QueueHandle_t hQueue = (QueueHandle_t)instance;
TtStatus stat;
if (tt_kernel_is_irq() != 0U) {
stat = TtStatusErrorISR;
} else if (hQueue == NULL) {
stat = TtStatusErrorParameter;
} else {
stat = TtStatusOk;
(void)xQueueReset(hQueue);
}
/* Return execution status */
return (stat);
}
@@ -0,0 +1,94 @@
/**
* @file message_queue.h
* MessageQueue
*/
#pragma once
#include "core_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void MessageQueue;
/** Allocate message queue
*
* @param[in] msg_count The message count
* @param[in] msg_size The message size
*
* @return pointer to MessageQueue instance
*/
MessageQueue* tt_message_queue_alloc(uint32_t msg_count, uint32_t msg_size);
/** Free queue
*
* @param instance pointer to MessageQueue instance
*/
void tt_message_queue_free(MessageQueue* instance);
/** Put message into queue
*
* @param instance pointer to MessageQueue instance
* @param[in] msg_ptr The message pointer
* @param[in] timeout The timeout
* @param[in] msg_prio The message prio
*
* @return The status.
*/
TtStatus tt_message_queue_put(MessageQueue* instance, const void* msg_ptr, uint32_t timeout);
/** Get message from queue
*
* @param instance pointer to MessageQueue instance
* @param msg_ptr The message pointer
* @param msg_prio The message prioority
* @param[in] timeout_ticks The timeout
*
* @return The status.
*/
TtStatus tt_message_queue_get(MessageQueue* instance, void* msg_ptr, uint32_t timeout_ticks);
/** Get queue capacity
*
* @param instance pointer to MessageQueue instance
*
* @return capacity in object count
*/
uint32_t tt_message_queue_get_capacity(MessageQueue* instance);
/** Get message size
*
* @param instance pointer to MessageQueue instance
*
* @return Message size in bytes
*/
uint32_t tt_message_queue_get_message_size(MessageQueue* instance);
/** Get message count in queue
*
* @param instance pointer to MessageQueue instance
*
* @return Message count
*/
uint32_t tt_message_queue_get_count(MessageQueue* instance);
/** Get queue available space
*
* @param instance pointer to MessageQueue instance
*
* @return Message count
*/
uint32_t tt_message_queue_get_space(MessageQueue* instance);
/** Reset queue
*
* @param instance pointer to MessageQueue instance
*
* @return The status.
*/
TtStatus tt_message_queue_reset(MessageQueue* instance);
#ifdef __cplusplus
}
#endif
+126
View File
@@ -0,0 +1,126 @@
#include "mutex.h"
#include "check.h"
#include "core_defines.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "log.h"
Mutex* tt_mutex_alloc(MutexType type) {
tt_assert(!TT_IS_IRQ_MODE());
SemaphoreHandle_t hMutex = NULL;
if (type == MutexTypeNormal) {
hMutex = xSemaphoreCreateMutex();
} else if (type == MutexTypeRecursive) {
hMutex = xSemaphoreCreateRecursiveMutex();
} else {
tt_crash("Programming error");
}
tt_check(hMutex != NULL);
if (type == MutexTypeRecursive) {
/* Set LSB as 'recursive mutex flag' */
hMutex = (SemaphoreHandle_t)((uint32_t)hMutex | 1U);
}
/* Return mutex ID */
return ((Mutex*)hMutex);
}
void tt_mutex_free(Mutex* instance) {
tt_assert(!TT_IS_IRQ_MODE());
tt_assert(instance);
vSemaphoreDelete((SemaphoreHandle_t)((uint32_t)instance & ~1U));
}
TtStatus tt_mutex_acquire(Mutex* instance, uint32_t timeout) {
SemaphoreHandle_t hMutex;
TtStatus stat;
uint32_t rmtx;
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
/* Extract recursive mutex flag */
rmtx = (uint32_t)instance & 1U;
stat = TtStatusOk;
if (TT_IS_IRQ_MODE()) {
stat = TtStatusErrorISR;
} else if (hMutex == NULL) {
stat = TtStatusErrorParameter;
} else {
if (rmtx != 0U) {
if (xSemaphoreTakeRecursive(hMutex, timeout) != pdPASS) {
if (timeout != 0U) {
stat = TtStatusErrorTimeout;
} else {
stat = TtStatusErrorResource;
}
}
} else {
if (xSemaphoreTake(hMutex, timeout) != pdPASS) {
if (timeout != 0U) {
stat = TtStatusErrorTimeout;
} else {
stat = TtStatusErrorResource;
}
}
}
}
/* Return execution status */
return (stat);
}
TtStatus tt_mutex_release(Mutex* instance) {
SemaphoreHandle_t hMutex;
TtStatus stat;
uint32_t rmtx;
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
/* Extract recursive mutex flag */
rmtx = (uint32_t)instance & 1U;
stat = TtStatusOk;
if (TT_IS_IRQ_MODE()) {
stat = TtStatusErrorISR;
} else if (hMutex == NULL) {
stat = TtStatusErrorParameter;
} else {
if (rmtx != 0U) {
if (xSemaphoreGiveRecursive(hMutex) != pdPASS) {
stat = TtStatusErrorResource;
}
} else {
if (xSemaphoreGive(hMutex) != pdPASS) {
stat = TtStatusErrorResource;
}
}
}
/* Return execution status */
return (stat);
}
ThreadId tt_mutex_get_owner(Mutex* instance) {
SemaphoreHandle_t hMutex;
ThreadId owner;
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
if ((TT_IS_IRQ_MODE()) || (hMutex == NULL)) {
owner = 0;
} else {
owner = (ThreadId)xSemaphoreGetMutexHolder(hMutex);
}
/* Return owner thread ID */
return (owner);
}
+62
View File
@@ -0,0 +1,62 @@
/**
* @file mutex.h
* Mutex
*/
#pragma once
#include "core_types.h"
#include "thread.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
MutexTypeNormal,
MutexTypeRecursive,
} MutexType;
typedef void Mutex;
/** Allocate Mutex
*
* @param[in] type The mutex type
*
* @return pointer to Mutex instance
*/
Mutex* tt_mutex_alloc(MutexType type);
/** Free Mutex
*
* @param instance The pointer to Mutex instance
*/
void tt_mutex_free(Mutex* instance);
/** Acquire mutex
*
* @param instance The pointer to Mutex instance
* @param[in] timeout The timeout
*
* @return The status.
*/
TtStatus tt_mutex_acquire(Mutex* instance, uint32_t timeout);
/** Release mutex
*
* @param instance The pointer to Mutex instance
*
* @return The status.
*/
TtStatus tt_mutex_release(Mutex* instance);
/** Get mutex owner thread id
*
* @param instance The pointer to Mutex instance
*
* @return The thread identifier.
*/
ThreadId tt_mutex_get_owner(Mutex* instance);
#ifdef __cplusplus
}
#endif
+93
View File
@@ -0,0 +1,93 @@
#include "pubsub.h"
#include "check.h"
#include "mutex.h"
#include <m-list.h>
struct PubSubSubscription {
PubSubCallback callback;
void* callback_context;
};
LIST_DEF(PubSubSubscriptionList, PubSubSubscription, M_POD_OPLIST);
struct PubSub {
PubSubSubscriptionList_t items;
Mutex* mutex;
};
PubSub* tt_pubsub_alloc() {
PubSub* pubsub = malloc(sizeof(PubSub));
pubsub->mutex = tt_mutex_alloc(MutexTypeRecursive);
tt_assert(pubsub->mutex);
PubSubSubscriptionList_init(pubsub->items);
return pubsub;
}
void tt_pubsub_free(PubSub* pubsub) {
tt_assert(pubsub);
tt_check(PubSubSubscriptionList_size(pubsub->items) == 0);
PubSubSubscriptionList_clear(pubsub->items);
tt_mutex_free(pubsub->mutex);
free(pubsub);
}
PubSubSubscription* tt_pubsub_subscribe(PubSub* pubsub, PubSubCallback callback, void* callback_context) {
tt_check(tt_mutex_acquire(pubsub->mutex, TtWaitForever) == TtStatusOk);
// put uninitialized item to the list
PubSubSubscription* item = PubSubSubscriptionList_push_raw(pubsub->items);
// initialize item
item->callback = callback;
item->callback_context = callback_context;
tt_check(tt_mutex_release(pubsub->mutex) == TtStatusOk);
return item;
}
void tt_pubsub_unsubscribe(PubSub* pubsub, PubSubSubscription* pubsub_subscription) {
tt_assert(pubsub);
tt_assert(pubsub_subscription);
tt_check(tt_mutex_acquire(pubsub->mutex, TtWaitForever) == TtStatusOk);
bool result = false;
// iterate over items
PubSubSubscriptionList_it_t it;
for (PubSubSubscriptionList_it(it, pubsub->items); !PubSubSubscriptionList_end_p(it);
PubSubSubscriptionList_next(it)) {
const PubSubSubscription* item = PubSubSubscriptionList_cref(it);
// if the iterator is equal to our element
if (item == pubsub_subscription) {
PubSubSubscriptionList_remove(pubsub->items, it);
result = true;
break;
}
}
tt_check(tt_mutex_release(pubsub->mutex) == TtStatusOk);
tt_check(result);
}
void tt_pubsub_publish(PubSub* pubsub, void* message) {
tt_check(tt_mutex_acquire(pubsub->mutex, TtWaitForever) == TtStatusOk);
// iterate over subscribers
PubSubSubscriptionList_it_t it;
for (PubSubSubscriptionList_it(it, pubsub->items); !PubSubSubscriptionList_end_p(it);
PubSubSubscriptionList_next(it)) {
const PubSubSubscription* item = PubSubSubscriptionList_cref(it);
item->callback(message, item->callback_context);
}
tt_check(tt_mutex_release(pubsub->mutex) == TtStatusOk);
}
+68
View File
@@ -0,0 +1,68 @@
/**
* @file pubsub.h
* PubSub
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/** PubSub Callback type */
typedef void (*PubSubCallback)(const void* message, void* context);
/** PubSub type */
typedef struct PubSub PubSub;
/** PubSubSubscription type */
typedef struct PubSubSubscription PubSubSubscription;
/** Allocate PubSub
*
* Reentrable, Not threadsafe, one owner
*
* @return pointer to PubSub instance
*/
PubSub* tt_pubsub_alloc();
/** Free PubSub
*
* @param pubsub PubSub instance
*/
void tt_pubsub_free(PubSub* pubsub);
/** Subscribe to PubSub
*
* Threadsafe, Reentrable
*
* @param pubsub pointer to PubSub instance
* @param[in] callback The callback
* @param callback_context The callback context
*
* @return pointer to PubSubSubscription instance
*/
PubSubSubscription*
tt_pubsub_subscribe(PubSub* pubsub, PubSubCallback callback, void* callback_context);
/** Unsubscribe from PubSub
*
* No use of `pubsub_subscription` allowed after call of this method
* Threadsafe, Reentrable.
*
* @param pubsub pointer to PubSub instance
* @param pubsub_subscription pointer to PubSubSubscription instance
*/
void tt_pubsub_unsubscribe(PubSub* pubsub, PubSubSubscription* pubsub_subscription);
/** Publish message to PubSub
*
* Threadsafe, Reentrable.
*
* @param pubsub pointer to PubSub instance
* @param message message pointer to publish
*/
void tt_pubsub_publish(PubSub* pubsub, void* message);
#ifdef __cplusplus
}
#endif
+119
View File
@@ -0,0 +1,119 @@
#include "semaphore.h"
#include "check.h"
#include "core_defines.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
Semaphore* tt_semaphore_alloc(uint32_t max_count, uint32_t initial_count) {
tt_assert(!TT_IS_IRQ_MODE());
tt_assert((max_count > 0U) && (initial_count <= max_count));
SemaphoreHandle_t hSemaphore = NULL;
if (max_count == 1U) {
hSemaphore = xSemaphoreCreateBinary();
if ((hSemaphore != NULL) && (initial_count != 0U)) {
if (xSemaphoreGive(hSemaphore) != pdPASS) {
vSemaphoreDelete(hSemaphore);
hSemaphore = NULL;
}
}
} else {
hSemaphore = xSemaphoreCreateCounting(max_count, initial_count);
}
tt_check(hSemaphore);
return (Semaphore*)hSemaphore;
}
void tt_semaphore_free(Semaphore* instance) {
tt_assert(instance);
tt_assert(!TT_IS_IRQ_MODE());
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
vSemaphoreDelete(hSemaphore);
}
TtStatus tt_semaphore_acquire(Semaphore* instance, uint32_t timeout) {
tt_assert(instance);
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
TtStatus status;
BaseType_t yield;
status = TtStatusOk;
if (TT_IS_IRQ_MODE()) {
if (timeout != 0U) {
status = TtStatusErrorParameter;
} else {
yield = pdFALSE;
if (xSemaphoreTakeFromISR(hSemaphore, &yield) != pdPASS) {
status = TtStatusErrorResource;
} else {
portYIELD_FROM_ISR(yield);
}
}
} else {
if (xSemaphoreTake(hSemaphore, (TickType_t)timeout) != pdPASS) {
if (timeout != 0U) {
status = TtStatusErrorTimeout;
} else {
status = TtStatusErrorResource;
}
}
}
return status;
}
TtStatus tt_semaphore_release(Semaphore* instance) {
tt_assert(instance);
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
TtStatus stat;
BaseType_t yield;
stat = TtStatusOk;
if (TT_IS_IRQ_MODE()) {
yield = pdFALSE;
if (xSemaphoreGiveFromISR(hSemaphore, &yield) != pdTRUE) {
stat = TtStatusErrorResource;
} else {
portYIELD_FROM_ISR(yield);
}
} else {
if (xSemaphoreGive(hSemaphore) != pdPASS) {
stat = TtStatusErrorResource;
}
}
/* Return execution status */
return (stat);
}
uint32_t tt_semaphore_get_count(Semaphore* instance) {
tt_assert(instance);
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
uint32_t count;
if (TT_IS_IRQ_MODE()) {
// TODO: uxSemaphoreGetCountFromISR is not supported on esp-idf 5.1.2 - perhaps later on?
#ifdef uxSemaphoreGetCountFromISR
count = (uint32_t)uxSemaphoreGetCountFromISR(hSemaphore);
#else
count = (uint32_t)uxQueueMessagesWaitingFromISR((QueueHandle_t)hSemaphore);
#endif
} else {
count = (uint32_t)uxSemaphoreGetCount(hSemaphore);
}
/* Return number of tokens */
return (count);
}
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include "core_types.h"
#include "thread.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void Semaphore;
/** Allocate semaphore
*
* @param[in] max_count The maximum count
* @param[in] initial_count The initial count
*
* @return pointer to Semaphore instance
*/
Semaphore* tt_semaphore_alloc(uint32_t max_count, uint32_t initial_count);
/** Free semaphore
*
* @param instance The pointer to Semaphore instance
*/
void tt_semaphore_free(Semaphore* instance);
/** Acquire semaphore
*
* @param instance The pointer to Semaphore instance
* @param[in] timeout The timeout
*
* @return The status.
*/
TtStatus tt_semaphore_acquire(Semaphore* instance, uint32_t timeout);
/** Release semaphore
*
* @param instance The pointer to Semaphore instance
*
* @return The status.
*/
TtStatus tt_semaphore_release(Semaphore* instance);
/** Get semaphore count
*
* @param instance The pointer to Semaphore instance
*
* @return Semaphore count
*/
uint32_t tt_semaphore_get_count(Semaphore* instance);
#ifdef __cplusplus
}
#endif
+62
View File
@@ -0,0 +1,62 @@
#include "log.h"
#include "service_i.h"
#include "tactility_core.h"
// region Alloc/free
ServiceData* tt_service_alloc(const ServiceManifest* _Nonnull manifest) {
ServiceData* data = malloc(sizeof(ServiceData));
*data = (ServiceData) {
.manifest = manifest,
.mutex = tt_mutex_alloc(MutexTypeRecursive),
.data = NULL
};
return data;
}
void tt_service_free(ServiceData* data) {
tt_assert(data);
tt_mutex_free(data->mutex);
free(data);
}
// endregion Alloc/free
// region Internal
static void tt_service_lock(ServiceData * data) {
tt_mutex_acquire(data->mutex, MutexTypeRecursive);
}
static void tt_service_unlock(ServiceData* data) {
tt_mutex_release(data->mutex);
}
// endregion Internal
// region Getters & Setters
const ServiceManifest* tt_service_get_manifest(Service service) {
ServiceData* data = (ServiceData*)service;
tt_service_lock(data);
const ServiceManifest* manifest = data->manifest;
tt_service_unlock(data);
return manifest;
}
void tt_service_set_data(Service service, void* value) {
ServiceData* data = (ServiceData*)service;
tt_service_lock(data);
data->data = value;
tt_service_unlock(data);
}
void* _Nullable tt_service_get_data(Service service) {
ServiceData* data = (ServiceData*)service;
tt_service_lock(data);
void* value = data->data;
tt_service_unlock(data);
return value;
}
// endregion Getters & Setters
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "service_manifest.h"
typedef void* Service;
const ServiceManifest* tt_service_get_manifest(Service service);
void tt_service_set_data(Service service, void* value);
void* _Nullable tt_service_get_data(Service service);
#ifdef __cplusplus
}
#endif
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include "service.h"
#include "mutex.h"
#include "service_manifest.h"
typedef struct {
Mutex* mutex;
const ServiceManifest* manifest;
void* data;
} ServiceData;
ServiceData* tt_service_alloc(const ServiceManifest* _Nonnull manifest);
void tt_service_free(ServiceData* _Nonnull service);
@@ -0,0 +1,34 @@
#pragma once
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void* Service;
typedef void (*ServiceOnStart)(Service service);
typedef void (*ServiceOnStop)(Service service);
typedef struct {
/**
* The identifier by which the app is launched by the system and other apps.
*/
const char* _Nonnull id;
/**
* Non-blocking method to call when service is started.
*/
const ServiceOnStart _Nullable on_start;
/**
* Non-blocking method to call when service is stopped.
*/
const ServiceOnStop _Nullable on_stop;
} ServiceManifest;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,136 @@
#include "service_registry.h"
#include "m-dict.h"
#include "m_cstr_dup.h"
#include "mutex.h"
#include "service_i.h"
#include "tactility_core.h"
#define TAG "service_registry"
DICT_DEF2(ServiceManifestDict, const char*, M_CSTR_DUP_OPLIST, const ServiceManifest*, M_PTR_OPLIST)
DICT_DEF2(ServiceInstanceDict, const char*, M_CSTR_DUP_OPLIST, const ServiceData*, M_PTR_OPLIST)
#define APP_REGISTRY_FOR_EACH(manifest_var_name, code_to_execute) \
{ \
service_registry_manifest_lock(); \
ServiceManifestDict_it_t it; \
for (ServiceManifestDict_it(it, service_manifest_dict); !ServiceManifestDict_end_p(it); ServiceManifestDict_next(it)) { \
const ServiceManifest*(manifest_var_name) = ServiceManifestDict_cref(it)->value; \
code_to_execute; \
} \
service_registry_manifest_unlock(); \
}
static ServiceManifestDict_t service_manifest_dict;
static ServiceInstanceDict_t service_instance_dict;
static Mutex* manifest_mutex = NULL;
static Mutex* instance_mutex = NULL;
void tt_service_registry_init() {
tt_assert(manifest_mutex == NULL);
manifest_mutex = tt_mutex_alloc(MutexTypeNormal);
ServiceManifestDict_init(service_manifest_dict);
tt_assert(instance_mutex == NULL);
instance_mutex = tt_mutex_alloc(MutexTypeNormal);
ServiceInstanceDict_init(service_instance_dict);
}
void service_registry_instance_lock() {
tt_assert(instance_mutex != NULL);
tt_mutex_acquire(instance_mutex, TtWaitForever);
}
void service_registry_instance_unlock() {
tt_assert(instance_mutex != NULL);
tt_mutex_release(instance_mutex);
}
void service_registry_manifest_lock() {
tt_assert(manifest_mutex != NULL);
tt_mutex_acquire(manifest_mutex, TtWaitForever);
}
void service_registry_manifest_unlock() {
tt_assert(manifest_mutex != NULL);
tt_mutex_release(manifest_mutex);
}
void tt_service_registry_add(const ServiceManifest _Nonnull* manifest) {
TT_LOG_I(TAG, "adding %s", manifest->id);
service_registry_manifest_lock();
ServiceManifestDict_set_at(service_manifest_dict, manifest->id, manifest);
service_registry_manifest_unlock();
}
void tt_service_registry_remove(const ServiceManifest _Nonnull* manifest) {
TT_LOG_I(TAG, "removing %s", manifest->id);
service_registry_manifest_lock();
ServiceManifestDict_erase(service_manifest_dict, manifest->id);
service_registry_manifest_unlock();
}
const ServiceManifest* _Nullable tt_service_registry_find_manifest_by_id(const char* id) {
service_registry_manifest_lock();
const ServiceManifest** _Nullable manifest = ServiceManifestDict_get(service_manifest_dict, id);
service_registry_manifest_unlock();
return (manifest != NULL) ? *manifest : NULL;
}
ServiceData* _Nullable service_registry_find_instance_by_id(const char* id) {
service_registry_instance_lock();
const ServiceData** _Nullable service_ptr = ServiceInstanceDict_get(service_instance_dict, id);
if (service_ptr == NULL) {
return NULL;
}
ServiceData* service = (ServiceData*)*service_ptr;
service_registry_instance_unlock();
return service;
}
void tt_service_registry_for_each_manifest(ServiceManifestCallback callback, void* _Nullable context) {
APP_REGISTRY_FOR_EACH(manifest, {
callback(manifest, context);
});
}
// TODO: return proper error/status instead of BOOL
bool tt_service_registry_start(const char* service_id) {
TT_LOG_I(TAG, "starting %s", service_id);
const ServiceManifest* manifest = tt_service_registry_find_manifest_by_id(service_id);
if (manifest == NULL) {
TT_LOG_E(TAG, "manifest not found for service %s", service_id);
return false;
}
Service service = tt_service_alloc(manifest);
manifest->on_start(service);
service_registry_instance_lock();
ServiceInstanceDict_set_at(service_instance_dict, manifest->id, service);
service_registry_instance_unlock();
TT_LOG_I(TAG, "started %s", service_id);
return true;
}
bool tt_service_registry_stop(const char* service_id) {
TT_LOG_I(TAG, "stopping %s", service_id);
ServiceData* service = service_registry_find_instance_by_id(service_id);
if (service == NULL) {
TT_LOG_W(TAG, "service not running: %s", service_id);
return false;
}
service->manifest->on_stop(service);
tt_service_free(service);
service_registry_instance_lock();
ServiceInstanceDict_erase(service_instance_dict, service_id);
service_registry_instance_unlock();
TT_LOG_I(TAG, "stopped %s", service_id);
return true;
}
@@ -0,0 +1,25 @@
#pragma once
#include "service_manifest.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
typedef void (*ServiceManifestCallback)(const ServiceManifest*, void* context);
void tt_service_registry_init();
void tt_service_registry_add(const ServiceManifest* manifest);
void tt_service_registry_remove(const ServiceManifest* manifest);
const ServiceManifest _Nullable* tt_service_registry_find_manifest_by_id(const char* id);
void tt_service_registry_for_each_manifest(ServiceManifestCallback callback, void* _Nullable context);
bool tt_service_registry_start(const char* service_id);
bool tt_service_registry_stop(const char* service_id);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,88 @@
#include "stream_buffer.h"
#include "check.h"
#include "core_defines.h"
#include "core_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/stream_buffer.h"
StreamBuffer* tt_stream_buffer_alloc(size_t size, size_t trigger_level) {
tt_assert(size != 0);
StreamBufferHandle_t handle = xStreamBufferCreate(size, trigger_level);
tt_check(handle);
return handle;
};
void tt_stream_buffer_free(StreamBuffer* stream_buffer) {
tt_assert(stream_buffer);
vStreamBufferDelete(stream_buffer);
};
bool tt_stream_set_trigger_level(StreamBuffer* stream_buffer, size_t trigger_level) {
tt_assert(stream_buffer);
return xStreamBufferSetTriggerLevel(stream_buffer, trigger_level) == pdTRUE;
};
size_t tt_stream_buffer_send(
StreamBuffer* stream_buffer,
const void* data,
size_t length,
uint32_t timeout
) {
size_t ret;
if (TT_IS_IRQ_MODE()) {
BaseType_t yield;
ret = xStreamBufferSendFromISR(stream_buffer, data, length, &yield);
portYIELD_FROM_ISR(yield);
} else {
ret = xStreamBufferSend(stream_buffer, data, length, timeout);
}
return ret;
};
size_t tt_stream_buffer_receive(
StreamBuffer* stream_buffer,
void* data,
size_t length,
uint32_t timeout
) {
size_t ret;
if (TT_IS_IRQ_MODE()) {
BaseType_t yield;
ret = xStreamBufferReceiveFromISR(stream_buffer, data, length, &yield);
portYIELD_FROM_ISR(yield);
} else {
ret = xStreamBufferReceive(stream_buffer, data, length, timeout);
}
return ret;
}
size_t tt_stream_buffer_bytes_available(StreamBuffer* stream_buffer) {
return xStreamBufferBytesAvailable(stream_buffer);
};
size_t tt_stream_buffer_spaces_available(StreamBuffer* stream_buffer) {
return xStreamBufferSpacesAvailable(stream_buffer);
};
bool tt_stream_buffer_is_full(StreamBuffer* stream_buffer) {
return xStreamBufferIsFull(stream_buffer) == pdTRUE;
};
bool tt_stream_buffer_is_empty(StreamBuffer* stream_buffer) {
return (xStreamBufferIsEmpty(stream_buffer) == pdTRUE);
};
TtStatus tt_stream_buffer_reset(StreamBuffer* stream_buffer) {
if (xStreamBufferReset(stream_buffer) == pdPASS) {
return TtStatusOk;
} else {
return TtStatusError;
}
}
@@ -0,0 +1,156 @@
/**
* @file stream_buffer.h
* Tactility stream buffer primitive.
*
* Stream buffers are used to send a continuous stream of data from one task or
* interrupt to another. Their implementation is light weight, making them
* particularly suited for interrupt to task and core to core communication
* scenarios.
*
* ***NOTE***: Stream buffer implementation assumes there is only one task or
* interrupt that will write to the buffer (the writer), and only one task or
* interrupt that will read from the buffer (the reader).
*/
#pragma once
#include "core_types.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void StreamBuffer;
/**
* @brief Allocate stream buffer instance.
* Stream buffer implementation assumes there is only one task or
* interrupt that will write to the buffer (the writer), and only one task or
* interrupt that will read from the buffer (the reader).
*
* @param size The total number of bytes the stream buffer will be able to hold at any one time.
* @param trigger_level The number of bytes that must be in the stream buffer
* before a task that is blocked on the stream buffer to wait for data is moved out of the blocked state.
* @return The stream buffer instance.
*/
StreamBuffer* tt_stream_buffer_alloc(size_t size, size_t trigger_level);
/**
* @brief Free stream buffer instance
*
* @param stream_buffer The stream buffer instance.
*/
void tt_stream_buffer_free(StreamBuffer* stream_buffer);
/**
* @brief Set trigger level for stream buffer.
* A stream buffer's trigger level is the number of bytes that must be in the
* stream buffer before a task that is blocked on the stream buffer to
* wait for data is moved out of the blocked state.
*
* @param stream_buffer The stream buffer instance
* @param trigger_level The new trigger level for the stream buffer.
* @return true if trigger level can be be updated (new trigger level was less than or equal to the stream buffer's length).
* @return false if trigger level can't be be updated (new trigger level was greater than the stream buffer's length).
*/
bool tt_stream_set_trigger_level(StreamBuffer* stream_buffer, size_t trigger_level);
/**
* @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer.
* Wakes up task waiting for data to become available if called from ISR.
*
* @param stream_buffer The stream buffer instance.
* @param data A pointer to the data that is to be copied into the stream buffer.
* @param length The maximum number of bytes to copy from data into the stream buffer.
* @param timeout The maximum amount of time the task should remain in the
* Blocked state to wait for space to become available if the stream buffer is full.
* Will return immediately if timeout is zero.
* Setting timeout to TtWaitForever will cause the task to wait indefinitely.
* Ignored if called from ISR.
* @return The number of bytes actually written to the stream buffer.
*/
size_t tt_stream_buffer_send(
StreamBuffer* stream_buffer,
const void* data,
size_t length,
uint32_t timeout
);
/**
* @brief Receives bytes from a stream buffer.
* Wakes up task waiting for space to become available if called from ISR.
*
* @param stream_buffer The stream buffer instance.
* @param data A pointer to the buffer into which the received bytes will be
* copied.
* @param length The length of the buffer pointed to by the data parameter.
* @param timeout The maximum amount of time the task should remain in the
* Blocked state to wait for data to become available if the stream buffer is empty.
* Will return immediately if timeout is zero.
* Setting timeout to TtWaitForever will cause the task to wait indefinitely.
* Ignored if called from ISR.
* @return The number of bytes read from the stream buffer, if any.
*/
size_t tt_stream_buffer_receive(
StreamBuffer* stream_buffer,
void* data,
size_t length,
uint32_t timeout
);
/**
* @brief Queries a stream buffer to see how much data it contains, which is equal to
* the number of bytes that can be read from the stream buffer before the stream
* buffer would be empty.
*
* @param stream_buffer The stream buffer instance.
* @return The number of bytes that can be read from the stream buffer before
* the stream buffer would be empty.
*/
size_t tt_stream_buffer_bytes_available(StreamBuffer* stream_buffer);
/**
* @brief Queries a stream buffer to see how much free space it contains, which is
* equal to the amount of data that can be sent to the stream buffer before it
* is full.
*
* @param stream_buffer The stream buffer instance.
* @return The number of bytes that can be written to the stream buffer before
* the stream buffer would be full.
*/
size_t tt_stream_buffer_spaces_available(StreamBuffer* stream_buffer);
/**
* @brief Queries a stream buffer to see if it is full.
*
* @param stream_buffer stream buffer instance.
* @return true if the stream buffer is full.
* @return false if the stream buffer is not full.
*/
bool tt_stream_buffer_is_full(StreamBuffer* stream_buffer);
/**
* @brief Queries a stream buffer to see if it is empty.
*
* @param stream_buffer The stream buffer instance.
* @return true if the stream buffer is empty.
* @return false if the stream buffer is not empty.
*/
bool tt_stream_buffer_is_empty(StreamBuffer* stream_buffer);
/**
* @brief Resets a stream buffer to its initial, empty, state. Any data that was
* in the stream buffer is discarded. A stream buffer can only be reset if there
* are no tasks blocked waiting to either send to or receive from the stream buffer.
*
* @param stream_buffer The stream buffer instance.
* @return TtStatusOk if the stream buffer is reset.
* @return TtStatusError if there was a task blocked waiting to send to or read
* from the stream buffer then the stream buffer is not reset.
*/
TtStatus tt_stream_buffer_reset(StreamBuffer* stream_buffer);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
#include <stdio.h>
#include "app.h"
#include "check.h"
#include "core_defines.h"
#include "core_extra_defines.h"
#include "core_types.h"
#include "critical.h"
#include "event_flag.h"
#include "log.h"
#include "service.h"
@@ -0,0 +1,3 @@
#pragma once
#define TT_CONFIG_THREAD_MAX_PRIORITIES (32)
+599
View File
@@ -0,0 +1,599 @@
#include "thread.h"
#include "check.h"
#include "core_defines.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "kernel.h"
#include "log.h"
#include "tt_string.h"
#include <esp_log.h>
#define TAG "Thread"
#define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers
// Limits
#define MAX_BITS_TASK_NOTIFY 31U
#define MAX_BITS_EVENT_GROUPS 24U
#define THREAD_FLAGS_INVALID_BITS (~((1UL << MAX_BITS_TASK_NOTIFY) - 1U))
#define EVENT_FLAGS_INVALID_BITS (~((1UL << MAX_BITS_EVENT_GROUPS) - 1U))
typedef struct ThreadStdout ThreadStdout;
struct ThreadStdout {
ThreadStdoutWriteCallback write_callback;
TtString* buffer;
};
struct Thread {
ThreadState state;
int32_t ret;
ThreadCallback callback;
void* context;
ThreadStateCallback state_callback;
void* state_context;
char* name;
char* appid;
ThreadPriority priority;
TaskHandle_t task_handle;
size_t heap_size;
ThreadStdout output;
// Keep all non-alignable byte types in one place,
// this ensures that the size of this structure is minimal
bool is_static;
bool heap_trace_enabled;
configSTACK_DEPTH_TYPE stack_size;
};
static size_t __tt_thread_stdout_write(Thread* thread, const char* data, size_t size);
static int32_t __tt_thread_stdout_flush(Thread* thread);
/** Catch threads that are trying to exit wrong way */
__attribute__((__noreturn__)) void tt_thread_catch() { //-V1082
// If you're here it means you're probably doing something wrong
// with critical sections or with scheduler state
asm volatile("nop"); // extra magic
tt_crash("You are doing it wrong"); //-V779
__builtin_unreachable();
}
static void tt_thread_set_state(Thread* thread, ThreadState state) {
tt_assert(thread);
thread->state = state;
if (thread->state_callback) {
thread->state_callback(state, thread->state_context);
}
}
static void tt_thread_body(void* context) {
tt_assert(context);
Thread* thread = context;
// store thread instance to thread local storage
tt_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) == NULL);
vTaskSetThreadLocalStoragePointer(NULL, 0, thread);
tt_assert(thread->state == ThreadStateStarting);
tt_thread_set_state(thread, ThreadStateRunning);
thread->ret = thread->callback(thread->context);
tt_assert(thread->state == ThreadStateRunning);
if (thread->is_static) {
ESP_LOGI(
TAG,
"%s service thread TCB memory will not be reclaimed",
thread->name ? thread->name : "<unnamed service>"
);
}
// flush stdout
__tt_thread_stdout_flush(thread);
tt_thread_set_state(thread, ThreadStateStopped);
vTaskDelete(NULL);
tt_thread_catch();
}
Thread* tt_thread_alloc() {
Thread* thread = malloc(sizeof(Thread));
// TODO: create default struct instead of using memset()
memset(thread, 0, sizeof(Thread));
thread->output.buffer = tt_string_alloc();
thread->is_static = false;
Thread* parent = NULL;
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
// TLS is not available, if we called not from thread context
parent = pvTaskGetThreadLocalStoragePointer(NULL, 0);
if (parent && parent->appid) {
tt_thread_set_appid(thread, parent->appid);
} else {
tt_thread_set_appid(thread, "unknown");
}
} else {
// if scheduler is not started, we are starting driver thread
tt_thread_set_appid(thread, "driver");
}
/*HalRtcHeapTrackMode mode = tt_hal_rtc_get_heap_track_mode();
if(mode == HalRtcHeapTrackModeAll) {
thread->heap_trace_enabled = true;
} else if(mode == HalRtcHeapTrackModeTree && tt_thread_get_current_id()) {
if(parent) thread->heap_trace_enabled = parent->heap_trace_enabled;
} else */
{
thread->heap_trace_enabled = false;
}
return thread;
}
Thread* tt_thread_alloc_ex(
const char* name,
uint32_t stack_size,
ThreadCallback callback,
void* context
) {
Thread* thread = tt_thread_alloc();
tt_thread_set_name(thread, name);
tt_thread_set_stack_size(thread, stack_size);
tt_thread_set_callback(thread, callback);
tt_thread_set_context(thread, context);
return thread;
}
void tt_thread_free(Thread* thread) {
tt_assert(thread);
// Ensure that use join before free
tt_assert(thread->state == ThreadStateStopped);
tt_assert(thread->task_handle == NULL);
if (thread->name) free(thread->name);
if (thread->appid) free(thread->appid);
tt_string_free(thread->output.buffer);
free(thread);
}
void tt_thread_set_name(Thread* thread, const char* name) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
if (thread->name) free(thread->name);
thread->name = name ? strdup(name) : NULL;
}
void tt_thread_set_appid(Thread* thread, const char* appid) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
if (thread->appid) free(thread->appid);
thread->appid = appid ? strdup(appid) : NULL;
}
void tt_thread_mark_as_static(Thread* thread) {
thread->is_static = true;
}
bool tt_thread_mark_is_service(ThreadId thread_id) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
assert(!TT_IS_IRQ_MODE() && (hTask != NULL));
Thread* thread = (Thread*)pvTaskGetThreadLocalStoragePointer(hTask, 0);
assert(thread != NULL);
return thread->is_static;
}
void tt_thread_set_stack_size(Thread* thread, size_t stack_size) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
tt_assert(stack_size % 4 == 0);
thread->stack_size = stack_size;
}
void tt_thread_set_callback(Thread* thread, ThreadCallback callback) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
thread->callback = callback;
}
void tt_thread_set_context(Thread* thread, void* context) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
thread->context = context;
}
void tt_thread_set_priority(Thread* thread, ThreadPriority priority) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
tt_assert(priority >= ThreadPriorityIdle && priority <= ThreadPriorityIsr);
thread->priority = priority;
}
void tt_thread_set_current_priority(ThreadPriority priority) {
UBaseType_t new_priority = priority ? priority : ThreadPriorityNormal;
vTaskPrioritySet(NULL, new_priority);
}
ThreadPriority tt_thread_get_current_priority() {
return (ThreadPriority)uxTaskPriorityGet(NULL);
}
void tt_thread_set_state_callback(Thread* thread, ThreadStateCallback callback) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
thread->state_callback = callback;
}
void tt_thread_set_state_context(Thread* thread, void* context) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
thread->state_context = context;
}
ThreadState tt_thread_get_state(Thread* thread) {
tt_assert(thread);
return thread->state;
}
void tt_thread_start(Thread* thread) {
tt_assert(thread);
tt_assert(thread->callback);
tt_assert(thread->state == ThreadStateStopped);
tt_assert(thread->stack_size > 0 && thread->stack_size < (UINT16_MAX * sizeof(StackType_t)));
tt_thread_set_state(thread, ThreadStateStarting);
uint32_t stack = thread->stack_size / sizeof(StackType_t);
UBaseType_t priority = thread->priority ? thread->priority : ThreadPriorityNormal;
if (thread->is_static) {
thread->task_handle = xTaskCreateStatic(
tt_thread_body,
thread->name,
stack,
thread,
priority,
malloc(sizeof(StackType_t) * stack),
malloc(sizeof(StaticTask_t))
);
} else {
BaseType_t ret = xTaskCreate(
tt_thread_body, thread->name, stack, thread, priority, &thread->task_handle
);
tt_check(ret == pdPASS);
}
tt_check(thread->task_handle);
}
void tt_thread_cleanup_tcb_event(TaskHandle_t task) {
Thread* thread = pvTaskGetThreadLocalStoragePointer(task, 0);
if (thread) {
// clear thread local storage
vTaskSetThreadLocalStoragePointer(task, 0, NULL);
tt_assert(thread->task_handle == task);
thread->task_handle = NULL;
}
}
bool tt_thread_join(Thread* thread) {
tt_assert(thread);
tt_check(tt_thread_get_current() != thread);
// !!! IMPORTANT NOTICE !!!
//
// If your thread exited, but your app stuck here: some other thread uses
// all cpu time, which delays kernel from releasing task handle
while (thread->task_handle) {
tt_delay_ms(10);
}
return true;
}
ThreadId tt_thread_get_id(Thread* thread) {
tt_assert(thread);
return thread->task_handle;
}
void tt_thread_enable_heap_trace(Thread* thread) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
thread->heap_trace_enabled = true;
}
void tt_thread_disable_heap_trace(Thread* thread) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
thread->heap_trace_enabled = false;
}
size_t tt_thread_get_heap_size(Thread* thread) {
tt_assert(thread);
tt_assert(thread->heap_trace_enabled == true);
return thread->heap_size;
}
int32_t tt_thread_get_return_code(Thread* thread) {
tt_assert(thread);
tt_assert(thread->state == ThreadStateStopped);
return thread->ret;
}
ThreadId tt_thread_get_current_id() {
return xTaskGetCurrentTaskHandle();
}
Thread* tt_thread_get_current() {
Thread* thread = pvTaskGetThreadLocalStoragePointer(NULL, 0);
return thread;
}
void tt_thread_yield() {
tt_assert(!TT_IS_IRQ_MODE());
taskYIELD();
}
uint32_t tt_thread_flags_set(ThreadId thread_id, uint32_t flags) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
uint32_t rflags;
BaseType_t yield;
if ((hTask == NULL) || ((flags & THREAD_FLAGS_INVALID_BITS) != 0U)) {
rflags = (uint32_t)TtStatusErrorParameter;
} else {
rflags = (uint32_t)TtStatusError;
if (TT_IS_IRQ_MODE()) {
yield = pdFALSE;
(void)xTaskNotifyIndexedFromISR(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits, &yield);
(void)xTaskNotifyAndQueryIndexedFromISR(
hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags, NULL
);
portYIELD_FROM_ISR(yield);
} else {
(void)xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits);
(void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags);
}
}
/* Return flags after setting */
return (rflags);
}
uint32_t tt_thread_flags_clear(uint32_t flags) {
TaskHandle_t hTask;
uint32_t rflags, cflags;
if (TT_IS_IRQ_MODE()) {
rflags = (uint32_t)TtStatusErrorISR;
} else if ((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
rflags = (uint32_t)TtStatusErrorParameter;
} else {
hTask = xTaskGetCurrentTaskHandle();
if (xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &cflags) ==
pdPASS) {
rflags = cflags;
cflags &= ~flags;
if (xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, cflags, eSetValueWithOverwrite) !=
pdPASS) {
rflags = (uint32_t)TtStatusError;
}
} else {
rflags = (uint32_t)TtStatusError;
}
}
/* Return flags before clearing */
return (rflags);
}
uint32_t tt_thread_flags_get(void) {
TaskHandle_t hTask;
uint32_t rflags;
if (TT_IS_IRQ_MODE()) {
rflags = (uint32_t)TtStatusErrorISR;
} else {
hTask = xTaskGetCurrentTaskHandle();
if (xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags) !=
pdPASS) {
rflags = (uint32_t)TtStatusError;
}
}
return (rflags);
}
uint32_t tt_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout) {
uint32_t rflags, nval;
uint32_t clear;
TickType_t t0, td, tout;
BaseType_t rval;
if (TT_IS_IRQ_MODE()) {
rflags = (uint32_t)TtStatusErrorISR;
} else if ((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
rflags = (uint32_t)TtStatusErrorParameter;
} else {
if ((options & TtFlagNoClear) == TtFlagNoClear) {
clear = 0U;
} else {
clear = flags;
}
rflags = 0U;
tout = timeout;
t0 = xTaskGetTickCount();
do {
rval = xTaskNotifyWaitIndexed(THREAD_NOTIFY_INDEX, 0, clear, &nval, tout);
if (rval == pdPASS) {
rflags &= flags;
rflags |= nval;
if ((options & TtFlagWaitAll) == TtFlagWaitAll) {
if ((flags & rflags) == flags) {
break;
} else {
if (timeout == 0U) {
rflags = (uint32_t)TtStatusErrorResource;
break;
}
}
} else {
if ((flags & rflags) != 0) {
break;
} else {
if (timeout == 0U) {
rflags = (uint32_t)TtStatusErrorResource;
break;
}
}
}
/* Update timeout */
td = xTaskGetTickCount() - t0;
if (td > tout) {
tout = 0;
} else {
tout -= td;
}
} else {
if (timeout == 0) {
rflags = (uint32_t)TtStatusErrorResource;
} else {
rflags = (uint32_t)TtStatusErrorTimeout;
}
}
} while (rval != pdFAIL);
}
/* Return flags before clearing */
return (rflags);
}
uint32_t tt_thread_enumerate(ThreadId* thread_array, uint32_t array_items) {
uint32_t i, count;
TaskStatus_t* task;
if (TT_IS_IRQ_MODE() || (thread_array == NULL) || (array_items == 0U)) {
count = 0U;
} else {
vTaskSuspendAll();
count = uxTaskGetNumberOfTasks();
task = pvPortMalloc(count * sizeof(TaskStatus_t));
if (task != NULL) {
count = uxTaskGetSystemState(task, count, NULL);
for (i = 0U; (i < count) && (i < array_items); i++) {
thread_array[i] = (ThreadId)task[i].xHandle;
}
count = i;
}
(void)xTaskResumeAll();
vPortFree(task);
}
return (count);
}
const char* tt_thread_get_name(ThreadId thread_id) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
const char* name;
if (TT_IS_IRQ_MODE() || (hTask == NULL)) {
name = NULL;
} else {
name = pcTaskGetName(hTask);
}
return (name);
}
const char* tt_thread_get_appid(ThreadId thread_id) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
const char* appid = "system";
if (!TT_IS_IRQ_MODE() && (hTask != NULL)) {
Thread* thread = (Thread*)pvTaskGetThreadLocalStoragePointer(hTask, 0);
if (thread) {
appid = thread->appid;
}
}
return (appid);
}
uint32_t tt_thread_get_stack_space(ThreadId thread_id) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
uint32_t sz;
if (TT_IS_IRQ_MODE() || (hTask == NULL)) {
sz = 0U;
} else {
sz = (uint32_t)(uxTaskGetStackHighWaterMark(hTask) * sizeof(StackType_t));
}
return (sz);
}
static size_t __tt_thread_stdout_write(Thread* thread, const char* data, size_t size) {
if (thread->output.write_callback != NULL) {
thread->output.write_callback(data, size);
} else {
TT_LOG_I(thread->name, "%s", data);
}
return size;
}
static int32_t __tt_thread_stdout_flush(Thread* thread) {
TtString* buffer = thread->output.buffer;
size_t size = tt_string_size(buffer);
if (size > 0) {
__tt_thread_stdout_write(thread, tt_string_get_cstr(buffer), size);
tt_string_reset(buffer);
}
return 0;
}
void tt_thread_suspend(ThreadId thread_id) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
vTaskSuspend(hTask);
}
void tt_thread_resume(ThreadId thread_id) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
if (TT_IS_IRQ_MODE()) {
xTaskResumeFromISR(hTask);
} else {
vTaskResume(hTask);
}
}
bool tt_thread_is_suspended(ThreadId thread_id) {
TaskHandle_t hTask = (TaskHandle_t)thread_id;
return eTaskGetState(hTask) == eSuspended;
}
+334
View File
@@ -0,0 +1,334 @@
#pragma once
#include "core_defines.h"
#include "core_types.h"
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/** ThreadState */
typedef enum {
ThreadStateStopped,
ThreadStateStarting,
ThreadStateRunning,
} ThreadState;
/** ThreadPriority */
typedef enum {
ThreadPriorityNone = 0, /**< Uninitialized, choose system default */
ThreadPriorityIdle = 1, /**< Idle priority */
ThreadPriorityLowest = 14, /**< Lowest */
ThreadPriorityLow = 15, /**< Low */
ThreadPriorityNormal = 16, /**< Normal */
ThreadPriorityHigh = 17, /**< High */
ThreadPriorityHighest = 18, /**< Highest */
ThreadPriorityIsr =
(TT_CONFIG_THREAD_MAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */
} ThreadPriority;
/** Thread anonymous structure */
typedef struct Thread Thread;
/** ThreadId proxy type to OS low level functions */
typedef void* ThreadId;
/** ThreadCallback Your callback to run in new thread
* @warning never use osThreadExit in Thread
*/
typedef int32_t (*ThreadCallback)(void* context);
/** Write to stdout callback
* @param data pointer to data
* @param size data size @warning your handler must consume everything
*/
typedef void (*ThreadStdoutWriteCallback)(const char* data, size_t size);
/** Thread state change callback called upon thread state change
* @param state new thread state
* @param context callback context
*/
typedef void (*ThreadStateCallback)(ThreadState state, void* context);
/** Allocate Thread
*
* @return Thread instance
*/
Thread* tt_thread_alloc();
/** Allocate Thread, shortcut version
*
* @param name
* @param stack_size
* @param callback
* @param context
* @return Thread*
*/
Thread* tt_thread_alloc_ex(
const char* name,
uint32_t stack_size,
ThreadCallback callback,
void* context
);
/** Release Thread
*
* @warning see tt_thread_join
*
* @param thread Thread instance
*/
void tt_thread_free(Thread* thread);
/** Set Thread name
*
* @param thread Thread instance
* @param name string
*/
void tt_thread_set_name(Thread* thread, const char* name);
/**
* @brief Set Thread appid
* Technically, it is like a "process id", but it is not a system-wide unique identifier.
* All threads spawned by the same app will have the same appid.
*
* @param thread
* @param appid
*/
void tt_thread_set_appid(Thread* thread, const char* appid);
/** Mark thread as service
* The service cannot be stopped or removed, and cannot exit from the thread body
*
* @param thread
*/
void tt_thread_mark_as_static(Thread* thread);
/** Set Thread stack size
*
* @param thread Thread instance
* @param stack_size stack size in bytes
*/
void tt_thread_set_stack_size(Thread* thread, size_t stack_size);
/** Set Thread callback
*
* @param thread Thread instance
* @param callback ThreadCallback, called upon thread run
*/
void tt_thread_set_callback(Thread* thread, ThreadCallback callback);
/** Set Thread context
*
* @param thread Thread instance
* @param context pointer to context for thread callback
*/
void tt_thread_set_context(Thread* thread, void* context);
/** Set Thread priority
*
* @param thread Thread instance
* @param priority ThreadPriority value
*/
void tt_thread_set_priority(Thread* thread, ThreadPriority priority);
/** Set current thread priority
*
* @param priority ThreadPriority value
*/
void tt_thread_set_current_priority(ThreadPriority priority);
/** Get current thread priority
*
* @return ThreadPriority value
*/
ThreadPriority tt_thread_get_current_priority();
/** Set Thread state change callback
*
* @param thread Thread instance
* @param callback state change callback
*/
void tt_thread_set_state_callback(Thread* thread, ThreadStateCallback callback);
/** Set Thread state change context
*
* @param thread Thread instance
* @param context pointer to context
*/
void tt_thread_set_state_context(Thread* thread, void* context);
/** Get Thread state
*
* @param thread Thread instance
*
* @return thread state from ThreadState
*/
ThreadState tt_thread_get_state(Thread* thread);
/** Start Thread
*
* @param thread Thread instance
*/
void tt_thread_start(Thread* thread);
/** Join Thread
*
* @warning Use this method only when CPU is not busy(Idle task receives
* control), otherwise it will wait forever.
*
* @param thread Thread instance
*
* @return bool
*/
bool tt_thread_join(Thread* thread);
/** Get FreeRTOS ThreadId for Thread instance
*
* @param thread Thread instance
*
* @return ThreadId or NULL
*/
ThreadId tt_thread_get_id(Thread* thread);
/** Enable heap tracing
*
* @param thread Thread instance
*/
void tt_thread_enable_heap_trace(Thread* thread);
/** Disable heap tracing
*
* @param thread Thread instance
*/
void tt_thread_disable_heap_trace(Thread* thread);
/** Get thread heap size
*
* @param thread Thread instance
*
* @return size in bytes
*/
size_t tt_thread_get_heap_size(Thread* thread);
/** Get thread return code
*
* @param thread Thread instance
*
* @return return code
*/
int32_t tt_thread_get_return_code(Thread* thread);
/** Thread related methods that doesn't involve Thread directly */
/** Get FreeRTOS ThreadId for current thread
*
* @param thread Thread instance
*
* @return ThreadId or NULL
*/
ThreadId tt_thread_get_current_id();
/** Get Thread instance for current thread
*
* @return pointer to Thread or NULL if this thread doesn't belongs to Tactility
*/
Thread* tt_thread_get_current();
/** Return control to scheduler */
void tt_thread_yield();
uint32_t tt_thread_flags_set(ThreadId thread_id, uint32_t flags);
uint32_t tt_thread_flags_clear(uint32_t flags);
uint32_t tt_thread_flags_get(void);
uint32_t tt_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout);
/**
* @brief Enumerate threads
*
* @param thread_array array of ThreadId, where thread ids will be stored
* @param array_items array size
* @return uint32_t threads count
*/
uint32_t tt_thread_enumerate(ThreadId* thread_array, uint32_t array_items);
/**
* @brief Get thread name
*
* @param thread_id
* @return const char* name or NULL
*/
const char* tt_thread_get_name(ThreadId thread_id);
/**
* @brief Get thread appid
*
* @param thread_id
* @return const char* appid
*/
const char* tt_thread_get_appid(ThreadId thread_id);
/**
* @brief Get thread stack watermark
*
* @param thread_id
* @return uint32_t
*/
uint32_t tt_thread_get_stack_space(ThreadId thread_id);
/** Get STDOUT callback for thead
*
* @return STDOUT callback
*/
ThreadStdoutWriteCallback tt_thread_get_stdout_callback();
/** Set STDOUT callback for thread
*
* @param callback callback or NULL to clear
*/
void tt_thread_set_stdout_callback(ThreadStdoutWriteCallback callback);
/** Write data to buffered STDOUT
*
* @param data input data
* @param size input data size
*
* @return size_t written data size
*/
size_t tt_thread_stdout_write(const char* data, size_t size);
/** Flush data to STDOUT
*
* @return int32_t error code
*/
int32_t tt_thread_stdout_flush();
/** Suspend thread
*
* @param thread_id thread id
*/
void tt_thread_suspend(ThreadId thread_id);
/** Resume thread
*
* @param thread_id thread id
*/
void tt_thread_resume(ThreadId thread_id);
/** Get thread suspended state
*
* @param thread_id thread id
* @return true if thread is suspended
*/
bool tt_thread_is_suspended(ThreadId thread_id);
bool tt_thread_mark_is_service(ThreadId thread_id);
#ifdef __cplusplus
}
#endif
+173
View File
@@ -0,0 +1,173 @@
#include "timer.h"
#include "check.h"
#include "kernel.h"
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
typedef struct {
TimerCallback func;
void* context;
} TimerCallback_t;
static void timer_callback(TimerHandle_t hTimer) {
TimerCallback_t* callb;
/* Retrieve pointer to callback function and context */
callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer);
/* Remove dynamic allocation flag */
callb = (TimerCallback_t*)((uint32_t)callb & ~1U);
if (callb != NULL) {
callb->func(callb->context);
}
}
Timer* tt_timer_alloc(TimerCallback func, TimerType type, void* context) {
tt_assert((tt_kernel_is_irq() == 0U) && (func != NULL));
TimerHandle_t hTimer;
TimerCallback_t* callb;
UBaseType_t reload;
hTimer = NULL;
/* Dynamic memory allocation is available: if memory for callback and */
/* its context is not provided, allocate it from dynamic memory pool */
callb = (TimerCallback_t*)malloc(sizeof(TimerCallback_t));
callb->func = func;
callb->context = context;
if (type == TimerTypeOnce) {
reload = pdFALSE;
} else {
reload = pdTRUE;
}
/* Store callback memory dynamic allocation flag */
callb = (TimerCallback_t*)((uint32_t)callb | 1U);
// TimerCallback function is always provided as a callback and is used to call application
// specified function with its context both stored in structure callb.
// TODO: should we use pointer to function or function directly as-is?
hTimer = xTimerCreate(NULL, portMAX_DELAY, reload, callb, timer_callback);
tt_check(hTimer);
/* Return timer ID */
return ((Timer*)hTimer);
}
void tt_timer_free(Timer* instance) {
tt_assert(!tt_kernel_is_irq());
tt_assert(instance);
TimerHandle_t hTimer = (TimerHandle_t)instance;
TimerCallback_t* callb;
callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer);
tt_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS);
while (tt_timer_is_running(instance)) tt_delay_tick(2);
if ((uint32_t)callb & 1U) {
/* Callback memory was allocated from dynamic pool, clear flag */
callb = (TimerCallback_t*)((uint32_t)callb & ~1U);
/* Return allocated memory to dynamic pool */
free(callb);
}
}
TtStatus tt_timer_start(Timer* instance, uint32_t ticks) {
tt_assert(!tt_kernel_is_irq());
tt_assert(instance);
tt_assert(ticks < portMAX_DELAY);
TimerHandle_t hTimer = (TimerHandle_t)instance;
TtStatus stat;
if (xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS) {
stat = TtStatusOk;
} else {
stat = TtStatusErrorResource;
}
/* Return execution status */
return (stat);
}
TtStatus tt_timer_restart(Timer* instance, uint32_t ticks) {
tt_assert(!tt_kernel_is_irq());
tt_assert(instance);
tt_assert(ticks < portMAX_DELAY);
TimerHandle_t hTimer = (TimerHandle_t)instance;
TtStatus stat;
if (xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS &&
xTimerReset(hTimer, portMAX_DELAY) == pdPASS) {
stat = TtStatusOk;
} else {
stat = TtStatusErrorResource;
}
/* Return execution status */
return (stat);
}
TtStatus tt_timer_stop(Timer* instance) {
tt_assert(!tt_kernel_is_irq());
tt_assert(instance);
TimerHandle_t hTimer = (TimerHandle_t)instance;
tt_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS);
return TtStatusOk;
}
uint32_t tt_timer_is_running(Timer* instance) {
tt_assert(!tt_kernel_is_irq());
tt_assert(instance);
TimerHandle_t hTimer = (TimerHandle_t)instance;
/* Return 0: not running, 1: running */
return (uint32_t)xTimerIsTimerActive(hTimer);
}
uint32_t tt_timer_get_expire_time(Timer* instance) {
tt_assert(!tt_kernel_is_irq());
tt_assert(instance);
TimerHandle_t hTimer = (TimerHandle_t)instance;
return (uint32_t)xTimerGetExpiryTime(hTimer);
}
void tt_timer_pending_callback(TimerPendigCallback callback, void* context, uint32_t arg) {
BaseType_t ret = pdFAIL;
if (tt_kernel_is_irq()) {
ret = xTimerPendFunctionCallFromISR(callback, context, arg, NULL);
} else {
ret = xTimerPendFunctionCall(callback, context, arg, TtWaitForever);
}
tt_check(ret == pdPASS);
}
void tt_timer_set_thread_priority(TimerThreadPriority priority) {
tt_assert(!tt_kernel_is_irq());
TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle();
tt_check(task_handle); // Don't call this method before timer task start
if (priority == TimerThreadPriorityNormal) {
vTaskPrioritySet(task_handle, configTIMER_TASK_PRIORITY);
} else if (priority == TimerThreadPriorityElevated) {
vTaskPrioritySet(task_handle, configMAX_PRIORITIES - 1);
} else {
tt_crash();
}
}
+106
View File
@@ -0,0 +1,106 @@
#pragma once
#include "core_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*TimerCallback)(void* context);
typedef enum {
TimerTypeOnce = 0, ///< One-shot timer.
TimerTypePeriodic = 1 ///< Repeating timer.
} TimerType;
typedef void Timer;
/** Allocate timer
*
* @param[in] func The callback function
* @param[in] type The timer type
* @param context The callback context
*
* @return The pointer to Timer instance
*/
Timer* tt_timer_alloc(TimerCallback func, TimerType type, void* context);
/** Free timer
*
* @param instance The pointer to Timer instance
*/
void tt_timer_free(Timer* instance);
/** Start timer
*
* @warning This is asynchronous call, real operation will happen as soon as
* timer service process this request.
*
* @param instance The pointer to Timer instance
* @param[in] ticks The interval in ticks
*
* @return The status.
*/
TtStatus tt_timer_start(Timer* instance, uint32_t ticks);
/** Restart timer with previous timeout value
*
* @warning This is asynchronous call, real operation will happen as soon as
* timer service process this request.
*
* @param instance The pointer to Timer instance
* @param[in] ticks The interval in ticks
*
* @return The status.
*/
TtStatus tt_timer_restart(Timer* instance, uint32_t ticks);
/** Stop timer
*
* @warning This is asynchronous call, real operation will happen as soon as
* timer service process this request.
*
* @param instance The pointer to Timer instance
*
* @return The status.
*/
TtStatus tt_timer_stop(Timer* instance);
/** Is timer running
*
* @warning This cal may and will return obsolete timer state if timer
* commands are still in the queue. Please read FreeRTOS timer
* documentation first.
*
* @param instance The pointer to Timer instance
*
* @return 0: not running, 1: running
*/
uint32_t tt_timer_is_running(Timer* instance);
/** Get timer expire time
*
* @param instance The Timer instance
*
* @return expire tick
*/
uint32_t tt_timer_get_expire_time(Timer* instance);
typedef void (*TimerPendigCallback)(void* context, uint32_t arg);
void tt_timer_pending_callback(TimerPendigCallback callback, void* context, uint32_t arg);
typedef enum {
TimerThreadPriorityNormal, /**< Lower then other threads */
TimerThreadPriorityElevated, /**< Same as other threads */
} TimerThreadPriority;
/** Set Timer thread priority
*
* @param[in] priority The priority
*/
void tt_timer_set_thread_priority(TimerThreadPriority priority);
#ifdef __cplusplus
}
#endif
+304
View File
@@ -0,0 +1,304 @@
#include "tt_string.h"
#include <m-string.h>
struct TtString {
string_t string;
};
#undef tt_string_alloc_set
#undef tt_string_set
#undef tt_string_cmp
#undef tt_string_cmpi
#undef tt_string_search
#undef tt_string_search_str
#undef tt_string_equal
#undef tt_string_replace
#undef tt_string_replace_str
#undef tt_string_replace_all
#undef tt_string_start_with
#undef tt_string_end_with
#undef tt_string_search_char
#undef tt_string_search_rchar
#undef tt_string_trim
#undef tt_string_cat
TtString* tt_string_alloc() {
TtString* string = malloc(sizeof(TtString));
string_init(string->string);
return string;
}
TtString* tt_string_alloc_set(const TtString* s) {
TtString* string = malloc(sizeof(TtString)); //-V799
string_init_set(string->string, s->string);
return string;
} //-V773
TtString* tt_string_alloc_set_str(const char cstr[]) {
TtString* string = malloc(sizeof(TtString)); //-V799
string_init_set(string->string, cstr);
return string;
} //-V773
TtString* tt_string_alloc_printf(const char format[], ...) {
va_list args;
va_start(args, format);
TtString* string = tt_string_alloc_vprintf(format, args);
va_end(args);
return string;
}
TtString* tt_string_alloc_vprintf(const char format[], va_list args) {
TtString* string = malloc(sizeof(TtString));
string_init_vprintf(string->string, format, args);
return string;
}
TtString* tt_string_alloc_move(TtString* s) {
TtString* string = malloc(sizeof(TtString));
string_init_move(string->string, s->string);
free(s);
return string;
}
void tt_string_free(TtString* s) {
string_clear(s->string);
free(s);
}
void tt_string_reserve(TtString* s, size_t alloc) {
string_reserve(s->string, alloc);
}
void tt_string_reset(TtString* s) {
string_reset(s->string);
}
void tt_string_swap(TtString* v1, TtString* v2) {
string_swap(v1->string, v2->string);
}
void tt_string_move(TtString* v1, TtString* v2) {
string_clear(v1->string);
string_init_move(v1->string, v2->string);
free(v2);
}
size_t tt_string_hash(const TtString* v) {
return string_hash(v->string);
}
char tt_string_get_char(const TtString* v, size_t index) {
return string_get_char(v->string, index);
}
const char* tt_string_get_cstr(const TtString* s) {
return string_get_cstr(s->string);
}
void tt_string_set(TtString* s, TtString* source) {
string_set(s->string, source->string);
}
void tt_string_set_str(TtString* s, const char cstr[]) {
string_set(s->string, cstr);
}
void tt_string_set_strn(TtString* s, const char str[], size_t n) {
string_set_strn(s->string, str, n);
}
void tt_string_set_char(TtString* s, size_t index, const char c) {
string_set_char(s->string, index, c);
}
int tt_string_cmp(const TtString* s1, const TtString* s2) {
return string_cmp(s1->string, s2->string);
}
int tt_string_cmp_str(const TtString* s1, const char str[]) {
return string_cmp(s1->string, str);
}
int tt_string_cmpi(const TtString* v1, const TtString* v2) {
return string_cmpi(v1->string, v2->string);
}
int tt_string_cmpi_str(const TtString* v1, const char p2[]) {
return string_cmpi_str(v1->string, p2);
}
size_t tt_string_search(const TtString* v, const TtString* needle, size_t start) {
return string_search(v->string, needle->string, start);
}
size_t tt_string_search_str(const TtString* v, const char needle[], size_t start) {
return string_search(v->string, needle, start);
}
bool tt_string_equal(const TtString* v1, const TtString* v2) {
return string_equal_p(v1->string, v2->string);
}
bool tt_string_equal_str(const TtString* v1, const char v2[]) {
return string_equal_p(v1->string, v2);
}
void tt_string_push_back(TtString* v, char c) {
string_push_back(v->string, c);
}
size_t tt_string_size(const TtString* s) {
return string_size(s->string);
}
int tt_string_printf(TtString* v, const char format[], ...) {
va_list args;
va_start(args, format);
int result = tt_string_vprintf(v, format, args);
va_end(args);
return result;
}
int tt_string_vprintf(TtString* v, const char format[], va_list args) {
return string_vprintf(v->string, format, args);
}
int tt_string_cat_printf(TtString* v, const char format[], ...) {
va_list args;
va_start(args, format);
int result = tt_string_cat_vprintf(v, format, args);
va_end(args);
return result;
}
int tt_string_cat_vprintf(TtString* v, const char format[], va_list args) {
TtString* string = tt_string_alloc();
int ret = tt_string_vprintf(string, format, args);
tt_string_cat(v, string);
tt_string_free(string);
return ret;
}
bool tt_string_empty(const TtString* v) {
return string_empty_p(v->string);
}
void tt_string_replace_at(TtString* v, size_t pos, size_t len, const char str2[]) {
string_replace_at(v->string, pos, len, str2);
}
size_t
tt_string_replace(TtString* string, TtString* needle, TtString* replace, size_t start) {
return string_replace(string->string, needle->string, replace->string, start);
}
size_t tt_string_replace_str(TtString* v, const char str1[], const char str2[], size_t start) {
return string_replace_str(v->string, str1, str2, start);
}
void tt_string_replace_all_str(TtString* v, const char str1[], const char str2[]) {
string_replace_all_str(v->string, str1, str2);
}
void tt_string_replace_all(TtString* v, const TtString* str1, const TtString* str2) {
string_replace_all(v->string, str1->string, str2->string);
}
bool tt_string_start_with(const TtString* v, const TtString* v2) {
return string_start_with_string_p(v->string, v2->string);
}
bool tt_string_start_with_str(const TtString* v, const char str[]) {
return string_start_with_str_p(v->string, str);
}
bool tt_string_end_with(const TtString* v, const TtString* v2) {
return string_end_with_string_p(v->string, v2->string);
}
bool tt_string_end_with_str(const TtString* v, const char str[]) {
return string_end_with_str_p(v->string, str);
}
size_t tt_string_search_char(const TtString* v, char c, size_t start) {
return string_search_char(v->string, c, start);
}
size_t tt_string_search_rchar(const TtString* v, char c, size_t start) {
return string_search_rchar(v->string, c, start);
}
void tt_string_left(TtString* v, size_t index) {
string_left(v->string, index);
}
void tt_string_right(TtString* v, size_t index) {
string_right(v->string, index);
}
void tt_string_mid(TtString* v, size_t index, size_t size) {
string_mid(v->string, index, size);
}
void tt_string_trim(TtString* v, const char charac[]) {
string_strim(v->string, charac);
}
void tt_string_cat(TtString* v, const TtString* v2) {
string_cat(v->string, v2->string);
}
void tt_string_cat_str(TtString* v, const char str[]) {
string_cat(v->string, str);
}
void tt_string_set_n(TtString* v, const TtString* ref, size_t offset, size_t length) {
string_set_n(v->string, ref->string, offset, length);
}
size_t tt_string_utf8_length(TtString* str) {
return string_length_u(str->string);
}
void tt_string_utf8_push(TtString* str, TtStringUnicodeValue u) {
string_push_u(str->string, u);
}
static m_str1ng_utf8_state_e tt_state_to_state(TtStringUTF8State state) {
switch (state) {
case TtStringUTF8StateStarting:
return M_STR1NG_UTF8_STARTING;
case TtStringUTF8StateDecoding1:
return M_STR1NG_UTF8_DECODING_1;
case TtStringUTF8StateDecoding2:
return M_STR1NG_UTF8_DECODING_2;
case TtStringUTF8StateDecoding3:
return M_STR1NG_UTF8_DECODING_3;
default:
return M_STR1NG_UTF8_ERROR;
}
}
static TtStringUTF8State state_to_tt_state(m_str1ng_utf8_state_e state) {
switch (state) {
case M_STR1NG_UTF8_STARTING:
return TtStringUTF8StateStarting;
case M_STR1NG_UTF8_DECODING_1:
return TtStringUTF8StateDecoding1;
case M_STR1NG_UTF8_DECODING_2:
return TtStringUTF8StateDecoding2;
case M_STR1NG_UTF8_DECODING_3:
return TtStringUTF8StateDecoding3;
default:
return TtStringUTF8StateError;
}
}
void tt_string_utf8_decode(char c, TtStringUTF8State* state, TtStringUnicodeValue* unicode) {
string_unicode_t m_u = *unicode;
m_str1ng_utf8_state_e m_state = tt_state_to_state(*state);
m_str1ng_utf8_decode(c, &m_state, &m_u);
*state = state_to_tt_state(m_state);
*unicode = m_u;
}
+738
View File
@@ -0,0 +1,738 @@
#pragma once
#include <m-core.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief String failure constant.
*/
#define TT_STRING_FAILURE ((size_t)-1)
/**
* @brief Tactility string primitive.
*/
typedef struct TtString TtString;
//---------------------------------------------------------------------------
// Constructors
//---------------------------------------------------------------------------
/**
* @brief Allocate new TtString.
* @return TtString*
*/
TtString* tt_string_alloc();
/**
* @brief Allocate new TtString and set it to string.
* Allocate & Set the string a to the string.
* @param source
* @return TtString*
*/
TtString* tt_string_alloc_set(const TtString* source);
/**
* @brief Allocate new TtString and set it to C string.
* Allocate & Set the string a to the C string.
* @param cstr_source
* @return TtString*
*/
TtString* tt_string_alloc_set_str(const char cstr_source[]);
/**
* @brief Allocate new TtString and printf to it.
* Initialize and set a string to the given formatted value.
* @param format
* @param ...
* @return TtString*
*/
TtString* tt_string_alloc_printf(const char format[], ...)
_ATTRIBUTE((__format__(__printf__, 1, 2)));
/**
* @brief Allocate new TtString and printf to it.
* Initialize and set a string to the given formatted value.
* @param format
* @param args
* @return TtString*
*/
TtString* tt_string_alloc_vprintf(const char format[], va_list args);
/**
* @brief Allocate new TtString and move source string content to it.
* Allocate the string, set it to the other one, and destroy the other one.
* @param source
* @return TtString*
*/
TtString* tt_string_alloc_move(TtString* source);
//---------------------------------------------------------------------------
// Destructors
//---------------------------------------------------------------------------
/**
* @brief Free TtString.
* @param string
*/
void tt_string_free(TtString* string);
//---------------------------------------------------------------------------
// String memory management
//---------------------------------------------------------------------------
/**
* @brief Reserve memory for string.
* Modify the string capacity to be able to handle at least 'alloc' characters (including final null char).
* @param string
* @param size
*/
void tt_string_reserve(TtString* string, size_t size);
/**
* @brief Reset string.
* Make the string empty.
* @param s
*/
void tt_string_reset(TtString* string);
/**
* @brief Swap two strings.
* Swap the two strings string_1 and string_2.
* @param string_1
* @param string_2
*/
void tt_string_swap(TtString* string_1, TtString* string_2);
/**
* @brief Move string_2 content to string_1.
* Set the string to the other one, and destroy the other one.
* @param string_1
* @param string_2
*/
void tt_string_move(TtString* string_1, TtString* string_2);
/**
* @brief Compute a hash for the string.
* @param string
* @return size_t
*/
size_t tt_string_hash(const TtString* string);
/**
* @brief Get string size (usually length, but not for UTF-8)
* @param string
* @return size_t
*/
size_t tt_string_size(const TtString* string);
/**
* @brief Check that string is empty or not
* @param string
* @return bool
*/
bool tt_string_empty(const TtString* string);
//---------------------------------------------------------------------------
// Getters
//---------------------------------------------------------------------------
/**
* @brief Get the character at the given index.
* Return the selected character of the string.
* @param string
* @param index
* @return char
*/
char tt_string_get_char(const TtString* string, size_t index);
/**
* @brief Return the string view a classic C string.
* @param string
* @return const char*
*/
const char* tt_string_get_cstr(const TtString* string);
//---------------------------------------------------------------------------
// Setters
//---------------------------------------------------------------------------
/**
* @brief Set the string to the other string.
* Set the string to the source string.
* @param string
* @param source
*/
void tt_string_set(TtString* string, TtString* source);
/**
* @brief Set the string to the other C string.
* Set the string to the source C string.
* @param string
* @param source
*/
void tt_string_set_str(TtString* string, const char source[]);
/**
* @brief Set the string to the n first characters of the C string.
* @param string
* @param source
* @param length
*/
void tt_string_set_strn(TtString* string, const char source[], size_t length);
/**
* @brief Set the character at the given index.
* @param string
* @param index
* @param c
*/
void tt_string_set_char(TtString* string, size_t index, const char c);
/**
* @brief Set the string to the n first characters of other one.
* @param string
* @param source
* @param offset
* @param length
*/
void tt_string_set_n(TtString* string, const TtString* source, size_t offset, size_t length);
/**
* @brief Format in the string the given printf format
* @param string
* @param format
* @param ...
* @return int
*/
int tt_string_printf(TtString* string, const char format[], ...)
_ATTRIBUTE((__format__(__printf__, 2, 3)));
/**
* @brief Format in the string the given printf format
* @param string
* @param format
* @param args
* @return int
*/
int tt_string_vprintf(TtString* string, const char format[], va_list args);
//---------------------------------------------------------------------------
// Appending
//---------------------------------------------------------------------------
/**
* @brief Append a character to the string.
* @param string
* @param c
*/
void tt_string_push_back(TtString* string, char c);
/**
* @brief Append a string to the string.
* Concatenate the string with the other string.
* @param string_1
* @param string_2
*/
void tt_string_cat(TtString* string_1, const TtString* string_2);
/**
* @brief Append a C string to the string.
* Concatenate the string with the C string.
* @param string_1
* @param cstring_2
*/
void tt_string_cat_str(TtString* string_1, const char cstring_2[]);
/**
* @brief Append to the string the formatted string of the given printf format.
* @param string
* @param format
* @param ...
* @return int
*/
int tt_string_cat_printf(TtString* string, const char format[], ...)
_ATTRIBUTE((__format__(__printf__, 2, 3)));
/**
* @brief Append to the string the formatted string of the given printf format.
* @param string
* @param format
* @param args
* @return int
*/
int tt_string_cat_vprintf(TtString* string, const char format[], va_list args);
//---------------------------------------------------------------------------
// Comparators
//---------------------------------------------------------------------------
/**
* @brief Compare two strings and return the sort order.
* @param string_1
* @param string_2
* @return int
*/
int tt_string_cmp(const TtString* string_1, const TtString* string_2);
/**
* @brief Compare string with C string and return the sort order.
* @param string_1
* @param cstring_2
* @return int
*/
int tt_string_cmp_str(const TtString* string_1, const char cstring_2[]);
/**
* @brief Compare two strings (case insensitive according to the current locale) and return the sort order.
* Note: doesn't work with UTF-8 strings.
* @param string_1
* @param string_2
* @return int
*/
int tt_string_cmpi(const TtString* string_1, const TtString* string_2);
/**
* @brief Compare string with C string (case insensitive according to the current locale) and return the sort order.
* Note: doesn't work with UTF-8 strings.
* @param string_1
* @param cstring_2
* @return int
*/
int tt_string_cmpi_str(const TtString* string_1, const char cstring_2[]);
//---------------------------------------------------------------------------
// Search
//---------------------------------------------------------------------------
/**
* @brief Search the first occurrence of the needle in the string from the position start.
* Return STRING_FAILURE if not found.
* By default, start is zero.
* @param string
* @param needle
* @param start
* @return size_t
*/
size_t tt_string_search(const TtString* string, const TtString* needle, size_t start);
/**
* @brief Search the first occurrence of the needle in the string from the position start.
* Return STRING_FAILURE if not found.
* @param string
* @param needle
* @param start
* @return size_t
*/
size_t tt_string_search_str(const TtString* string, const char needle[], size_t start);
/**
* @brief Search for the position of the character c from the position start (include) in the string.
* Return STRING_FAILURE if not found.
* By default, start is zero.
* @param string
* @param c
* @param start
* @return size_t
*/
size_t tt_string_search_char(const TtString* string, char c, size_t start);
/**
* @brief Reverse search for the position of the character c from the position start (include) in the string.
* Return STRING_FAILURE if not found.
* By default, start is zero.
* @param string
* @param c
* @param start
* @return size_t
*/
size_t tt_string_search_rchar(const TtString* string, char c, size_t start);
//---------------------------------------------------------------------------
// Equality
//---------------------------------------------------------------------------
/**
* @brief Test if two strings are equal.
* @param string_1
* @param string_2
* @return bool
*/
bool tt_string_equal(const TtString* string_1, const TtString* string_2);
/**
* @brief Test if the string is equal to the C string.
* @param string_1
* @param cstring_2
* @return bool
*/
bool tt_string_equal_str(const TtString* string_1, const char cstring_2[]);
//---------------------------------------------------------------------------
// Replace
//---------------------------------------------------------------------------
/**
* @brief Replace in the string the sub-string at position 'pos' for 'len' bytes into the C string 'replace'.
* @param string
* @param pos
* @param len
* @param replace
*/
void tt_string_replace_at(TtString* string, size_t pos, size_t len, const char replace[]);
/**
* @brief Replace a string 'needle' to string 'replace' in a string from 'start' position.
* By default, start is zero.
* Return STRING_FAILURE if 'needle' not found or replace position.
* @param string
* @param needle
* @param replace
* @param start
* @return size_t
*/
size_t
tt_string_replace(TtString* string, TtString* needle, TtString* replace, size_t start);
/**
* @brief Replace a C string 'needle' to C string 'replace' in a string from 'start' position.
* By default, start is zero.
* Return STRING_FAILURE if 'needle' not found or replace position.
* @param string
* @param needle
* @param replace
* @param start
* @return size_t
*/
size_t tt_string_replace_str(
TtString* string,
const char needle[],
const char replace[],
size_t start
);
/**
* @brief Replace all occurrences of 'needle' string into 'replace' string.
* @param string
* @param needle
* @param replace
*/
void tt_string_replace_all(
TtString* string,
const TtString* needle,
const TtString* replace
);
/**
* @brief Replace all occurrences of 'needle' C string into 'replace' C string.
* @param string
* @param needle
* @param replace
*/
void tt_string_replace_all_str(TtString* string, const char needle[], const char replace[]);
//---------------------------------------------------------------------------
// Start / End tests
//---------------------------------------------------------------------------
/**
* @brief Test if the string starts with the given string.
* @param string
* @param start
* @return bool
*/
bool tt_string_start_with(const TtString* string, const TtString* start);
/**
* @brief Test if the string starts with the given C string.
* @param string
* @param start
* @return bool
*/
bool tt_string_start_with_str(const TtString* string, const char start[]);
/**
* @brief Test if the string ends with the given string.
* @param string
* @param end
* @return bool
*/
bool tt_string_end_with(const TtString* string, const TtString* end);
/**
* @brief Test if the string ends with the given C string.
* @param string
* @param end
* @return bool
*/
bool tt_string_end_with_str(const TtString* string, const char end[]);
//---------------------------------------------------------------------------
// Trim
//---------------------------------------------------------------------------
/**
* @brief Trim the string left to the first 'index' bytes.
* @param string
* @param index
*/
void tt_string_left(TtString* string, size_t index);
/**
* @brief Trim the string right from the 'index' position to the last position.
* @param string
* @param index
*/
void tt_string_right(TtString* string, size_t index);
/**
* @brief Trim the string from position index to size bytes.
* See also tt_string_set_n.
* @param string
* @param index
* @param size
*/
void tt_string_mid(TtString* string, size_t index, size_t size);
/**
* @brief Trim a string from the given set of characters (default is " \n\r\t").
* @param string
* @param chars
*/
void tt_string_trim(TtString* string, const char chars[]);
//---------------------------------------------------------------------------
// UTF8
//---------------------------------------------------------------------------
/**
* @brief An unicode value.
*/
typedef unsigned int TtStringUnicodeValue;
/**
* @brief Compute the length in UTF8 characters in the string.
* @param string
* @return size_t
*/
size_t tt_string_utf8_length(TtString* string);
/**
* @brief Push unicode into string, encoding it in UTF8.
* @param string
* @param unicode
*/
void tt_string_utf8_push(TtString* string, TtStringUnicodeValue unicode);
/**
* @brief State of the UTF8 decoding machine state.
*/
typedef enum {
TtStringUTF8StateStarting,
TtStringUTF8StateDecoding1,
TtStringUTF8StateDecoding2,
TtStringUTF8StateDecoding3,
TtStringUTF8StateError
} TtStringUTF8State;
/**
* @brief Main generic UTF8 decoder.
* It takes a character, and the previous state and the previous value of the unicode value.
* It updates the state and the decoded unicode value.
* A decoded unicode encoded value is valid only when the state is TtStringUTF8StateStarting.
* @param c
* @param state
* @param unicode
*/
void tt_string_utf8_decode(char c, TtStringUTF8State* state, TtStringUnicodeValue* unicode);
//---------------------------------------------------------------------------
// Lasciate ogne speranza, voi chentrate
//---------------------------------------------------------------------------
/**
*
* Select either the string function or the str function depending on
* the b operand to the function.
* func1 is the string function / func2 is the str function.
*/
/**
* @brief Select for 1 argument
*/
#define TT_STRING_SELECT1(func1, func2, a) \
_Generic((a), char*: func2, const char*: func2, TtString*: func1, const TtString*: func1)(a)
/**
* @brief Select for 2 arguments
*/
#define TT_STRING_SELECT2(func1, func2, a, b) \
_Generic((b), char*: func2, const char*: func2, TtString*: func1, const TtString*: func1)(a, b)
/**
* @brief Select for 3 arguments
*/
#define TT_STRING_SELECT3(func1, func2, a, b, c) \
_Generic((b), char*: func2, const char*: func2, TtString*: func1, const TtString*: func1)(a, b, c)
/**
* @brief Select for 4 arguments
*/
#define TT_STRING_SELECT4(func1, func2, a, b, c, d) \
_Generic((b), char*: func2, const char*: func2, TtString*: func1, const TtString*: func1)(a, b, c, d)
/**
* @brief Allocate new TtString and set it content to string (or C string).
* ([c]string)
*/
#define tt_string_alloc_set(a) \
TT_STRING_SELECT1(tt_string_alloc_set, tt_string_alloc_set_str, a)
/**
* @brief Set the string content to string (or C string).
* (string, [c]string)
*/
#define tt_string_set(a, b) TT_STRING_SELECT2(tt_string_set, tt_string_set_str, a, b)
/**
* @brief Compare string with string (or C string) and return the sort order.
* Note: doesn't work with UTF-8 strings.
* (string, [c]string)
*/
#define tt_string_cmp(a, b) TT_STRING_SELECT2(tt_string_cmp, tt_string_cmp_str, a, b)
/**
* @brief Compare string with string (or C string) (case insensitive according to the current locale) and return the sort order.
* Note: doesn't work with UTF-8 strings.
* (string, [c]string)
*/
#define tt_string_cmpi(a, b) TT_STRING_SELECT2(tt_string_cmpi, tt_string_cmpi_str, a, b)
/**
* @brief Test if the string is equal to the string (or C string).
* (string, [c]string)
*/
#define tt_string_equal(a, b) TT_STRING_SELECT2(tt_string_equal, tt_string_equal_str, a, b)
/**
* @brief Replace all occurrences of string into string (or C string to another C string) in a string.
* (string, [c]string, [c]string)
*/
#define tt_string_replace_all(a, b, c) \
TT_STRING_SELECT3(tt_string_replace_all, tt_string_replace_all_str, a, b, c)
/**
* @brief Search for a string (or C string) in a string
* (string, [c]string[, start=0])
*/
#define tt_string_search(...) \
M_APPLY( \
TT_STRING_SELECT3, \
tt_string_search, \
tt_string_search_str, \
M_DEFAULT_ARGS(3, (0), __VA_ARGS__) \
)
/**
* @brief Search for a C string in a string
* (string, cstring[, start=0])
*/
#define tt_string_search_str(...) tt_string_search_str(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
/**
* @brief Test if the string starts with the given string (or C string).
* (string, [c]string)
*/
#define tt_string_start_with(a, b) \
TT_STRING_SELECT2(tt_string_start_with, tt_string_start_with_str, a, b)
/**
* @brief Test if the string ends with the given string (or C string).
* (string, [c]string)
*/
#define tt_string_end_with(a, b) \
TT_STRING_SELECT2(tt_string_end_with, tt_string_end_with_str, a, b)
/**
* @brief Append a string (or C string) to the string.
* (string, [c]string)
*/
#define tt_string_cat(a, b) TT_STRING_SELECT2(tt_string_cat, tt_string_cat_str, a, b)
/**
* @brief Trim a string from the given set of characters (default is " \n\r\t").
* (string[, set=" \n\r\t"])
*/
#define tt_string_trim(...) tt_string_trim(M_DEFAULT_ARGS(2, (" \n\r\t"), __VA_ARGS__))
/**
* @brief Search for a character in a string.
* (string, character[, start=0])
*/
#define tt_string_search_char(...) tt_string_search_char(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
/**
* @brief Reverse Search for a character in a string.
* (string, character[, start=0])
*/
#define tt_string_search_rchar(...) tt_string_search_rchar(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
/**
* @brief Replace a string to another string (or C string to another C string) in a string.
* (string, [c]string, [c]string[, start=0])
*/
#define tt_string_replace(...) \
M_APPLY( \
TT_STRING_SELECT4, \
tt_string_replace, \
tt_string_replace_str, \
M_DEFAULT_ARGS(4, (0), __VA_ARGS__) \
)
/**
* @brief Replace a C string to another C string in a string.
* (string, cstring, cstring[, start=0])
*/
#define tt_string_replace_str(...) tt_string_replace_str(M_DEFAULT_ARGS(4, (0), __VA_ARGS__))
/**
* @brief INIT OPLIST for TtString.
*/
#define F_STR_INIT(a) ((a) = tt_string_alloc())
/**
* @brief INIT SET OPLIST for TtString.
*/
#define F_STR_INIT_SET(a, b) ((a) = tt_string_alloc_set(b))
/**
* @brief INIT MOVE OPLIST for TtString.
*/
#define F_STR_INIT_MOVE(a, b) ((a) = tt_string_alloc_move(b))
/**
* @brief OPLIST for TtString.
*/
#define TT_STRING_OPLIST \
(INIT(F_STR_INIT), \
INIT_SET(F_STR_INIT_SET), \
SET(tt_string_set), \
INIT_MOVE(F_STR_INIT_MOVE), \
MOVE(tt_string_move), \
SWAP(tt_string_swap), \
RESET(tt_string_reset), \
EMPTY_P(tt_string_empty), \
CLEAR(tt_string_free), \
HASH(tt_string_hash), \
EQUAL(tt_string_equal), \
CMP(tt_string_cmp), \
TYPE(TtString*))
#ifdef __cplusplus
}
#endif