/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifndef FREERTOS
#include <sys/socket.h>
#include <linux/netlink.h>
#include <unistd.h>
#include <assert.h>
#endif

#include "osal.h"
#include <api_oam_sdk/common/registers/inc/mr_registers.h>
#include <api_oam_sdk/metroam_hal/inc/mr_hal.h>
#include <api_oam_sdk/isr/inc/cfm_isr.h>
#include <api_oam_sdk/fault_notification_generator/inc/fng.h>
#include <api_oam_sdk/continuity_check/src/cc_types.h>
#include <api_oam_sdk/table_access/inc/table_access.h>
//VR Disabling RPC for now
//#include "rpc_server.h"
#include "egl_assert.h"

#define CFM_INT_TASK_STACKSIZE 	(1024)

#ifdef FREERTOS
static osSemaphore_t cfmInterruptSem;
#else
static int netlinkSockFd = -1;
static int open_netlink(void);
static int close_netlink(int sockFd);
#endif

static int	calcEventByteSize(struct eventTLV_s *eventTLVList, uint8_t eventCount)
{
    uint8_t	k;
    int		byteSize = 0;

    for (k=0;k<eventCount;k++)
    {
        // The event size is made up of the eventTLV struct + the data but excludes the dataPtr
        // in essence size is: type (enum) + length (uint16_t) + data (a data struct whose size is sent in 'length')
        byteSize += eventTLVList[k].length + sizeof(struct eventTLV_s) - sizeof(void*);
    }
    return byteSize;
}

static void copyEventTlvsToBuf(uint8_t *eventBuf,struct eventFrame_s *eventFrame, struct eventTLV_s *eventTLVList, uint8_t eventCount)
{
    uint8_t		k;
    uint8_t		*nextByte = eventBuf;
    uint16_t	dataSize;

    ASSERT_NOT_NULL(eventBuf);

    // copy the eventFrame to the start of the buffer
    memcpy(nextByte,eventFrame,sizeof(struct eventFrame_s));
    nextByte += sizeof(struct eventFrame_s);

    dataSize = sizeof(struct eventTLV_s) - sizeof(void*);				// calculate the byte size of the Type and Length fields

    // next copy each TLV in sequence...
    for (k=0;k<eventCount;k++)
    {
        memcpy(nextByte,&eventTLVList[k],dataSize);						// Copy type and length
        nextByte += dataSize;											// advance the buffer pointer
        memcpy(nextByte,eventTLVList[k].dataPtr,eventTLVList[k].length);	// Copy the TLV data
        nextByte += eventTLVList[k].length;								// advance the buffer pointer
    }
    *nextByte = 0;
}


// -----------------------------------------------------------------------------------------------------
//
// -----------------------------------------------------------------------------------------------------

