/*
 * MOST NetServices "Light" V3.2.7.0.1796 MultiInstance Patch
 *
 * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You may also obtain this software under a propriety license from Microchip.
 * Please contact Microchip for further information.
 *
 */

/*!
 * \file
 * \brief Implementation of Port Message Channel
 *
 * \cond MNS_INTERNAL_DOC
 * \addtogroup  G_PMC
 * @{
 */

/*------------------------------------------------------------------------------------------------*/
/* Includes                                                                                       */
/*------------------------------------------------------------------------------------------------*/
#include "mns_pmchannel.h"
#include "mns_pmp.h"
#include "mns_pmcmd.h"
#include "mns_misc.h"

/*------------------------------------------------------------------------------------------------*/
/* Internal Constants                                                                             */
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
/* Internal typedefs                                                                              */
/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
/* Internal prototypes                                                                            */
/*------------------------------------------------------------------------------------------------*/
/* LLD related interface functions */
static Mns_Lld_RxMsg_t* Pmch_RxAllocate(void *self, uint16_t buffer_size);
static void Pmch_RxUnused(void *self, Mns_Lld_RxMsg_t *msg_ptr);
static void Pmch_RxReceive(void *self, Mns_Lld_RxMsg_t *msg_ptr);
static void Pmch_TxRelease(void *self, Mns_Lld_TxMsg_t *msg_ptr);

/*------------------------------------------------------------------------------------------------*/
/* Implementation                                                                                 */
/*------------------------------------------------------------------------------------------------*/

/*! \brief  Constructor of class CPmChannel
 *  \param  self        The instance
 *  \param  init_ptr    Reference to initialization data structure
 *  \param  inst_ptr    TKU: MultiInstance param
 */
void Pmch_Ctor(CPmChannel *self, const Pmch_InitData_t *init_ptr, void *inst_ptr)
{
    uint16_t cnt;
    MISC_MEM_SET(self, 0, sizeof(*self));

    self->init_data    = *init_ptr;
    self->inst_ptr     = inst_ptr;
    self->lld_active   = false;
 
    self->mns_iface.rx_allocate_fptr       = &Pmch_RxAllocate;
    self->mns_iface.rx_receive_fptr        = &Pmch_RxReceive;
    self->mns_iface.rx_free_unused_fptr    = &Pmch_RxUnused;
    self->mns_iface.tx_release_fptr        = &Pmch_TxRelease;

    Pool_Ctor(&self->rx_msgs_pool, self->rx_msgs,                   /* initialize Rx message pool */
              PMCH_POOL_SIZE_RX, self->init_data.mns_inst_id);
    for (cnt = 0U; cnt < PMCH_POOL_SIZE_RX; cnt++)                  /* and assign LLD Rx handles  */
    {
        Msg_SetLldHandle(&self->rx_msgs[cnt], &self->lld_rx_msgs[cnt]);
        self->lld_rx_msgs[cnt].msg_ptr = &self->rx_msgs[cnt];
    }
}

/*! \brief      Registers an Rx callback function dedicated to one FIFO
 *  \param      self      The instance
 *  \param      fifo_id   The FIFO identifier
 *  \param      rx_fptr   The Rx callback function
 *  \param      inst_ptr  Reference to the instance required to invoke the callback
 */
void Pmch_RegisterReceiver(CPmChannel *self, Pmp_FifoId_t fifo_id, Pmch_OnRxMsg_t rx_fptr, void *inst_ptr)
{
    TR_ASSERT(self->init_data.mns_inst_id, "[PMCH]", (((uint8_t)fifo_id == (uint8_t)PMP_FIFO_ID_ICM)||((uint8_t)fifo_id == (uint8_t)PMP_FIFO_ID_MCM)||((uint8_t)fifo_id == (uint8_t)PMP_FIFO_ID_RCM)));

    self->receivers[fifo_id].rx_fptr = rx_fptr;
    self->receivers[fifo_id].inst_ptr = inst_ptr;
}

/*! \brief      Un-initializes the LLD interface of the channel
 *  \param      self    The instance
 */
