/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/

/*-----------------------------------------------------------------------------
 *
 * $Author: jason.chen $
 * $Date: 2015/12/24 $
 * $RCSfile: cb_low.c,v $
 * $Revision: #3 $
 *
 *---------------------------------------------------------------------------*/

/** @file cb_low.c
 *  This file implements low-layer support of callback interface.
 */


//-----------------------------------------------------------------------------
// Include files
//-----------------------------------------------------------------------------

#include "cb_tim.h"
#include "cb_low.h"
#include "x_linux.h"
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <x_debug.h>

//-----------------------------------------------------------------------------
// Static variables
//-----------------------------------------------------------------------------

DECLARE_WAIT_QUEUE_HEAD(_rWq);
static DLIST_T(_CB_CALLBACK_EVENT_T) _qCbEvent;

/* Event type to callback thread ownership array */
static pid_t _arCbId[CB_FCT_NUM];
#ifdef CC_MTAL_MULTIPROCESS_CALLBACK
static pid_t _arMultiCbId[CB_FCT_NUM][3];
#endif
static BOOL fgInit=FALSE;
static int iCbCount=0;

static CB_FCT_ID_T CbDisplayFctIdMin=CB_TIM_TRIGGER;
static CB_FCT_ID_T CbDisplayFctIdMax=CB_FCT_NUM;
static BOOL fgCbDisplayFctId=FALSE;

static int _arCbFctId[CB_FCT_NUM];
#ifdef CC_MTAL_MULTIPROCESS_CALLBACK
static int _arMultiCbFctId[CB_FCT_NUM][3];
#endif

//-----------------------------------------------------------------------------
// Static functions
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/** Add callback event into event queue.
 *  @param  prCbEv  Pointer to callback event.
 *  @retval CBR_OK          Success.
 *  @retval CBR_ERR_SYS     Internal system error.
 */
//-----------------------------------------------------------------------------
static INT32 _CbPutEvent(CB_CALLBACK_EVENT_T *prCbEv)
{
    CRIT_STATE_T rState;

    rState = x_crit_start();
    DLIST_INSERT_TAIL(prCbEv, &_qCbEvent, rLink);
    iCbCount++;	
	_arCbFctId[prCbEv->rGetCb.eFctId]++;
    #ifdef CC_MTAL_MULTIPROCESS_CALLBACK
    if(prCbEv->u4BitMask & BITMASK_MULTI1)
    {
	  _arMultiCbFctId[prCbEv->rGetCb.eFctId][0]++;
    }
	if(prCbEv->u4BitMask & BITMASK_MULTI2)
	{
	  _arMultiCbFctId[prCbEv->rGetCb.eFctId][1]++;
	}
	if(prCbEv->u4BitMask & BITMASK_MULTI3)
	{
	  _arMultiCbFctId[prCbEv->rGetCb.eFctId][2]++;
	}
    #endif
    x_crit_end(rState);

    wake_up_interruptible_all(&_rWq);

    return CBR_OK;
}


//-----------------------------------------------------------------------------
// Inter-file functions
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/** Initialize callback support.
 */
//-----------------------------------------------------------------------------
#if defined(CC_FAST_INIT)
extern int  mt53xx_drvset_init(void);
#endif
void _CB_Init(void)
{
    CRIT_STATE_T rState;
	UINT32 idx;

    #if defined(CC_FAST_INIT)
    mt53xx_drvset_init();
    #endif
    #if defined(CC_SUPPORT_STR)
    mt53xx_STR_init();
    #endif
    if (fgInit)
    {
        return;
    }
    
    rState = x_crit_start();
    fgInit = TRUE;
    DLIST_INIT(&_qCbEvent);
    iCbCount = 0;
	for(idx=0;idx<CB_FCT_NUM;idx++)
	{
	  _arCbFctId[idx] = 0;
      #ifdef CC_MTAL_MULTIPROCESS_CALLBACK
      _arMultiCbFctId[idx][0] = 0;
	  _arMultiCbFctId[idx][1] = 0;
	  _arMultiCbFctId[idx][2] = 0;
      #endif	
	}
    x_crit_end(rState);
}

//-----------------------------------------------------------------------------
/** To enable/register a callback function with the related ID.
 *  @param  eFctId  Callback function ID to enable.
 *  @param  i4SubId Callback function Sub ID.
 *  @return The return value of related callback registration function.
 */