int convertEventTlvToIsrRpt(uint8_t* arrayPtr,isrRpt_t* isrRpt, uint16_t arrayLen)
{
    struct eventFrame_s 		*eventFrame;
    struct eventTLVextract_s	*eventTLV;
    uint8_t						*nextTlv;
    uint8_t						*endTlv;

    // Clear the isrRpt
    memset(isrRpt,0,sizeof(isrRpt_t));

    // Check the frame signature
    eventFrame = (struct eventFrame_s *)arrayPtr;
    if (eventFrame->signature != EVENT_TLV_SIG)
        return -1;

    // Check that there is an END TLV where we expect it
    endTlv = arrayPtr + eventFrame->length + sizeof(struct eventFrame_s) - 1;
    if (*endTlv != 0)
        return -1;

    // the data array looks good so start extracting data from it
    isrRpt->intTime = eventFrame->timeStamp;
    isrRpt->intMask = eventFrame->intMask;

    // point to the first TLV
    nextTlv = arrayPtr + sizeof(struct eventFrame_s);
    eventTLV  = (struct eventTLVextract_s*)nextTlv;
    // Loop until the END TLV is found
    while (eventTLV->type != 0)
    {
        switch(eventTLV->type)
        {
        case EVENT_TLV_CONN:
            // Copy the TLV data into the appropriate struct in the isrRpt
            memcpy(&isrRpt->connIntInfo,&eventTLV->data,eventTLV->length);
            break;
        case EVENT_TLV_XCONN:
            // Copy the TLV data into the appropriate struct in the isrRpt
            memcpy(&isrRpt->xConnIntInfo,&eventTLV->data,eventTLV->length);
            break;
        case EVENT_TLV_RMEP_ERR:
            // Copy the TLV data into the appropriate struct in the isrRpt
            memcpy(&isrRpt->rMepErrIntInfo,&eventTLV->data,eventTLV->length);
            break;
        case EVENT_TLV_SA_CNG:
            // Copy the TLV data into the appropriate struct in the isrRpt
            memcpy(&isrRpt->saChngIntInfo,&eventTLV->data,eventTLV->length);
            break;
        case EVENT_TLV_RDI_CNG:
            // Copy the TLV data into the appropriate struct in the isrRpt
            memcpy(&isrRpt->rdiChngIntInfo,&eventTLV->data,eventTLV->length);
            break;
        case EVENT_TLV_ANY_TLV_CNG:
            // Copy the TLV data into the appropriate struct in the isrRpt
            memcpy(&isrRpt->tlvChngIntInfo,&eventTLV->data,eventTLV->length);
            break;
        default:
            break;
        }
        // Move to the next TLV
        nextTlv = nextTlv + sizeof(enum eventTLV) + sizeof(uint16_t) + eventTLV->length;
        if (nextTlv > (arrayPtr + arrayLen))
            return -2;
        eventTLV  = (struct eventTLVextract_s*)nextTlv;
    }

    return(0);
}

/*
 * Process any CFM interrupts that occur and take what action
 * is necessary to update the MetRoam system.
 */