void Pmch_Initialize(CPmChannel *self)
{
    if (self->lld_active == false)
    {
        self->lld_active   = true;
        TR_INFO((self->init_data.mns_inst_id, "[PMCH]", "Pmch_Initialize(): LLD_START()", 0U));
        self->init_data.lld_iface.start_fptr(&self->mns_iface, self, self->inst_ptr);
    }
}

/*! \brief      Un-initializes the LLD interface of the channel
 *  \param      self    The instance
 */
extern void Pmch_Uninitialize(CPmChannel *self)
{
    TR_INFO((self->init_data.mns_inst_id, "[PMCH]", "Pmch_Uninitialize(): Channel un-synchronization started", 0U));

    if (self->lld_active != false)
    {
        self->lld_active = false;
        TR_INFO((self->init_data.mns_inst_id, "[PMCH]", "Pmch_Uninitialize(): LLD_STOP()", 0U));
        self->init_data.lld_iface.stop_fptr(self->inst_ptr);
    }
}

/*! \brief      Wrapper for LLD transmit
 *  \details    This function which shall be used by all internal classes. No class shall
 *              invoke the LLD transmit function directly. Thus, it might be possible 
 *              in future to handle transmission failures and retries.
 *  \param      self    The instance
 *  \param      msg_ptr Reference to the public LLD message structure
 */
void Pmch_Transmit(CPmChannel *self, Mns_Lld_TxMsg_t *msg_ptr)
{
    if (self->lld_active != false)
    {
        self->init_data.lld_iface.tx_transmit_fptr(msg_ptr, self->inst_ptr);
    }
    else
    {
        Pmch_TxRelease(self, msg_ptr);
    }
}

/*------------------------------------------------------------------------------------------------*/
/* The exposed low-level driver interface                                                         */
/*------------------------------------------------------------------------------------------------*/
/*! \brief  Allocates an Rx message object 
 *  \param  self        The instance
 *  \param  buffer_size Size of the memory chunk in bytes which is needed to
 *                      copy the Rx message.
 *  \return Reference to an allocated Rx message object or \c NULL if no message object is available.
 */
static Mns_Lld_RxMsg_t* Pmch_RxAllocate(void *self, uint16_t buffer_size)
{
    CMessage *msg_ptr = NULL;
    Mns_Lld_RxMsg_t *handle = NULL;
    CPmChannel *self_ = (CPmChannel*)self;

    if (buffer_size <= MSG_SIZE_RSVD_BUFFER)
    {
        msg_ptr = Pool_GetMsg(&self_->rx_msgs_pool);

        if (msg_ptr != NULL)
        {
            Msg_Cleanup(msg_ptr);
            handle = &((Lld_IntRxMsg_t*)Msg_GetLldHandle(msg_ptr))->lld_msg;

            TR_ASSERT(self_->init_data.mns_inst_id, "[PMCH]", (handle != NULL));
 
            handle->data_size       = buffer_size;
            handle->data_ptr        = Msg_GetHeader(msg_ptr);
        }
        else
        {
            self_->rx_trigger_available = true;
            TR_INFO((self_->init_data.mns_inst_id, "[PMCH]", "Pmch_RxAllocate(): Allocation failed, size=%u", 1U, buffer_size));
        }
    }
    else
    {
        self_->rx_trigger_available = true;         
        TR_FAILED_ASSERT(self_->init_data.mns_inst_id, "[PMCH]");
    }

    return handle;
}

/*! \brief  Frees an unused Rx message object
 *  \param  self        The instance
 *  \param  msg_ptr     Reference to the unused Rx message object
 */
static void Pmch_RxUnused(void *self, Mns_Lld_RxMsg_t *msg_ptr)
{
    CPmChannel *self_ = (CPmChannel*)self;
    CMessage *pb_handle = ((Lld_IntRxMsg_t*)(void*)msg_ptr)->msg_ptr;

    TR_ASSERT(self_->init_data.mns_inst_id, "[PMCH]", (pb_handle != NULL));
    Pmch_ReturnRxToPool(self_, pb_handle);
}

/*! \brief  Pass an Rx message to MOST NetServices 
 *  \param  self        The instance
 *  \param  msg_ptr     Reference to the Rx message object containing the received
 *                      message.
 */