//-----------------------------------------------------------------------------
INT32 _CB_RegCbFct(CB_FCT_ID_T eFctId, INT32 i4SubId, pid_t rCbPid)
{
    BOOL fgWakeUp=FALSE;
    CRIT_STATE_T rState;
    BOOL fgSCBypass=TRUE;
    struct task_struct *pTask, *pTmp;

	if((eFctId < 0) ||  (eFctId >= CB_FCT_NUM))
	{
		Printf("[Error] eFctId out of range %d\n", eFctId);
		return CBR_ERR_REG;
	}

    if(eFctId == CB_SC_TRIGGER && current_uid() != 0){
        /* non root process ignore SC_TRIGGER register callback */
        Printf("$$$ Thread PID not root(%d) ignore\n", current_uid());
        return CBR_OK;
    }
    if(eFctId == CB_SC_TRIGGER)
    {
        Printf("$$$ Try to register CB_SC_TRIGGER rCbPid=%d, current pid=%d, current comm=%s\n", rCbPid, current->pid, current->comm);
          do_each_thread(pTask, pTmp)
          {
            if(pTmp->pid == rCbPid)
            {
              Printf("_CB_RegCbFct[CB_SC_TRIGGER RESET] set by (%d)(%s)\n",pTmp->pid, pTmp->comm);
              if(strncmp(pTmp->comm,"dtv_svc",strlen(pTmp->comm))==0)
              {
                Printf("dtv_svc grasped CB_SC_TRIGGER_RESET\n");
                fgSCBypass = FALSE;
              }
                  break;
            }
          } while_each_thread(pTask, pTmp);
    }
    if((eFctId == CB_SC_TRIGGER) && fgSCBypass){
        Printf("$$$ _CB_RegCbFct[CB SC] pid (%d) skip set by dtv_svc\n",rCbPid);
        return CBR_OK;
    }

    #if 0
    if ((_arMultiCbId[eFctId][0] != 0) ||
        (_arMultiCbId[eFctId][1] != 0) ||
        (_arMultiCbId[eFctId][2] != 0))
    {
        return CBR_ERR_REG;
    }
    #endif
    rState = x_crit_start();
    if (eFctId < CB_FCT_NUM)
    {
        if ((_arCbId[eFctId] != (pid_t)0) &&
            (_arCbId[eFctId] != rCbPid))
        {
            fgWakeUp = TRUE;
        }
            
        _arCbId[eFctId] = rCbPid;
    }
    x_crit_end(rState);
#ifndef CONFIG_TV_DRV_VFY     
    if (apfnCbRegIf[eFctId] != NULL)
    {
        return apfnCbRegIf[eFctId](i4SubId);
    }
#endif
    if (fgWakeUp)
    {
        wake_up_interruptible_all(&_rWq);
    }
    
    /*
    Printf("*** Thread 0x%x registers callback (0x%x, 0x%x)\n",
           (INT32)current, (INT32)eFctId, (INT32)i4SubId);
    */

    return CBR_OK;
}

#ifdef CC_MTAL_MULTIPROCESS_CALLBACK
//-----------------------------------------------------------------------------
/** To enable/register a multiple callback function with the related ID.
 *  @param  eFctId  Callback function ID to enable.
 *  @param  i4SubId Callback function Sub ID.
 *  @return The return value of related multiple callback registration function.
 */