static void cfmProcessInterrupts(void)
{
    uint32_t			interruptBits;
    uint32_t			maskBits;
    uint32_t			rdData,rdData_rdi;
    ccmTlv_t			oldCcmTlv;
    ccmTlvByteArray_t	oldTlvBytes;
    uint8_t				k;

    struct eventTLV_s	eventTLVList[12];
    struct eventFrame_s	eventFrame;
    uint8_t				eventCount;
    uint16_t			dataSize;
    uint8_t				*eventBuf;

    fngConnInfo_t		connIntInfo;
    fngXConInfo_t		xConnIntInfo;
    fngRMepErrInfo_t	rMepErrIntInfo;
    fngSaChngInfo_t		saChngIntInfo;
    fngRdiChngInfo_t	rdiChngIntInfo;
    fngTlvChngInfo_t	tlvChngIntInfo;
    fngPktProbeInfo_t	pktProbeIntInfo;

    int					tlvError;

    // read the interrupt reason register and mask bits
    mrHalRegRead(INT_REASON_REG_ADDR,&interruptBits);
    mrHalRegRead(INT_MASK_REG_ADDR,&maskBits);
   // mrHalRegRead(CONN_INT_REG_ADDR,&rdData);
   // mrHalRegRead(RDI_CNG_INT_REG_ADDR,&rdData_rdi);

#ifdef DEBUG_PRINT_L2
    ts_printf("cfmProcessInterrupts : interruptBits = 0x%x,maskBits =0x%x \n",interruptBits,maskBits);
#endif
    eventCount = 0;

    // For an interrupt to be regarded as 'Set'  both the interrupt reason bit and the mask bit must be set
    if (maskBits & interruptBits & (CONN_INT_MASK | XCONN_INT_MASK | RMEP_ERR_INT_MASK | SA_CNG_INT_MASK | RDI_CNG_INT_MASK | ANY_TLV_CNG_INT_MASK))
    {

        // Note: For an interrupt to be regarded as 'Set'  both the
        // interrupt reason bit and the mask bit must be set

        //------------------------------- CONN_INT ----------------------------------------------
        if (maskBits & interruptBits & CONN_INT_MASK)
        {
            //ts_printf("cfmProcessInterrupts2 : Connection Interrupt \n");
            // The continuity interrupt has been set.
            //Find out which remote MEP caused the interrupt and the continuity state
            mrHalRegRead(CONN_INT_REG_ADDR,&rdData);	     // Get the content of the conn reason register.
            // Reading this reg clears the interrupt!
#ifdef DEBUG_PRINT_L2
            ts_printf("cfmProcessInterrupts2 : Connection Interrupt : CONN_INT_REG_ADDR = 0x%x \n",rdData);
#endif
            connIntInfo.eventOccurred = True;

            // Extract the value
            connIntInfo.connIsDown = (bool) ((rdData & CONN_INT_REG_VALUE_MASK) >> CONN_INT_REG_VALUE_OFFSET);
            if(connIntInfo.connIsDown == 1)
            {
                ts_printf("Connection status - DOWN\n");
            }
            else
            {
                ts_printf("Connection status - UP\n");
            }
            // Extract the rMepNum for which the connectivity state changed...
            connIntInfo.rMepNum = ((rdData & CONN_INT_REG_RMEP_NUM_MASK) >> CONN_INT_REG_RMEP_NUM_OFFSET);

            eventTLVList[eventCount].type = EVENT_TLV_CONN;
            eventTLVList[eventCount].length = sizeof(connIntInfo);
            eventTLVList[eventCount].dataPtr = &connIntInfo;
            eventCount++;
        }

        //---------------------------------------------------------------------------------------

        //------------------------------- XCONN_INT ---------------------------------------------
        if (maskBits & interruptBits & XCONN_INT_MASK)
        {
            //ts_printf("cfmProcessInterrupts3 : inside if \n");
            // The cross connect interrupt has been set. Find which local MEP received the cross connect and get the captured packet
            mrHalRegRead(XCONN_INT_REG_ADDR,&rdData);				    // get the content of the xConn reason register.

            xConnIntInfo.eventOccurred = True;
            xConnIntInfo.errDetected = (bool) ((rdData & XCONN_INT_REG_VALUE_MASK) >> XCONN_INT_REG_VALUE_OFFSET);
            xConnIntInfo.defectReason = (xConnDefectReason_t) ((rdData & XCONN_INT_REG_REASON_MASK) >> XCONN_INT_REG_REASON_OFFSET);
            xConnIntInfo.lMepNum = ((rdData  & XCONN_INT_REG_LMEP_NUM_MASK) >> XCONN_INT_REG_LMEP_NUM_OFFSET);

            // before clearing the interrupt, get the captured packet if an error was detected and it's not an AIS pkt
            if ((xConnIntInfo.errDetected) && (xConnIntInfo.defectReason != AIS_DETECTED))
            {
                readXConnStore(xConnIntInfo.captPkt);		// get the captured packet
            }

            // clear the interrupt by writing 1 to the interrupt reason register
            mrHalRegWrite(XCONN_INT_REG_ADDR,XCONN_INT_REG_CLR_INT_MASK << XCONN_INT_REG_CLR_INT_OFFSET);

            eventTLVList[eventCount].type = EVENT_TLV_XCONN;
            eventTLVList[eventCount].length = sizeof(xConnIntInfo);
            eventTLVList[eventCount].dataPtr = &xConnIntInfo;
            eventCount++;
        }
        //---------------------------------------------------------------------------------------

        //---------------------------- RMEP_ERR_INT ---------------------------------------------
        if (interruptBits & RMEP_ERR_INT_MASK)
        {
            //ts_printf("cfmProcessInterrupts4 : inside if \n");
            // The remote Mep Error interrupt has been set. Find which local MEP received the error and get the captured packet
            mrHalRegRead(RMEP_ERR_INT_REG_ADDR,&rdData);	 // get the content of the xConn reason register.

            rMepErrIntInfo.eventOccurred = True;
            rMepErrIntInfo.errDetected = (bool) ((rdData & RMEP_ERR_INT_REG_VALUE_MASK) >> RMEP_ERR_INT_REG_VALUE_OFFSET);
            rMepErrIntInfo.defectReason = (rMepErrDefectReason_t) ((rdData & RMEP_ERR_INT_REG_REASON_MASK) >> RMEP_ERR_INT_REG_REASON_OFFSET);
            rMepErrIntInfo.lMepNum = ((rdData & RMEP_ERR_INT_REG_LMEP_NUM_MASK) >> RMEP_ERR_INT_REG_LMEP_NUM_OFFSET);

            // before clearing the interrupt, get the captured packet if an error was detected and it's not a LCK
            if ((rMepErrIntInfo.errDetected) && (rMepErrIntInfo.defectReason != LCK_DETECTED))
            {
                readRMepErrStore(rMepErrIntInfo.captPkt);	    // get the captured packet
            }

            // clear the interrupt by writing 1 to the interrupt reason register
            mrHalRegWrite(RMEP_ERR_INT_REG_ADDR,RMEP_ERR_INT_REG_CLR_INT_MASK << RMEP_ERR_INT_REG_CLR_INT_OFFSET);

            eventTLVList[eventCount].type = EVENT_TLV_RMEP_ERR;
            eventTLVList[eventCount].length = sizeof(rMepErrIntInfo);
            eventTLVList[eventCount].dataPtr = &rMepErrIntInfo;
            eventCount++;
        }
        //---------------------------------------------------------------------------------------

        //------------------------------ SA_CNG_INT ---------------------------------------------
        if (maskBits & interruptBits & SA_CNG_INT_MASK)
        {
            ts_printf("cfmProcessInterrupts5 : inside if \n");
            // The SA Change interrupt has been set. Find which remote MEP received the new SA and retrieve it.
            mrHalRegRead(SA_CNG_INT_REG_ADDR,&rdData);	   // get the content of the SA change reason register.

            saChngIntInfo.eventOccurred = True;
            saChngIntInfo.rMepNum = (rdData & SA_CNG_INT_REG_LMEP_NUM_MASK) >> SA_CNG_INT_REG_LMEP_NUM_OFFSET;

            // Read the new SA from the remote MEP database
            readSAInRMepDB(saChngIntInfo.rMepNum,saChngIntInfo.value);

            // clear the interrupt by writing 1 to the interrupt reason register
            mrHalRegWrite(SA_CNG_INT_REG_ADDR,SA_CNG_INT_REG_CLR_INT_MASK << SA_CNG_INT_REG_CLR_INT_OFFSET);

            eventTLVList[eventCount].type = EVENT_TLV_SA_CNG;
            eventTLVList[eventCount].length = sizeof(saChngIntInfo);
            eventTLVList[eventCount].dataPtr = &saChngIntInfo;
            eventCount++;
        }
        //---------------------------------------------------------------------------------------

        //------------------------------ RDI_CNG_INT --------------------------------------------
        if (maskBits & interruptBits & RDI_CNG_INT_MASK)
        {
#ifdef DEBUG_PRINT_L1
            ts_printf("Remote Defect Indication Change detected \n");
#endif
            // The SA Change interrupt has been set. Find which remote MEP received the new SA and retrieve it.
            mrHalRegRead(RDI_CNG_INT_REG_ADDR,&rdData_rdi); //mm	  // get the content of the SA change reason register.

            rdiChngIntInfo.eventOccurred = True;
            rdiChngIntInfo.rMepNum = ((rdData_rdi & RDI_CNG_INT_REG_RMEP_NUM_MASK) >> RDI_CNG_INT_REG_RMEP_NUM_OFFSET);

            rdiChngIntInfo.rdiIsSet = (bool) ((rdData_rdi & RDI_CNG_INT_REG_VALUE_MASK) >> RDI_CNG_INT_REG_VALUE_OFFSET);

            // clear the interrupt by writing 1 to the interrupt reason register
            mrHalRegWrite(RDI_CNG_INT_REG_ADDR,RDI_CNG_INT_REG_CLR_INT_MASK << RDI_CNG_INT_REG_CLR_INT_OFFSET);

            eventTLVList[eventCount].type = EVENT_TLV_RDI_CNG;
            eventTLVList[eventCount].length = sizeof(rdiChngIntInfo);
            eventTLVList[eventCount].dataPtr = &rdiChngIntInfo;
#ifdef DEBUG_PRINT_L2
            ts_printf("cfmProcessInterrupts : RDI_CNG_INT_REG_ADDR rdData = 0x%x\n , rdiChngIntInfo.rMepNum = 0x%x,rdiIsSet =0x%x",rdData_rdi,rdiChngIntInfo.rMepNum,rdiChngIntInfo.rdiIsSet);
            ts_printf("cfmProcessInterrupts : eventCount = 0x%x\n ",eventCount);
#endif
#ifdef DEBUG_PRINT_L1
            ts_printf("RDI value =0x%x\n\r",rdiChngIntInfo.rdiIsSet);
#endif
            eventCount++;

        }

        //---------------------------------------------------------------------------------------

        //------------------------------ ANY_TLV_CNG_INT ----------------------------------------
        if (maskBits & interruptBits & ANY_TLV_CNG_INT_MASK)
        {
            //ts_printf("cfmProcessInterrupts7 : inside if \n");
            ccmTlv_t	newCcmTlv;
            // The TLV Change interrupt has been set. Find which remote MEP received the new TLV and retrieve it.
            mrHalRegRead(TLV_CNG_INT_REG_ADDR,&rdData);	  // get the content of the TLV change reason register.

            tlvChngIntInfo.eventOccurred = True;
            tlvChngIntInfo.rMepNum = ((rdData & TLV_CNG_INT_REG_RMEP_NUM_MASK) >> TLV_CNG_INT_REG_RMEP_NUM_OFFSET);

            // Read the new TLV from the remote MEP database
            readTlvInRMepDB(tlvChngIntInfo.rMepNum, tlvChngIntInfo.newTlvBytes);

            // Read the old TLV...backed up by HW when interrupt occurred
            for (k = 0;k<14;k++)
            {
                mrHalRamRead(k,TLV_RAM_SEL,&rdData);
                oldTlvBytes[k<<2] = (uint8_t)(rdData & 0xFF);
                oldTlvBytes[(k<<2)+1] = (uint8_t)((rdData >> 8) & 0xFF);
                oldTlvBytes[(k<<2)+2] = (uint8_t)((rdData >> 16) & 0xFF);
                oldTlvBytes[(k<<2)+3] = (uint8_t)((rdData >> 24) & 0xFF);
            }

            // extract the TLV fields from the old array.
            _extractTlvsFromArray(oldTlvBytes,&oldCcmTlv);

            // extract the TLV fields from the array.
            tlvError = _extractTlvsFromArray(tlvChngIntInfo.newTlvBytes,&newCcmTlv);

            tlvChngIntInfo.changeMask = 0;
            if (tlvError == 0)
            {
                // make sure HW knows that this is a good TLV
                clrInvalidTlvFlag(tlvChngIntInfo.rMepNum);

                // Detect which TLVs have changed if any
                if (oldCcmTlv.psTlv.present != newCcmTlv.psTlv.present)
                    tlvChngIntInfo.changeMask |= MR_PS_TLV_CHANGE_MASK; // The presence/absence of a TLV has changed
                else
                {
                    // Both are present or both are not...but if present check it's value
                    if (oldCcmTlv.psTlv.present)
                        if (oldCcmTlv.psTlv.value != newCcmTlv.psTlv.value)
                            tlvChngIntInfo.changeMask |= MR_PS_TLV_CHANGE_MASK;
                }

                if (oldCcmTlv.isTlv.present != newCcmTlv.isTlv.present)
                    tlvChngIntInfo.changeMask |= MR_IS_TLV_CHANGE_MASK;
                else
                {
                    if (oldCcmTlv.isTlv.present)
                        if (oldCcmTlv.isTlv.value != newCcmTlv.isTlv.value)
                            tlvChngIntInfo.changeMask |= MR_IS_TLV_CHANGE_MASK;
                }

                if (oldCcmTlv.orgTlv.present != newCcmTlv.orgTlv.present)
                    tlvChngIntInfo.changeMask |= MR_ORG_TLV_CHANGE_MASK;
                else
                {
                    if (oldCcmTlv.orgTlv.present)
                    {
                        if (oldCcmTlv.orgTlv.length != newCcmTlv.orgTlv.length)
                        {
                            tlvChngIntInfo.changeMask |= MR_ORG_TLV_CHANGE_MASK;
                        }
                    }
                }

                if (oldCcmTlv.sidTlv.present != newCcmTlv.sidTlv.present)
                    tlvChngIntInfo.changeMask |= MR_SID_TLV_CHANGE_MASK;
                {
                    if (oldCcmTlv.sidTlv.present)
                    {
                        if (oldCcmTlv.sidTlv.length != newCcmTlv.sidTlv.length)
                        {
                            tlvChngIntInfo.changeMask |= MR_SID_TLV_CHANGE_MASK;
                        }
                    }
                }
            }
            else
            {
                // Let HW know that this is a bad TLV
                setInvalidTlvFlag(tlvChngIntInfo.rMepNum);
            }

            // Copy the parsed TLVs into the info struct, replacing the raw TLV data
            memcpy(&tlvChngIntInfo.newTlv,&newCcmTlv,sizeof(tlvChngIntInfo.newTlv));

            // clear the interrupt by writing 1 to the interrupt reason register
            mrHalRegWrite(TLV_CNG_INT_REG_ADDR,TLV_CNG_INT_REG_CLR_INT_MASK << TLV_CNG_INT_REG_CLR_INT_OFFSET);

            eventTLVList[eventCount].type = EVENT_TLV_ANY_TLV_CNG;
            eventTLVList[eventCount].length = sizeof(tlvChngIntInfo);
            eventTLVList[eventCount].dataPtr = &tlvChngIntInfo;
            eventCount++;
        }
        //---------------------------------------------------------------------------------------
    }

    //-------------------------- PROBE_STORE_FULL_INT ---------------------------------------
    if (maskBits & interruptBits & PROBE_STORE_FULL_INT_MASK)
    {
        ts_printf("cfmProcessInterrupts8 : inside if \n");
        // The probe store full interrupt has been set so fetch the stored packet and clear the interrupt

        mrHalRegRead(PROBE_MASK_REG_ADDR,(uint32_t*)&pktProbeIntInfo.probeMask);	// get the probe mask used

        readProbeStore((uint8_t*)pktProbeIntInfo.captPkt);	     	// get the captured packet

        mrHalRegRead(PROBE_CTRL_REG_ADDR,&rdData);					// get the content of the probe control reg
        pktProbeIntInfo.lMepNum = (rdData & PROBE_CTRL_REG_LMEP_NUM_MASK) >> PROBE_CTRL_REG_LMEP_NUM_OFFSET;
        pktProbeIntInfo.rMepNum = (rdData & PROBE_CTRL_REG_RMEP_NUM_MASK) >> PROBE_CTRL_REG_RMEP_NUM_OFFSET;

        pktProbeIntInfo.eventOccurred = True;

        rdData |= PROBE_CTRL_REG_CLR_INT_MASK;						// Set the clear bit for the interrupt
        mrHalRegWrite(PROBE_CTRL_REG_ADDR,rdData);					// Clear the interrupt bit

        eventTLVList[eventCount].type = EVENT_TLV_PROBE_STORE;
        eventTLVList[eventCount].length = sizeof(pktProbeIntInfo);
        eventTLVList[eventCount].dataPtr = &pktProbeIntInfo;
        eventCount++;
    }

    //isrReport->intTime = time(NULL);
    //isrReport->intMask = interruptBits & maskBits;

    //-------------------------- All EFM Interrupts ---------------------------------------
    //isrReport->efmIntInfo.eventOccurred = False;
    if (maskBits & interruptBits & (EFM_DISC_INT_MASK | EFM_FR_INT_MASK | EFM_FRS_INT_MASK))
    {
        ts_printf("cfmProcessInterrupts9 : inside if \n");
        // EFM is not enabled so just clear the interrupt
        mrHalRegWrite(EFM_DISC_REAS_REG_ADDR,1);
    }

    if (eventCount > 0)
    {

        // Build up the event Frame...
        eventFrame.signature 	= EVENT_TLV_SIG;
        eventFrame.length 		= calcEventByteSize(eventTLVList,eventCount) + 1; // +1 is to provide space for END TLV
        eventFrame.timeStamp 	= time(NULL);
        eventFrame.intMask		= interruptBits & maskBits;

        dataSize = eventFrame.length + sizeof(eventFrame);
        eventBuf = ts_malloc(dataSize);
#ifdef DEBUG_PRINT_L2
        ts_printf("cfmProcessInterrupts10 : dataSize = %d \n",dataSize);
#endif
        if (eventBuf == NULL)
        {
            ts_printf("eventBuf == NULL\n\r");

            return;
        }

        copyEventTlvsToBuf(eventBuf,&eventFrame,eventTLVList,eventCount);

        // Send the interrupt information to the FNG State Machine Task for processing
        // *** IMPORTANT *** This function will keep retrying to send every 5ms if queue full.
        fngAddCfmEventMsgToQ(eventBuf,dataSize);
    }
}

