diff --git a/esp-idf/dpp-esp32devkitc/components/qp-c/CMakeLists.txt b/esp-idf/dpp-esp32devkitc/components/qp-c/CMakeLists.txt index 4a7e9e94..e7a67a52 100644 --- a/esp-idf/dpp-esp32devkitc/components/qp-c/CMakeLists.txt +++ b/esp-idf/dpp-esp32devkitc/components/qp-c/CMakeLists.txt @@ -7,14 +7,14 @@ set(required_modules "freertos") set(qpc_path "../../../../../") -set(src_dirs ${qpc_path}/ports/esp-idf +set(src_dirs ports/esp-idf ${qpc_path}/src/qf ${qpc_path}/include ) set(include_dirs ${qpc_path}/include - ${qpc_path}ports/esp-idf ${qpc_path}/src + ports/esp-idf ) if(CONFIG_QPC_QSPY_ENABLE) diff --git a/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/README.md b/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/README.md new file mode 100644 index 00000000..f9ef6bdd --- /dev/null +++ b/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/README.md @@ -0,0 +1,29 @@ +This directory contains the "experimental" port to the [Espressif ESP-IDF][1] +IoT Framework, which is loosely based on the [FreeRTOS kernel][2]. + + +--------------------------------------------------------------------------- +# About QP/C Port to ESP-IDF +"Experimental" means that the port has not been thouroughly tested at +Quantum Leaps and no working examples are provided. + + +--------------------------------------------------------------------------- +# About Espressif ESP-IDF + +The [Espressif ESP-IDF][1] is based on a +[significantly changed version of the FreeRTOS kernel][3] +developed by Espressif to support the ESP32 multi-core CPUs (see [ESP-IDF][1]). + +The Espressif version of FreeRTOS is __NOT__ compatible with the baseline [FreeRTOS][2] +and it needs to be treated as a separate RTOS kernel. According to the comments +in the Espressif source code, FreeRTOS-ESP-IDF is based on FreeRTOS V8.2.0, but +apparently FreeRTOS-ESP32 has been updated with the newer features introduced to +the original FreeRTOS in the later versions. For example, FreeRTOS-ESP-IDF supports +the "static allocation", first introduced in baseline FreeRTOS V9.x. This QP port +to FreeRTOS-ESP-IDF takes advantage of the "static allocation". + + +[1]: https://www.espressif.com/en/products/sdks/esp-idf +[2]: https://freertos.org +[3]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos.html diff --git a/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/qf_port.c b/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/qf_port.c new file mode 100644 index 00000000..bf8436cd --- /dev/null +++ b/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/qf_port.c @@ -0,0 +1,805 @@ +//============================================================================ +// QP/C Real-Time Embedded Framework (RTEF) +// Copyright (C) 2005 Quantum Leaps, LLC. All rights reserved. +// +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-QL-commercial +// +// This software is dual-licensed under the terms of the open source GNU +// General Public License version 3 (or any later version), or alternatively, +// under the terms of one of the closed source Quantum Leaps commercial +// licenses. +// +// The terms of the open source GNU General Public License version 3 +// can be found at: +// +// The terms of the closed source Quantum Leaps commercial licenses +// can be found at: +// +// Redistributions in source code must retain this top-level comment block. +// Plagiarizing this software to sidestep the license obligations is illegal. +// +// Contact information: +// +// +//============================================================================ +//! @date Last updated on: 2024-05-08 +//! @version Last updated for: @ref qpc_7_3_4 +//! +//! @file +//! @brief "Experimental" QF/C port to Espressif ESP-IDF (version 5.x) + +#define QP_IMPL // this is QP implementation +#include "qp_port.h" // QP port +#include "qp_pkg.h" // QP package-level interface +#include "qsafe.h" // QP Functional Safety (FuSa) Subsystem +#ifdef Q_SPY // QS software tracing enabled? + #include "qs_port.h" // QS port + #include "qs_pkg.h" // QS package-scope internal interface +#else + #include "qs_dummy.h" // disable the QS software tracing +#endif // Q_SPY + +Q_DEFINE_THIS_MODULE("qf_port") + +#if ( configSUPPORT_STATIC_ALLOCATION == 0 ) +#error "This QP/C port to FreeRTOS requires configSUPPORT_STATIC_ALLOCATION " +#endif + +#if ( configMAX_PRIORITIES < QF_MAX_ACTIVE ) +#error "FreeRTOS configMAX_PRIORITIES must not be less than QF_MAX_ACTIVE" +#endif + +// Global objects ---------------------------------------------------------- +PRIVILEGED_DATA portMUX_TYPE QF_esp32mux = portMUX_INITIALIZER_UNLOCKED; + +// Local objects ----------------------------------------------------------- +static void task_function(void *pvParameters); // FreeRTOS task signature + +// The following macro provides the number of free slots in the FreeRTOS +// queue. +// +// NOTE1: +// The official FreeRTOS API uxQueueSpacesAvailable() is not used +// here, because that API uses task-level critical section internally. +// Instead, the free slots calculation happens here in already +// established critical section. Unfortunately, the bizarre "information +// obfuscating" policy of FreeRTOS (incorrectly called "information +// hiding") forces the use of the StaticQueue_t with "dummy" members. +// This could potentially break in the future releases of FreeRTOS. +// +// Currently, the correspondence between xQUEUE and StaticQueue_t +// is as follows (see queue.c and FreeRTOS.h, respectively): +// +// xQUEUE.uxMessagesWaiting == StaticQueue_t.uxDummy4[0]; +// xQUEUE.uxLength == StaticQueue_t.uxDummy4[1]; +// +#define FREERTOS_QUEUE_GET_FREE(me_) \ + ((me_)->osObject.uxDummy4[1] - (me_)->osObject.uxDummy4[0]) + +//============================================================================ +void QF_init(void) { + // empty for ESP-IDF +} +//............................................................................ +int_t QF_run(void) { + QF_onStartup(); // the startup callback (configure/enable interrupts) + + // produce the QS_QF_RUN trace record + QF_CRIT_STAT + QF_CRIT_ENTRY(); + QS_BEGIN_PRE_(QS_QF_RUN, 0U) + QS_END_PRE_() + QF_CRIT_EXIT(); + + return 0; // dummy return to make the compiler happy +} +//............................................................................ +void QF_stop(void) { + QF_onCleanup(); // cleanup callback +} + +//............................................................................ +static void task_function(void *pvParameters) { // FreeRTOS task signature + QActive *act = (QActive *)pvParameters; + +#ifdef QACTIVE_CAN_STOP + while (act->eQueue != (QueueHandle_t)0) +#else + for (;;) // for-ever +#endif + { + QEvt const *e = QActive_get_(act); // wait for event + QASM_DISPATCH(&act->super, e, act->prio); // dispatch to the SM + QF_gc(e); // check if the event is garbage, and collect it if so + } +#ifdef QACTIVE_CAN_STOP + QActive_unregister_(act); // remove this object from the framewrok + vTaskDelete((TaskHandle_t)0); // delete this FreeRTOS task +#endif +} + +//............................................................................ +void QActive_start_(QActive * const me, + QPrioSpec const prioSpec, + QEvt const * * const qSto, + uint_fast16_t const qLen, + void * const stkSto, + uint_fast16_t const stkSize, + void const * const par) +{ + QF_CRIT_STAT + QF_CRIT_ENTRY(); + // precondition: + // - queue storage must be provided + // - queue size must be provided + // - stack storage must be provided + // - stack size must be provided + Q_REQUIRE_INCRIT(200, + (qSto != (QEvt const **)0) && (qLen > 0U) + && (stkSto != (void *)0) && (stkSize > 0U)); + QF_CRIT_EXIT(); + + // create FreeRTOS message queue + me->eQueue = xQueueCreateStatic( + (UBaseType_t)qLen, // length of the queue + (UBaseType_t)sizeof(QEvt *), // element size + (uint8_t *)qSto, // storage buffer + &me->osObject); // static queue buffer + QF_CRIT_ENTRY(); + Q_ASSERT_INCRIT(210, me->eQueue != (QueueHandle_t)0); + QF_CRIT_EXIT(); + + me->prio = (uint8_t)(prioSpec & 0xFFU); // QF-priority of the AO + me->pthre = 0U; // preemption-threshold (not used for AO registration) + QActive_register_(me); // register this AO + + // top-most initial tran. (virtual call) + (*me->super.vptr->init)(&me->super, par, me->prio); + QS_FLUSH(); // flush the QS trace buffer to the host + + // task name provided by the user in QActive_setAttr() or default name + char const *taskName = (me->thread.pxDummy1 != (void *)0) + ? (char const *)me->thread.pxDummy1 + : (char const *)"AO"; + + // The FreeRTOS priority of the AO thread can be specificed in two ways: + // + // 1. Implictily based on the AO's priority (by the forumla specified + // in the macro FREERTOS_TASK_PRIO(), see qp_port.h). This option + // is chosen, when the higher-byte of the prioSpec parameter is set + // to zero. + // + // 2. Explicitly as the higher-byte of the prioSpec parameter. + // This option is chosen when the prioSpec parameter is not-zero. + // For example, Q_PRIO(10U, 5U) will explicitly specify AO priority + // as 10 and FreeRTOS priority as 5. + // + // NOTE: The explicit FreeRTOS priority is NOT sanity-checked, + // so it is the responsibility of the application to ensure that + // it is consistent witht the AO's priority. An example of + // inconsistent setting would be assigning FreeRTOS priorities that + // would result in a different relative priritization of AO's threads + // than indicated by the AO priorities assigned. + // + UBaseType_t freertos_prio = (prioSpec >> 8U); + if (freertos_prio == 0U) { + freertos_prio = FREERTOS_TASK_PRIO(me->prio); + } + + // statically create the FreeRTOS task for the AO + TaskHandle_t task = xTaskCreateStaticPinnedToCore( + &task_function, // the task function + taskName, // the name of the task + stkSize/sizeof(portSTACK_TYPE), // stack length + (void *)me, // the 'pvParameters' parameter + freertos_prio, // FreeRTOS priority + (StackType_t *)stkSto, // stack storage + &me->thread, // task buffer + QPC_CPU_NUM); // CPU number + + QF_CRIT_ENTRY(); + Q_ASSERT_INCRIT(220, task != (TaskHandle_t)0); + QF_CRIT_EXIT(); +} +//............................................................................ +#ifdef QACTIVE_CAN_STOP +void QActive_stop(QActive * const me) { + QActive_unsubscribeAll(me); // unsubscribe from all events + me->eQueue = (QueueHandle_t)0; // stop the thread (see task_function()) +} +#endif +//............................................................................ +void QActive_setAttr(QActive *const me, uint32_t attr1, void const *attr2) { + QF_CRIT_STAT + QF_CRIT_ENTRY(); + // this function must be called before QACTIVE_START(), + // which implies that me->thread.pxDummy1 must not be used yet; + Q_REQUIRE_INCRIT(300, me->thread.pxDummy1 == (void *)0); + switch (attr1) { + case TASK_NAME_ATTR: + // temporarily store the name, cast 'const' away + me->thread.pxDummy1 = (void *)attr2; + break; + // ... + } + QF_CRIT_EXIT(); +} + +//============================================================================ +bool IRAM_ATTR QActive_post_(QActive * const me, QEvt const * const e, + uint_fast16_t const margin, void const * const sender) +{ +#ifndef Q_SPY + Q_UNUSED_PAR(sender); +#endif + + QF_CRIT_STAT + QF_CRIT_ENTRY(); + + // find the number of free slots available in the queue + uint_fast16_t const nFree = (uint_fast16_t)FREERTOS_QUEUE_GET_FREE(me); + + bool status; + if (margin == QF_NO_MARGIN) { + if (nFree > 0U) { + status = true; // can post + } + else { + status = false; // cannot post + Q_ERROR_INCRIT(510); // must be able to post the event + } + } + else if (nFree > margin) { + status = true; // can post + } + else { + status = false; // cannot post + } + + if (status) { // can post the event? + + QS_BEGIN_PRE_(QS_QF_ACTIVE_POST, me->prio) + QS_TIME_PRE_(); // timestamp + QS_OBJ_PRE_(sender); // the sender object + QS_SIG_PRE_(e->sig); // the signal of the event + QS_OBJ_PRE_(me); // this active object (recipient) + QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_); // pool Id&ref Count + QS_EQC_PRE_((QEQueueCtr)nFree); // # free entries + QS_EQC_PRE_(0U); // min # free entries (unknown) + QS_END_PRE_() + + if (QEvt_getPoolNum_(e) != 0U) { // is it a pool event? + QEvt_refCtr_inc_(e); // increment the reference counter + } + QF_CRIT_EXIT(); + + BaseType_t err = xQueueSendToBack( + me->eQueue, (void const *)&e, (TickType_t)0); + + // posting to the FreeRTOS message queue must succeed, see NOTE3 + QF_CRIT_ENTRY(); + Q_ASSERT_INCRIT(520, err == pdPASS); + } + else { + + QS_BEGIN_PRE_(QS_QF_ACTIVE_POST_ATTEMPT, me->prio) + QS_TIME_PRE_(); // timestamp + QS_OBJ_PRE_(sender); // the sender object + QS_SIG_PRE_(e->sig); // the signal of the event + QS_OBJ_PRE_(me); // this active object (recipient) + QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_); // pool Id&ref Count + QS_EQC_PRE_((QEQueueCtr)nFree); // # free entries + QS_EQC_PRE_(margin); // margin requested + QS_END_PRE_() + } + QF_CRIT_EXIT(); + + return status; +} +//............................................................................ +void IRAM_ATTR QActive_postLIFO_(QActive * const me, QEvt const * const e) { + QF_CRIT_STAT + QF_CRIT_ENTRY(); + + QS_BEGIN_PRE_(QS_QF_ACTIVE_POST_LIFO, me->prio) + QS_TIME_PRE_(); // timestamp + QS_SIG_PRE_(e->sig); // the signal of this event + QS_OBJ_PRE_(me); // this active object + QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_); // pool Id&ref Count + QS_EQC_PRE_((QEQueueCtr)FREERTOS_QUEUE_GET_FREE(me)); // # free + QS_EQC_PRE_(0U); // min # free entries (unknown) + QS_END_PRE_() + + if (QEvt_getPoolNum_(e) != 0U) { // is it a pool event? + QEvt_refCtr_inc_(e); // increment the reference counter + } + QF_CRIT_EXIT(); + + BaseType_t err = xQueueSendToFront( + me->eQueue, (void const *)&e, (TickType_t)0); + + // LIFO posting to the FreeRTOS queue must succeed, see NOTE3 + QF_CRIT_ENTRY(); + Q_ASSERT_INCRIT(610, err == pdPASS); + QF_CRIT_EXIT(); +} +//............................................................................ +QEvt const *QActive_get_(QActive * const me) { + QEvt const *e; + xQueueReceive(me->eQueue, (void *)&e, portMAX_DELAY); + + QS_CRIT_STAT + QS_CRIT_ENTRY(); + QS_BEGIN_PRE_(QS_QF_ACTIVE_GET, me->prio) + QS_TIME_PRE_(); // timestamp + QS_SIG_PRE_(e->sig); // the signal of this event + QS_OBJ_PRE_(me); // this active object + QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_); // pool Id&ref Count + QS_EQC_PRE_((QEQueueCtr)FREERTOS_QUEUE_GET_FREE(me)); // # free + QS_END_PRE_() + QS_CRIT_EXIT(); + + return e; +} + +//============================================================================ +// The "FromISR" QP APIs for the FreeRTOS port... +bool IRAM_ATTR QActive_postFromISR_(QActive * const me, QEvt const * const e, + uint_fast16_t const margin, + BaseType_t * const pxHigherPriorityTaskWoken, + void const * const sender) +{ + portENTER_CRITICAL_ISR(&QF_esp32mux); + + // find the number of free slots available in the queue + uint_fast16_t const nFree = (uint_fast16_t)FREERTOS_QUEUE_GET_FREE(me); + + bool status; + if (margin == QF_NO_MARGIN) { + if (nFree > 0U) { + status = true; // can post + } + else { + status = false; // cannot post + Q_ERROR_INCRIT(810); // must be able to post the event + } + } + else if (nFree > margin) { + status = true; // can post + } + else { + status = false; // cannot post + } + + if (status) { // can post the event? + + QS_BEGIN_PRE_(QS_QF_ACTIVE_POST, me->prio) + QS_TIME_PRE_(); // timestamp + QS_OBJ_PRE_(sender); // the sender object + QS_SIG_PRE_(e->sig); // the signal of the event + QS_OBJ_PRE_(me); // this active object (recipient) + QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_); // pool Id&ref Count + QS_EQC_PRE_(nFree); // # free entries available + QS_EQC_PRE_(0U); // min # free entries (unknown) + QS_END_PRE_() + + if (QEvt_getPoolNum_(e) != 0U) { // is it a pool event? + QEvt_refCtr_inc_(e); // increment the reference counter + } + portEXIT_CRITICAL_ISR(&QF_esp32mux); + + BaseType_t err = xQueueSendToBackFromISR(me->eQueue, + (void const *)&e, + pxHigherPriorityTaskWoken); + + // posting to the FreeRTOS message queue must succeed + portENTER_CRITICAL_ISR(&QF_esp32mux); + Q_ASSERT_INCRIT(820, err == pdPASS); + portEXIT_CRITICAL_ISR(&QF_esp32mux); + } + else { + + QS_BEGIN_PRE_(QS_QF_ACTIVE_POST_ATTEMPT, me->prio) + QS_TIME_PRE_(); // timestamp + QS_OBJ_PRE_(sender); // the sender object + QS_SIG_PRE_(e->sig); // the signal of the event + QS_OBJ_PRE_(me); // this active object (recipient) + QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_); // pool Id&ref Count + QS_EQC_PRE_(nFree); // # free entries available + QS_EQC_PRE_(margin); // margin requested + QS_END_PRE_() + portEXIT_CRITICAL_ISR(&QF_esp32mux); + + QF_gcFromISR(e); // recycle the event to avoid a leak + } + + return status; +} +//............................................................................ +void IRAM_ATTR QActive_publishFromISR_(QEvt const * const e, + BaseType_t * const pxHigherPriorityTaskWoken, + void const * const sender) +{ + QSignal const sig = e->sig; + + portENTER_CRITICAL_ISR(&QF_esp32mux); + + //! @pre the published signal must be within the configured range + Q_REQUIRE_INCRIT(500, sig < (QSignal)QActive_maxPubSignal_); + Q_REQUIRE_INCRIT(502, + QPSet_verify_(&QActive_subscrList_[sig].set, + &QActive_subscrList_[sig].set_dis)); + + QS_BEGIN_PRE_(QS_QF_PUBLISH, 0U) + QS_TIME_PRE_(); // the timestamp + QS_OBJ_PRE_(sender); // the sender object + QS_SIG_PRE_(sig); // the signal of the event + QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_);// pool-Id & ref-Count + QS_END_PRE_() + + // is it a dynamic event? + if (QEvt_getPoolNum_(e) != 0U) { + // NOTE: The reference counter of a dynamic event is incremented to + // prevent premature recycling of the event while the multicasting + // is still in progress. At the end of the function, the garbage + // collector step (QF_gcFromISR()) decrements the reference counter + // and recycles the event if the counter drops to zero. This covers + // the case when the event was published without any subscribers. + QEvt_refCtr_inc_(e); + } + + // make a local, modifiable copy of the subscriber list + QPSet subscrSet = QActive_subscrList_[sig].set; + portEXIT_CRITICAL_ISR(&QF_esp32mux); + + if (QPSet_notEmpty(&subscrSet)) { // any subscribers? + // the highest-prio subscriber + uint_fast8_t p = QPSet_findMax(&subscrSet); + + // no need to lock the scheduler in the ISR context + do { // loop over all subscribers + // the prio of the AO must be registered with the framework + portENTER_CRITICAL_ISR(&QF_esp32mux); + Q_ASSERT_INCRIT(510, QActive_registry_[p] != (QActive *)0); + portEXIT_CRITICAL_ISR(&QF_esp32mux); + + // QACTIVE_POST_FROM_ISR() asserts if the queue overflows + QACTIVE_POST_FROM_ISR(QActive_registry_[p], e, + pxHigherPriorityTaskWoken, sender); + + QPSet_remove(&subscrSet, p); // remove the handled subscriber + if (QPSet_notEmpty(&subscrSet)) { // still more subscribers? + p = QPSet_findMax(&subscrSet); // the highest-prio subscriber + } + else { + p = 0U; // no more subscribers + } + } while (p != 0U); + // no need to unlock the scheduler in the ISR context + } + + // The following garbage collection step decrements the reference counter + // and recycles the event if the counter drops to zero. This covers both + // cases when the event was published with or without any subscribers. + QF_gcFromISR(e); +} +//............................................................................ +void IRAM_ATTR QTimeEvt_tickFromISR_(uint_fast8_t const tickRate, + BaseType_t * const pxHigherPriorityTaskWoken, + void const * const sender) +{ + portENTER_CRITICAL_ISR(&QF_esp32mux); + + QTimeEvt *prev = &QTimeEvt_timeEvtHead_[tickRate]; + + QS_BEGIN_PRE_(QS_QF_TICK, 0U) + ++prev->ctr; + QS_TEC_PRE_(prev->ctr); // tick ctr + QS_U8_PRE_(tickRate); // tick rate + QS_END_PRE_() + + // scan the linked-list of time events at this rate... + for (;;) { + QTimeEvt *t = prev->next; // advance down the time evt. list + + // end of the list? + if (t == (QTimeEvt *)0) { + + // any new time events armed since the last QTimeEvt_tick_()? + if (QTimeEvt_timeEvtHead_[tickRate].act != (void *)0) { + + // sanity check + Q_ASSERT_INCRIT(610, prev != (QTimeEvt *)0); + prev->next = (QTimeEvt *)QTimeEvt_timeEvtHead_[tickRate].act; + QTimeEvt_timeEvtHead_[tickRate].act = (void *)0; + t = prev->next; // switch to the new list + } + else { + break; // all currently armed time evts. processed + } + } + + // time event scheduled for removal? + if (t->ctr == 0U) { + prev->next = t->next; + // mark time event 't' as NOT linked + t->super.refCtr_ &= (uint8_t)(~QTE_IS_LINKED); + // do NOT advance the prev pointer + // exit crit. section to reduce latency + portEXIT_CRITICAL_ISR(&QF_esp32mux); + } + else { + --t->ctr; + + // is time evt about to expire? + if (t->ctr == 0U) { + QActive *act = (QActive *)t->act; // temp. for volatile + + // periodic time evt? + if (t->interval != 0U) { + t->ctr = t->interval; // rearm the time event + prev = t; // advance to this time event + } + // one-shot time event: automatically disarm + else { + prev->next = t->next; + // mark time event 't' as NOT linked + t->super.refCtr_ &= (uint8_t)(~QTE_IS_LINKED); + // do NOT advance the prev pointer + + QS_BEGIN_PRE_(QS_QF_TIMEEVT_AUTO_DISARM, act->prio) + QS_OBJ_PRE_(t); // this time event object + QS_OBJ_PRE_(act); // the target AO + QS_U8_PRE_(tickRate); // tick rate + QS_END_PRE_() + } + + QS_BEGIN_PRE_(QS_QF_TIMEEVT_POST, act->prio) + QS_TIME_PRE_(); // timestamp + QS_OBJ_PRE_(t); // the time event object + QS_SIG_PRE_(t->super.sig); // signal of time event + QS_OBJ_PRE_(act); // the target AO + QS_U8_PRE_(tickRate); // tick rate + QS_END_PRE_() + + // exit critical section before posting + portEXIT_CRITICAL_ISR(&QF_esp32mux); + + // QACTIVE_POST_FROM_ISR() asserts if the queue overflows + QACTIVE_POST_FROM_ISR(act, &t->super, + pxHigherPriorityTaskWoken, + sender); + } + else { + prev = t; // advance to this time event + // exit crit. section to reduce latency + portEXIT_CRITICAL_ISR(&QF_esp32mux); + } + } + // re-enter crit. section to continue + portENTER_CRITICAL_ISR(&QF_esp32mux); + } + portEXIT_CRITICAL_ISR(&QF_esp32mux); +} +//............................................................................ +QEvt IRAM_ATTR *QF_newXFromISR_(uint_fast16_t const evtSize, + uint_fast16_t const margin, enum_t const sig) +{ + // find the poolId that fits the requested event size ... + uint_fast8_t idx; + for (idx = 0U; idx < QF_priv_.maxPool_; ++idx) { + if (evtSize <= QF_EPOOL_EVENT_SIZE_(QF_priv_.ePool_[idx])) { + break; + } + } + // cannot run out of registered pools + portENTER_CRITICAL_ISR(&QF_esp32mux); + Q_REQUIRE_INCRIT(700, idx < QF_priv_.maxPool_); + portEXIT_CRITICAL_ISR(&QF_esp32mux); + + // get e -- platform-dependent +#ifdef Q_SPY + QEvt *e = QMPool_getFromISR(&QF_priv_.ePool_[idx], + ((margin != QF_NO_MARGIN) ? margin : 0U), + (uint_fast8_t)QS_EP_ID + idx + 1U); +#else + QEvt *e = QMPool_getFromISR(&QF_priv_.ePool_[idx], + ((margin != QF_NO_MARGIN) ? margin : 0U), 0U); +#endif + + // was e allocated correctly? + if (e != (QEvt *)0) { + e->sig = (QSignal)sig; // set the signal + e->refCtr_ = 0U; // initialize the reference counter to 0 + e->evtTag_ = (uint8_t)(QEVT_MARKER | (idx + 1U)); // pool ID + +#ifdef Q_SPY + portENTER_CRITICAL_ISR(&QF_esp32mux); + QS_BEGIN_PRE_(QS_QF_NEW, + (uint_fast8_t)QS_EP_ID + idx + 1U) + QS_TIME_PRE_(); // timestamp + QS_EVS_PRE_(evtSize); // the size of the event + QS_SIG_PRE_(sig); // the signal of the event + QS_END_PRE_() + portEXIT_CRITICAL_ISR(&QF_esp32mux); +#endif // Q_SPY + } + else { // event cannot be allocated + // must tolerate bad alloc. + portENTER_CRITICAL_ISR(&QF_esp32mux); + Q_ASSERT_INCRIT(720, margin != QF_NO_MARGIN); + portEXIT_CRITICAL_ISR(&QF_esp32mux); + +#ifdef Q_SPY + portENTER_CRITICAL_ISR(&QF_esp32mux); + QS_BEGIN_PRE_(QS_QF_NEW_ATTEMPT, + (uint_fast8_t)QS_EP_ID + idx + 1U) + QS_TIME_PRE_(); // timestamp + QS_EVS_PRE_(evtSize); // the size of the event + QS_SIG_PRE_(sig); // the signal of the event + QS_END_PRE_() + portEXIT_CRITICAL_ISR(&QF_esp32mux); +#endif // Q_SPY + } + return e; // can't be NULL if we can't tolerate bad allocation +} +//............................................................................ +void IRAM_ATTR QF_gcFromISR(QEvt const * const e) { + // is it a dynamic event? + if (QEvt_getPoolNum_(e) != 0U) { + portENTER_CRITICAL_ISR(&QF_esp32mux); + + // isn't this the last ref? + if (e->refCtr_ > 1U) { + QEvt_refCtr_dec_(e); // decrement the ref counter + + QS_BEGIN_PRE_(QS_QF_GC_ATTEMPT, + (uint_fast8_t)QEvt_getPoolId_(e)) + QS_TIME_PRE_(); // timestamp + QS_SIG_PRE_(e->sig); // the signal of the event + QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_);//pool-Id&ref-Count + QS_END_PRE_() + + portEXIT_CRITICAL_ISR(&QF_esp32mux); + } + // this is the last reference to this event, recycle it + else { + uint_fast8_t idx = (uint_fast8_t)QEvt_getPoolNum_(e) - 1U; + + QS_BEGIN_PRE_(QS_QF_GC, (uint_fast8_t)QEvt_getPoolId_(e)) + QS_TIME_PRE_(); // timestamp + QS_SIG_PRE_(e->sig); // the signal of the event + QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_);//pool-Id&ref-Count + QS_END_PRE_() + + // pool ID must be in range + Q_ASSERT_INCRIT(810, idx < QF_priv_.maxPool_); + + portEXIT_CRITICAL_ISR(&QF_esp32mux); + +#ifdef Q_SPY + // cast 'const' away in (QEvt *)e is OK because it's a pool event + QMPool_putFromISR(&QF_priv_.ePool_[idx], (QEvt *)e, + (uint_fast8_t)QS_EP_ID + QEvt_getPoolId_(e)); +#else + QMPool_putFromISR(&QF_priv_.ePool_[idx], (QEvt *)e, 0U); +#endif + } + } +} +//............................................................................ +void IRAM_ATTR QMPool_putFromISR(QMPool * const me, void *block, + uint_fast8_t const qs_id) +{ +#ifndef Q_SPY + Q_UNUSED_PAR(qs_id); +#endif + + QFreeBlock * const fb = (QFreeBlock *)block; + + portENTER_CRITICAL_ISR(&QF_esp32mux); + + // precondition: + // - # free blocks cannot exceed the total # blocks and + // - the block pointer must be from this pool. + Q_REQUIRE_INCRIT(900, (me->nFree < me->nTot) + && (me->start <= fb) && (fb <= me->end)); + + fb->next = me->free_head; // link into list +#ifndef Q_UNSAFE + fb->next_dis = (uintptr_t)(~Q_UINTPTR_CAST_(fb->next)); +#endif + + me->free_head = block; // set as new head of the free list + ++me->nFree; // one more free block in this pool + + QS_BEGIN_PRE_(QS_QF_MPOOL_PUT, qs_id) + QS_TIME_PRE_(); // timestamp + QS_OBJ_PRE_(me); // this memory pool + QS_MPC_PRE_(me->nFree); // the number of free blocks in the pool + QS_END_PRE_() + + portEXIT_CRITICAL_ISR(&QF_esp32mux); +} +//............................................................................ +void *QMPool_getFromISR(QMPool * const me, uint_fast16_t const margin, + uint_fast8_t const qs_id) +{ +#ifndef Q_SPY + Q_UNUSED_PAR(qs_id); +#endif + + portENTER_CRITICAL_ISR(&QF_esp32mux); + + // have more free blocks than the requested margin? + QFreeBlock *fb; + if (me->nFree > (QMPoolCtr)margin) { + fb = me->free_head; // get a free block + + // the pool has some free blocks, so a free block must be available + Q_ASSERT_INCRIT(900, fb != (QFreeBlock *)0); + + QFreeBlock * const fb_next = fb->next; // fast temporary to avoid UB + + // the free block must have integrity (duplicate inverse storage) + Q_ASSERT_INCRIT(902, Q_UINTPTR_CAST_(fb_next) + == (uintptr_t)~fb->next_dis); + + // is the pool becoming empty? + --me->nFree; // one less free block + if (me->nFree == 0U) { + // pool is becoming empty, so the next free block must be NULL + Q_ASSERT_INCRIT(920, fb_next == (QFreeBlock *)0); + + me->nMin = 0U; // remember that the pool got empty + } + else { + // invariant + // The pool is not empty, so the next free-block pointer, + // so the next free block must be in range. + // + // NOTE: The next free block pointer can fall out of range + // when the client code writes past the memory block, thus + // corrupting the next block. + Q_ASSERT_INCRIT(930, + (me->start <= fb_next) && (fb_next <= me->end)); + + // is the number of free blocks the new minimum so far? + if (me->nMin > me->nFree) { + me->nMin = me->nFree; // remember the new minimum + } + } + + me->free_head = fb_next; // set the head to the next free block + + QS_BEGIN_PRE_(QS_QF_MPOOL_GET, qs_id) + QS_TIME_PRE_(); // timestamp + QS_OBJ_PRE_(me); // this memory pool + QS_MPC_PRE_(me->nFree); // # free blocks in the pool + QS_MPC_PRE_(me->nMin); // min # free blocks ever in the pool + QS_END_PRE_() + } + else { // don't have enough free blocks at this point + fb = (QFreeBlock *)0; + + QS_BEGIN_PRE_(QS_QF_MPOOL_GET_ATTEMPT, qs_id) + QS_TIME_PRE_(); // timestamp + QS_OBJ_PRE_(me); // this memory pool + QS_MPC_PRE_(me->nFree); // # free blocks in the pool + QS_MPC_PRE_(margin); // the requested margin + QS_END_PRE_() + } + portEXIT_CRITICAL_ISR(&QF_esp32mux); + + return fb; // return the block or NULL pointer to the caller +} + +//============================================================================ +// NOTE3: +// The event posting to FreeRTOS message queue occurs OUTSIDE critical section, +// which means that the remaining margin of available slots in the queue +// cannot be guaranteed. The problem is that interrupts and other tasks can +// preempt the event posting after checking the margin, but before actually +// posting the event to the queue. +// + diff --git a/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/qp_port.h b/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/qp_port.h new file mode 100644 index 00000000..cfdebe15 --- /dev/null +++ b/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/qp_port.h @@ -0,0 +1,269 @@ +//============================================================================ +// QP/C Real-Time Embedded Framework (RTEF) +// +// Q u a n t u m L e a P s +// ------------------------ +// Modern Embedded Software +// +// Copyright (C) 2005 Quantum Leaps, LLC. All rights reserved. +// +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-QL-commercial +// +// This software is dual-licensed under the terms of the open source GNU +// General Public License version 3 (or any later version), or alternatively, +// under the terms of one of the closed source Quantum Leaps commercial +// licenses. +// +// The terms of the open source GNU General Public License version 3 +// can be found at: +// +// The terms of the closed source Quantum Leaps commercial licenses +// can be found at: +// +// Redistributions in source code must retain this top-level comment block. +// Plagiarizing this software to sidestep the license obligations is illegal. +// +// Contact information: +// +// +//============================================================================ +//! @date Last updated on: 2024-05-08 +//! @version Last updated for: @ref qpc_7_3_4 +//! +//! @file +//! @brief QP/C port to Espressif ESP-IDF (version 5.x), Experimental, NOTE0 +//! +#ifndef QP_PORT_H_ +#define QP_PORT_H_ + +#include // Exact-width types. WG14/N843 C99 Standard +#include // Boolean type. WG14/N843 C99 Standard + +#ifdef QP_CONFIG +#include "qp_config.h" // external QP configuration +#endif + +// no-return function specifier (C11 Standard) +#define Q_NORETURN _Noreturn void + +// QActive event queue and thread types for Espressif ESP-IDF +#define QACTIVE_EQUEUE_TYPE QueueHandle_t +#define QACTIVE_OS_OBJ_TYPE StaticQueue_t +#define QACTIVE_THREAD_TYPE StaticTask_t + +// FreeRTOS requires the "FromISR" API in QP/C++ +#define QF_ISR_API 1 + +// QF interrupt disabling for FreeRTOS-ESP32 (task level), see NOTE2 +#define QF_INT_DISABLE() portENTER_CRITICAL(&QF_esp32mux) +#define QF_INT_ENABLE() portEXIT_CRITICAL(&QF_esp32mux) + +// QF critical section for FreeRTOS-ESP32 (task level), see NOTE2 +#define QF_CRIT_STAT +#define QF_CRIT_ENTRY() portENTER_CRITICAL(&QF_esp32mux) +#define QF_CRIT_EXIT() portEXIT_CRITICAL(&QF_esp32mux) + +// include files ------------------------------------------------------------- +#include "freertos/FreeRTOS.h" // FreeRTOS master include file, see NOTE3 +#include "freertos/task.h" // FreeRTOS task management +#include "freertos/queue.h" // FreeRTOS queue management + +#include "qequeue.h" // QP event queue (for deferring events) +#include "qmpool.h" // QP memory pool (for event pools) +#include "qp.h" // QP platform-independent public interface + +// global spinlock "mutex" for all critical sections in QF (see NOTE4) +extern PRIVILEGED_DATA portMUX_TYPE QF_esp32mux; + +#if defined( CONFIG_QPC_PINNED_TO_CORE_0 ) + #define QPC_CPU_NUM PRO_CPU_NUM +#elif defined( CONFIG_QPC_PINNED_TO_CORE_1 ) + #define QPC_CPU_NUM APP_CPU_NUM +#else + // Defaults to APP_CPU + #define QPC_CPU_NUM APP_CPU_NUM +#endif + +// the "FromISR" versions of the QF APIs, see NOTE3 +#ifdef Q_SPY + +#define QACTIVE_POST_FROM_ISR(me_, e_, pxHigherPrioTaskWoken_, sender_) \ + ((void)QActive_postFromISR_((me_), (e_), QF_NO_MARGIN, \ + (pxHigherPrioTaskWoken_), (sender_))) + +#define QACTIVE_POST_X_FROM_ISR(me_, e_, margin_, \ + pxHigherPrioTaskWoken_, sender_) \ + (QActive_postFromISR_((me_), (e_), (margin_), \ + (pxHigherPrioTaskWoken_), (sender_))) + +#define QACTIVE_PUBLISH_FROM_ISR(e_, pxHigherPrioTaskWoken_, sender_) \ + (QActive_publishFromISR_((e_), (pxHigherPrioTaskWoken_), \ + (void const *)(sender_))) + +#define QTIMEEVT_TICK_FROM_ISR(tickRate_, pxHigherPrioTaskWoken_, sender_) \ + (QTimeEvt_tickFromISR_((tickRate_), (pxHigherPrioTaskWoken_), (sender_))) + +#else // ndef Q_SPY + +#define QACTIVE_POST_FROM_ISR(me_, e_, pxHigherPrioTaskWoken_, dummy) \ + ((void)QActive_postFromISR_((me_), (e_), QF_NO_MARGIN, \ + (pxHigherPrioTaskWoken_), (void *)0)) + +#define QACTIVE_POST_X_FROM_ISR(me_, e_, margin_, \ + pxHigherPrioTaskWoken_, dummy) \ + (QActive_postFromISR_((me_), (e_), (margin_), \ + (pxHigherPrioTaskWoken_), (void *)0)) + +#define QACTIVE_PUBLISH_FROM_ISR(e_, pxHigherPrioTaskWoken_, dummy) \ + (QActive_publishFromISR_((e_), (pxHigherPrioTaskWoken_), \ + (void *)0)) + +#define QTIMEEVT_TICK_FROM_ISR(tickRate_, pxHigherPrioTaskWoken_, dummy) \ + (QTimeEvt_tickFromISR_((tickRate_), (pxHigherPrioTaskWoken_), (void *)0)) + +#endif // Q_SPY + +bool IRAM_ATTR QActive_postFromISR_(QActive * const me, QEvt const * const e, + uint_fast16_t const margin, + BaseType_t * const pxHigherPriorityTaskWoken, + void const * const sender); + +void IRAM_ATTR QActive_publishFromISR_(QEvt const * const e, + BaseType_t * const pxHigherPriorityTaskWoken, + void const * const sender); + +void IRAM_ATTR QTimeEvt_tickFromISR_(uint_fast8_t const tickRate, + BaseType_t * const pxHigherPriorityTaskWoken, + void const * const sender); + +#define QF_TICK_FROM_ISR(pxHigherPrioTaskWoken_, sender_) \ + QTIMEEVT_TICK_FROM_ISR(0U, pxHigherPrioTaskWoken_, sender_) + +#ifdef QEVT_DYN_CTOR // Shall the ctor for the ::QEvt class be provided? + + #define Q_NEW_FROM_ISR(evtT_, sig_, ...) \ + (evtT_##_ctor((evtT_ *)QF_newXFromISR_(sizeof(evtT_), \ + QF_NO_MARGIN, (sig_)), __VA_ARGS__)) + + #define Q_NEW_X_FROM_ISR(e_, evtT_, margin_, sig_, ...) do { \ + (e_) = (evtT_ *)QF_newXFromISR_(sizeof(evtT_), \ + (margin_), (sig_)); \ + if ((e_) != (evtT_ *)0) { \ + evtT_##_ctor((e_), __VA_ARGS__); \ + } \ + } while (false) + +#else // no ::QEvt ctor + + #define Q_NEW_FROM_ISR(evtT_, sig_) \ + ((evtT_ *)QF_newXFromISR_((uint_fast16_t)sizeof(evtT_), \ + QF_NO_MARGIN, (sig_))) + + #define Q_NEW_X_FROM_ISR(e_, evtT_, margin_, sig_) ((e_) = \ + (evtT_ *)QF_newXFromISR_((uint_fast16_t)sizeof(evtT_), \ + (margin_), (sig_))) + +#endif // QEVT_DYN_CTOR + +void QF_gcFromISR(QEvt const * const e); + +QEvt *QF_newXFromISR_(uint_fast16_t const evtSize, + uint_fast16_t const margin, enum_t const sig); + +void *QMPool_getFromISR(QMPool * const me, uint_fast16_t const margin, + uint_fast8_t const qs_id); +void QMPool_putFromISR(QMPool * const me, void *block, + uint_fast8_t const qs_id); + +enum FreeRTOS_TaskAttrs { + TASK_NAME_ATTR +}; + +// FreeRTOS hooks prototypes (not provided by FreeRTOS) +#if (configUSE_IDLE_HOOK > 0) + void vApplicationIdleHook(void); +#endif +#if (configUSE_TICK_HOOK > 0) + void vApplicationTickHook(void); +#endif +#if (configCHECK_FOR_STACK_OVERFLOW > 0) + void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName); +#endif +#if (configSUPPORT_STATIC_ALLOCATION > 0) + void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, + StackType_t **ppxIdleTaskStackBuffer, + uint32_t *pulIdleTaskStackSize); +#endif + +//============================================================================ +// interface used only inside QF, but not in applications + +#ifdef QP_IMPL + #define FREERTOS_TASK_PRIO(qp_prio_) \ + ((UBaseType_t)((qp_prio_) + tskIDLE_PRIORITY)) + + // FreeRTOS scheduler locking for QF_publish_() (task context only) + #define QF_SCHED_STAT_ + #define QF_SCHED_LOCK_(prio_) (vTaskSuspendAll()) + #define QF_SCHED_UNLOCK_() ((void)xTaskResumeAll()) + + // native QF event pool customization + #define QF_EPOOL_TYPE_ QMPool + #define QF_EPOOL_INIT_(p_, poolSto_, poolSize_, evtSize_) \ + (QMPool_init(&(p_), (poolSto_), (poolSize_), (evtSize_))) + #define QF_EPOOL_EVENT_SIZE_(p_) ((uint_fast16_t)(p_).blockSize) + #define QF_EPOOL_GET_(p_, e_, m_, qs_id_) \ + ((e_) = (QEvt *)QMPool_get(&(p_), (m_), (qs_id_))) + #define QF_EPOOL_PUT_(p_, e_, qs_id_) \ + (QMPool_put(&(p_), (e_), (qs_id_))) + +#endif // QP_IMPL + +//============================================================================ +// NOTE0: +// This is the "experimental" port to the [Espressif ESP-IDF][1] +// IoT Framework, which is loosely based on the [FreeRTOS kernel][2]. +// +// "Experimental" means that the port has not been thouroughly tested at +// Quantum Leaps and no working examples are provided. +// +// The [Espressif ESP-IDF][1] is based on a significantly changed version +// of the FreeRTOS kernel developed by Espressif to support the ESP32 +// multi-core CPUs (see [ESP-IDF][1]). +// +// The Espressif version of FreeRTOS is **NOT** compatible with the baseline +// FreeRTOS and it needs to be treated as a separate RTOS kernel. +// According to the comments in the Espressif source code, FreeRTOS-ESP-IDF +// is based on FreeRTOS V8.2.0, but apparently FreeRTOS-ESP-IDF has been +// updated with the newer features introduced to the original FreeRTOS in the +// later versions. For example, FreeRTOS-ESP32 supports the "static allocation", +// first introduced in baseline FreeRTOS V9.x. This QP port to FreeRTOS-ESP-IDF +// takes advantage of the "static allocation". +// +// [1]: https://www.espressif.com/en/products/sdks/esp-idf +// [2]: https://freertos.org +// +// NOTE1: +// The maximum number of active objects QF_MAX_ACTIVE can be increased to 64, +// inclusive, but it can be reduced to save some memory. Also, the number of +// active objects cannot exceed the number of FreeRTOS task priorities, +// because each QP active object requires a unique priority level. +// +// NOTE2: +// The critical section definition applies only to the FreeRTOS "task level" +// APIs. The "FromISR" APIs are defined separately. +// +// NOTE3: +// The design of FreeRTOS requires using different APIs inside the ISRs +// (the "FromISR" variant) than at the task level. Accordingly, this port +// provides the "FromISR" variants for QP functions and "FROM_ISR" variants +// for QP macros to be used inside ISRs. ONLY THESE "FROM_ISR" VARIANTS +// ARE ALLOWED INSIDE ISRs AND CALLING THE TASK-LEVEL APIs IS AN ERROR. +// +// NOTE4: +// This QF port to FreeRTOS-ESP32 uses the FreeRTOS-ESP32 spin lock "mutex", +// similar to the internal implementation of FreeRTOS-ESP32 (see tasks.c). +// However, the QF port uses its own "mutex" object QF_esp32mux. + +#endif // QP_PORT_H_ + diff --git a/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/qs_port.h b/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/qs_port.h new file mode 100644 index 00000000..d9e9d278 --- /dev/null +++ b/esp-idf/dpp-esp32devkitc/components/qp-c/ports/esp-idf/qs_port.h @@ -0,0 +1,56 @@ +//============================================================================ +// QP/C Real-Time Embedded Framework (RTEF) +// Copyright (C) 2005 Quantum Leaps, LLC. All rights reserved. +// +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-QL-commercial +// +// This software is dual-licensed under the terms of the open source GNU +// General Public License version 3 (or any later version), or alternatively, +// under the terms of one of the closed source Quantum Leaps commercial +// licenses. +// +// The terms of the open source GNU General Public License version 3 +// can be found at: +// +// The terms of the closed source Quantum Leaps commercial licenses +// can be found at: +// +// Redistributions in source code must retain this top-level comment block. +// Plagiarizing this software to sidestep the license obligations is illegal. +// +// Contact information: +// +// +//============================================================================ +//! @date Last updated on: 2024-05-08 +//! @version Last updated for: @ref qpc_7_3_4 +//! +//! @file +//! @brief QS/C port to a 32-bit CPU and a generic C11 compiler. + +#ifndef QS_PORT_H_ +#define QS_PORT_H_ + +// QS time-stamp size in bytes +#define QS_TIME_SIZE 4U + +// object pointer size in bytes +#define QS_OBJ_PTR_SIZE 4U + +// function pointer size in bytes +#define QS_FUN_PTR_SIZE 4U + +//============================================================================ +// NOTE: QS might be used with or without other QP components, in which +// case the separate definitions of the macros QF_CRIT_STAT, QF_CRIT_ENTRY(), +// and QF_CRIT_EXIT() are needed. In this port QS is configured to be used +// with the other QP component, by simply including "qp_port.h" +//*before* "qs.h". +#ifndef QP_PORT_H_ +#include "qp_port.h" // use QS with QF +#endif + +#include "qs.h" // QS platform-independent public interface + +#endif // QS_PORT_H_ +