//$file${src::qxk::qxk_xthr.c} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
//
// Model: qpc.qm
// File:  ${src::qxk::qxk_xthr.c}
//
// This code has been generated by QM 5.3.0 <www.state-machine.com/qm>.
// DO NOT EDIT THIS FILE MANUALLY. All your changes will be lost.
//
// This code is covered by the following QP license:
// License #    : LicenseRef-QL-dual
// Issued to    : Any user of the QP/C real-time embedded framework
// Framework(s) : qpc
// Support ends : 2024-12-31
// License scope:
//
// Copyright (C) 2005 Quantum Leaps, LLC <state-machine.com>.
//
//                    Q u a n t u m  L e a P s
//                    ------------------------
//                    Modern Embedded Software
//
// 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: <www.gnu.org/licenses/gpl-3.0>
//
// The terms of the closed source Quantum Leaps commercial licenses
// can be found at: <www.state-machine.com/licensing>
//
// Redistributions in source code must retain this top-level comment block.
// Plagiarizing this software to sidestep the license obligations is illegal.
//
// Contact information:
// <www.state-machine.com/licensing>
// <info@state-machine.com>
//
//$endhead${src::qxk::qxk_xthr.c} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#define QP_IMPL           // this is QP implementation
#include "qp_port.h"      // QP port
#include "qp_pkg.h"       // QP package-scope 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 facilities for pre-defined trace records
#else
    #include "qs_dummy.h" // disable the QS software tracing
#endif // Q_SPY

// protection against including this source file in a wrong project
#ifndef QXK_H_
    #error "Source file included in a project NOT based on the QXK kernel"
#endif // QXK_H_

Q_DEFINE_THIS_MODULE("qxk_xthr")

//$skip${QP_VERSION} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// Check for the minimum required QP version
#if (QP_VERSION < 730U) || (QP_VERSION != ((QP_RELEASE^4294967295U) % 0x3E8U))
#error qpc version 7.3.0 or higher required
#endif
//$endskip${QP_VERSION} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

//$define${QXK::QXThread} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

//${QXK::QXThread} ...........................................................
QXThread const * QXThread_dummy;

//${QXK::QXThread::ctor} .....................................................
//! @public @memberof QXThread
void QXThread_ctor(QXThread * const me,
    QXThreadHandler const handler,
    uint_fast8_t const tickRate)
{
    union QAsmAttr tmp;
    tmp.thr = handler;
    QActive_ctor(&me->super, tmp.fun); // superclass' ctor

    me->super.super.state.act = Q_ACTION_CAST(0); // mark as extended thread

    // instantiate the time-event member in the QXThread class
    QTimeEvt_ctorX(&me->timeEvt, &me->super,
                   (enum_t)QXK_DELAY_SIG, tickRate);
}

//${QXK::QXThread::delay} ....................................................
//! @public @memberof QXThread
bool QXThread_delay(QTimeEvtCtr const nTicks) {
    QF_CRIT_STAT
    QF_CRIT_ENTRY();
    QF_MEM_SYS();

    QXThread * const thr = QXTHREAD_CAST_(QXK_priv_.curr);

    // precondition, this function:
    // - must NOT be called from an ISR;
    // - number of ticks cannot be zero
    // - be called from an extended thread;
    // - the thread must NOT be already blocked on any object.
    Q_REQUIRE_INCRIT(800, (!QXK_ISR_CONTEXT_())
        && (nTicks != 0U)
        && (thr != (QXThread *)0)
        && (thr->super.super.temp.obj == (QMState *)0));
    // - the thread must NOT be holding a scheduler lock.
    Q_REQUIRE_INCRIT(801,
        QXK_priv_.lockHolder != (uint_fast8_t)thr->super.prio);

    // remember the blocking object
    thr->super.super.temp.obj = QXK_PTR_CAST_(QMState const*, &thr->timeEvt);
    QXThread_teArm_(thr, (enum_t)QXK_DELAY_SIG, nTicks);
    QXThread_block_(thr);

    QF_MEM_APP();
    QF_CRIT_EXIT();
    QF_CRIT_EXIT_NOP(); // BLOCK here

    // after unblocking...
    QF_CRIT_ENTRY();
    QF_MEM_SYS();

    // the blocking object must be the time event
    Q_ASSERT_INCRIT(890, thr->super.super.temp.obj
                          == QXK_PTR_CAST_(QMState const*, &thr->timeEvt));
    thr->super.super.temp.obj =  (QMState *)0; // clear

    QF_MEM_APP();
    QF_CRIT_EXIT();

    // signal of zero means that the time event was posted without
    // being canceled.
    return thr->timeEvt.super.sig == 0U;
}