/*
 * This function is intended to be run as a OS task. It
 * waits for a semaphore to indicate a CFM interrupt
 * event has occurred and will then process the interrupt.
 */

#define MAX_PAYLOAD 1536
#define NETLINK_CFM_CMD          0xFD
#define NETLINK_MAC_CMD          0xFE
#define MIP_MAC_NO_RESERVED      0xEF
#define NETLINK_MIP_ETH_CFM_INTR 30 

#ifndef FREERTOS
char buffer[NLMSG_SPACE(MAX_PAYLOAD)];
struct msghdr msg;
struct sockaddr_nl nlAddr;
struct nlmsghdr *pNlh;
struct sockaddr_nl src_addr, dst_addr;
struct iovec iov;
#endif

static void cfmInterruptHandler(void)
{
    uint32_t	rdData;

#ifdef FREERTOS
    ASSERT_NOT_NULL(cfmInterruptSem);
#else
    unsigned int len = 0u;
    unsigned char *pBuf;
#endif
#ifdef DEBUG_PRINT_L2
    ts_printf("Started CFM ISR process, PID = x");
#endif

    for( ;; )
    {
        // Wait for a CFM interrupt to occur...
#ifdef FREERTOS
        osSemaphoreTake(cfmInterruptSem,0);
#else
        // switch over to receiving only from now on...
        pNlh->nlmsg_len = NLMSG_SPACE(NLMSG_SPACE(MAX_PAYLOAD));
        iov.iov_len = pNlh->nlmsg_len;

        //fprintf(stderr, "%s: about to wait on recvmsg()\n", __func__);
        len = recvmsg(netlinkSockFd, &msg, 0);
        if (len <= 0u)
        {
            perror("recvmsg()");
            exit(EXIT_FAILURE);
        }
        else
        {
            pBuf = NLMSG_DATA(pNlh);
        }
#endif

#ifndef FREERTOS
        if (pBuf[0] == NETLINK_CFM_CMD)
        {
            //fprintf(stderr, "%s: Netlink notification received - processing CFM interrupt\n", __func__);
#endif
            // A CFM event has occurred, process it...
#ifdef DEBUG_PRINT_L2
            ts_printf("A CFM event has occurred, process it...\n\r");
#endif
            cfmProcessInterrupts();

            // Clear the HW CFM 'interrupt disable' bit to allow further interrupts
            mrHalRegRead(RPT_EN_REG_ADDR,&rdData);
            mrHalRegWrite(RPT_EN_REG_ADDR,rdData & ~HW_INT_DISABLE);
#ifndef FREERTOS
        }
#endif
    }
}