//-----------------------------------------------------------------------------
INT32 _CB_RegMultiCbFct(CB_FCT_ID_T eFctId, INT32 i4SubId, pid_t rCbPid)
{
    BOOL fgWakeUp=FALSE;
    CRIT_STATE_T rState;
    INT32 i4Ret = 0;
    BOOL fgSCBypass=TRUE;
    struct task_struct *pTask, *pTmp;

	if((eFctId < 0) ||  (eFctId >= CB_FCT_NUM))
	{
		Printf("[Error] eFctId out of range %d\n", eFctId);
		return CBR_ERR_REG;
	}

    if(eFctId == CB_SC_TRIGGER && current_uid() != 0){
        /* non root process ignore SC_TRIGGER register callback */
        Printf("$$$ Thread PID not root(%d) ignore\n", current_uid());
        return CBR_OK;
    }

    if((eFctId == CB_SC_TRIGGER) && (_arMultiCbId[CB_SC_TRIGGER][0] == 0))
    {
        Printf("$$$ Try to register CB_SC_TRIGGER rCbPid=%d, current pid=%d, current comm=%s\n", rCbPid, current->pid, current->comm);
          do_each_thread(pTask, pTmp)
          {
            if(pTmp->pid == rCbPid)
            {
              Printf("_CB_RegMultiCbFct[CB_SC_TRIGGER RESET] set by (%d)(%s)\n",pTmp->pid, pTmp->comm);
              if(strncmp(pTmp->comm,"dtv_svc",strlen(pTmp->comm))==0)
              {
                Printf("dtv_svc grasped CB_SC_TRIGGER_RESET\n");
                fgSCBypass = FALSE;
              }
                  break;
            }
          } while_each_thread(pTask, pTmp);
    }
    if((eFctId == CB_SC_TRIGGER) && fgSCBypass){
        Printf("$$$ _CB_RegMultiCbFct[CB SC] pid (%d) skip set by dtv_svc\n",rCbPid);
        return CBR_OK;
    }

    rState = x_crit_start();
    if (eFctId < CB_FCT_NUM)
    {
        if ((_arCbId[eFctId] != (pid_t)0) &&
            (_arCbId[eFctId] != rCbPid))
        {
            fgWakeUp = TRUE;    // wakeup to leave.
        }
            
        _arCbId[eFctId] = 0;    // Remove single callback.
        if (_arMultiCbId[eFctId][0] == 0)
        {
            _arMultiCbId[eFctId][0] = rCbPid;
        }
        else if (_arMultiCbId[eFctId][1] == 0)
        {
            if(eFctId == CB_SC_TRIGGER)            
		    {            	  
				Printf("_CB_RegMultiCbFct[CB_SC_TRIGGER RESET](%d)\n",rCbPid);
			}            
			else            
			{        	                
			   _arMultiCbId[eFctId][1] = rCbPid;            
			}
        }
        else if (_arMultiCbId[eFctId][2] == 0)
        {
            _arMultiCbId[eFctId][2] = rCbPid;
        }
        else
        {
            #if CB_MULTIPROCESS_LAST_REPLACE
			if((rCbPid != _arMultiCbId[eFctId][0]) && (rCbPid != _arMultiCbId[eFctId][1]) &&
				(rCbPid != _arMultiCbId[eFctId][2]))
			{
			    i4Ret = CBR_ERR_REG;
			}
			else
			{
			    Printf("_CB_RegMultiCbFct multi-fctid used in process(%d)\n",rCbPid);
			}
			#else
            i4Ret = CBR_ERR_REG;
			#endif
        }
		_arCbId[eFctId] = (pid_t)0;
    }
    x_crit_end(rState);

    if (i4Ret) return i4Ret;

#ifndef CONFIG_TV_DRV_VFY     
    if (apfnCbRegIf[eFctId] != NULL)
    {
        return apfnCbRegIf[eFctId](i4SubId);
    }
#endif
    if (fgWakeUp)
    {
        wake_up_interruptible_all(&_rWq);
    }
    
    /*
    Printf("*** Thread 0x%x registers multiple callback (0x%x, 0x%x)\n",
           (INT32)current, (INT32)eFctId, (INT32)i4SubId);
    */

    return CBR_OK;
}
#endif

pid_t _CB_GetPid(CB_FCT_ID_T eFctId)
{
    if(eFctId < CB_FCT_NUM)
    {
        #ifdef CC_MTAL_MULTIPROCESS_CALLBACK
        if ((current->tgid == _arCbId[eFctId]) ||
            (current->tgid == _arMultiCbId[eFctId][0]) ||
            (current->tgid == _arMultiCbId[eFctId][1]) ||
            (current->tgid == _arMultiCbId[eFctId][2]))
        {
            return current->tgid;
        }

        if (_arCbId[eFctId]) return _arCbId[eFctId];
        if (_arMultiCbId[eFctId][0]) return _arMultiCbId[eFctId][0];
        if (_arMultiCbId[eFctId][1]) return _arMultiCbId[eFctId][1];
        if (_arMultiCbId[eFctId][2]) return _arMultiCbId[eFctId][2];
        #endif
        return _arCbId[eFctId];
    }
    else
    {
        return 0;
    }
}