//${QXK::QXThread::delayCancel} ..............................................
//! @public @memberof QXThread
bool QXThread_delayCancel(QXThread * const me) {
    QF_CRIT_STAT
    QF_CRIT_ENTRY();
    QF_MEM_SYS();

    bool wasArmed;
    if (me->super.super.temp.obj == QXK_PTR_CAST_(QMState*, &me->timeEvt)) {
        wasArmed = QXThread_teDisarm_(me);
        QXThread_unblock_(me);
    }
    else {
        wasArmed = false;
    }
    QF_MEM_APP();
    QF_CRIT_EXIT();

    return wasArmed;
}

//${QXK::QXThread::queueGet} .................................................
//! @static @public @memberof QXThread
QEvt const * QXThread_queueGet(QTimeEvtCtr const nTicks) {
    QF_CRIT_STAT
    QF_CRIT_ENTRY();
    QF_MEM_SYS();

    QXThread * const thr = QXTHREAD_CAST_(QXK_priv_.curr);

    // precondition, this function:
    // - must NOT be called from an ISR;
    // - be called from an extended thread;
    // - the thread must NOT be already blocked on any object.
    Q_REQUIRE_INCRIT(500, (!QXK_ISR_CONTEXT_())
        && (thr != (QXThread *)0)
        && (thr->super.super.temp.obj == (QMState *)0));
    // - the thread must NOT be holding a scheduler lock.
    Q_REQUIRE_INCRIT(501,
        QXK_priv_.lockHolder != (uint_fast8_t)thr->super.prio);

    // is the queue empty?
    if (thr->super.eQueue.frontEvt == (QEvt *)0) {

        // remember the blocking object (the thread's queue)
        thr->super.super.temp.obj
            = QXK_PTR_CAST_(QMState const*, &thr->super.eQueue);

        QXThread_teArm_(thr, (enum_t)QXK_TIMEOUT_SIG, nTicks);
        QPSet_remove(&QXK_priv_.readySet, (uint_fast8_t)thr->super.prio);
    #ifndef Q_UNSAFE
        QPSet_update_(&QXK_priv_.readySet, &QXK_priv_.readySet_dis);
    #endif

        (void)QXK_sched_(); // schedule other threads

        QF_MEM_APP();
        QF_CRIT_EXIT();
        QF_CRIT_EXIT_NOP(); // BLOCK here

        // after unblocking...
        QF_CRIT_ENTRY();
        QF_MEM_SYS();

        // the blocking object must be this queue
        Q_ASSERT_INCRIT(510, thr->super.super.temp.obj
                        == QXK_PTR_CAST_(QMState const*, &thr->super.eQueue));
        thr->super.super.temp.obj = (QMState *)0; // clear
    }

    // is the queue not empty?
    QEvt const *e;
    if (thr->super.eQueue.frontEvt != (QEvt *)0) {
        e = thr->super.eQueue.frontEvt; // remove from the front
        QEQueueCtr const nFree= thr->super.eQueue.nFree + 1U;
        thr->super.eQueue.nFree = nFree; // update the # free

        // any events in the ring buffer?
        if (nFree <= thr->super.eQueue.end) {

            // remove event from the tail
            thr->super.eQueue.frontEvt =
                thr->super.eQueue.ring[thr->super.eQueue.tail];
            if (thr->super.eQueue.tail == 0U) { // need to wrap?
                thr->super.eQueue.tail = thr->super.eQueue.end;  // wrap
            }
            --thr->super.eQueue.tail;

            QS_BEGIN_PRE_(QS_QF_ACTIVE_GET, thr->super.prio)
                QS_TIME_PRE_();      // timestamp
                QS_SIG_PRE_(e->sig); // the signal of this event
                QS_OBJ_PRE_(&thr->super); // this active object
                QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_); // poolId & refCtr
                QS_EQC_PRE_(nFree);  // # free entries
            QS_END_PRE_()
        }
        else {
            thr->super.eQueue.frontEvt = (QEvt *)0; // empty queue

            // all entries in the queue must be free (+1 for fronEvt)
            Q_ASSERT_INCRIT(520, nFree == (thr->super.eQueue.end + 1U));

            QS_BEGIN_PRE_(QS_QF_ACTIVE_GET_LAST, thr->super.prio)
                QS_TIME_PRE_();      // timestamp
                QS_SIG_PRE_(e->sig); // the signal of this event
                QS_OBJ_PRE_(&thr->super); // this active object
                QS_2U8_PRE_(QEvt_getPoolId_(e), e->refCtr_); // poolId & refCtr
            QS_END_PRE_()
        }
    }
    else { // the queue is still empty -- the timeout must have fired
         e = (QEvt *)0;
    }
    QF_MEM_APP();
    QF_CRIT_EXIT();

    return e;
}