/*
 * This function will launch a dedicated task for processing CFM interrupt
 * events. The ISR routine will inform this thread of a CFM event using a
 * semaphore. The task will remain asleep until an event occurs.
 * This function must only be called once at system startup.
 */
void	initCfmInterruptHandler(void)
{
    uint32_t	rdData;

#ifdef FREERTOS
    if (cfmInterruptSem == NULL)
        osSemaphoreCreateBinary(cfmInterruptSem);

    ASSERT_NOT_NULL(cfmInterruptSem);
#else
    netlinkSockFd = open_netlink();
    assert(netlinkSockFd >= 0);
#endif

    // Launch the task to take care of processing CFM interrupts
    osTaskCreate( (void (*)( void * ))cfmInterruptHandler, "cfmIntHdlr  ", CFM_INT_TASK_STACKSIZE, NULL, OS_STACK_THREAD_PRIO - 1, NULL );

    // Clear the HW_INT_DISABLE in case the ISR triggered and set it before the handler task was run (unlikely!)
    mrHalRegRead(RPT_EN_REG_ADDR,&rdData);
    mrHalRegWrite(RPT_EN_REG_ADDR,rdData & ~HW_INT_DISABLE);
}

#ifdef FREERTOS
/* this function has moved down into Linux Kernel ... */
/*
 * This function should be called by the ISR used to handle the
 * CFM interrupts. (In the current HW there in just one ISR for MetRoam
 * which shares CFM interrupts and Captured packet interrupts)
 * It is used in conjunction with initCfmInterruptHandler to
 * handle CFM interrupt events.
 * !!WARNING!! this function must not call mrHalRamRead or
 * mrHalRamWrite as these are not protected from ISR unless
 * HAL_ALLOW_CALLS_FROM_ISR is set.
 */