CB_EVENT_COUNT_T _CB_ParsingEventQueue(void)
{
    CB_CALLBACK_EVENT_T *prLast=NULL;
    CB_EVENT_COUNT_T rCbIdleFctIdInfo;
    CB_FCT_ID_T eType;
    CRIT_STATE_T rState;

    for(eType=CB_TIM_TRIGGER;eType<CB_FCT_NUM;eType++)
    {
      rCbIdleFctIdInfo.puCB00IdleFctPid[eType]=0;
	  #ifdef CC_MTAL_MULTIPROCESS_CALLBACK
      rCbIdleFctIdInfo.puCB01IdleFctPid[eType]=0;
      rCbIdleFctIdInfo.puCB02IdleFctPid[eType]=0;	  
	  #endif
    }
    rState = x_crit_start();	
    DLIST_FOR_EACH(prLast, &_qCbEvent, rLink)
    {
      if (!prLast)
      {
        break;
      }
      if (prLast->rGetCb.eFctId >= CB_FCT_NUM)
      {
        break;
      }
	  for(eType=CB_TIM_TRIGGER;eType<CB_FCT_NUM;eType++)
	  {
	    if(eType == prLast->rGetCb.eFctId)
	    {
          rCbIdleFctIdInfo.puCB00IdleFctPid[eType]++;
	      #ifdef CC_MTAL_MULTIPROCESS_CALLBACK
          rCbIdleFctIdInfo.puCB01IdleFctPid[eType]++;
          rCbIdleFctIdInfo.puCB02IdleFctPid[eType]++;	  
	      #endif	    
	    }
	  }
	}
	x_crit_end(rState);
	
	return rCbIdleFctIdInfo;
}

int _CB_GetCbCount(void)
{
    CRIT_STATE_T rState;
	int i4Count;
	CB_FCT_ID_T Fctidx;
    //Printf("_CB_GetCbCount = %d!\n",iCbCount);
    rState = x_crit_start();	
    i4Count = iCbCount;
    x_crit_end(rState);

    Printf("[%s]-->dump CB count list\n",__FUNCTION__);
	for(Fctidx=CB_TIM_TRIGGER;Fctidx<CB_FCT_NUM;Fctidx++)
	{
	  Printf("CBID[%d] count = %d\n",Fctidx,_arCbFctId[Fctidx]);
      #ifdef CC_MTAL_MULTIPROCESS_CALLBACK
	  Printf("1stMultipleCBID[%d] count = %d\n",Fctidx,_arMultiCbFctId[Fctidx][0]);
	  Printf("2ndMultipleCBID[%d] count = %d\n",Fctidx,_arMultiCbFctId[Fctidx][1]);
	  Printf("3rdMultipleCBID[%d] count = %d\n",Fctidx,_arMultiCbFctId[Fctidx][2]);
	  #endif	
	}
    return i4Count;
}

void _CB_GetCbIdDisplayInfo(void)
{
  Printf("[%s]--->\n",__FUNCTION__);
  Printf("fgCbDisplayFctId = %d\n",fgCbDisplayFctId);
  Printf("CbDisplayFctId(Min/Max) = %d / %d\n",CbDisplayFctIdMin,CbDisplayFctIdMax);	  
}

int _CB_SetCbIdDisplayInfo(BOOL fgDisplay, CB_FCT_ID_T Min, CB_FCT_ID_T Max)
{
  int i4Ret = -1;
  
  Printf("[%s]\n",__FUNCTION__);
  fgCbDisplayFctId = fgDisplay;
  if((Max > CB_FCT_NUM) || (Min > Max))
  {
    Printf("setting error and disable display!!\n");
	fgCbDisplayFctId = FALSE;
  }
  else
  {
    i4Ret = 0;
    CbDisplayFctIdMin = Min;
    CbDisplayFctIdMax = Max;
  }
  return i4Ret;
}

#ifdef CC_MTAL_MULTIPROCESS_CALLBACK
//-----------------------------------------------------------------------------
/** To unregister a callback function with the related ID.
 *  @param  eFctId  Callback function ID to enable.
 *  @param  i4SubId Callback function Sub ID.
 *  @return The return value of related callback unregistration function.
 */
//-----------------------------------------------------------------------------
INT32 _CB_UnRegCbFct(CB_FCT_ID_T eFctId, INT32 i4SubId, pid_t rCbPid)
{
    CB_CALLBACK_EVENT_T *prLast=NULL,tmpCBEvt;
    CRIT_STATE_T rState;

    rState = x_crit_start();
    if ((eFctId < CB_FCT_NUM) && (rCbPid == _arCbId[eFctId]))
    {
        _arCbId[eFctId] = 0;
    }
    DLIST_FOR_EACH(prLast, &_qCbEvent, rLink)
    {
        if ((rCbPid == _arCbId[prLast->rGetCb.eFctId]) &&
            (prLast->rGetCb.eFctId == eFctId))
        {
            prLast->u4BitMask &= ~(BITMASK_SINGLE);
            if (prLast->u4BitMask == 0)
            {
                DLIST_REMOVE(prLast, &_qCbEvent, rLink);
				memcpy(&tmpCBEvt, prLast, sizeof(CB_CALLBACK_EVENT_T));
                kfree(prLast);
				prLast = &tmpCBEvt ;
            }
        }
    }
    x_crit_end(rState);
    
    return CBR_OK;
}

