Merge pull request 'added host osal' (#165) from KSat/fsfw:mueller/hostosal into master

Reviewed-on: #165
This commit is contained in:
Steffen Gaisser 2020-09-15 15:33:51 +02:00
commit 21094c4926
17 changed files with 1575 additions and 5 deletions

227
osal/host/Clock.cpp Normal file
View File

@ -0,0 +1,227 @@
#include "../../serviceinterface/ServiceInterfaceStream.h"
#include "../../timemanager/Clock.h"
#include <chrono>
#if defined(WIN32)
#include <windows.h>
#elif defined(LINUX)
#include <fstream>
#endif
uint16_t Clock::leapSeconds = 0;
MutexIF* Clock::timeMutex = NULL;
using SystemClock = std::chrono::system_clock;
uint32_t Clock::getTicksPerSecond(void){
sif::warning << "Clock::getTicksPerSecond: not implemented yet" << std::endl;
return 0;
//return CLOCKS_PER_SEC;
//uint32_t ticks = sysconf(_SC_CLK_TCK);
//return ticks;
}
ReturnValue_t Clock::setClock(const TimeOfDay_t* time) {
// do some magic with chrono
sif::warning << "Clock::setClock: not implemented yet" << std::endl;
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t Clock::setClock(const timeval* time) {
// do some magic with chrono
#if defined(WIN32)
return HasReturnvaluesIF::RETURN_OK;
#elif defined(LINUX)
return HasReturnvaluesIF::RETURN_OK;
#else
#endif
sif::warning << "Clock::getUptime: Not implemented for found OS" << std::endl;
return HasReturnvaluesIF::RETURN_FAILED;
}
ReturnValue_t Clock::getClock_timeval(timeval* time) {
#if defined(WIN32)
auto now = std::chrono::system_clock::now();
auto secondsChrono = std::chrono::time_point_cast<std::chrono::seconds>(now);
auto epoch = now.time_since_epoch();
time->tv_sec = std::chrono::duration_cast<std::chrono::seconds>(epoch).count();
auto fraction = now - secondsChrono;
time->tv_usec = std::chrono::duration_cast<std::chrono::microseconds>(
fraction).count();
return HasReturnvaluesIF::RETURN_OK;
#elif defined(LINUX)
timespec timeUnix;
int status = clock_gettime(CLOCK_REALTIME,&timeUnix);
if(status!=0){
return HasReturnvaluesIF::RETURN_FAILED;
}
time->tv_sec = timeUnix.tv_sec;
time->tv_usec = timeUnix.tv_nsec / 1000.0;
return HasReturnvaluesIF::RETURN_OK;
#else
sif::warning << "Clock::getUptime: Not implemented for found OS" << std::endl;
return HasReturnvaluesIF::RETURN_FAILED;
#endif
}
ReturnValue_t Clock::getClock_usecs(uint64_t* time) {
// do some magic with chrono
sif::warning << "Clock::gerClock_usecs: not implemented yet" << std::endl;
return HasReturnvaluesIF::RETURN_OK;
}
timeval Clock::getUptime() {
timeval timeval;
#if defined(WIN32)
auto uptime = std::chrono::milliseconds(GetTickCount64());
auto secondsChrono = std::chrono::duration_cast<std::chrono::seconds>(uptime);
timeval.tv_sec = secondsChrono.count();
auto fraction = uptime - secondsChrono;
timeval.tv_usec = std::chrono::duration_cast<std::chrono::microseconds>(
fraction).count();
#elif defined(LINUX)
double uptimeSeconds;
if (std::ifstream("/proc/uptime", std::ios::in) >> uptimeSeconds)
{
// value is rounded down automatically
timeval.tv_sec = uptimeSeconds;
timeval.tv_usec = uptimeSeconds *(double) 1e6 - (timeval.tv_sec *1e6);
}
#else
sif::warning << "Clock::getUptime: Not implemented for found OS" << std::endl;
#endif
return timeval;
}
ReturnValue_t Clock::getUptime(timeval* uptime) {
*uptime = getUptime();
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t Clock::getUptime(uint32_t* uptimeMs) {
timeval uptime = getUptime();
*uptimeMs = uptime.tv_sec * 1000 + uptime.tv_usec / 1000;
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t Clock::getDateAndTime(TimeOfDay_t* time) {
// do some magic with chrono (C++20!)
// Right now, the library doesn't have the new features yet.
// so we work around that for now.
auto now = SystemClock::now();
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
auto fraction = now - seconds;
time_t tt = SystemClock::to_time_t(now);
struct tm* timeInfo;
timeInfo = gmtime(&tt);
time->year = timeInfo->tm_year + 1900;
time->month = timeInfo->tm_mon+1;
time->day = timeInfo->tm_mday;
time->hour = timeInfo->tm_hour;
time->minute = timeInfo->tm_min;
time->second = timeInfo->tm_sec;
auto usecond = std::chrono::duration_cast<std::chrono::microseconds>(fraction);
time->usecond = usecond.count();
//sif::warning << "Clock::getDateAndTime: not implemented yet" << std::endl;
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t Clock::convertTimeOfDayToTimeval(const TimeOfDay_t* from,
timeval* to) {
struct tm time_tm;
time_tm.tm_year = from->year - 1900;
time_tm.tm_mon = from->month - 1;
time_tm.tm_mday = from->day;
time_tm.tm_hour = from->hour;
time_tm.tm_min = from->minute;
time_tm.tm_sec = from->second;
time_t seconds = mktime(&time_tm);
to->tv_sec = seconds;
to->tv_usec = from->usecond;
//Fails in 2038..
return HasReturnvaluesIF::RETURN_OK;
sif::warning << "Clock::convertTimeBla: not implemented yet" << std::endl;
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t Clock::convertTimevalToJD2000(timeval time, double* JD2000) {
*JD2000 = (time.tv_sec - 946728000. + time.tv_usec / 1000000.) / 24.
/ 3600.;
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t Clock::convertUTCToTT(timeval utc, timeval* tt) {
//SHOULDDO: works not for dates in the past (might have less leap seconds)
if (timeMutex == NULL) {
return HasReturnvaluesIF::RETURN_FAILED;
}
uint16_t leapSeconds;
ReturnValue_t result = getLeapSeconds(&leapSeconds);
if (result != HasReturnvaluesIF::RETURN_OK) {
return result;
}
timeval leapSeconds_timeval = { 0, 0 };
leapSeconds_timeval.tv_sec = leapSeconds;
//initial offset between UTC and TAI
timeval UTCtoTAI1972 = { 10, 0 };
timeval TAItoTT = { 32, 184000 };
*tt = utc + leapSeconds_timeval + UTCtoTAI1972 + TAItoTT;
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t Clock::setLeapSeconds(const uint16_t leapSeconds_) {
if(checkOrCreateClockMutex()!=HasReturnvaluesIF::RETURN_OK){
return HasReturnvaluesIF::RETURN_FAILED;
}
ReturnValue_t result = timeMutex->lockMutex(MutexIF::BLOCKING);
if (result != HasReturnvaluesIF::RETURN_OK) {
return result;
}
leapSeconds = leapSeconds_;
result = timeMutex->unlockMutex();
return result;
}
ReturnValue_t Clock::getLeapSeconds(uint16_t* leapSeconds_) {
if(timeMutex == nullptr){
return HasReturnvaluesIF::RETURN_FAILED;
}
ReturnValue_t result = timeMutex->lockMutex(MutexIF::BLOCKING);
if (result != HasReturnvaluesIF::RETURN_OK) {
return result;
}
*leapSeconds_ = leapSeconds;
result = timeMutex->unlockMutex();
return result;
}
ReturnValue_t Clock::checkOrCreateClockMutex(){
if(timeMutex == nullptr){
MutexFactory* mutexFactory = MutexFactory::instance();
if (mutexFactory == nullptr) {
return HasReturnvaluesIF::RETURN_FAILED;
}
timeMutex = mutexFactory->createMutex();
if (timeMutex == nullptr) {
return HasReturnvaluesIF::RETURN_FAILED;
}
}
return HasReturnvaluesIF::RETURN_OK;
}

View File

@ -0,0 +1,197 @@
#include "../../osal/host/FixedTimeslotTask.h"
#include "../../ipc/MutexFactory.h"
#include "../../osal/host/Mutex.h"
#include "../../osal/host/FixedTimeslotTask.h"
#include "../../serviceinterface/ServiceInterfaceStream.h"
#include "../../tasks/ExecutableObjectIF.h"
#include <thread>
#include <chrono>
#if defined(WIN32)
#include <windows.h>
#elif defined(LINUX)
#include <pthread.h>
#endif
FixedTimeslotTask::FixedTimeslotTask(const char *name, TaskPriority setPriority,
TaskStackSize setStack, TaskPeriod setPeriod,
void (*setDeadlineMissedFunc)()) :
started(false), pollingSeqTable(setPeriod*1000), taskName(name),
period(setPeriod), deadlineMissedFunc(setDeadlineMissedFunc) {
// It is propably possible to set task priorities by using the native
// task handles for Windows / Linux
mainThread = std::thread(&FixedTimeslotTask::taskEntryPoint, this, this);
#if defined(WIN32)
/* List of possible priority classes:
* https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/
* nf-processthreadsapi-setpriorityclass
* And respective thread priority numbers:
* https://docs.microsoft.com/en-us/windows/
* win32/procthread/scheduling-priorities */
int result = SetPriorityClass(
reinterpret_cast<HANDLE>(mainThread.native_handle()),
ABOVE_NORMAL_PRIORITY_CLASS);
if(result != 0) {
sif::error << "FixedTimeslotTask: Windows SetPriorityClass failed with code "
<< GetLastError() << std::endl;
}
result = SetThreadPriority(
reinterpret_cast<HANDLE>(mainThread.native_handle()),
THREAD_PRIORITY_NORMAL);
if(result != 0) {
sif::error << "FixedTimeslotTask: Windows SetPriorityClass failed with code "
<< GetLastError() << std::endl;
}
#elif defined(LINUX)
// we can just copy and paste the code from linux here.
#endif
}
FixedTimeslotTask::~FixedTimeslotTask(void) {
//Do not delete objects, we were responsible for ptrs only.
terminateThread = true;
if(mainThread.joinable()) {
mainThread.join();
}
delete this;
}
void FixedTimeslotTask::taskEntryPoint(void* argument) {
FixedTimeslotTask *originalTask(reinterpret_cast<FixedTimeslotTask*>(argument));
if (not originalTask->started) {
// we have to suspend/block here until the task is started.
// if semaphores are implemented, use them here.
std::unique_lock<std::mutex> lock(initMutex);
initCondition.wait(lock);
}
this->taskFunctionality();
sif::debug << "FixedTimeslotTask::taskEntryPoint: "
"Returned from taskFunctionality." << std::endl;
}
ReturnValue_t FixedTimeslotTask::startTask() {
started = true;
// Notify task to start.
std::lock_guard<std::mutex> lock(initMutex);
initCondition.notify_one();
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t FixedTimeslotTask::sleepFor(uint32_t ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
return HasReturnvaluesIF::RETURN_OK;
}
void FixedTimeslotTask::taskFunctionality() {
pollingSeqTable.intializeSequenceAfterTaskCreation();
// A local iterator for the Polling Sequence Table is created to
// find the start time for the first entry.
auto slotListIter = pollingSeqTable.current;
// Get start time for first entry.
chron_ms interval(slotListIter->pollingTimeMs);
auto currentStartTime {
std::chrono::duration_cast<chron_ms>(
std::chrono::system_clock::now().time_since_epoch())
};
if(interval.count() > 0) {
delayForInterval(&currentStartTime, interval);
}
/* Enter the loop that defines the task behavior. */
for (;;) {
if(terminateThread.load()) {
break;
}
//The component for this slot is executed and the next one is chosen.
this->pollingSeqTable.executeAndAdvance();
if (not pollingSeqTable.slotFollowsImmediately()) {
// we need to wait before executing the current slot
//this gives us the time to wait:
interval = chron_ms(this->pollingSeqTable.getIntervalToPreviousSlotMs());
delayForInterval(&currentStartTime, interval);
//TODO deadline missed check
}
}
}
ReturnValue_t FixedTimeslotTask::addSlot(object_id_t componentId,
uint32_t slotTimeMs, int8_t executionStep) {
ExecutableObjectIF* executableObject = objectManager->
get<ExecutableObjectIF>(componentId);
if (executableObject != nullptr) {
pollingSeqTable.addSlot(componentId, slotTimeMs, executionStep,
executableObject, this);
return HasReturnvaluesIF::RETURN_OK;
}
sif::error << "Component " << std::hex << componentId <<
" not found, not adding it to pst" << std::endl;
return HasReturnvaluesIF::RETURN_FAILED;
}
ReturnValue_t FixedTimeslotTask::checkSequence() const {
return pollingSeqTable.checkSequence();
}
uint32_t FixedTimeslotTask::getPeriodMs() const {
return period * 1000;
}
bool FixedTimeslotTask::delayForInterval(chron_ms * previousWakeTimeMs,
const chron_ms interval) {
bool shouldDelay = false;
//Get current wakeup time
auto currentStartTime =
std::chrono::duration_cast<chron_ms>(
std::chrono::system_clock::now().time_since_epoch());
/* Generate the tick time at which the task wants to wake. */
auto nextTimeToWake_ms = (*previousWakeTimeMs) + interval;
if (currentStartTime < *previousWakeTimeMs) {
/* The tick count has overflowed since this function was
lasted called. In this case the only time we should ever
actually delay is if the wake time has also overflowed,
and the wake time is greater than the tick time. When this
is the case it is as if neither time had overflowed. */
if ((nextTimeToWake_ms < *previousWakeTimeMs)
&& (nextTimeToWake_ms > currentStartTime)) {
shouldDelay = true;
}
} else {
/* The tick time has not overflowed. In this case we will
delay if either the wake time has overflowed, and/or the
tick time is less than the wake time. */
if ((nextTimeToWake_ms < *previousWakeTimeMs)
|| (nextTimeToWake_ms > currentStartTime)) {
shouldDelay = true;
}
}
/* Update the wake time ready for the next call. */
(*previousWakeTimeMs) = nextTimeToWake_ms;
if (shouldDelay) {
auto sleepTime = std::chrono::duration_cast<chron_ms>(
nextTimeToWake_ms - currentStartTime);
std::this_thread::sleep_for(sleepTime);
return true;
}
//We are shifting the time in case the deadline was missed like rtems
(*previousWakeTimeMs) = currentStartTime;
return false;
}

View File

@ -0,0 +1,130 @@
#ifndef FRAMEWORK_OSAL_HOST_FIXEDTIMESLOTTASK_H_
#define FRAMEWORK_OSAL_HOST_FIXEDTIMESLOTTASK_H_
#include "../../objectmanager/ObjectManagerIF.h"
#include "../../tasks/FixedSlotSequence.h"
#include "../../tasks/FixedTimeslotTaskIF.h"
#include "../../tasks/Typedef.h"
#include <vector>
#include <thread>
#include <condition_variable>
#include <atomic>
class ExecutableObjectIF;
/**
* @brief This class represents a task for periodic activities with multiple
* steps and strict timeslot requirements for these steps.
* @details
* @ingroup task_handling
*/
class FixedTimeslotTask: public FixedTimeslotTaskIF {
public:
/**
* @brief Standard constructor of the class.
* @details
* The class is initialized without allocated objects. These need to be
* added with #addComponent.
* @param priority
* @param stack_size
* @param setPeriod
* @param setDeadlineMissedFunc
* The function pointer to the deadline missed function that shall be
* assigned.
*/
FixedTimeslotTask(const char *name, TaskPriority setPriority,
TaskStackSize setStack, TaskPeriod setPeriod,
void (*setDeadlineMissedFunc)());
/**
* @brief Currently, the executed object's lifetime is not coupled with
* the task object's lifetime, so the destructor is empty.
*/
virtual ~FixedTimeslotTask(void);
/**
* @brief The method to start the task.
* @details The method starts the task with the respective system call.
* Entry point is the taskEntryPoint method described below.
* The address of the task object is passed as an argument
* to the system call.
*/
ReturnValue_t startTask(void);
/**
* Add timeslot to the polling sequence table.
* @param componentId
* @param slotTimeMs
* @param executionStep
* @return
*/
ReturnValue_t addSlot(object_id_t componentId,
uint32_t slotTimeMs, int8_t executionStep);
ReturnValue_t checkSequence() const override;
uint32_t getPeriodMs() const;
ReturnValue_t sleepFor(uint32_t ms);
protected:
using chron_ms = std::chrono::milliseconds;
bool started;
//!< Typedef for the List of objects.
typedef std::vector<ExecutableObjectIF*> ObjectList;
std::thread mainThread;
std::atomic<bool> terminateThread = false;
//! Polling sequence table which contains the object to execute
//! and information like the timeslots and the passed execution step.
FixedSlotSequence pollingSeqTable;
std::condition_variable initCondition;
std::mutex initMutex;
std::string taskName;
/**
* @brief The period of the task.
* @details
* The period determines the frequency of the task's execution.
* It is expressed in clock ticks.
*/
TaskPeriod period;
/**
* @brief The pointer to the deadline-missed function.
* @details
* This pointer stores the function that is executed if the task's deadline
* is missed. So, each may react individually on a timing failure.
* The pointer may be NULL, then nothing happens on missing the deadline.
* The deadline is equal to the next execution of the periodic task.
*/
void (*deadlineMissedFunc)(void);
/**
* @brief This is the function executed in the new task's context.
* @details
* It converts the argument back to the thread object type and copies the
* class instance to the task context.
* The taskFunctionality method is called afterwards.
* @param A pointer to the task object itself is passed as argument.
*/
void taskEntryPoint(void* argument);
/**
* @brief The function containing the actual functionality of the task.
* @details
* The method sets and starts the task's period, then enters a loop that is
* repeated as long as the isRunning attribute is true. Within the loop,
* all performOperation methods of the added objects are called. Afterwards
* the checkAndRestartPeriod system call blocks the task until the next
* period. On missing the deadline, the deadlineMissedFunction is executed.
*/
void taskFunctionality(void);
bool delayForInterval(chron_ms * previousWakeTimeMs,
const chron_ms interval);
};
#endif /* FRAMEWORK_OSAL_HOST_FIXEDTIMESLOTTASK_H_ */

159
osal/host/MessageQueue.cpp Normal file
View File

@ -0,0 +1,159 @@
#include "MessageQueue.h"
#include "QueueMapManager.h"
#include "../../serviceinterface/ServiceInterfaceStream.h"
#include "../../ipc/MutexFactory.h"
#include "../../ipc/MutexHelper.h"
MessageQueue::MessageQueue(size_t messageDepth, size_t maxMessageSize):
messageSize(maxMessageSize), messageDepth(messageDepth) {
queueLock = MutexFactory::instance()->createMutex();
auto result = QueueMapManager::instance()->addMessageQueue(this, &mqId);
if(result != HasReturnvaluesIF::RETURN_OK) {
sif::error << "MessageQueue::MessageQueue:"
<< " Could not be created" << std::endl;
}
}
MessageQueue::~MessageQueue() {
MutexFactory::instance()->deleteMutex(queueLock);
}
ReturnValue_t MessageQueue::sendMessage(MessageQueueId_t sendTo,
MessageQueueMessageIF* message, bool ignoreFault) {
return sendMessageFrom(sendTo, message, this->getId(), ignoreFault);
}
ReturnValue_t MessageQueue::sendToDefault(MessageQueueMessageIF* message) {
return sendToDefaultFrom(message, this->getId());
}
ReturnValue_t MessageQueue::sendToDefaultFrom(MessageQueueMessageIF* message,
MessageQueueId_t sentFrom, bool ignoreFault) {
return sendMessageFrom(defaultDestination,message,sentFrom,ignoreFault);
}
ReturnValue_t MessageQueue::reply(MessageQueueMessageIF* message) {
if (this->lastPartner != 0) {
return sendMessageFrom(this->lastPartner, message, this->getId());
} else {
return MessageQueueIF::NO_REPLY_PARTNER;
}
}
ReturnValue_t MessageQueue::sendMessageFrom(MessageQueueId_t sendTo,
MessageQueueMessageIF* message, MessageQueueId_t sentFrom,
bool ignoreFault) {
return sendMessageFromMessageQueue(sendTo, message, sentFrom,
ignoreFault);
}
ReturnValue_t MessageQueue::receiveMessage(MessageQueueMessageIF* message,
MessageQueueId_t* receivedFrom) {
ReturnValue_t status = this->receiveMessage(message);
if(status == HasReturnvaluesIF::RETURN_OK) {
*receivedFrom = this->lastPartner;
}
return status;
}
ReturnValue_t MessageQueue::receiveMessage(MessageQueueMessageIF* message) {
if(messageQueue.empty()) {
return MessageQueueIF::EMPTY;
}
// not sure this will work..
//*message = std::move(messageQueue.front());
MutexHelper mutexLock(queueLock, MutexIF::TimeoutType::WAITING, 20);
MessageQueueMessage* currentMessage = &messageQueue.front();
std::copy(currentMessage->getBuffer(),
currentMessage->getBuffer() + messageSize, message->getBuffer());
messageQueue.pop();
// The last partner is the first uint32_t field in the message
this->lastPartner = message->getSender();
return HasReturnvaluesIF::RETURN_OK;
}
MessageQueueId_t MessageQueue::getLastPartner() const {
return lastPartner;
}
ReturnValue_t MessageQueue::flush(uint32_t* count) {
*count = messageQueue.size();
// Clears the queue.
messageQueue = std::queue<MessageQueueMessage>();
return HasReturnvaluesIF::RETURN_OK;
}
MessageQueueId_t MessageQueue::getId() const {
return mqId;
}
void MessageQueue::setDefaultDestination(MessageQueueId_t defaultDestination) {
defaultDestinationSet = true;
this->defaultDestination = defaultDestination;
}
MessageQueueId_t MessageQueue::getDefaultDestination() const {
return defaultDestination;
}
bool MessageQueue::isDefaultDestinationSet() const {
return defaultDestinationSet;
}
// static core function to send messages.
ReturnValue_t MessageQueue::sendMessageFromMessageQueue(MessageQueueId_t sendTo,
MessageQueueMessageIF* message, MessageQueueId_t sentFrom,
bool ignoreFault) {
if(message->getMessageSize() > message->getMaximumMessageSize()) {
// Actually, this should never happen or an error will be emitted
// in MessageQueueMessage.
// But I will still return a failure here.
return HasReturnvaluesIF::RETURN_FAILED;
}
MessageQueue* targetQueue = dynamic_cast<MessageQueue*>(
QueueMapManager::instance()->getMessageQueue(sendTo));
if(targetQueue == nullptr) {
if(not ignoreFault) {
InternalErrorReporterIF* internalErrorReporter =
objectManager->get<InternalErrorReporterIF>(
objects::INTERNAL_ERROR_REPORTER);
if (internalErrorReporter != nullptr) {
internalErrorReporter->queueMessageNotSent();
}
}
// TODO: Better returnvalue
return HasReturnvaluesIF::RETURN_FAILED;
}
if(targetQueue->messageQueue.size() < targetQueue->messageDepth) {
MutexHelper mutexLock(targetQueue->queueLock,
MutexIF::TimeoutType::WAITING, 20);
// not ideal, works for now though.
MessageQueueMessage* mqmMessage =
dynamic_cast<MessageQueueMessage*>(message);
if(message != nullptr) {
targetQueue->messageQueue.push(*mqmMessage);
}
else {
sif::error << "MessageQueue::sendMessageFromMessageQueue: Message"
"is not MessageQueueMessage!" << std::endl;
}
}
else {
return MessageQueueIF::FULL;
}
message->setSender(sentFrom);
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t MessageQueue::lockQueue(MutexIF::TimeoutType timeoutType,
dur_millis_t lockTimeout) {
return queueLock->lockMutex(timeoutType, lockTimeout);
}
ReturnValue_t MessageQueue::unlockQueue() {
return queueLock->unlockMutex();
}

231
osal/host/MessageQueue.h Normal file
View File

@ -0,0 +1,231 @@
#ifndef FRAMEWORK_OSAL_HOST_MESSAGEQUEUE_H_
#define FRAMEWORK_OSAL_HOST_MESSAGEQUEUE_H_
#include "../../internalError/InternalErrorReporterIF.h"
#include "../../ipc/MessageQueueIF.h"
#include "../../ipc/MessageQueueMessage.h"
#include "../../ipc/MutexIF.h"
#include "../../timemanager/Clock.h"
#include <queue>
#include <memory>
/**
* @brief This class manages sending and receiving of
* message queue messages.
* @details
* Message queues are used to pass asynchronous messages between processes.
* They work like post boxes, where all incoming messages are stored in FIFO
* order. This class creates a new receiving queue and provides methods to fetch
* received messages. Being a child of MessageQueueSender, this class also
* provides methods to send a message to a user-defined or a default destination.
* In addition it also provides a reply method to answer to the queue it
* received its last message from.
*
* The MessageQueue should be used as "post box" for a single owning object.
* So all message queue communication is "n-to-one".
* For creating the queue, as well as sending and receiving messages, the class
* makes use of the operating system calls provided.
*
* Please keep in mind that FreeRTOS offers different calls for message queue
* operations if called from an ISR.
* For now, the system context needs to be switched manually.
* @ingroup osal
* @ingroup message_queue
*/
class MessageQueue : public MessageQueueIF {
friend class MessageQueueSenderIF;
public:
/**
* @brief The constructor initializes and configures the message queue.
* @details
* By making use of the according operating system call, a message queue is
* created and initialized. The message depth - the maximum number of
* messages to be buffered - may be set with the help of a parameter,
* whereas the message size is automatically set to the maximum message
* queue message size. The operating system sets the message queue id, or
* in case of failure, it is set to zero.
* @param message_depth
* The number of messages to be buffered before passing an error to the
* sender. Default is three.
* @param max_message_size
* With this parameter, the maximum message size can be adjusted.
* This should be left default.
*/
MessageQueue(size_t messageDepth = 3,
size_t maxMessageSize = MessageQueueMessage::MAX_MESSAGE_SIZE);
/** Copying message queues forbidden */
MessageQueue(const MessageQueue&) = delete;
MessageQueue& operator=(const MessageQueue&) = delete;
/**
* @brief The destructor deletes the formerly created message queue.
* @details This is accomplished by using the delete call provided
* by the operating system.
*/
virtual ~MessageQueue();
/**
* @brief This operation sends a message to the given destination.
* @details It directly uses the sendMessage call of the MessageQueueSender
* parent, but passes its queue id as "sentFrom" parameter.
* @param sendTo This parameter specifies the message queue id of the
* destination message queue.
* @param message A pointer to a previously created message, which is sent.
* @param ignoreFault If set to true, the internal software fault counter
* is not incremented if queue is full.
*/
ReturnValue_t sendMessage(MessageQueueId_t sendTo,
MessageQueueMessageIF* message, bool ignoreFault = false) override;
/**
* @brief This operation sends a message to the default destination.
* @details As in the sendMessage method, this function uses the
* sendToDefault call of the MessageQueueSender parent class and adds its
* queue id as "sentFrom" information.
* @param message A pointer to a previously created message, which is sent.
*/
ReturnValue_t sendToDefault(MessageQueueMessageIF* message) override;
/**
* @brief This operation sends a message to the last communication partner.
* @details This operation simplifies answering an incoming message by using
* the stored lastPartner information as destination. If there was no
* message received yet (i.e. lastPartner is zero), an error code is returned.
* @param message A pointer to a previously created message, which is sent.
*/
ReturnValue_t reply(MessageQueueMessageIF* message) override;
/**
* @brief With the sendMessage call, a queue message is sent to a
* receiving queue.
* @details
* This method takes the message provided, adds the sentFrom information and
* passes it on to the destination provided with an operating system call.
* The OS's return value is returned.
* @param sendTo This parameter specifies the message queue id to send
* the message to.
* @param message This is a pointer to a previously created message,
* which is sent.
* @param sentFrom The sentFrom information can be set to inject the
* sender's queue id into the message. This variable is set to zero by
* default.
* @param ignoreFault If set to true, the internal software fault counter
* is not incremented if queue is full.
*/
virtual ReturnValue_t sendMessageFrom( MessageQueueId_t sendTo,
MessageQueueMessageIF* message, MessageQueueId_t sentFrom = NO_QUEUE,
bool ignoreFault = false) override;
/**
* @brief The sendToDefault method sends a queue message to the default
* destination.
* @details
* In all other aspects, it works identical to the sendMessage method.
* @param message This is a pointer to a previously created message,
* which is sent.
* @param sentFrom The sentFrom information can be set to inject the
* sender's queue id into the message. This variable is set to zero by
* default.
*/
virtual ReturnValue_t sendToDefaultFrom( MessageQueueMessageIF* message,
MessageQueueId_t sentFrom = NO_QUEUE,
bool ignoreFault = false) override;
/**
* @brief This function reads available messages from the message queue
* and returns the sender.
* @details
* It works identically to the other receiveMessage call, but in addition
* returns the sender's queue id.
* @param message A pointer to a message in which the received data is stored.
* @param receivedFrom A pointer to a queue id in which the sender's id is stored.
*/
ReturnValue_t receiveMessage(MessageQueueMessageIF* message,
MessageQueueId_t *receivedFrom) override;
/**
* @brief This function reads available messages from the message queue.
* @details
* If data is available it is stored in the passed message pointer.
* The message's original content is overwritten and the sendFrom
* information is stored in the lastPartner attribute. Else, the lastPartner
* information remains untouched, the message's content is cleared and the
* function returns immediately.
* @param message A pointer to a message in which the received data is stored.
*/
ReturnValue_t receiveMessage(MessageQueueMessageIF* message) override;
/**
* Deletes all pending messages in the queue.
* @param count The number of flushed messages.
* @return RETURN_OK on success.
*/
ReturnValue_t flush(uint32_t* count) override;
/**
* @brief This method returns the message queue id of the last
* communication partner.
*/
MessageQueueId_t getLastPartner() const override;
/**
* @brief This method returns the message queue id of this class's
* message queue.
*/
MessageQueueId_t getId() const override;
/**
* @brief This method is a simple setter for the default destination.
*/
void setDefaultDestination(MessageQueueId_t defaultDestination) override;
/**
* @brief This method is a simple getter for the default destination.
*/
MessageQueueId_t getDefaultDestination() const override;
bool isDefaultDestinationSet() const override;
ReturnValue_t lockQueue(MutexIF::TimeoutType timeoutType,
dur_millis_t lockTimeout);
ReturnValue_t unlockQueue();
protected:
/**
* @brief Implementation to be called from any send Call within
* MessageQueue and MessageQueueSenderIF.
* @details
* This method takes the message provided, adds the sentFrom information and
* passes it on to the destination provided with an operating system call.
* The OS's return value is returned.
* @param sendTo
* This parameter specifies the message queue id to send the message to.
* @param message
* This is a pointer to a previously created message, which is sent.
* @param sentFrom
* The sentFrom information can be set to inject the sender's queue id into
* the message. This variable is set to zero by default.
* @param ignoreFault
* If set to true, the internal software fault counter is not incremented
* if queue is full.
* @param context Specify whether call is made from task or from an ISR.
*/
static ReturnValue_t sendMessageFromMessageQueue(MessageQueueId_t sendTo,
MessageQueueMessageIF* message, MessageQueueId_t sentFrom = NO_QUEUE,
bool ignoreFault=false);
//static ReturnValue_t handleSendResult(BaseType_t result, bool ignoreFault);
private:
std::queue<MessageQueueMessage> messageQueue;
/**
* @brief The class stores the queue id it got assigned.
* If initialization fails, the queue id is set to zero.
*/
MessageQueueId_t mqId = 0;
size_t messageSize = 0;
size_t messageDepth = 0;
MutexIF* queueLock;
bool defaultDestinationSet = false;
MessageQueueId_t defaultDestination = 0;
MessageQueueId_t lastPartner = 0;
};
#endif /* FRAMEWORK_OSAL_HOST_MESSAGEQUEUE_H_ */

39
osal/host/Mutex.cpp Normal file
View File

@ -0,0 +1,39 @@
#include "Mutex.h"
#include "../../serviceinterface/ServiceInterfaceStream.h"
Mutex::Mutex() {}
ReturnValue_t Mutex::lockMutex(TimeoutType timeoutType, uint32_t timeoutMs) {
if(timeoutMs == MutexIF::BLOCKING) {
mutex.lock();
locked = true;
return HasReturnvaluesIF::RETURN_OK;
}
else if(timeoutMs == MutexIF::POLLING) {
if(mutex.try_lock()) {
locked = true;
return HasReturnvaluesIF::RETURN_OK;
}
}
else if(timeoutMs > MutexIF::POLLING){
auto chronoMs = std::chrono::milliseconds(timeoutMs);
if(mutex.try_lock_for(chronoMs)) {
locked = true;
return HasReturnvaluesIF::RETURN_OK;
}
}
return MutexIF::MUTEX_TIMEOUT;
}
ReturnValue_t Mutex::unlockMutex() {
if(not locked) {
return MutexIF::CURR_THREAD_DOES_NOT_OWN_MUTEX;
}
mutex.unlock();
locked = false;
return HasReturnvaluesIF::RETURN_OK;
}
std::timed_mutex* Mutex::getMutexHandle() {
return &mutex;
}

29
osal/host/Mutex.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef FSFW_OSAL_HOSTED_MUTEX_H_
#define FSFW_OSAL_HOSTED_MUTEX_H_
#include "../../ipc/MutexIF.h"
#include <mutex>
/**
* @brief OS component to implement MUTual EXclusion
*
* @details
* Mutexes are binary semaphores which include a priority inheritance mechanism.
* Documentation: https://www.freertos.org/Real-time-embedded-RTOS-mutexes.html
* @ingroup osal
*/
class Mutex : public MutexIF {
public:
Mutex();
ReturnValue_t lockMutex(TimeoutType timeoutType =
TimeoutType::BLOCKING, uint32_t timeoutMs = 0) override;
ReturnValue_t unlockMutex() override;
std::timed_mutex* getMutexHandle();
private:
bool locked = false;
std::timed_mutex mutex;
};
#endif /* FSFW_OSAL_HOSTED_MUTEX_H_ */

View File

@ -0,0 +1,28 @@
#include "../../ipc/MutexFactory.h"
#include "../../osal/host/Mutex.h"
//TODO: Different variant than the lazy loading in QueueFactory.
//What's better and why? -> one is on heap the other on bss/data
//MutexFactory* MutexFactory::factoryInstance = new MutexFactory();
MutexFactory* MutexFactory::factoryInstance = nullptr;
MutexFactory::MutexFactory() {
}
MutexFactory::~MutexFactory() {
}
MutexFactory* MutexFactory::instance() {
if (factoryInstance == nullptr){
factoryInstance = new MutexFactory();
}
return MutexFactory::factoryInstance;
}
MutexIF* MutexFactory::createMutex() {
return new Mutex();
}
void MutexFactory::deleteMutex(MutexIF* mutex) {
delete mutex;
}

176
osal/host/PeriodicTask.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "Mutex.h"
#include "PeriodicTask.h"
#include "../../ipc/MutexFactory.h"
#include "../../serviceinterface/ServiceInterfaceStream.h"
#include "../../tasks/ExecutableObjectIF.h"
#include <thread>
#include <chrono>
#if defined(WIN32)
#include <windows.h>
#elif defined(LINUX)
#include <pthread.h>
#endif
PeriodicTask::PeriodicTask(const char *name, TaskPriority setPriority,
TaskStackSize setStack, TaskPeriod setPeriod,
void (*setDeadlineMissedFunc)()) :
started(false), taskName(name), period(setPeriod),
deadlineMissedFunc(setDeadlineMissedFunc) {
// It is propably possible to set task priorities by using the native
// task handles for Windows / Linux
mainThread = std::thread(&PeriodicTask::taskEntryPoint, this, this);
#if defined(WIN32)
/* List of possible priority classes:
* https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/
* nf-processthreadsapi-setpriorityclass
* And respective thread priority numbers:
* https://docs.microsoft.com/en-us/windows/
* win32/procthread/scheduling-priorities */
int result = SetPriorityClass(
reinterpret_cast<HANDLE>(mainThread.native_handle()),
ABOVE_NORMAL_PRIORITY_CLASS);
if(result != 0) {
sif::error << "PeriodicTask: Windows SetPriorityClass failed with code "
<< GetLastError() << std::endl;
}
result = SetThreadPriority(
reinterpret_cast<HANDLE>(mainThread.native_handle()),
THREAD_PRIORITY_NORMAL);
if(result != 0) {
sif::error << "PeriodicTask: Windows SetPriorityClass failed with code "
<< GetLastError() << std::endl;
}
#elif defined(LINUX)
// we can just copy and paste the code from linux here.
#endif
}
PeriodicTask::~PeriodicTask(void) {
//Do not delete objects, we were responsible for ptrs only.
terminateThread = true;
if(mainThread.joinable()) {
mainThread.join();
}
delete this;
}
void PeriodicTask::taskEntryPoint(void* argument) {
PeriodicTask *originalTask(reinterpret_cast<PeriodicTask*>(argument));
if (not originalTask->started) {
// we have to suspend/block here until the task is started.
// if semaphores are implemented, use them here.
std::unique_lock<std::mutex> lock(initMutex);
initCondition.wait(lock);
}
this->taskFunctionality();
sif::debug << "PeriodicTask::taskEntryPoint: "
"Returned from taskFunctionality." << std::endl;
}
ReturnValue_t PeriodicTask::startTask() {
started = true;
// Notify task to start.
std::lock_guard<std::mutex> lock(initMutex);
initCondition.notify_one();
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t PeriodicTask::sleepFor(uint32_t ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
return HasReturnvaluesIF::RETURN_OK;
}
void PeriodicTask::taskFunctionality() {
std::chrono::milliseconds periodChrono(static_cast<uint32_t>(period*1000));
auto currentStartTime {
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
};
auto nextStartTime{ currentStartTime };
/* Enter the loop that defines the task behavior. */
for (;;) {
if(terminateThread.load()) {
break;
}
for (ObjectList::iterator it = objectList.begin();
it != objectList.end(); ++it) {
(*it)->performOperation();
}
if(not delayForInterval(&currentStartTime, periodChrono)) {
sif::warning << "PeriodicTask: " << taskName <<
" missed deadline!\n" << std::flush;
if(deadlineMissedFunc != nullptr) {
this->deadlineMissedFunc();
}
}
}
}
ReturnValue_t PeriodicTask::addComponent(object_id_t object) {
ExecutableObjectIF* newObject = objectManager->get<ExecutableObjectIF>(
object);
if (newObject == nullptr) {
return HasReturnvaluesIF::RETURN_FAILED;
}
objectList.push_back(newObject);
return HasReturnvaluesIF::RETURN_OK;
}
uint32_t PeriodicTask::getPeriodMs() const {
return period * 1000;
}
bool PeriodicTask::delayForInterval(chron_ms* previousWakeTimeMs,
const chron_ms interval) {
bool shouldDelay = false;
//Get current wakeup time
auto currentStartTime =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
/* Generate the tick time at which the task wants to wake. */
auto nextTimeToWake_ms = (*previousWakeTimeMs) + interval;
if (currentStartTime < *previousWakeTimeMs) {
/* The tick count has overflowed since this function was
lasted called. In this case the only time we should ever
actually delay is if the wake time has also overflowed,
and the wake time is greater than the tick time. When this
is the case it is as if neither time had overflowed. */
if ((nextTimeToWake_ms < *previousWakeTimeMs)
&& (nextTimeToWake_ms > currentStartTime)) {
shouldDelay = true;
}
} else {
/* The tick time has not overflowed. In this case we will
delay if either the wake time has overflowed, and/or the
tick time is less than the wake time. */
if ((nextTimeToWake_ms < *previousWakeTimeMs)
|| (nextTimeToWake_ms > currentStartTime)) {
shouldDelay = true;
}
}
/* Update the wake time ready for the next call. */
(*previousWakeTimeMs) = nextTimeToWake_ms;
if (shouldDelay) {
auto sleepTime = std::chrono::duration_cast<std::chrono::milliseconds>(
nextTimeToWake_ms - currentStartTime);
std::this_thread::sleep_for(sleepTime);
return true;
}
//We are shifting the time in case the deadline was missed like rtems
(*previousWakeTimeMs) = currentStartTime;
return false;
}

123
osal/host/PeriodicTask.h Normal file
View File

@ -0,0 +1,123 @@
#ifndef FRAMEWORK_OSAL_HOST_PERIODICTASK_H_
#define FRAMEWORK_OSAL_HOST_PERIODICTASK_H_
#include "../../objectmanager/ObjectManagerIF.h"
#include "../../tasks/PeriodicTaskIF.h"
#include "../../tasks/Typedef.h"
#include <vector>
#include <thread>
#include <condition_variable>
#include <atomic>
class ExecutableObjectIF;
/**
* @brief This class represents a specialized task for
* periodic activities of multiple objects.
* @details
*
* @ingroup task_handling
*/
class PeriodicTask: public PeriodicTaskIF {
public:
/**
* @brief Standard constructor of the class.
* @details
* The class is initialized without allocated objects. These need to be
* added with #addComponent.
* @param priority
* @param stack_size
* @param setPeriod
* @param setDeadlineMissedFunc
* The function pointer to the deadline missed function that shall be
* assigned.
*/
PeriodicTask(const char *name, TaskPriority setPriority, TaskStackSize setStack,
TaskPeriod setPeriod,void (*setDeadlineMissedFunc)());
/**
* @brief Currently, the executed object's lifetime is not coupled with
* the task object's lifetime, so the destructor is empty.
*/
virtual ~PeriodicTask(void);
/**
* @brief The method to start the task.
* @details The method starts the task with the respective system call.
* Entry point is the taskEntryPoint method described below.
* The address of the task object is passed as an argument
* to the system call.
*/
ReturnValue_t startTask(void);
/**
* Adds an object to the list of objects to be executed.
* The objects are executed in the order added.
* @param object Id of the object to add.
* @return
* -@c RETURN_OK on success
* -@c RETURN_FAILED if the object could not be added.
*/
ReturnValue_t addComponent(object_id_t object);
uint32_t getPeriodMs() const;
ReturnValue_t sleepFor(uint32_t ms);
protected:
using chron_ms = std::chrono::milliseconds;
bool started;
//!< Typedef for the List of objects.
typedef std::vector<ExecutableObjectIF*> ObjectList;
std::thread mainThread;
std::atomic<bool> terminateThread = false;
/**
* @brief This attribute holds a list of objects to be executed.
*/
ObjectList objectList;
std::condition_variable initCondition;
std::mutex initMutex;
std::string taskName;
/**
* @brief The period of the task.
* @details
* The period determines the frequency of the task's execution.
* It is expressed in clock ticks.
*/
TaskPeriod period;
/**
* @brief The pointer to the deadline-missed function.
* @details
* This pointer stores the function that is executed if the task's deadline
* is missed. So, each may react individually on a timing failure.
* The pointer may be NULL, then nothing happens on missing the deadline.
* The deadline is equal to the next execution of the periodic task.
*/
void (*deadlineMissedFunc)(void);
/**
* @brief This is the function executed in the new task's context.
* @details
* It converts the argument back to the thread object type and copies the
* class instance to the task context.
* The taskFunctionality method is called afterwards.
* @param A pointer to the task object itself is passed as argument.
*/
void taskEntryPoint(void* argument);
/**
* @brief The function containing the actual functionality of the task.
* @details
* The method sets and starts the task's period, then enters a loop that is
* repeated as long as the isRunning attribute is true. Within the loop,
* all performOperation methods of the added objects are called. Afterwards
* the checkAndRestartPeriod system call blocks the task until the next
* period. On missing the deadline, the deadlineMissedFunction is executed.
*/
void taskFunctionality(void);
bool delayForInterval(chron_ms * previousWakeTimeMs,
const chron_ms interval);
};
#endif /* PERIODICTASK_H_ */

View File

@ -0,0 +1,41 @@
#include "../../ipc/QueueFactory.h"
#include "../../osal/host/MessageQueue.h"
#include "../../serviceinterface/ServiceInterfaceStream.h"
#include <cstring>
QueueFactory* QueueFactory::factoryInstance = nullptr;
ReturnValue_t MessageQueueSenderIF::sendMessage(MessageQueueId_t sendTo,
MessageQueueMessageIF* message, MessageQueueId_t sentFrom,
bool ignoreFault) {
return MessageQueue::sendMessageFromMessageQueue(sendTo,message,
sentFrom,ignoreFault);
return HasReturnvaluesIF::RETURN_OK;
}
QueueFactory* QueueFactory::instance() {
if (factoryInstance == nullptr) {
factoryInstance = new QueueFactory;
}
return factoryInstance;
}
QueueFactory::QueueFactory() {
}
QueueFactory::~QueueFactory() {
}
MessageQueueIF* QueueFactory::createMessageQueue(uint32_t messageDepth,
size_t maxMessageSize) {
// A thread-safe queue can be implemented by using a combination
// of std::queue and std::mutex. This uses dynamic memory allocation
// which could be alleviated by using a custom allocator, external library
// (etl::queue) or simply using std::queue, we're on a host machine anyway.
return new MessageQueue(messageDepth, maxMessageSize);
}
void QueueFactory::deleteMessageQueue(MessageQueueIF* queue) {
delete queue;
}

View File

@ -0,0 +1,52 @@
#include "QueueMapManager.h"
#include "../../ipc/MutexFactory.h"
#include "../../ipc/MutexHelper.h"
QueueMapManager* QueueMapManager::mqManagerInstance = nullptr;
QueueMapManager::QueueMapManager() {
mapLock = MutexFactory::instance()->createMutex();
}
QueueMapManager* QueueMapManager::instance() {
if (mqManagerInstance == nullptr){
mqManagerInstance = new QueueMapManager();
}
return QueueMapManager::mqManagerInstance;
}
ReturnValue_t QueueMapManager::addMessageQueue(
MessageQueueIF* queueToInsert, MessageQueueId_t* id) {
// Not thread-safe, but it is assumed all message queues are created
// at software initialization now. If this is to be made thread-safe in
// the future, it propably would be sufficient to lock the increment
// operation here
uint32_t currentId = queueCounter++;
auto returnPair = queueMap.emplace(currentId, queueToInsert);
if(not returnPair.second) {
// this should never happen for the atomic variable.
sif::error << "QueueMapManager: This ID is already inside the map!"
<< std::endl;
return HasReturnvaluesIF::RETURN_FAILED;
}
if (id != nullptr) {
*id = currentId;
}
return HasReturnvaluesIF::RETURN_OK;
}
MessageQueueIF* QueueMapManager::getMessageQueue(
MessageQueueId_t messageQueueId) const {
MutexHelper(mapLock, MutexIF::TimeoutType::WAITING, 50);
auto queueIter = queueMap.find(messageQueueId);
if(queueIter != queueMap.end()) {
return queueIter->second;
}
else {
sif::warning << "QueueMapManager::getQueueHandle: The ID" <<
messageQueueId << " does not exists in the map" << std::endl;
return nullptr;
}
}

View File

@ -0,0 +1,47 @@
#ifndef FSFW_OSAL_HOST_QUEUEMAPMANAGER_H_
#define FSFW_OSAL_HOST_QUEUEMAPMANAGER_H_
#include "../../ipc/MessageQueueSenderIF.h"
#include "../../osal/host/MessageQueue.h"
#include <unordered_map>
#include <atomic>
using QueueMap = std::unordered_map<MessageQueueId_t, MessageQueueIF*>;
/**
* An internal map to map message queue IDs to message queues.
* This propably should be a singleton..
*/
class QueueMapManager {
public: