//$file${src::qxk::qxk_sema.c} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
//
// Model: qpc.qm
// File:  ${src::qxk::qxk_sema.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_sema.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_sema")

//$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::QXSemaphore} vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

//${QXK::QXSemaphore} ........................................................

//${QXK::QXSemaphore::init} ..................................................
//! @public @memberof QXSemaphore
void QXSemaphore_init(QXSemaphore * const me,
    uint_fast8_t const count,
    uint_fast8_t const max_count)
{
    QF_CRIT_STAT
    QF_CRIT_ENTRY();
    QF_MEM_SYS();

    Q_REQUIRE_INCRIT(100, (count <= max_count)
        && (0U < max_count) && (max_count <= 0xFFU));

    me->count     = (uint8_t)count;
    me->max_count = (uint8_t)max_count;
    QPSet_setEmpty(&me->waitSet);

    QF_MEM_APP();
    QF_CRIT_EXIT();
}

//${QXK::QXSemaphore::wait} ..................................................
//! @public @memberof QXSemaphore
bool QXSemaphore_wait(QXSemaphore * const me,
    QTimeEvtCtr const nTicks)
{
    QF_CRIT_STAT
    QF_CRIT_ENTRY();
    QF_MEM_SYS();

    QXThread * const curr = QXK_PTR_CAST_(QXThread*, QXK_priv_.curr);

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

    bool taken = true; // assume that the semaphore will be signaled
    if (me->count > 0U) {
        --me->count; // semaphore taken: decrement the count

        QS_BEGIN_PRE_(QS_SEM_TAKE, curr->super.prio)
            QS_TIME_PRE_();    // timestamp
            QS_OBJ_PRE_(me);   // this semaphore
            QS_2U8_PRE_(curr->super.prio, me->count);
        QS_END_PRE_()
    }
    else { // semaphore not available -- BLOCK the thread
        uint_fast8_t const p = (uint_fast8_t)curr->super.prio;
        // remove the curr prio from the ready set (will block)
        // and insert to the waiting set on this semaphore
        QPSet_remove(&QXK_priv_.readySet, p);
    #ifndef Q_UNSAFE
        QPSet_update_(&QXK_priv_.readySet, &QXK_priv_.readySet_dis);
    #endif
        QPSet_insert(&me->waitSet,  p);

        // remember the blocking object (this semaphore)
        curr->super.super.temp.obj = QXK_PTR_CAST_(QMState*, me);
        QXThread_teArm_(curr, (enum_t)QXK_TIMEOUT_SIG, nTicks);

        QS_BEGIN_PRE_(QS_SEM_BLOCK, curr->super.prio)
            QS_TIME_PRE_();    // timestamp
            QS_OBJ_PRE_(me);   // this semaphore
            QS_2U8_PRE_(curr->super.prio, me->count);
        QS_END_PRE_()

        // schedule the next thread if multitasking started
        (void)QXK_sched_(); // schedule other threads

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

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

        // the blocking object must be this semaphore
        Q_ASSERT_INCRIT(240, curr->super.super.temp.obj
                         == QXK_PTR_CAST_(QMState*, me));

        // did the blocking time-out? (signal of zero means that it did)
        if (curr->timeEvt.super.sig == 0U) {
            if (QPSet_hasElement(&me->waitSet, p)) { // still waiting?
                QPSet_remove(&me->waitSet, p); // remove unblocked thread
                taken = false; // the semaphore was NOT taken
            }
        }
        else { // blocking did NOT time out
            // the thread must NOT be waiting on this semaphore
            Q_ASSERT_INCRIT(250, !QPSet_hasElement(&me->waitSet, p));
        }
        curr->super.super.temp.obj = (QMState *)0; // clear blocking obj.
    }
    QF_MEM_APP();
    QF_CRIT_EXIT();

    return taken;
}