//-----------------------------------------------------------------------------
/** To unregister a multiple callback function with the related ID.
 *  @param  eFctId  Callback function ID to enable.
 *  @param  i4SubId Callback function Sub ID.
 *  @return The return value of related callback unregistration function.
 */
//-----------------------------------------------------------------------------
INT32 _CB_UnRegMultiCbFct(CB_FCT_ID_T eFctId, INT32 i4SubId, pid_t rCbPid)
{
    CB_CALLBACK_EVENT_T *prLast=NULL, tmpCBEvt;
    CRIT_STATE_T rState;
    UINT32 u4Multi = 0;

    rState = x_crit_start();
    if (eFctId < CB_FCT_NUM)
    {
        if (rCbPid == _arMultiCbId[eFctId][0])
        {
            _arMultiCbId[eFctId][0] = 0;
            u4Multi = 1;
        }
        if (rCbPid == _arMultiCbId[eFctId][1])
        {
            _arMultiCbId[eFctId][1] = 0;
            u4Multi = 2;
        }
        if (rCbPid == _arMultiCbId[eFctId][2])
        {
            _arMultiCbId[eFctId][2] = 0;
            u4Multi = 3;
        }

        DLIST_FOR_EACH(prLast, &_qCbEvent, rLink)
        {
            if ((u4Multi != 0) &&
                (rCbPid == _arMultiCbId[prLast->rGetCb.eFctId][u4Multi-1]) &&
                (prLast->rGetCb.eFctId == eFctId))
            {
                prLast->u4BitMask &= ~(BITMASK_MULTI(u4Multi));
                if (prLast->u4BitMask == 0)
                {
                    DLIST_REMOVE(prLast, &_qCbEvent, rLink);
					memcpy(&tmpCBEvt, prLast, sizeof(CB_CALLBACK_EVENT_T));
                    kfree(prLast);
					prLast = &tmpCBEvt ;
                }
            }
        }
    }
    x_crit_end(rState);
    
    return CBR_OK;
}
#else
//-----------------------------------------------------------------------------
/** To unregister a callback function with the related ID.
 *  @param  eFctId  Callback function ID to enable.
 *  @param  i4SubId Callback function Sub ID.
 *  @return The return value of related callback unregistration function.
 */
//-----------------------------------------------------------------------------
INT32 _CB_UnRegCbFct(CB_FCT_ID_T eFctId, INT32 i4SubId, pid_t rCbPid)
{
    CRIT_STATE_T rState;
    CB_CALLBACK_EVENT_T *prLast=NULL,tmpCBEvt;

    if (eFctId >= CB_FCT_NUM)
        return CBR_ERR_REG;

    rState = x_crit_start();
    if (rCbPid != _arCbId[eFctId] || !_arCbId[eFctId])
        goto done;


	_arCbId[eFctId] = 0;

	// Check if this is on event queue.
	DLIST_FOR_EACH(prLast, &_qCbEvent, rLink)
	{
		if (!prLast)
		{
			break;
		}

		if (prLast->rGetCb.eFctId >= CB_FCT_NUM)
		{
			break;
		}

		if (prLast->rGetCb.eFctId == eFctId)
		{
			DLIST_REMOVE(prLast, &_qCbEvent, rLink);
			//workaround for kfree will write the 0x6b for poison check
			memcpy(&tmpCBEvt, prLast, sizeof(CB_CALLBACK_EVENT_T));
			kfree(prLast);
			prLast = &tmpCBEvt ;
		}

		if (prLast->rLink.pt_next == NULL)
		{
			break;
		}
	}
        
done:
    x_crit_end(rState);
    
    return CBR_OK;
}
#endif

//-----------------------------------------------------------------------------
/** Add callback event into event queue.
 *  @param  eFctId      Callback function ID to notify.
 *  @param  i4TagSize   Data size to callback.
 *  @param  pvTag       Data to callback.
 *  @retval CBR_OK          Success.
 *  @retval CBR_NO_MEM      Out of memory.
 */