static void Pmch_RxReceive(void *self, Mns_Lld_RxMsg_t *msg_ptr)
{
    bool found = false;
    CPmChannel *self_ = (CPmChannel*)self;

    if (msg_ptr->data_ptr != NULL)
    {
        if (msg_ptr->data_size >= PMP_PM_MIN_SIZE_HEADER)                     /* ignore incomplete messages */
        {
            uint8_t fifo_no = (uint8_t)Pmp_GetFifoId(msg_ptr->data_ptr);    /* get channel id (FIFO number) */

            if ((fifo_no < PMP_MAX_NUM_FIFOS) && (self_->receivers[fifo_no].inst_ptr != NULL))
            {
                CMessage *handle = ((Lld_IntRxMsg_t*)(void*)msg_ptr)->msg_ptr;
                                                                            /* forward message to the respective FIFO/channel */
                self_->receivers[fifo_no].rx_fptr(self_->receivers[fifo_no].inst_ptr, handle); 
                found = true;
            }
            else
            {
                TR_ERROR((self_->init_data.mns_inst_id, "[PMCH]", "Pmch_RxReceive(): received message for unregistered FIFO no=%u", 1U, fifo_no));
            }
        }
        else
        {
            TR_ERROR((self_->init_data.mns_inst_id, "[PMCH]", "Pmch_RxReceive(): received incomplete message of size=%u", 1U, msg_ptr->data_size));
        }
    }
    else
    {
        TR_ERROR((self_->init_data.mns_inst_id, "[PMCH]", "Pmch_RxReceive(): message data is not valid", 0U));
    }

    if (false == found)
    {
        Pmch_RxUnused(self_, msg_ptr);                                      /* Just return message to pool until PMC is implemented */
    }
}

/*! \brief  Notifies that the LLD no longer needs to access the Tx message object
 *  \param  self        The instance
 *  \param  msg_ptr     Reference to the Tx message object which is no longer accessed
 *                      by the low-level driver
 */
static void Pmch_TxRelease(void *self, Mns_Lld_TxMsg_t *msg_ptr)
{
    CPmChannel *self_ = (CPmChannel*)self;
    Lld_IntTxMsg_t *tx_ptr = (Lld_IntTxMsg_t*)(void*)msg_ptr;

    if ((tx_ptr->owner_ptr == NULL) && (tx_ptr->msg_ptr == NULL))           /* tx_ptr is command */
    {
        Pmcmd_Release((CPmCommand*)(void*)tx_ptr);
    }
    else if (tx_ptr->owner_ptr != NULL)                                     /* release message to FIFO */
    {
        self_->init_data.tx_release_fptr(tx_ptr->owner_ptr, msg_ptr);
    }
    else
    {
        TR_FAILED_ASSERT(self_->init_data.mns_inst_id, "[PMCH]"); /* unknown FIFO - invalid message object */
    }

    TR_ASSERT(self_->init_data.mns_inst_id, "[PMCH]", (NULL == msg_ptr->custom_next_msg_ptr));  /* concatenation destroyed by the LLD */
 
}

/*------------------------------------------------------------------------------------------------*/
/* FIFO Related Callback Functions                                                                */
/*------------------------------------------------------------------------------------------------*/
/*! \brief  Returns an unused Rx message object back to the pool
 *  \param  self    The instance
 *  \param  msg_ptr The unused Rx message object 
 */
void Pmch_ReturnRxToPool(void *self, CMessage *msg_ptr)
{
    CPmChannel *self_ = (CPmChannel*)self;

    Pool_ReturnMsg(msg_ptr);

    if (self_->rx_trigger_available == true)
    {
        self_->rx_trigger_available = false;

        if (self_->init_data.lld_iface.rx_available_fptr != NULL)
        {
            self_->init_data.lld_iface.rx_available_fptr(self_->inst_ptr);
        }
    }
}

/*!
 * @}
 * \endcond
 */

/*------------------------------------------------------------------------------------------------*/
/* End of file                                                                                    */
/*------------------------------------------------------------------------------------------------*/