//${QXK::QXSemaphore::tryWait} ...............................................
//! @public @memberof QXSemaphore
bool QXSemaphore_tryWait(QXSemaphore * const me) {
    QF_CRIT_STAT
    QF_CRIT_ENTRY();
    QF_MEM_SYS();

    // precondition:
    // - the semaphore must be initialized
    Q_REQUIRE_INCRIT(300, me->max_count > 0U);

    #ifdef Q_SPY
    QActive const * const curr = QXK_PTR_CAST_(QActive*, QXK_priv_.curr);
    #endif // Q_SPY

    bool taken;
    // is the semaphore available?
    if (me->count > 0U) {
        --me->count;
        taken = true;

        QS_BEGIN_PRE_(QS_SEM_TAKE, curr->prio)
            QS_TIME_PRE_();    // timestamp
            QS_OBJ_PRE_(me);   // this semaphore
            QS_2U8_PRE_(curr->prio, me->count);
        QS_END_PRE_()
    }
    else { // the semaphore is NOT available (would block)
        taken = false;

        QS_BEGIN_PRE_(QS_SEM_BLOCK_ATTEMPT, curr->prio)
            QS_TIME_PRE_();    // timestamp
            QS_OBJ_PRE_(me);   // this semaphore
            QS_2U8_PRE_(curr->prio, me->count);
        QS_END_PRE_()
    }
    QF_MEM_APP();
    QF_CRIT_EXIT();

    return taken;
}

//${QXK::QXSemaphore::signal} ................................................
//! @public @memberof QXSemaphore
bool QXSemaphore_signal(QXSemaphore * const me) {
    bool signaled = true; // assume that the semaphore will be signaled

    QF_CRIT_STAT
    QF_CRIT_ENTRY();
    QF_MEM_SYS();

    // precondition:
    // - the semaphore must be initialized
    Q_REQUIRE_INCRIT(400, me->max_count > 0U);

    // any threads blocked on this semaphore?
    if (QPSet_notEmpty(&me->waitSet)) {
        // find the highest-prio. thread waiting on this semaphore
        uint_fast8_t const p = QPSet_findMax(&me->waitSet);
        QXThread * const thr =
            QXK_PTR_CAST_(QXThread*, QActive_registry_[p]);

        // assert that the tread:
        // - must be registered in QF;
        // - must be extended; and
        // - must be blocked on this semaphore;
        Q_ASSERT_INCRIT(410, (thr != (QXThread *)0)
            && (thr->super.osObject != (void *)0)
            && (thr->super.super.temp.obj
                == QXK_PTR_CAST_(QMState*, me)));

        // disarm the internal time event
        (void)QXThread_teDisarm_(thr);

        // make the thread ready to run and remove from the wait-list
        QPSet_insert(&QXK_priv_.readySet, p);
    #ifndef Q_UNSAFE
        QPSet_update_(&QXK_priv_.readySet, &QXK_priv_.readySet_dis);
    #endif
        QPSet_remove(&me->waitSet,  p);

        QS_BEGIN_PRE_(QS_SEM_TAKE, thr->super.prio)
            QS_TIME_PRE_();    // timestamp
            QS_OBJ_PRE_(me);   // this semaphore
            QS_2U8_PRE_(thr->super.prio, me->count);
        QS_END_PRE_()

        if (!QXK_ISR_CONTEXT_()) { // not inside ISR?
            (void)QXK_sched_(); // schedule other threads
        }
    }
    else if (me->count < me->max_count) {
        ++me->count; // increment the semaphore count

        QS_BEGIN_PRE_(QS_SEM_SIGNAL, 0U)
            QS_TIME_PRE_();    // timestamp
            QS_OBJ_PRE_(me);   // this semaphore
            QS_2U8_PRE_(0U, me->count);
        QS_END_PRE_()
    }
    else {
        signaled = false; // semaphore NOT signaled
    }
    QF_MEM_APP();
    QF_CRIT_EXIT();

    return signaled;
}
//$enddef${QXK::QXSemaphore} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