//-----------------------------------------------------------------------------
INT32 _CB_PutEvent(CB_FCT_ID_T eFctId, INT32 i4TagSize, void *pvTag)
{
    CB_CALLBACK_EVENT_T *prCbEv;
    UINT32 flags;
    #ifdef CC_MTAL_MULTIPROCESS_CALLBACK
    UINT32 u4BitMask=0;
    #endif
    if (!fgInit)
    {
        Printf("Error! CB not yet initialized!\n");
        return CBR_ERR_SYS;
    }

    if (in_interrupt())
    {
        flags = GFP_ATOMIC;
    }
    else
    {
        flags = GFP_KERNEL;
    }

    prCbEv = (CB_CALLBACK_EVENT_T *)
             kmalloc(sizeof(CB_CALLBACK_EVENT_T) + i4TagSize, flags);

    if (prCbEv == NULL)
    {
        return CBR_NO_MEM;
    }
    #ifdef CC_MTAL_MULTIPROCESS_CALLBACK
    if (_arCbId[eFctId])
    {
        u4BitMask = BITMASK_SINGLE;
    }
    else
    {
        u4BitMask = (_arMultiCbId[eFctId][0] ? (BITMASK_MULTI1) : 0) |
                    (_arMultiCbId[eFctId][1] ? (BITMASK_MULTI2) : 0) |
                    (_arMultiCbId[eFctId][2] ? (BITMASK_MULTI3) : 0);
    }
    if (!u4BitMask)
    {
      #if 0 //based on SONY ARIB request
        //No one register callback , skip put event
        Printf("[Error] No one register callback , skip put event!\n");
	return CBR_OK;
      #else
	  Printf("$$$ _CB_PutEvent bitmask==0!!! (pid %d,eFctId %d)\n", current->pid, eFctId);
	  u4BitMask = BITMASK_MULTI1;
	  #endif
    }

    prCbEv->u4BitMask = u4BitMask;
    #endif
    if(fgCbDisplayFctId)
    {
      if(eFctId >= CbDisplayFctIdMin && eFctId <= CbDisplayFctIdMax)
      {
        Printf("_CB_PutEvent (pid %d,eFctId %d)(Max/Min)\n", 
			   current->pid, eFctId, CbDisplayFctIdMin, CbDisplayFctIdMax);
      }
    }	
    prCbEv->rGetCb.eFctId = eFctId;
    prCbEv->rGetCb.i4TagSize = i4TagSize;
    memcpy((void *)((UINT32)prCbEv + sizeof(CB_CALLBACK_EVENT_T)), pvTag,
           i4TagSize);

    _CbPutEvent(prCbEv);
    return CBR_OK;
}
#ifdef CC_MTAL_MULTIPROCESS_CALLBACK
//-----------------------------------------------------------------------------
/** Get the first event from event queue.
 *  @param  pu4DoneMask output parameter to transfer the Done Mask bit.
 *  @return If success and has event in queue, pointer of the callback event.\n
 *          Otherwise, NULL.
 */
//-----------------------------------------------------------------------------
CB_CALLBACK_EVENT_T *_CB_GetEvent(UINT32 *pu4DoneMask)
{
    CB_CALLBACK_EVENT_T *prLast=NULL;
    CRIT_STATE_T rState;
    BOOL fgFound=FALSE;
    UINT32 u4DoneMask = 0;

    rState = x_crit_start();
    DLIST_FOR_EACH(prLast, &_qCbEvent, rLink)
    {
        if (!prLast)
        {
            break;
        }

        if (prLast->rGetCb.eFctId >= CB_FCT_NUM)
        {
            break;
        }

        if (current->tgid == _arCbId[prLast->rGetCb.eFctId])
        {
            fgFound = TRUE;
            u4DoneMask = BITMASK_SINGLE;
            break;
        }
        else if ((current->tgid == _arMultiCbId[prLast->rGetCb.eFctId][0]) &&
                 (prLast->u4BitMask & BITMASK_MULTI1))
        {
            fgFound = TRUE;
            u4DoneMask = BITMASK_MULTI1;
            break;
        }
        else if ((current->tgid == _arMultiCbId[prLast->rGetCb.eFctId][1]) &&
                 (prLast->u4BitMask & BITMASK_MULTI2))
        {
            fgFound = TRUE;
            u4DoneMask = BITMASK_MULTI2;
            break;
    }
        else if ((current->tgid == _arMultiCbId[prLast->rGetCb.eFctId][2]) &&
                 (prLast->u4BitMask & BITMASK_MULTI3))
    {        	
            fgFound = TRUE;
            u4DoneMask = BITMASK_MULTI3;
            break;
	      }
    }    
    x_crit_end(rState);
    if (pu4DoneMask!=NULL)
    {
        *pu4DoneMask = u4DoneMask;
    }

    if (fgFound)
    {
        return prLast;
    }
    else
    {
        return NULL;
    }
}