//${QXK::QXThread::block_} ...................................................
//! @private @memberof QXThread
void QXThread_block_(QXThread const * const me) {
    // NOTE: must be called IN a critical section

    Q_REQUIRE_INCRIT(600,
        QXK_priv_.lockHolder != (uint_fast8_t)me->super.prio);

    QPSet_remove(&QXK_priv_.readySet, (uint_fast8_t)me->super.prio);
    #ifndef Q_UNSAFE
    QPSet_update_(&QXK_priv_.readySet, &QXK_priv_.readySet_dis);
    #endif

    (void)QXK_sched_(); // schedule other threads
}

//${QXK::QXThread::unblock_} .................................................
//! @private @memberof QXThread
void QXThread_unblock_(QXThread const * const me) {
    // NOTE: must be called IN a critical section

    QPSet_insert(&QXK_priv_.readySet, (uint_fast8_t)me->super.prio);
    #ifndef Q_UNSAFE
    QPSet_update_(&QXK_priv_.readySet, &QXK_priv_.readySet_dis);
    #endif

    if ((!QXK_ISR_CONTEXT_()) // not inside ISR?
        && (QActive_registry_[0] != (QActive *)0)) // kernel started?
    {
        (void)QXK_sched_(); // schedule other threads
    }
}

//${QXK::QXThread::timeout_} .................................................
//! @private @memberof QXThread
void QXThread_timeout_(QActive * const act) {
    // NOTE: must be called IN a critical section

    // the private time event is now disarmed and not in any queue,
    // so it is safe to change its signal. The signal of 0 means
    // that the time event has expired.
    QXTHREAD_CAST_(act)->timeEvt.super.sig = 0U;

    QXThread_unblock_(QXTHREAD_CAST_(act));
}

//${QXK::QXThread::teArm_} ...................................................
//! @private @memberof QXThread
void QXThread_teArm_(QXThread * const me,
    enum_t const sig,
    QTimeEvtCtr const nTicks)
{
    // NOTE: must be called IN a critical section

    // precondition:
    // - the time event must be unused
    Q_REQUIRE_INCRIT(700, me->timeEvt.ctr == 0U);

    me->timeEvt.super.sig = (QSignal)sig;

    if (nTicks != QXTHREAD_NO_TIMEOUT) {
        me->timeEvt.ctr = (QTimeEvtCtr)nTicks;
        me->timeEvt.interval = 0U;

        // is the time event unlinked?
        // NOTE: For the duration of a single clock tick of the specified tick
        // rate a time event can be disarmed and yet still linked in the list,
        // because un-linking is performed exclusively in QTimeEvt_tick_().
        if ((me->timeEvt.super.refCtr_ & QTE_IS_LINKED) == 0U) {
            uint_fast8_t const tickRate
                 = ((uint_fast8_t)me->timeEvt.super.refCtr_ & QTE_TICK_RATE);
            Q_ASSERT_INCRIT(710, tickRate < QF_MAX_TICK_RATE);

            // mark as linked
            me->timeEvt.super.refCtr_ |= QTE_IS_LINKED;

            // The time event is initially inserted into the separate
            // "freshly armed" list based on QTimeEvt_timeEvtHead_[tickRate].act.
            // Only later, inside the QTimeEvt_tick_() function, the "freshly
            // armed" list is appended to the main list of armed time events
            // based on QTimeEvt_timeEvtHead_[tickRate].next. Again, this is
            // to keep any changes to the main list exclusively inside
            // QTimeEvt_tick_().
            me->timeEvt.next
                = QXK_PTR_CAST_(QTimeEvt*, QTimeEvt_timeEvtHead_[tickRate].act);
            QTimeEvt_timeEvtHead_[tickRate].act = &me->timeEvt;
        }
    }
}

//${QXK::QXThread::teDisarm_} ................................................
//! @private @memberof QXThread
bool QXThread_teDisarm_(QXThread * const me) {
    // NOTE: must be called IN a critical section

    bool wasArmed;
    // is the time evt running?
    if (me->timeEvt.ctr != 0U) {
        wasArmed = true;
        me->timeEvt.ctr = 0U; // schedule removal from list
    }
    // the time event was already automatically disarmed
    else {
        wasArmed = false;
    }
    return wasArmed;
}
//$enddef${QXK::QXThread} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
