TactilityCore improvements (#187)

FreeRTOS handles were stored plainly and they were deleted in the destructor of classes.
This meant that if a class were to be copied, the destructor would be called twice on the same handles and lead to double-free.

Seha on Discord suggested to fix this by using `std::unique_ptr` with a custom deletion function.

The changes affect:
- Thread
- Semaphore
- Mutex
- StreamBuffer
- Timer
- MessageQueue
- EventFlag

Thread  changes:
- Removal of the hack with the `Data` struct
- Thread's main body is now just a private static function inside the class.
- The C functions were relocated to static class members

PubSub changes:
- Refactored pubsub into class
- Renamed files to `PubSub` instead of `Pubsub`
- `PubSubSubscription` is now a private inner struct and `PubSub` only exposes `SubscriptionHandle`

Lockable, ScopedLockable, Mutex:
- Added `lock()` method that locks indefinitely
- Remove deprecated `acquire()` and `release()` methods
- Removed `TtWaitForever` in favour of `portMAX_DELAY`
This commit is contained in:
Ken Van Hoeylandt
2025-01-25 17:29:11 +01:00
committed by GitHub
parent c2edbad0fb
commit 686f7cce83
60 changed files with 711 additions and 831 deletions
-42
View File
@@ -1,42 +0,0 @@
#pragma once
#include <cstdint>
#include "TactilityCoreConfig.h"
namespace tt {
[[deprecated("Using this is poor software design in most scenarios")]]
typedef enum {
TtWaitForever = 0xFFFFFFFFU,
} TtWait;
[[deprecated("Define flags as needed")]]
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;
[[deprecated("Use bool or specific type")]]
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;
} // namespace
+5 -9
View File
@@ -5,17 +5,13 @@
namespace tt {
#define TAG "dispatcher"
#define BACKPRESSURE_WARNING_COUNT 100
#define WAIT_FLAG 1
Dispatcher::Dispatcher() :
mutex(Mutex::Type::Normal)
{}
#define BACKPRESSURE_WARNING_COUNT ((EventBits_t)100)
#define WAIT_FLAG ((EventBits_t)1)
Dispatcher::~Dispatcher() {
// Wait for Mutex usage
mutex.acquire(TtWaitForever);
mutex.release();
mutex.lock();
mutex.unlock();
}
void Dispatcher::dispatch(Function function, std::shared_ptr<void> context) {
@@ -37,7 +33,7 @@ void Dispatcher::dispatch(Function function, std::shared_ptr<void> context) {
uint32_t Dispatcher::consume(TickType_t timeout) {
// Wait for signal and clear
TickType_t start_ticks = kernel::getTicks();
if (eventFlag.wait(WAIT_FLAG, TtFlagWaitAny, timeout) == WAIT_FLAG) {
if (eventFlag.wait(WAIT_FLAG, EventFlag::WaitAny, timeout)) {
eventFlag.clear(WAIT_FLAG);
} else {
return 0;
+2 -2
View File
@@ -43,7 +43,7 @@ private:
public:
explicit Dispatcher();
explicit Dispatcher() = default;
~Dispatcher();
/**
@@ -58,7 +58,7 @@ public:
* @param[in] timeout the ticks to wait for a message
* @return the amount of messages that were consumed
*/
uint32_t consume(TickType_t timeout);
uint32_t consume(TickType_t timeout = portMAX_DELAY);
};
} // namespace
+20 -20
View File
@@ -8,15 +8,15 @@
namespace tt {
EventFlag::EventFlag() {
EventFlag::EventFlag() :
handle(xEventGroupCreate())
{
assert(!TT_IS_IRQ_MODE());
handle = xEventGroupCreate();
tt_check(handle);
}
EventFlag::~EventFlag() {
assert(!TT_IS_IRQ_MODE());
vEventGroupDelete(handle);
}
uint32_t EventFlag::set(uint32_t flags) const {
@@ -28,14 +28,14 @@ uint32_t EventFlag::set(uint32_t flags) const {
if (TT_IS_IRQ_MODE()) {
yield = pdFALSE;
if (xEventGroupSetBitsFromISR(handle, (EventBits_t)flags, &yield) == pdFAIL) {
rflags = (uint32_t)TtFlagErrorResource;
if (xEventGroupSetBitsFromISR(handle.get(), (EventBits_t)flags, &yield) == pdFAIL) {
rflags = (uint32_t)ErrorResource;
} else {
rflags = flags;
portYIELD_FROM_ISR(yield);
}
} else {
rflags = xEventGroupSetBits(handle, (EventBits_t)flags);
rflags = xEventGroupSetBits(handle.get(), (EventBits_t)flags);
}
/* Return event flags after setting */
@@ -48,10 +48,10 @@ uint32_t EventFlag::clear(uint32_t flags) const {
uint32_t rflags;
if (TT_IS_IRQ_MODE()) {
rflags = xEventGroupGetBitsFromISR(handle);
rflags = xEventGroupGetBitsFromISR(handle.get());
if (xEventGroupClearBitsFromISR(handle, (EventBits_t)flags) == pdFAIL) {
rflags = (uint32_t)TtStatusErrorResource;
if (xEventGroupClearBitsFromISR(handle.get(), (EventBits_t)flags) == pdFAIL) {
rflags = (uint32_t)ErrorResource;
} 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. */
@@ -59,7 +59,7 @@ uint32_t EventFlag::clear(uint32_t flags) const {
portYIELD_FROM_ISR(pdTRUE);
}
} else {
rflags = xEventGroupClearBits(handle, (EventBits_t)flags);
rflags = xEventGroupClearBits(handle.get(), (EventBits_t)flags);
}
/* Return event flags before clearing */
@@ -70,9 +70,9 @@ uint32_t EventFlag::get() const {
uint32_t rflags;
if (TT_IS_IRQ_MODE()) {
rflags = xEventGroupGetBitsFromISR(handle);
rflags = xEventGroupGetBitsFromISR(handle.get());
} else {
rflags = xEventGroupGetBits(handle);
rflags = xEventGroupGetBits(handle.get());
}
/* Return current event flags */
@@ -91,40 +91,40 @@ uint32_t EventFlag::wait(
BaseType_t exit_clear;
uint32_t rflags;
if (options & TtFlagWaitAll) {
if (options & WaitAll) {
wait_all = pdTRUE;
} else {
wait_all = pdFAIL;
}
if (options & TtFlagNoClear) {
if (options & NoClear) {
exit_clear = pdFAIL;
} else {
exit_clear = pdTRUE;
}
rflags = xEventGroupWaitBits(
handle,
handle.get(),
(EventBits_t)flags,
exit_clear,
wait_all,
(TickType_t)timeout
);
if (options & TtFlagWaitAll) {
if (options & WaitAll) {
if ((flags & rflags) != flags) {
if (timeout > 0U) {
rflags = (uint32_t)TtStatusErrorTimeout;
rflags = (uint32_t)ErrorTimeout;
} else {
rflags = (uint32_t)TtStatusErrorResource;
rflags = (uint32_t)ErrorResource;
}
}
} else {
if ((flags & rflags) == 0U) {
if (timeout > 0U) {
rflags = (uint32_t)TtStatusErrorTimeout;
rflags = (uint32_t)ErrorTimeout;
} else {
rflags = (uint32_t)TtStatusErrorResource;
rflags = (uint32_t)ErrorResource;
}
}
}
+27 -4
View File
@@ -1,7 +1,7 @@
#pragma once
#include "CoreTypes.h"
#include "RtosCompatEventGroups.h"
#include <memory>
namespace tt {
@@ -10,17 +10,40 @@ namespace tt {
*/
class EventFlag {
private:
EventGroupHandle_t handle;
struct EventGroupHandleDeleter {
void operator()(EventGroupHandle_t handleToDelete) {
vEventGroupDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<EventGroupHandle_t>, EventGroupHandleDeleter> handle;
public:
EventFlag();
~EventFlag();
enum Flag {
WaitAny = 0x00000000U, ///< Wait for any flag (default).
WaitAll = 0x00000001U, ///< Wait for all flags.
NoClear = 0x00000002U, ///< Do not clear flags which have been specified to wait for.
Error = 0x80000000U, ///< Error indicator.
ErrorUnknown = 0xFFFFFFFFU, ///< TtStatusError (-1).
ErrorTimeout = 0xFFFFFFFEU, ///< TtStatusErrorTimeout (-2).
ErrorResource = 0xFFFFFFFDU, ///< TtStatusErrorResource (-3).
ErrorParameter = 0xFFFFFFFCU, ///< TtStatusErrorParameter (-4).
ErrorISR = 0xFFFFFFFAU, ///< TtStatusErrorISR (-6).
};
uint32_t set(uint32_t flags) const;
uint32_t clear(uint32_t flags) const;
uint32_t get() const;
uint32_t wait(
uint32_t flags,
uint32_t options = TtFlagWaitAny,
uint32_t timeout = TtWaitForever
uint32_t options = WaitAny,
uint32_t timeout = portMAX_DELAY
) const;
};
+6
View File
@@ -3,6 +3,8 @@
#include "Check.h"
#include "RtosCompat.h"
#include <memory>
#include <functional>
#include <algorithm>
namespace tt {
@@ -15,6 +17,8 @@ public:
virtual bool lock(TickType_t timeoutTicks) const = 0;
virtual bool lock() const { return lock(portMAX_DELAY); }
virtual bool unlock() const = 0;
std::unique_ptr<ScopedLockableUsage> scoped() const;
@@ -43,6 +47,8 @@ public:
return lockable.lock(timeout);
}
bool lock() const override { return lock(portMAX_DELAY); }
bool unlock() const override {
return lockable.unlock();
}
+25 -24
View File
@@ -4,63 +4,64 @@
namespace tt {
MessageQueue::MessageQueue(uint32_t capacity, uint32_t msg_size) {
assert(!TT_IS_ISR() && (capacity > 0U) && (msg_size > 0U));
queue_handle = xQueueCreate(capacity, msg_size);
tt_check(queue_handle);
static inline QueueHandle_t createQueue(uint32_t capacity, uint32_t messageSize) {
assert(!TT_IS_ISR() && (capacity > 0U) && (messageSize > 0U));
return xQueueCreate(capacity, messageSize);
}
MessageQueue::MessageQueue(uint32_t capacity, uint32_t messageSize) : handle(createQueue(capacity, messageSize)) {
tt_check(handle != nullptr);
}
MessageQueue::~MessageQueue() {
assert(!TT_IS_ISR());
vQueueDelete(queue_handle);
}
bool MessageQueue::put(const void* message, uint32_t timeout) {
bool MessageQueue::put(const void* message, TickType_t timeout) {
bool result = true;
BaseType_t yield;
if (TT_IS_ISR()) {
if ((queue_handle == nullptr) || (message == nullptr) || (timeout != 0U)) {
if ((handle == nullptr) || (message == nullptr) || (timeout != 0U)) {
result = false;
} else {
yield = pdFALSE;
if (xQueueSendToBackFromISR(queue_handle, message, &yield) != pdTRUE) {
if (xQueueSendToBackFromISR(handle.get(), message, &yield) != pdTRUE) {
result = false;
} else {
portYIELD_FROM_ISR(yield);
}
}
} else if ((queue_handle == nullptr) || (message == nullptr)) {
} else if ((handle == nullptr) || (message == nullptr)) {
result = false;
} else if (xQueueSendToBack(queue_handle, message, (TickType_t)timeout) != pdPASS) {
} else if (xQueueSendToBack(handle.get(), message, (TickType_t)timeout) != pdPASS) {
result = false;
}
return result;
}
bool MessageQueue::get(void* msg_ptr, uint32_t timeout_ticks) {
bool MessageQueue::get(void* msg_ptr, TickType_t timeout) {
bool result = true;
BaseType_t yield;
if (TT_IS_ISR()) {
if ((queue_handle == nullptr) || (msg_ptr == nullptr) || (timeout_ticks != 0U)) {
if ((handle == nullptr) || (msg_ptr == nullptr) || (timeout != 0U)) {
result = false;
} else {
yield = pdFALSE;
if (xQueueReceiveFromISR(queue_handle, msg_ptr, &yield) != pdPASS) {
if (xQueueReceiveFromISR(handle.get(), msg_ptr, &yield) != pdPASS) {
result = false;
} else {
portYIELD_FROM_ISR(yield);
}
}
} else {
if ((queue_handle == nullptr) || (msg_ptr == nullptr)) {
if ((handle == nullptr) || (msg_ptr == nullptr)) {
result = false;
} else if (xQueueReceive(queue_handle, msg_ptr, (TickType_t)timeout_ticks) != pdPASS) {
} else if (xQueueReceive(handle.get(), msg_ptr, (TickType_t)timeout) != pdPASS) {
result = false;
}
}
@@ -69,7 +70,7 @@ bool MessageQueue::get(void* msg_ptr, uint32_t timeout_ticks) {
}
uint32_t MessageQueue::getCapacity() const {
auto* mq = (StaticQueue_t*)(queue_handle);
auto* mq = (StaticQueue_t*)(handle.get());
if (mq == nullptr) {
return 0U;
} else {
@@ -78,7 +79,7 @@ uint32_t MessageQueue::getCapacity() const {
}
uint32_t MessageQueue::getMessageSize() const {
auto* mq = (StaticQueue_t*)(queue_handle);
auto* mq = (StaticQueue_t*)(handle.get());
if (mq == nullptr) {
return 0U;
} else {
@@ -89,12 +90,12 @@ uint32_t MessageQueue::getMessageSize() const {
uint32_t MessageQueue::getCount() const {
UBaseType_t count;
if (queue_handle == nullptr) {
if (handle == nullptr) {
count = 0U;
} else if (TT_IS_ISR()) {
count = uxQueueMessagesWaitingFromISR(queue_handle);
count = uxQueueMessagesWaitingFromISR(handle.get());
} else {
count = uxQueueMessagesWaiting(queue_handle);
count = uxQueueMessagesWaiting(handle.get());
}
/* Return number of queued messages */
@@ -102,7 +103,7 @@ uint32_t MessageQueue::getCount() const {
}
uint32_t MessageQueue::getSpace() const {
auto* mq = (StaticQueue_t*)(queue_handle);
auto* mq = (StaticQueue_t*)(handle.get());
uint32_t space;
uint32_t isrm;
@@ -124,10 +125,10 @@ uint32_t MessageQueue::getSpace() const {
bool MessageQueue::reset() {
tt_check(!TT_IS_ISR());
if (queue_handle == nullptr) {
if (handle == nullptr) {
return false;
} else {
xQueueReset(queue_handle);
xQueueReset(handle.get());
return true;
}
}
+13 -6
View File
@@ -7,7 +7,7 @@
*/
#pragma once
#include "CoreTypes.h"
#include <memory>
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
@@ -25,7 +25,14 @@ namespace tt {
*/
class MessageQueue {
private:
QueueHandle_t queue_handle;
struct QueueHandleDeleter {
void operator()(QueueHandle_t handleToDelete) {
vQueueDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<QueueHandle_t>, QueueHandleDeleter> handle;
public:
/** Allocate message queue
@@ -39,17 +46,17 @@ public:
/** Post a message to the queue.
* The message is queued by copy, not by reference.
* @param[in] message A pointer to a message. The message will be copied into a buffer.
* @param[in] timeoutTicks
* @param[in] timeout
* @return success result
*/
bool put(const void* message, uint32_t timeoutTicks);
bool put(const void* message, TickType_t timeout);
/** Get message from queue
* @param[out] message A pointer to an already allocated message object
* @param[in] timeoutTicks
* @param[in] timeout
* @return success result
*/
bool get(void* message, uint32_t timeoutTicks);
bool get(void* message, TickType_t timeout);
/**
* @return The maximum amount of messages that can be in the queue at any given time.
+21 -53
View File
@@ -22,88 +22,56 @@ namespace tt {
#define tt_mutex_info(mutex, text)
#endif
Mutex::Mutex(Type type) : type(type) {
tt_mutex_info(data, "alloc");
static inline SemaphoreHandle_t createSemaphoreHandle(Mutex::Type type) {
switch (type) {
case Type::Normal:
semaphore = xSemaphoreCreateMutex();
break;
case Type::Recursive:
semaphore = xSemaphoreCreateRecursiveMutex();
break;
case Mutex::Type::Normal:
return xSemaphoreCreateMutex();
case Mutex::Type::Recursive:
return xSemaphoreCreateRecursiveMutex();
default:
tt_crash("Mutex type unknown/corrupted");
}
assert(semaphore != nullptr);
}
Mutex::~Mutex() {
assert(!TT_IS_IRQ_MODE());
vSemaphoreDelete(semaphore);
semaphore = nullptr; // If the mutex is used after release, this might help debugging
Mutex::Mutex(Type type) : handle(createSemaphoreHandle(type)), type(type) {
tt_mutex_info(data, "alloc");
assert(handle != nullptr);
}
TtStatus Mutex::acquire(TickType_t timeout) const {
bool Mutex::lock(TickType_t timeout) const {
assert(!TT_IS_IRQ_MODE());
assert(semaphore != nullptr);
assert(handle != nullptr);
tt_mutex_info(mutex, "acquire");
switch (type) {
case Type::Normal:
if (xSemaphoreTake(semaphore, timeout) != pdPASS) {
if (timeout != 0U) {
return TtStatusErrorTimeout;
} else {
return TtStatusErrorResource;
}
} else {
return TtStatusOk;
}
return xSemaphoreTake(handle.get(), timeout) == pdPASS;
case Type::Recursive:
if (xSemaphoreTakeRecursive(semaphore, timeout) != pdPASS) {
if (timeout != 0U) {
return TtStatusErrorTimeout;
} else {
return TtStatusErrorResource;
}
} else {
return TtStatusOk;
}
return xSemaphoreTakeRecursive(handle.get(), timeout) == pdPASS;
default:
tt_crash("mutex type unknown/corrupted");
tt_crash();
}
}
TtStatus Mutex::release() const {
bool Mutex::unlock() const {
assert(!TT_IS_IRQ_MODE());
assert(semaphore);
assert(handle != nullptr);
tt_mutex_info(mutex, "release");
switch (type) {
case Type::Normal: {
if (xSemaphoreGive(semaphore) != pdPASS) {
return TtStatusErrorResource;
} else {
return TtStatusOk;
}
}
case Type::Normal:
return xSemaphoreGive(handle.get()) == pdPASS;
case Type::Recursive:
if (xSemaphoreGiveRecursive(semaphore) != pdPASS) {
return TtStatusErrorResource;
} else {
return TtStatusOk;
}
return xSemaphoreGiveRecursive(handle.get()) == pdPASS;
default:
tt_crash("mutex type unknown/corrupted");
tt_crash();
}
}
ThreadId Mutex::getOwner() const {
assert(!TT_IS_IRQ_MODE());
assert(semaphore);
return (ThreadId)xSemaphoreGetMutexHolder(semaphore);
assert(handle != nullptr);
return (ThreadId)xSemaphoreGetMutexHolder(handle.get());
}
} // namespace
+18 -18
View File
@@ -4,7 +4,6 @@
*/
#pragma once
#include "CoreTypes.h"
#include "Thread.h"
#include "RtosCompatSemaphore.h"
#include "Check.h"
@@ -15,9 +14,9 @@ namespace tt {
/**
* Wrapper for FreeRTOS xSemaphoreCreateMutex and xSemaphoreCreateRecursiveMutex
* Can be used in IRQ mode (within ISR context)
* Cannot be used in IRQ mode (within ISR context)
*/
class Mutex : public Lockable {
class Mutex final : public Lockable {
public:
@@ -28,35 +27,36 @@ public:
private:
SemaphoreHandle_t semaphore;
struct SemaphoreHandleDeleter {
void operator()(QueueHandle_t handleToDelete) {
assert(!TT_IS_IRQ_MODE());
vSemaphoreDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<QueueHandle_t>, SemaphoreHandleDeleter> handle;
Type type;
public:
explicit Mutex(Type type = Type::Normal);
~Mutex() override;
/** Attempt to lock the mutex. Blocks until timeout passes or lock is acquired.
* @param[in] timeout
* @return status result
*/
TtStatus acquire(TickType_t timeout) const;
/** Attempt to unlock the mutex.
* @return status result
*/
TtStatus release() const;
~Mutex() override = default;
/** Attempt to lock the mutex. Blocks until timeout passes or lock is acquired.
* @param[in] timeout
* @return success result
*/
bool lock(TickType_t timeout) const override { return acquire(timeout) == TtStatusOk; }
bool lock(TickType_t timeout) const override;
/** Attempt to lock the mutex. Blocks until lock is acquired, without timeout.
* @return success result
*/
bool lock() const override { return lock(portMAX_DELAY); }
/** Attempt to unlock the mutex.
* @return success result
*/
bool unlock() const override { return release() == TtStatusOk; }
bool unlock() const override;
/** @return the owner of the thread */
ThreadId getOwner() const;
+47
View File
@@ -0,0 +1,47 @@
#include "PubSub.h"
#include "Check.h"
namespace tt {
PubSub::SubscriptionHandle PubSub::subscribe(PubSubCallback callback, void* callbackParameter) {
mutex.lock();
items.push_back({
.id = (++lastId),
.callback = callback,
.callbackParameter = callbackParameter});
mutex.unlock();
return (Subscription*)lastId;
}
void PubSub::unsubscribe(SubscriptionHandle subscription) {
assert(subscription);
mutex.lock();
bool result = false;
auto id = (uint64_t)subscription;
for (auto it = items.begin(); it != items.end(); it++) {
if (it->id == id) {
items.erase(it);
result = true;
break;
}
}
mutex.unlock();
tt_check(result);
}
void PubSub::publish(void* message) {
mutex.lock();
// Iterate over subscribers
for (auto& it : items) {
it.callback(message, it.callbackParameter);
}
mutex.unlock();
}
} // namespace
+61
View File
@@ -0,0 +1,61 @@
/**
* @file pubsub.h
* PubSub
*/
#pragma once
#include "Mutex.h"
#include <list>
namespace tt {
/** PubSub Callback type */
typedef void (*PubSubCallback)(const void* message, void* context);
/** Publish and subscribe to messages in a thread-safe manner. */
class PubSub {
private:
struct Subscription {
uint64_t id;
PubSubCallback callback;
void* callbackParameter;
};
typedef std::list<Subscription> Subscriptions;
uint64_t lastId = 0;
Subscriptions items;
Mutex mutex;
public:
typedef void* SubscriptionHandle;
PubSub() = default;
~PubSub() {
tt_check(items.empty());
}
/** Start receiving messages at the specified handle (Threadsafe, Re-entrable)
* @param[in] callback
* @param[in] callbackParameter the data to pass to the callback
* @return subscription instance
*/
SubscriptionHandle subscribe(PubSubCallback callback, void* callbackParameter);
/** Stop receiving messages at the specified handle (Threadsafe, Re-entrable.)
* No use of `tt_pubsub_subscription` allowed after call of this method
* @param[in] subscription
*/
void unsubscribe(SubscriptionHandle subscription);
/** Publish message to all subscribers (Threadsafe, Re-entrable.)
* @param[in] message message pointer to publish - it is passed as-is to the callback
*/
void publish(void* message);
};
} // namespace
-52
View File
@@ -1,52 +0,0 @@
#include "Pubsub.h"
#include "Check.h"
namespace tt {
PubSubSubscription* tt_pubsub_subscribe(std::shared_ptr<PubSub> pubsub, PubSubCallback callback, void* callbackContext) {
tt_check(pubsub->mutex.acquire(TtWaitForever) == TtStatusOk);
PubSubSubscription subscription = {
.id = (++pubsub->last_id),
.callback = callback,
.callback_context = callbackContext
};
pubsub->items.push_back(
subscription
);
tt_check(pubsub->mutex.release() == TtStatusOk);
return (PubSubSubscription*)pubsub->last_id;
}
void tt_pubsub_unsubscribe(std::shared_ptr<PubSub> pubsub, PubSubSubscription* pubsub_subscription) {
assert(pubsub);
assert(pubsub_subscription);
tt_check(pubsub->mutex.acquire(TtWaitForever) == TtStatusOk);
bool result = false;
auto id = (uint64_t)pubsub_subscription;
for (auto it = pubsub->items.begin(); it != pubsub->items.end(); it++) {
if (it->id == id) {
pubsub->items.erase(it);
result = true;
break;
}
}
tt_check(pubsub->mutex.release() == TtStatusOk);
tt_check(result);
}
void tt_pubsub_publish(std::shared_ptr<PubSub> pubsub, void* message) {
tt_check(pubsub->mutex.acquire(TtWaitForever) == TtStatusOk);
// Iterate over subscribers
for (auto& it : pubsub->items) {
it.callback(message, it.callback_context);
}
tt_check(pubsub->mutex.release() == TtStatusOk);
}
} // namespace
-57
View File
@@ -1,57 +0,0 @@
/**
* @file pubsub.h
* PubSub
*/
#pragma once
#include "Mutex.h"
#include <list>
namespace tt {
/** PubSub Callback type */
typedef void (*PubSubCallback)(const void* message, void* context);
struct PubSubSubscription {
uint64_t id;
PubSubCallback callback;
void* callback_context;
};
struct PubSub {
typedef std::list<PubSubSubscription> Subscriptions;
uint64_t last_id = 0;
Subscriptions items;
Mutex mutex;
~PubSub() {
tt_check(items.empty());
}
};
/** Subscribe to PubSub
* Threadsafe, Reentrable
* @param[in] pubsub pointer to PubSub instance
* @param[in] callback
* @param[in] callbackContext the data to pass to the callback
* @return subscription instance
*/
PubSubSubscription*
tt_pubsub_subscribe(std::shared_ptr<PubSub> pubsub, PubSubCallback callback, void* callbackContext);
/** Unsubscribe from PubSub
* No use of `tt_pubsub_subscription` allowed after call of this method
* Threadsafe, Re-entrable.
* @param[in] pubsub
* @param[in] subscription
*/
void tt_pubsub_unsubscribe(std::shared_ptr<PubSub> pubsub, PubSubSubscription* subscription);
/** Publish message to PubSub
* Threadsafe, Reentrable.
* @param[in] pubsub
* @param[in] message message pointer to publish - it is passed as-is to the callback
*/
void tt_pubsub_publish(std::shared_ptr<PubSub> pubsub, void* message);
} // namespace
+14 -12
View File
@@ -4,28 +4,30 @@
namespace tt {
Semaphore::Semaphore(uint32_t maxCount, uint32_t initialCount) {
assert(!TT_IS_IRQ_MODE());
static inline struct QueueDefinition* createHandle(uint32_t maxCount, uint32_t initialCount) {
assert((maxCount > 0U) && (initialCount <= maxCount));
if (maxCount == 1U) {
handle = xSemaphoreCreateBinary();
auto handle = xSemaphoreCreateBinary();
if ((handle != nullptr) && (initialCount != 0U)) {
if (xSemaphoreGive(handle) != pdPASS) {
vSemaphoreDelete(handle);
handle = nullptr;
}
}
return handle;
} else {
handle = xSemaphoreCreateCounting(maxCount, initialCount);
return xSemaphoreCreateCounting(maxCount, initialCount);
}
}
tt_check(handle);
Semaphore::Semaphore(uint32_t maxCount, uint32_t initialCount) : handle(createHandle(maxCount, initialCount)){
assert(!TT_IS_IRQ_MODE());
tt_check(handle != nullptr);
}
Semaphore::~Semaphore() {
assert(!TT_IS_IRQ_MODE());
vSemaphoreDelete(handle);
}
bool Semaphore::acquire(uint32_t timeout) const {
@@ -35,7 +37,7 @@ bool Semaphore::acquire(uint32_t timeout) const {
} else {
BaseType_t yield = pdFALSE;
if (xSemaphoreTakeFromISR(handle, &yield) != pdPASS) {
if (xSemaphoreTakeFromISR(handle.get(), &yield) != pdPASS) {
return false;
} else {
portYIELD_FROM_ISR(yield);
@@ -43,21 +45,21 @@ bool Semaphore::acquire(uint32_t timeout) const {
}
}
} else {
return xSemaphoreTake(handle, (TickType_t)timeout) == pdPASS;
return xSemaphoreTake(handle.get(), (TickType_t)timeout) == pdPASS;
}
}
bool Semaphore::release() const {
if (TT_IS_IRQ_MODE()) {
BaseType_t yield = pdFALSE;
if (xSemaphoreGiveFromISR(handle, &yield) != pdTRUE) {
if (xSemaphoreGiveFromISR(handle.get(), &yield) != pdTRUE) {
return false;
} else {
portYIELD_FROM_ISR(yield);
return true;
}
} else {
return xSemaphoreGive(handle) == pdPASS;
return xSemaphoreGive(handle.get()) == pdPASS;
}
}
@@ -65,12 +67,12 @@ uint32_t Semaphore::getCount() const {
if (TT_IS_IRQ_MODE()) {
// TODO: uxSemaphoreGetCountFromISR is not supported on esp-idf 5.1.2 - perhaps later on?
#ifdef uxSemaphoreGetCountFromISR
return uxSemaphoreGetCountFromISR(handle);
return uxSemaphoreGetCountFromISR(handle.get());
#else
return uxQueueMessagesWaitingFromISR((QueueHandle_t)hSemaphore);
#endif
} else {
return uxSemaphoreGetCount(handle);
return uxSemaphoreGetCount(handle.get());
}
}
+13 -2
View File
@@ -1,7 +1,8 @@
#pragma once
#include "CoreTypes.h"
#include "Thread.h"
#include <cassert>
#include <memory>
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
@@ -18,8 +19,18 @@ namespace tt {
* Can be used from IRQ/ISR mode, but cannot be created/destroyed from such a context.
*/
class Semaphore {
private:
SemaphoreHandle_t handle;
struct SemaphoreHandleDeleter {
void operator()(QueueHandle_t handleToDelete) {
assert(!TT_IS_IRQ_MODE());
vSemaphoreDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<QueueHandle_t>, SemaphoreHandleDeleter> handle;
public:
/**
* Cannot be called from IRQ/ISR mode.
+15 -17
View File
@@ -2,22 +2,20 @@
#include "Check.h"
#include "CoreDefines.h"
#include "CoreTypes.h"
namespace tt {
StreamBuffer::StreamBuffer(size_t size, size_t triggerLevel) {
inline static StreamBufferHandle_t createStreamBuffer(size_t size, size_t triggerLevel) {
assert(size != 0);
handle = xStreamBufferCreate(size, triggerLevel);
return xStreamBufferCreate(size, triggerLevel);
}
StreamBuffer::StreamBuffer(size_t size, size_t triggerLevel) : handle(createStreamBuffer(size, triggerLevel)) {
tt_check(handle);
};
StreamBuffer::~StreamBuffer() {
vStreamBufferDelete(handle);
};
bool StreamBuffer::setTriggerLevel(size_t triggerLevel) const {
return xStreamBufferSetTriggerLevel(handle, triggerLevel) == pdTRUE;
return xStreamBufferSetTriggerLevel(handle.get(), triggerLevel) == pdTRUE;
};
size_t StreamBuffer::send(
@@ -27,11 +25,11 @@ size_t StreamBuffer::send(
) const {
if (TT_IS_IRQ_MODE()) {
BaseType_t yield;
size_t result = xStreamBufferSendFromISR(handle, data, length, &yield);
size_t result = xStreamBufferSendFromISR(handle.get(), data, length, &yield);
portYIELD_FROM_ISR(yield);
return result;
} else {
return xStreamBufferSend(handle, data, length, timeout);
return xStreamBufferSend(handle.get(), data, length, timeout);
}
};
@@ -42,32 +40,32 @@ size_t StreamBuffer::receive(
) const {
if (TT_IS_IRQ_MODE()) {
BaseType_t yield;
size_t result = xStreamBufferReceiveFromISR(handle, data, length, &yield);
size_t result = xStreamBufferReceiveFromISR(handle.get(), data, length, &yield);
portYIELD_FROM_ISR(yield);
return result;
} else {
return xStreamBufferReceive(handle, data, length, timeout);
return xStreamBufferReceive(handle.get(), data, length, timeout);
}
}
size_t StreamBuffer::getAvailableReadBytes() const {
return xStreamBufferBytesAvailable(handle);
return xStreamBufferBytesAvailable(handle.get());
};
size_t StreamBuffer::getAvailableWriteBytes() const {
return xStreamBufferSpacesAvailable(handle);
return xStreamBufferSpacesAvailable(handle.get());
};
bool StreamBuffer::isFull() const {
return xStreamBufferIsFull(handle) == pdTRUE;
return xStreamBufferIsFull(handle.get()) == pdTRUE;
};
bool StreamBuffer::isEmpty() const {
return xStreamBufferIsEmpty(handle) == pdTRUE;
return xStreamBufferIsEmpty(handle.get()) == pdTRUE;
};
bool StreamBuffer::reset() const {
return xStreamBufferReset(handle) == pdPASS;
return xStreamBufferReset(handle.get()) == pdPASS;
}
} // namespace
+13 -7
View File
@@ -1,8 +1,6 @@
#pragma once
#include "CoreTypes.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
@@ -25,8 +23,16 @@ namespace tt {
* interrupt that will read from the buffer (the reader).
*/
class StreamBuffer {
private:
StreamBufferHandle_t handle;
struct StreamBufferHandleDeleter {
void operator()(StreamBufferHandle_t handleToDelete) {
vStreamBufferDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<StreamBufferHandle_t>, StreamBufferHandleDeleter> handle;
public:
@@ -42,7 +48,7 @@ public:
*/
StreamBuffer(size_t size, size_t triggerLevel);
~StreamBuffer();
~StreamBuffer() = default;
/**
* @brief Set trigger level for stream buffer.
@@ -65,7 +71,7 @@ public:
* @param[in] 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.
* Setting timeout to portMAX_DELAY will cause the task to wait indefinitely.
* Ignored if called from ISR.
* @return The number of bytes actually written to the stream buffer.
*/
@@ -85,7 +91,7 @@ public:
* @param[in] 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.
* Setting timeout to portMAX_DELAY will cause the task to wait indefinitely.
* Ignored if called from ISR.
* @return The number of bytes read from the stream buffer, if any.
*/
-1
View File
@@ -5,7 +5,6 @@
#include "Check.h"
#include "CoreDefines.h"
#include "CoreExtraDefines.h"
#include "CoreTypes.h"
#include "EventFlag.h"
#include "kernel/Kernel.h"
#include "kernel/critical/Critical.h"
+105 -158
View File
@@ -1,10 +1,13 @@
#include "Thread.h"
#include <string>
#include "Check.h"
#include "CoreDefines.h"
#include "EventFlag.h"
#include "kernel/Kernel.h"
#include "Log.h"
#include "TactilityCoreConfig.h"
#include <string>
namespace tt {
@@ -22,180 +25,155 @@ namespace tt {
static_assert(static_cast<UBaseType_t>(Thread::Priority::Critical) <= TT_CONFIG_THREAD_MAX_PRIORITIES, "highest thread priority is higher than max priority");
static_assert(TT_CONFIG_THREAD_MAX_PRIORITIES <= configMAX_PRIORITIES, "highest tactility priority is higher than max FreeRTOS priority");
void setState(Thread::Data* data, Thread::State state) {
data->state = state;
if (data->stateCallback) {
data->stateCallback(state, data->stateCallbackContext);
void Thread::setState(Thread::State newState) {
state = newState;
if (stateCallback) {
stateCallback(state, stateCallbackContext);
}
}
static_assert(configSUPPORT_DYNAMIC_ALLOCATION == 1);
/** Catch threads that are trying to exit wrong way */
__attribute__((__noreturn__)) void 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
__attribute__((__noreturn__)) void threadCatch() { //-V1082
// If you're here it means you're probably doing something wrong with critical sections or with scheduler state
asm volatile("nop");
tt_crash();
__builtin_unreachable();
}
static void thread_body(void* context) {
assert(context);
auto* data = static_cast<Thread::Data*>(context);
void Thread::mainBody(void* context) {
assert(context != nullptr);
auto* thread = static_cast<Thread*>(context);
// Store thread data instance to thread local storage
assert(pvTaskGetThreadLocalStoragePointer(nullptr, 0) == nullptr);
vTaskSetThreadLocalStoragePointer(nullptr, 0, data->thread);
vTaskSetThreadLocalStoragePointer(nullptr, 0, thread);
assert(data->state == Thread::State::Starting);
setState(data, Thread::State::Running);
data->callbackResult = data->callback(data->callbackContext);
assert(data->state == Thread::State::Running);
assert(thread->state == Thread::State::Starting);
thread->setState(Thread::State::Running);
thread->callbackResult = thread->callback(thread->callbackContext);
assert(thread->state == Thread::State::Running);
setState(data, Thread::State::Stopped);
thread->setState(Thread::State::Stopped);
vTaskSetThreadLocalStoragePointer(nullptr, 0, nullptr);
data->taskHandle = nullptr;
thread->taskHandle = nullptr;
vTaskDelete(nullptr);
thread_catch();
threadCatch();
}
Thread::Thread() :
data({
.thread = nullptr,
.taskHandle = nullptr,
.state = State::Stopped,
.callback = nullptr,
.callbackContext = nullptr,
.callbackResult = 0,
.stateCallback = nullptr,
.stateCallbackContext = nullptr,
.name = std::string(),
.priority = Priority::Normal,
.stackSize = 0,
}) { }
Thread::Thread(
const std::string& name,
std::string name,
configSTACK_DEPTH_TYPE stackSize,
Callback callback,
_Nullable void* callbackContext,
portBASE_TYPE affinity
) :
data({
.thread = nullptr,
.taskHandle = nullptr,
.state = State::Stopped,
.callback = callback,
.callbackContext = callbackContext,
.callbackResult = 0,
.stateCallback = nullptr,
.stateCallbackContext = nullptr,
.name = name,
.priority = Priority::Normal,
.stackSize = stackSize,
.affinity = affinity
}) { }
callback(callback),
callbackContext(callbackContext),
name(std::move(name)),
stackSize(stackSize),
affinity(affinity)
{}
Thread::~Thread() {
// Ensure that use join before free
assert(data.state == State::Stopped);
assert(data.taskHandle == nullptr);
assert(state == State::Stopped);
assert(taskHandle == nullptr);
}
void Thread::setName(const std::string& newName) {
assert(data.state == State::Stopped);
data.name = newName;
void Thread::setName(std::string newName) {
assert(state == State::Stopped);
name = std::move(newName);
}
void Thread::setStackSize(size_t stackSize) {
assert(data.state == State::Stopped);
void Thread::setStackSize(size_t newStackSize) {
assert(state == State::Stopped);
assert(stackSize % 4 == 0);
data.stackSize = stackSize;
stackSize = newStackSize;
}
void Thread::setCallback(Callback callback, _Nullable void* callbackContext) {
assert(data.state == State::Stopped);
data.callback = callback;
data.callbackContext = callbackContext;
void Thread::setCallback(Callback newCallback, _Nullable void* newCallbackContext) {
assert(state == State::Stopped);
callback = newCallback;
callbackContext = newCallbackContext;
}
void Thread::setPriority(Priority priority) {
assert(data.state == State::Stopped);
data.priority = priority;
void Thread::setPriority(Priority newPriority) {
assert(state == State::Stopped);
priority = newPriority;
}
void Thread::setStateCallback(StateCallback callback, _Nullable void* callbackContext) {
assert(data.state == State::Stopped);
data.stateCallback = callback;
data.stateCallbackContext = callbackContext;
assert(state == State::Stopped);
stateCallback = callback;
stateCallbackContext = callbackContext;
}
Thread::State Thread::getState() const {
return data.state;
return state;
}
void Thread::start() {
assert(data.callback);
assert(data.state == State::Stopped);
assert(data.stackSize > 0U && data.stackSize < (UINT16_MAX * sizeof(StackType_t)));
assert(callback);
assert(state == State::Stopped);
assert(stackSize > 0U && stackSize < (UINT16_MAX * sizeof(StackType_t)));
setState(&data, State::Starting);
setState(State::Starting);
uint32_t stack_depth = data.stackSize / sizeof(StackType_t);
uint32_t stack_depth = stackSize / sizeof(StackType_t);
BaseType_t result;
if (data.affinity != -1) {
if (affinity != -1) {
#ifdef ESP_PLATFORM
result = xTaskCreatePinnedToCore(
thread_body,
data.name.c_str(),
mainBody,
name.c_str(),
stack_depth,
this,
static_cast<UBaseType_t>(data.priority),
&(data.taskHandle),
data.affinity
static_cast<UBaseType_t>(priority),
&taskHandle,
affinity
);
#else
TT_LOG_W(TAG, "Pinned tasks are not supported by current FreeRTOS platform - creating regular one");
result = xTaskCreate(
thread_body,
data.name.c_str(),
mainBody,
name.c_str(),
stack_depth,
this,
static_cast<UBaseType_t>(data.priority),
&(data.taskHandle)
static_cast<UBaseType_t>(priority),
&taskHandle
);
#endif
} else {
result = xTaskCreate(
thread_body,
data.name.c_str(),
mainBody,
name.c_str(),
stack_depth,
this,
static_cast<UBaseType_t>(data.priority),
&(data.taskHandle)
static_cast<UBaseType_t>(priority),
&taskHandle
);
}
tt_check(result == pdPASS);
tt_check(data.state == State::Stopped || data.taskHandle);
tt_check(state == State::Stopped || taskHandle);
}
bool Thread::join(TickType_t timeout, TickType_t pollInterval) {
tt_check(thread_get_current() != this);
tt_check(getCurrent() != this);
// !!! 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
TickType_t start_ticks = kernel::getTicks();
while (data.taskHandle) {
while (taskHandle) {
kernel::delayTicks(pollInterval);
if ((kernel::getTicks() - start_ticks) > timeout) {
return false;
@@ -206,45 +184,27 @@ bool Thread::join(TickType_t timeout, TickType_t pollInterval) {
}
ThreadId Thread::getId() const {
return data.taskHandle;
return taskHandle;
}
int32_t Thread::getReturnCode() const {
assert(data.state == State::Stopped);
return data.callbackResult;
assert(state == State::Stopped);
return callbackResult;
}
ThreadId thread_get_current_id() {
return xTaskGetCurrentTaskHandle();
Thread* Thread::getCurrent() {
return static_cast<Thread*>(pvTaskGetThreadLocalStoragePointer(nullptr, 0));
}
Thread* thread_get_current() {
auto* thread = static_cast<Thread*>(pvTaskGetThreadLocalStoragePointer(nullptr, 0));
return thread;
}
void thread_set_current_priority(Thread::Priority priority) {
vTaskPrioritySet(nullptr, static_cast<UBaseType_t>(priority));
}
Thread::Priority thread_get_current_priority() {
return (Thread::Priority)uxTaskPriorityGet(nullptr);
}
void thread_yield() {
assert(!TT_IS_IRQ_MODE());
taskYIELD();
}
uint32_t thread_flags_set(ThreadId thread_id, uint32_t flags) {
auto hTask = (TaskHandle_t)thread_id;
uint32_t Thread::setFlags(ThreadId threadId, uint32_t flags) {
auto hTask = (TaskHandle_t)threadId;
uint32_t rflags;
BaseType_t yield;
if ((hTask == nullptr) || ((flags & THREAD_FLAGS_INVALID_BITS) != 0U)) {
rflags = (uint32_t)TtStatusErrorParameter;
rflags = (uint32_t)EventFlag::ErrorParameter;
} else {
rflags = (uint32_t)TtStatusError;
rflags = (uint32_t)EventFlag::Error;
if (TT_IS_IRQ_MODE()) {
yield = pdFALSE;
@@ -264,14 +224,14 @@ uint32_t thread_flags_set(ThreadId thread_id, uint32_t flags) {
return (rflags);
}
uint32_t thread_flags_clear(uint32_t flags) {
uint32_t Thread::clearFlags(uint32_t flags) {
TaskHandle_t hTask;
uint32_t rflags, cflags;
if (TT_IS_IRQ_MODE()) {
rflags = (uint32_t)TtStatusErrorISR;
rflags = (uint32_t)EventFlag::ErrorISR;
} else if ((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
rflags = (uint32_t)TtStatusErrorParameter;
rflags = (uint32_t)EventFlag::ErrorParameter;
} else {
hTask = xTaskGetCurrentTaskHandle();
@@ -282,10 +242,10 @@ uint32_t thread_flags_clear(uint32_t flags) {
if (xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, cflags, eSetValueWithOverwrite) !=
pdPASS) {
rflags = (uint32_t)TtStatusError;
rflags = (uint32_t)EventFlag::Error;
}
} else {
rflags = (uint32_t)TtStatusError;
rflags = (uint32_t)EventFlag::Error;
}
}
@@ -293,36 +253,36 @@ uint32_t thread_flags_clear(uint32_t flags) {
return (rflags);
}
uint32_t thread_flags_get() {
uint32_t Thread::getFlags() {
TaskHandle_t hTask;
uint32_t rflags;
if (TT_IS_IRQ_MODE()) {
rflags = (uint32_t)TtStatusErrorISR;
rflags = (uint32_t)EventFlag::ErrorISR;
} else {
hTask = xTaskGetCurrentTaskHandle();
if (xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags) !=
pdPASS) {
rflags = (uint32_t)TtStatusError;
rflags = (uint32_t)EventFlag::Error;
}
}
return (rflags);
}
uint32_t thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout) {
uint32_t Thread::awaitFlags(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;
rflags = (uint32_t)EventFlag::ErrorISR;
} else if ((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
rflags = (uint32_t)TtStatusErrorParameter;
rflags = (uint32_t)EventFlag::ErrorParameter;
} else {
if ((options & TtFlagNoClear) == TtFlagNoClear) {
if ((options & EventFlag::NoClear) == EventFlag::NoClear) {
clear = 0U;
} else {
clear = flags;
@@ -339,12 +299,12 @@ uint32_t thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout) {
rflags &= flags;
rflags |= nval;
if ((options & TtFlagWaitAll) == TtFlagWaitAll) {
if ((options & EventFlag::WaitAll) == EventFlag::WaitAll) {
if ((flags & rflags) == flags) {
break;
} else {
if (timeout == 0U) {
rflags = (uint32_t)TtStatusErrorResource;
rflags = (uint32_t)EventFlag::ErrorResource;
break;
}
}
@@ -353,7 +313,7 @@ uint32_t thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout) {
break;
} else {
if (timeout == 0U) {
rflags = (uint32_t)TtStatusErrorResource;
rflags = (uint32_t)EventFlag::ErrorResource;
break;
}
}
@@ -369,9 +329,9 @@ uint32_t thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout) {
}
} else {
if (timeout == 0) {
rflags = (uint32_t)TtStatusErrorResource;
rflags = (uint32_t)EventFlag::ErrorResource;
} else {
rflags = (uint32_t)TtStatusErrorTimeout;
rflags = (uint32_t)EventFlag::ErrorTimeout;
}
}
} while (rval != pdFAIL);
@@ -381,21 +341,8 @@ uint32_t thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout) {
return (rflags);
}
const char* thread_get_name(ThreadId thread_id) {
auto hTask = (TaskHandle_t)thread_id;
const char* name;
if (TT_IS_IRQ_MODE() || (hTask == nullptr)) {
name = nullptr;
} else {
name = pcTaskGetName(hTask);
}
return (name);
}
uint32_t thread_get_stack_space(ThreadId thread_id) {
auto hTask = (TaskHandle_t)thread_id;
uint32_t Thread::getStackSpace(ThreadId threadId) {
auto hTask = (TaskHandle_t)threadId;
uint32_t sz;
if (TT_IS_IRQ_MODE() || (hTask == nullptr)) {
@@ -407,13 +354,13 @@ uint32_t thread_get_stack_space(ThreadId thread_id) {
return (sz);
}
void thread_suspend(ThreadId thread_id) {
auto hTask = (TaskHandle_t)thread_id;
void Thread::suspend(ThreadId threadId) {
auto hTask = (TaskHandle_t)threadId;
vTaskSuspend(hTask);
}
void thread_resume(ThreadId thread_id) {
auto hTask = (TaskHandle_t)thread_id;
void Thread::resume(ThreadId threadId) {
auto hTask = (TaskHandle_t)threadId;
if (TT_IS_IRQ_MODE()) {
xTaskResumeFromISR(hTask);
} else {
@@ -421,8 +368,8 @@ void thread_resume(ThreadId thread_id) {
}
}
bool thread_is_suspended(ThreadId thread_id) {
auto hTask = (TaskHandle_t)thread_id;
bool Thread::isSuspended(ThreadId threadId) {
auto hTask = (TaskHandle_t)threadId;
return eTaskGetState(hTask) == eSuspended;
}
+57 -77
View File
@@ -1,11 +1,9 @@
#pragma once
#include "CoreDefines.h"
#include "CoreTypes.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <memory>
#include "RtosCompatTask.h"
@@ -14,6 +12,7 @@ namespace tt {
typedef TaskHandle_t ThreadId;
class Thread {
public:
enum class State{
@@ -34,6 +33,7 @@ public:
Critical = 7U
};
/** ThreadCallback Your callback to run in new thread
* @warning never use osThreadExit in Thread
*/
@@ -51,22 +51,27 @@ public:
*/
typedef void (*StateCallback)(State state, void* context);
typedef struct {
Thread* thread;
TaskHandle_t taskHandle;
State state;
Callback callback;
void* callbackContext;
int32_t callbackResult;
StateCallback stateCallback;
void* stateCallbackContext;
std::string name;
Priority priority;
configSTACK_DEPTH_TYPE stackSize;
portBASE_TYPE affinity;
} Data;
private:
Thread();
static void mainBody(void* context);
TaskHandle_t taskHandle = nullptr;
State state = State::Stopped;
Callback callback = nullptr;
void* callbackContext = nullptr;
int32_t callbackResult = 0;
StateCallback stateCallback = nullptr;
void* stateCallbackContext = nullptr;
std::string name = {};
Priority priority = Priority::Normal;
configSTACK_DEPTH_TYPE stackSize = 0;
portBASE_TYPE affinity = -1;
void setState(Thread::State state);
public:
Thread() = default;
/** Allocate Thread, shortcut version
* @param[in] name the name of the thread
@@ -76,7 +81,7 @@ public:
* @param[in] affinity Which CPU core to pin this task to, -1 means unpinned (only works on ESP32)
*/
Thread(
const std::string& name,
std::string name,
configSTACK_DEPTH_TYPE stackSize,
Callback callback,
_Nullable void* callbackContext,
@@ -88,7 +93,7 @@ public:
/** Set Thread name
* @param[in] name string
*/
void setName(const std::string& name);
void setName(std::string name);
/** Set Thread stack size
* @param[in] stackSize stack size in bytes
@@ -140,9 +145,39 @@ public:
*/
int32_t getReturnCode() const;
private:
/** Suspend thread
* @param[in] threadId thread id
*/
static void suspend(ThreadId threadId);
Data data;
/** Resume thread
* @param[in] threadId thread id
*/
static void resume(ThreadId threadId);
/** Get thread suspended state
* @param[in] threadId thread id
* @return true if thread is suspended
*/
static bool isSuspended(ThreadId threadId);
/**
* @brief Get thread stack watermark
* @param[in] threadId
* @return uint32_t
*/
static uint32_t getStackSpace(ThreadId threadId);
/** @return pointer to Thread instance or nullptr if this thread doesn't belong to Tactility */
static Thread* getCurrent();
static uint32_t setFlags(ThreadId threadId, uint32_t flags);
static uint32_t clearFlags(uint32_t flags);
static uint32_t getFlags();
static uint32_t awaitFlags(uint32_t flags, uint32_t options, uint32_t timeout);
};
#define THREAD_PRIORITY_APP Thread::PriorityNormal
@@ -150,59 +185,4 @@ private:
#define THREAD_PRIORITY_RENDER Thread::Priority::Higher
#define THREAD_PRIORITY_ISR Thread::Priority::Critical
/** Set current thread priority
* @param[in] priority ThreadPriority value
*/
void thread_set_current_priority(Thread::Priority priority);
/** @return ThreadPriority value */
Thread::Priority thread_get_current_priority();
/** @return FreeRTOS ThreadId or NULL */
ThreadId thread_get_current_id();
/** @return pointer to Thread instance or NULL if this thread doesn't belongs to Tactility */
Thread* thread_get_current();
/** Return control to scheduler */
void thread_yield();
uint32_t thread_flags_set(ThreadId thread_id, uint32_t flags);
uint32_t thread_flags_clear(uint32_t flags);
uint32_t thread_flags_get();
uint32_t thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout);
/**
* @brief Get thread name
* @param[in] threadId
* @return const char* name or NULL
*/
const char* thread_get_name(ThreadId threadId);
/**
* @brief Get thread stack watermark
* @param[in] threadId
* @return uint32_t
*/
uint32_t thread_get_stack_space(ThreadId threadId);
/** Suspend thread
* @param[in] threadId thread id
*/
void thread_suspend(ThreadId threadId);
/** Resume thread
* @param[in] threadId thread id
*/
void thread_resume(ThreadId threadId);
/** Get thread suspended state
* @param[in] threadId thread id
* @return true if thread is suspended
*/
bool thread_is_suspended(ThreadId threadId);
} // namespace
+21 -16
View File
@@ -6,7 +6,7 @@
namespace tt {
static void timer_callback(TimerHandle_t hTimer) {
void Timer::onCallback(TimerHandle_t hTimer) {
auto* timer = static_cast<Timer*>(pvTimerGetTimerID(hTimer));
if (timer != nullptr) {
@@ -14,54 +14,59 @@ static void timer_callback(TimerHandle_t hTimer) {
}
}
Timer::Timer(Type type, Callback callback, std::shared_ptr<void> callbackContext) {
assert((!TT_IS_ISR()) && (callback != nullptr));
this->callback = callback;
this->callbackContext = std::move(callbackContext);
static inline TimerHandle_t createTimer(Timer::Type type, void* timerId, TimerCallbackFunction_t callback) {
assert(timerId != nullptr);
assert(callback != nullptr);
UBaseType_t reload;
if (type == Type::Once) {
if (type == Timer::Type::Once) {
reload = pdFALSE;
} else {
reload = pdTRUE;
}
this->timerHandle = xTimerCreate(nullptr, portMAX_DELAY, (BaseType_t)reload, this, timer_callback);
assert(this->timerHandle);
return xTimerCreate(nullptr, portMAX_DELAY, (BaseType_t)reload, timerId, callback);
}
Timer::Timer(Type type, Callback callback, std::shared_ptr<void> callbackContext) :
callback(callback),
callbackContext(std::move(callbackContext)),
handle(createTimer(type, this, onCallback))
{
assert(!TT_IS_ISR());
assert(handle != nullptr);
}
Timer::~Timer() {
assert(!TT_IS_ISR());
tt_check(xTimerDelete(timerHandle, portMAX_DELAY) == pdPASS);
}
bool Timer::start(TickType_t interval) {
assert(!TT_IS_ISR());
assert(interval < portMAX_DELAY);
return xTimerChangePeriod(timerHandle, interval, portMAX_DELAY) == pdPASS;
return xTimerChangePeriod(handle.get(), interval, portMAX_DELAY) == pdPASS;
}
bool Timer::restart(TickType_t interval) {
assert(!TT_IS_ISR());
assert(interval < portMAX_DELAY);
return xTimerChangePeriod(timerHandle, interval, portMAX_DELAY) == pdPASS &&
xTimerReset(timerHandle, portMAX_DELAY) == pdPASS;
return xTimerChangePeriod(handle.get(), interval, portMAX_DELAY) == pdPASS &&
xTimerReset(handle.get(), portMAX_DELAY) == pdPASS;
}
bool Timer::stop() {
assert(!TT_IS_ISR());
return xTimerStop(timerHandle, portMAX_DELAY) == pdPASS;
return xTimerStop(handle.get(), portMAX_DELAY) == pdPASS;
}
bool Timer::isRunning() {
assert(!TT_IS_ISR());
return xTimerIsTimerActive(timerHandle) == pdTRUE;
return xTimerIsTimerActive(handle.get()) == pdTRUE;
}
TickType_t Timer::getExpireTime() {
assert(!TT_IS_ISR());
return xTimerGetExpiryTime(timerHandle);
return xTimerGetExpiryTime(handle.get());
}
bool Timer::setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeout) {
+14 -4
View File
@@ -1,7 +1,5 @@
#pragma once
#include "CoreTypes.h"
#include "RtosCompatTimers.h"
#include "Thread.h"
#include <memory>
@@ -9,15 +7,27 @@
namespace tt {
class Timer {
private:
TimerHandle_t timerHandle;
public:
typedef void (*Callback)(std::shared_ptr<void> context);
typedef void (*PendingCallback)(void* context, uint32_t arg);
private:
struct TimerHandleDeleter {
void operator()(TimerHandle_t handleToDelete) {
xTimerDelete(handleToDelete, portMAX_DELAY);
}
};
Callback callback;
std::shared_ptr<void> callbackContext;
std::unique_ptr<std::remove_pointer_t<TimerHandle_t>, TimerHandleDeleter> handle;
static void onCallback(TimerHandle_t hTimer);
public:
enum class Type {
Once = 0, ///< One-shot timer.
+25 -55
View File
@@ -1,12 +1,11 @@
#include "kernel/Kernel.h"
#include "Check.h"
#include "CoreDefines.h"
#include "CoreTypes.h"
#include "RtosCompatTask.h"
#ifdef ESP_PLATFORM
#include "rom/ets_sys.h"
#else
#include <cassert>
#include <unistd.h>
#endif
@@ -16,90 +15,72 @@ bool isRunning() {
return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING;
}
int32_t lock() {
bool lock() {
assert(!TT_IS_ISR());
int32_t lock;
switch (xTaskGetSchedulerState()) {
// Already suspended
case taskSCHEDULER_SUSPENDED:
lock = 1;
break;
return true;
case taskSCHEDULER_RUNNING:
vTaskSuspendAll();
lock = 0;
break;
return true;
case taskSCHEDULER_NOT_STARTED:
default:
lock = (int32_t)TtStatusError;
break;
return false;
}
/* Return previous lock state */
return (lock);
}
int32_t unlock() {
bool unlock() {
assert(!TT_IS_ISR());
int32_t lock;
switch (xTaskGetSchedulerState()) {
case taskSCHEDULER_SUSPENDED:
lock = 1;
if (xTaskResumeAll() != pdTRUE) {
if (xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) {
lock = (int32_t)TtStatusError;
return false;
}
}
break;
return true;
case taskSCHEDULER_RUNNING:
lock = 0;
break;
return true;
case taskSCHEDULER_NOT_STARTED:
default:
lock = (int32_t)TtStatusError;
break;
return false;
}
/* Return previous lock state */
return (lock);
}
int32_t restoreLock(int32_t lock) {
bool restoreLock(bool lock) {
assert(!TT_IS_ISR());
switch (xTaskGetSchedulerState()) {
case taskSCHEDULER_SUSPENDED:
case taskSCHEDULER_RUNNING:
if (lock == 1) {
if (lock) {
vTaskSuspendAll();
return true;
} else {
if (lock != 0) {
lock = (int32_t)TtStatusError;
} else {
if (xTaskResumeAll() != pdTRUE) {
if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) {
lock = (int32_t)TtStatusError;
}
if (xTaskResumeAll() != pdTRUE) {
if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) {
return false;
}
}
return true;
}
break;
case taskSCHEDULER_NOT_STARTED:
default:
lock = (int32_t)TtStatusError;
break;
return false;
}
/* Return new lock state */
return (lock);
}
uint32_t getTickFrequency() {
@@ -116,13 +97,11 @@ void delayTicks(TickType_t ticks) {
}
}
TtStatus delayUntilTick(TickType_t tick) {
bool delayUntilTick(TickType_t tick) {
assert(!TT_IS_ISR());
TickType_t tcnt, delay;
TtStatus stat;
stat = TtStatusOk;
tcnt = xTaskGetTickCount();
/* Determine remaining number of tick to delay */
@@ -130,29 +109,20 @@ TtStatus delayUntilTick(TickType_t tick) {
/* 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;
if (xTaskDelayUntil(&tcnt, delay) == pdPASS) {
return true;
}
} else {
/* No delay or already expired */
stat = TtStatusErrorParameter;
}
/* Return execution status */
return (stat);
return false;
}
TickType_t getTicks() {
TickType_t ticks;
if (TT_IS_ISR() != 0U) {
ticks = xTaskGetTickCountFromISR();
return xTaskGetTickCountFromISR();
} else {
ticks = xTaskGetTickCount();
return xTaskGetTickCount();
}
return ticks;
}
TickType_t millisToTicks(uint32_t milliseconds) {
+8 -10
View File
@@ -1,7 +1,5 @@
#pragma once
#include "CoreTypes.h"
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#else
@@ -23,22 +21,22 @@ bool isRunning();
/** Lock kernel, pause process scheduling
* @warning don't call from ISR context
* @return previous lock state(0 - unlocked, 1 - locked)
* @return true on success
*/
int32_t lock();
bool lock();
/** Unlock kernel, resume process scheduling
* @warning don't call from ISR context
* @return previous lock state(0 - unlocked, 1 - locked)
* @return true on success
*/
int32_t unlock();
bool unlock();
/** Restore kernel lock state
* @warning don't call from ISR context
* @param[in] lock The lock state
* @return new lock state or error
* @return true on success
*/
int32_t restoreLock(int32_t lock);
bool restoreLock(bool lock);
/** Get kernel systick frequency
* @return systick counts per second
@@ -57,9 +55,9 @@ void delayTicks(TickType_t ticks);
/** Delay until tick
* @warning don't call from ISR context
* @param[in] ticks The tick until which kerel should delay task execution
* @return the status
* @return true on success
*/
TtStatus delayUntilTick(TickType_t tick);
bool delayUntilTick(TickType_t tick);
/** Convert milliseconds to ticks
*