//-----------------------------------------------------------------------------
/** Remove the event from event queue and free the element.
 *  @param  prCbEv      event element pointer to be removed and free.
 *  @param  u4DoneMask  input parameter to identify the Done Mask bit.
 */
//-----------------------------------------------------------------------------
void _CB_FreeEvent(CB_CALLBACK_EVENT_T *prCbEv, UINT32 u4DoneMask)
{
    CRIT_STATE_T rState;

    rState = x_crit_start();
    prCbEv->u4BitMask &= ~(u4DoneMask);
    if (prCbEv->u4BitMask == 0)
    {
        //if(prCbEv->rGetCb.eFctId == CB_MTAT_CEC_5V_STATUS_NFY)
	//{
	  if(iCbCount > 0)
	  {
	    iCbCount--;
	  }
	//}
        DLIST_REMOVE(prCbEv, &_qCbEvent, rLink);
        kfree(prCbEv);
    }
    x_crit_end(rState);
} 
#else
//-----------------------------------------------------------------------------
/** Get the first event from event queue.
 *  @return If success and has event in queue, pointer of the callback event.\n
 *          Otherwise, NULL.
 */
//-----------------------------------------------------------------------------
CB_CALLBACK_EVENT_T *_CB_GetEvent(void)
{
    CB_CALLBACK_EVENT_T *prLast=NULL;
    CRIT_STATE_T rState;
    BOOL fgFound=FALSE;

    rState = x_crit_start();
    DLIST_FOR_EACH(prLast, &_qCbEvent, rLink)
    {
        if (!prLast)
        {
            break;
        }

        if (prLast->rGetCb.eFctId >= CB_FCT_NUM)
        {
            break;
        }
        
        if (current->tgid == _arCbId[prLast->rGetCb.eFctId])
        {
            fgFound = TRUE;
            break;
        }

        if (prLast->rLink.pt_next == NULL)
        {
            break;
        }
    }
    if (fgFound && (prLast != NULL))
    {
        DLIST_REMOVE(prLast, &_qCbEvent, rLink);
    }
    x_crit_end(rState);

    if (fgFound)
    {
        return prLast;
    }
    else
    {
        return NULL;
    }
}
#endif
//-----------------------------------------------------------------------------
/** Put the current thread into wait queue.
 */
//-----------------------------------------------------------------------------
#if 0
void _CB_PutThdIntoWQ(void)
{
    DECLARE_WAITQUEUE(wait, current);

    add_wait_queue(&_rWq, &wait);
    set_current_state(TASK_INTERRUPTIBLE);
    schedule();
    set_current_state(TASK_RUNNING);
    remove_wait_queue(&_rWq, &wait);
}
#endif

#ifdef CC_MTAL_MULTIPROCESS_CALLBACK
//-----------------------------------------------------------------------------
/** Clear specified PID entries in event list
 */
//-----------------------------------------------------------------------------
void _CB_ClearPendingEventEntries(void)
{
    CB_CALLBACK_EVENT_T *prLast=NULL;
    CRIT_STATE_T rState;
    BOOL fgFound;
    UINT32 u4DoneMask;

    do {
        fgFound = FALSE;
        
        rState = x_crit_start();
        if ((prLast = _CB_GetEvent(&u4DoneMask)) != NULL)
            {
                fgFound = TRUE;
            _CB_FreeEvent(prLast, u4DoneMask);
        }
        x_crit_end(rState);
    } while (fgFound);
}

//-----------------------------------------------------------------------------
/** Clear entries with specified eFctId and PID in event list
 */