void cfmISRCallback(uint32_t maskBits,uint32_t interruptBits)
{
    uint32_t	rdData;
    long int	ctxt;
#ifdef DEBUG_PRINT_L2
    ts_printf("out cfmISRCallback\n\r");
#endif
    // For an interrupt to be regarded as 'Set'  both the interrupt reason bit and the mask bit must be set
    if (maskBits & interruptBits & (CONN_INT_MASK | XCONN_INT_MASK | RMEP_ERR_INT_MASK | SA_CNG_INT_MASK | RDI_CNG_INT_MASK | ANY_TLV_CNG_INT_MASK))
    {
        // The HW_INT_DISABLE bit in RPT_EN_REG_ADDR acts as a HW semaphore to
        // disable further OAM interrupts and when set will trigger code to
        // process any set interrupts (in PUSH socket server task).
#ifdef DEBUG_PRINT_L2
        ts_printf("In cfmISRCallback\n\r");
#endif
        mrHalRegRead(RPT_EN_REG_ADDR,&rdData);
        mrHalRegWrite(RPT_EN_REG_ADDR,rdData | HW_INT_DISABLE);

        if (cfmInterruptSem != NULL)
            osSemaphoreGiveFromISR(cfmInterruptSem, &ctxt);
    }
}
#endif