//-----------------------------------------------------------------------------
void _CB_ClearPendingFctEventEntries(CB_FCT_ID_T eFctId)
{
    CB_CALLBACK_EVENT_T *prLast=NULL;
    CRIT_STATE_T rState;
    BOOL fgFound;
    UINT32 u4DoneMask;

    do {
        fgFound = FALSE;
        
        rState = x_crit_start();
        DLIST_FOR_EACH(prLast, &_qCbEvent, rLink)
        {
            if (!prLast)
            {
                break;
            }
            
            if (prLast->rGetCb.eFctId >= CB_FCT_NUM)
            {
                break;
            }
            
            if (prLast->rGetCb.eFctId != eFctId)
            {
                continue;
            }

            if (current->tgid == _arCbId[prLast->rGetCb.eFctId])
            {
                u4DoneMask = BITMASK_SINGLE;
                fgFound = TRUE;
                break;
            }
            else if (current->tgid == _arMultiCbId[prLast->rGetCb.eFctId][0])
                {
                u4DoneMask = BITMASK_MULTI1;
                    fgFound = TRUE;
                    break;
                }
            else if (current->tgid == _arMultiCbId[prLast->rGetCb.eFctId][1])
            {
                u4DoneMask = BITMASK_MULTI2;
                fgFound = TRUE;
                break;
            }
            else if (current->tgid == _arMultiCbId[prLast->rGetCb.eFctId][2])
            {
                u4DoneMask = BITMASK_MULTI3;
                fgFound = TRUE;
                break;
            }
        }
        if (fgFound && (prLast != NULL))
        {
            _CB_FreeEvent(prLast, u4DoneMask);
        }
        x_crit_end(rState);
    } while (fgFound);
}
#else
//-----------------------------------------------------------------------------
/** Clear specified PID entries in event list
 */
//-----------------------------------------------------------------------------
void _CB_ClearPendingEventEntries(void)
{
    CB_CALLBACK_EVENT_T *prLast=NULL;
    CRIT_STATE_T rState;
    BOOL fgFound;

    do {
        fgFound = FALSE;
        
        rState = x_crit_start();
        DLIST_FOR_EACH(prLast, &_qCbEvent, rLink)
        {
            if (!prLast)
            {
                break;
            }
            
            if (prLast->rGetCb.eFctId >= CB_FCT_NUM)
            {
                break;
            }
            
            if (current->tgid == _arCbId[prLast->rGetCb.eFctId])
            {
                fgFound = TRUE;
                break;
            }
        
            if (prLast->rLink.pt_next == NULL)
            {
                break;
            }
        }
        if (fgFound && (prLast != NULL))
        {
            DLIST_REMOVE(prLast, &_qCbEvent, rLink);
			kfree(prLast);
        }
        x_crit_end(rState);
    } while (fgFound);
}

//-----------------------------------------------------------------------------
/** Clear entries with specified eFctId and PID in event list
 */
//-----------------------------------------------------------------------------
void _CB_ClearPendingFctEventEntries(CB_FCT_ID_T eFctId)
{
    CB_CALLBACK_EVENT_T *prLast=NULL;
    CRIT_STATE_T rState;
    BOOL fgFound;

    do {
        fgFound = FALSE;
        
        rState = x_crit_start();
        DLIST_FOR_EACH(prLast, &_qCbEvent, rLink)
        {
            if (!prLast)
            {
                break;
            }
            
            if (prLast->rGetCb.eFctId >= CB_FCT_NUM)
            {
                break;
            }
            
            if (current->tgid == _arCbId[prLast->rGetCb.eFctId])
            {
                if (prLast->rGetCb.eFctId == eFctId)
                {
                    fgFound = TRUE;
                    break;
                }
            }
        
            if (prLast->rLink.pt_next == NULL)
            {
                break;
            }
        }
        if (fgFound && (prLast != NULL))
        {
            DLIST_REMOVE(prLast, &_qCbEvent, rLink);
			kfree(prLast);
        }
        x_crit_end(rState);
    } while (fgFound);
}
#endif

//-----------------------------------------------------------------------------
/** Clear specified PID entries in CB PID table
 */
//-----------------------------------------------------------------------------
void _CB_ClearCbIdArray(void)
{
    INT32 i;
    CRIT_STATE_T rState;

    rState = x_crit_start();
    for (i=0; i<CB_FCT_NUM; i++)
    {
        if (current->tgid == _arCbId[i])
        {
            _arCbId[i] = (pid_t)0;
        }
        #ifdef CC_MTAL_MULTIPROCESS_CALLBACK
        if (current->tgid == _arMultiCbId[i][0])
        {
            _arMultiCbId[i][0] = (pid_t)0;
        }
        if (current->tgid == _arMultiCbId[i][1])
        {
            _arMultiCbId[i][1] = (pid_t)0;
        }
        if (current->tgid == _arMultiCbId[i][2])
        {
            _arMultiCbId[i][2] = (pid_t)0;
        }
        #endif
    }
    x_crit_end(rState);
}