#ifndef FREERTOS
static int open_netlink(void)
{
    int sockFd, result;
    unsigned char mac_pid_frame[] = { NETLINK_MAC_CMD, MIP_MAC_NO_RESERVED};

    sockFd = socket(AF_NETLINK, SOCK_RAW, NETLINK_MIP_ETH_CFM_INTR);
    if (sockFd < 0)
    {
        perror("socket()");
        return sockFd;
    }

    memset((void*) &src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid    = getpid();
    src_addr.nl_groups = 0;

    result =  bind(sockFd, (struct sockaddr *)&src_addr, sizeof(src_addr));
    if (result < 0)
    {
        perror("bind()");
        return result;
    }

    /*
     * Sending first message to kernel netlink subsystem, so that the
     * kernel can save the application pid, as pid number is required by
     * netlink subsystem to transfer further packets.
     */
    memset((void*) &dst_addr, 0, sizeof(dst_addr));
    dst_addr.nl_family = AF_NETLINK;
    dst_addr.nl_pid    = 0;
    dst_addr.nl_groups = 0;

    //struct nlmsghdr *pNlh;
    pNlh = (struct nlmsghdr *) &(buffer[0]);
    assert(pNlh != NULL);

    pNlh->nlmsg_len = NLMSG_SPACE(sizeof(mac_pid_frame));
    pNlh->nlmsg_type = 0u;
    pNlh->nlmsg_flags = 0u;
    pNlh->nlmsg_seq = 0u;
    pNlh->nlmsg_pid = getpid();

    iov.iov_base = (void *)pNlh;
    iov.iov_len = pNlh->nlmsg_len;

    //struct msghdr msg;
    msg.msg_name = (void *)&dst_addr;
    msg.msg_namelen =  sizeof(dst_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    strncpy(NLMSG_DATA(pNlh), (const char *)mac_pid_frame, sizeof(mac_pid_frame));
    result = sendmsg(sockFd, & msg, 0);

    if (result < 0)
    {
        perror("sendmsg()");
        return result;
    }

    return sockFd;
}

static int close_netlink(int sockFd)
{
    int result;

    result = close(sockFd);
    if (result < 0)
    {
        perror("close()");
    }
    return result;
}


void cfm_isr_shutdown(void)
{
    if (netlinkSockFd >= 0)
    {
        if (close_netlink(netlinkSockFd) == 0)
        {
            netlinkSockFd = -1;
        }
    }
}
#endif
