/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* 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, using version 2 of the License.             */
/*                                                                           */
/* 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, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
    @file   phStbNetDev_Common.c
    @brief  Common items for phStbNetDev component.

@b  Component:  phStbNetDev.

*//*

Rev Date       Author      Comments
--------------------------------------------------------------------------------
  1 20070919   batelaan    Initial from refactoring Linux code.
  2 20070921   batelaan    Portability fix
  3 20070925   batelaan    Add phStbNetDev_CreatePool. Use phStbKBufMgr
  4 20071218   hutch       WinCE porting, don't do void* arithmetic.
  5 20071220   batelaan    Ensure we use cached memory on Linux
5.1.1 20080422 batelaan    Update file header
  6 20080215   hutchinson  Merge in EB's changes from 14/02/2008 release.
  7 20080318   steel       delinux cache flushing
  8 20080419   batelaan    Locking improvements
  9 20080423   batelaan    Merge 5.1.1
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

#include <tmCompId.h>
#include <tmml.h>
#include <phStbNetDev_Internal.h>
#if !PH_STBNETDEV_DSP
#include <phStbKBufMgr.h>
#endif

/***********************************************
* LOCAL MACROS                                 *
* recommendation only: <MODULE><_WORD>+        *
************************************************/

#if PH_STBNETDEV_DSP
  #define PH_STBNETDEV_MAILBOX PH_STBNETDEV_MAILBOX0
#else
  #define PH_STBNETDEV_MAILBOX PH_STBNETDEV_MAILBOX1
#endif

/******************************************************************
* FUNCTION PROTOTYPES                    [p]ph<Module>_<Word>+    *
*******************************************************************/

/******************************************************************
* LOCAL TYPEDEFS                                                  *
* recommendation only:                   [p]ph<Module>_<Word>+_t  *
*******************************************************************/

/*******************************************************************
* EXPORTED DATA                 g[k|p|kp|pk|kpk]ph<Module>_<Word>+ *
********************************************************************/

pphStbNetDev_Pool_t gpStbNetDev_Pool;
UInt32 gpStbNetDev_PoolPhys;
UInt32 gphStbNetDev_MailboxBase = (KSEG1 | PH_STBNETDEV_MAILBOX);
#if PH_STBNETDEV_DEBUG
UInt32 gphStbNetDev_Debug = 2U;
#endif

/*******************************************************************
* LOCAL DATA                                                       *
********************************************************************/

/*******************************************************************************
* FUNCTION IMPLEMENTATION  <Module>[_<Word>+] for static functions             *
*                          tm[<layer>]<Module>[_<Word>+] for exported functions*
********************************************************************************/

/******************************************************************************/
#if !PH_STBNETDEV_DSP
int phStbNetDev_CreatePool(
    size_t mtu,
    UInt32 share)
{
    uint32_t addr; /* KSEG0 address - cached, physical */
    uint32_t size;
    tmErrorCode_t err;
    err = phStbKBufMgr_GetBuffer(phStbKBufMgr_Buffer_NETDEV,
        &addr, &size);

    if (err != TM_OK) {
        ERRPRINT((ERRPREFIX "StbNetDev: no memory for packet pool\n"));
        return 1;
    }
    MSG_LPRINT(1, ("phStbNetDev_AllocPool => %u bytes at 0x%x\n", size, addr));

    return phStbNetDev_InitPool(
        (phStbNetDev_Pool_t*) addr,
        UNCAC_ADDR(addr),
        size,
        mtu,
        share);
}

/******************************************************************************/
/*
 * Set up the devices' packet pool ::gpStbNetDev_Pool.
 */
int phStbNetDev_InitPool(
    phStbNetDev_Pool_t *pPoolVirt,
    UInt32 pPoolPhys,
    size_t poolSize,
    size_t mtu,
    UInt32 share)
{
    int numPackets;
    int i;
    phStbNetDev_Packet_t *pkt;
    phStbNetDev_Packet_t * volatile *pPacketList;
    size_t packetSize;
    uint packetsAllocation;
    uint headerSizeAligned;

    if (mtu == 0U)
    {
        mtu = (size_t)ETH_DATA_LEN;
    }

    if (share == 0)
    {
        share = PH_STBNETDEV_SHARE_DEFAULT;
    }
    else
    {
        /* share param should be a percentage 50..100 */
        if ((share < 50) || (share > 100))
        {
            ERRPRINT((ERRPREFIX "phStbNetDev: share param (%u) should be 50..100\n", share));
            return -1;
        }
    }

    /* Calculate pool parameters */
    packetSize = PH_STBNETDEV_PACKET_HEADER_SIZE + ETH_HLEN + mtu;
    packetSize = L1_CACHE_ALIGN(packetSize);
    headerSizeAligned = L1_CACHE_ALIGN(sizeof(phStbNetDev_PoolHeader_t));
    numPackets = (uint)((poolSize - headerSizeAligned) / packetSize);
    packetsAllocation = numPackets * share / 100;
    MSG_LPRINT(1, (KERN_NOTICE "StbNetDev: poolSize=%u numPackets=%u packetsAllocation=%u packetSize=%u\n",
                 poolSize, numPackets, packetsAllocation, packetSize));
    if (packetsAllocation < 1U)
    {
        ERRPRINT((ERRPREFIX "StbNetDev: pool and/or share params too small\n"));
        return -EINVAL;
    }

    gpStbNetDev_Pool = pPoolVirt;
    gpStbNetDev_PoolPhys = pPoolPhys;

    /* Init all header fields to 0. */
    memset(gpStbNetDev_Pool, 0, sizeof(phStbNetDev_PoolHeader_t));
    gpStbNetDev_Pool->header.packetSize = packetSize;
    gpStbNetDev_Pool->header.mtu = mtu;
    gpStbNetDev_Pool->header.numPackets = numPackets;
    gpStbNetDev_Pool->header.packetsAllocation = packetsAllocation;

    phStbNetDev_LockInitUnlocked(&gpStbNetDev_Pool->header.lock);
    gpStbNetDev_Pool->header.magic = PH_STBNETDEV_POOL_MAGIC;

    MSG_LPRINT(1, (KERN_NOTICE "StbNetDev: PH_STBNETDEV_POOL_HEADER=0x%x\n", &PH_STBNETDEV_POOL_HEADER));

    /* Setup free packets list */
    pPacketList = &gpStbNetDev_Pool->header.packetListFree;
    pkt = (phStbNetDev_Packet_t*)(((char*)gpStbNetDev_Pool) + headerSizeAligned);
    for (i = 0; i < numPackets; i++) {
        pkt->header.next = *pPacketList;
        *pPacketList = pkt;
        pkt = (phStbNetDev_Packet_t*)(((char*)pkt) + packetSize);
    }
    tmmlCacheFlush(gpStbNetDev_Pool, poolSize);
    return 0;
}
#endif /* #if !PH_STBNETDEV_DSP */

/******************************************************************************/
UInt32 phStbNetDev_GetPoolPhysical(void)
{
    return gpStbNetDev_PoolPhys;
}

/******************************************************************************/
/**
 * Get a free buffer packet.
 * @return NULL if the free packet list is empty,
 * or the packet allocation share was already reached.
 */
phStbNetDev_Packet_t *phStbNetDev_GetFreeBuffer(pphStbNetDev_Device_t dev)
{
    phStbNetDev_Packet_t *pkt;
    phStbNetDev_Packet_t * volatile *pPacketList = &PH_STBNETDEV_POOL_HEADER.packetListFree;
    bool stopQueue = true;

    MSG_LPRINT(9, (KERN_NOTICE ">>StbNetDev_GetFreeBuffer ...\n"));
    PH_STBNETDEV_POOL_LOCK;

    pkt = *pPacketList;
    if (pkt == NULL)
    {
        MSG_LPRINT(2, (KERN_NOTICE "phStbNetDev_GetFreeBuffer: No free packet\n"));
    }
    else
    {
        /* Reached maximum packet allocation share limit? */
        if (PH_STBNETDEV_POOL_HEADER.packetsAllocated[PH_STBNETDEV_ID_SELF] == PH_STBNETDEV_POOL_HEADER.packetsAllocation)
        {
            /* Yes, don't return a packet. */
            MSG_LPRINT(2, (KERN_NOTICE "phStbNetDev_GetFreeBuffer: free buffer denied (packet share limit already reached)\n"));
            pkt = NULL;
        }
        else
        {
            /* Yes, we have a free packet.
               Must ensure packet memory is invalidated, because we need to access its next pointer */
            tmmlCacheInvalidate(pkt, PH_STBNETDEV_PACKET_HEADER_SIZE);
            *pPacketList = pkt->header.next;
            pkt->header.next = NULL;

            PH_STBNETDEV_POOL_HEADER.packetsAllocated[PH_STBNETDEV_ID_SELF]++;
                    stopQueue = false;
                }
            }
    if (stopQueue && (!PH_STBNETDEV_DEVICE_QUEUE_STOPPED(dev)))
    {
        MSG_LPRINT(2, (KERN_NOTICE "phStbNetDev_GetFreeBuffer: stopping queue\n"));
        PH_STBNETDEV_DEVICE_STOP_QUEUE(dev);
    }
    PH_STBNETDEV_POOL_UNLOCK;
    MSG_LPRINT(5, (KERN_NOTICE "<<StbNetDev_GetFreeBuffer => %p\n", pkt));
    return pkt;
}

/******************************************************************************/
/** Put a buffer into the free packet list.
    If the peer cpu had reached its packet allocation limit, we interrupt it
    because there is now a free packet available for use and the limit is now cleared.
    If our own transmit device queue is stopped, AND the free list was totally empty
    then we can wake it up again, since we now have a free packet usable (if we are
    quick, since the peer CPU may grab it instead :-) for transmission.
 */
void phStbNetDev_PutFreeBuffer(pphStbNetDev_Device_t dev, phStbNetDev_Packet_t *pkt)
{
    phStbNetDev_Packet_t * volatile *pPacketList = &PH_STBNETDEV_POOL_HEADER.packetListFree;
    bool was_empty;
    bool notifyPeerCpu = true;
    MSG_LPRINT(5, (KERN_NOTICE ">>phStbNetDev_PutFreeBuffer seqNr=%lu %p ...\n", pkt->header.seqNr, pkt));

    PH_STBNETDEV_POOL_LOCK;
    was_empty = ((*pPacketList) == NULL);
    pkt->header.next = *pPacketList;
    /* Must ensure packet memory is written back, because we have updated its next pointer */
    tmmlCacheFlush(pkt, PH_STBNETDEV_PACKET_HEADER_SIZE);

    *pPacketList = pkt;
    PH_STBNETDEV_POOL_HEADER.packetsAllocated[PH_STBNETDEV_ID_PEER]--;

    /* Have we gone under the maximum packet allocation share limit for the peer cpu? */
    if (PH_STBNETDEV_POOL_HEADER.packetsAllocated[PH_STBNETDEV_ID_PEER] == PH_STBNETDEV_POOL_HEADER.packetsAllocation - 1)
    {
        /* Yes. */
        MSG_LPRINT(2, (KERN_NOTICE "phStbNetDev_PutFreeBuffer: peer cpu packet share limit cleared\n"));
    }
    else
    {
        if (was_empty)
        {
            MSG_LPRINT(2, (KERN_NOTICE "phStbNetDev_PutFreeBuffer: free packet available, notify peer cpu\n"));
        }
        else
        {
            notifyPeerCpu = false;
        }
    }
    if (notifyPeerCpu)
    {
        MMIO(gphStbNetDev_MailboxBase + PH_STBNETDEV_MAILBOX_EVENT_SET) = 1;
    }

    if (PH_STBNETDEV_DEVICE_QUEUE_STOPPED(dev))
    {
        if (was_empty &&
            (PH_STBNETDEV_POOL_HEADER.packetsAllocated[PH_STBNETDEV_ID_SELF] < PH_STBNETDEV_POOL_HEADER.packetsAllocation))
        {
            MSG_LPRINT(2, (KERN_NOTICE "phStbNetDev_PutFreeBuffer: free packet available, call WAKE_QUEUE\n"));
            PH_STBNETDEV_DEVICE_WAKE_QUEUE(dev);
        }
    }

    PH_STBNETDEV_POOL_UNLOCK;

    MSG_LPRINT(9, (KERN_NOTICE "<<phStbNetDev_PutFreeBuffer\n"));
}

/******************************************************************************/
/**
 * Get a buffer from the full (incoming) packet buffer list.
 * The packet returned has its memory cache invalidated.
 * @return NULL if the full packet list is empty.
 */
phStbNetDev_Packet_t *phStbNetDev_GetFullBuffer(pphStbNetDev_Device_t dev)
{
    phStbNetDev_Packet_t *pkt;
    phStbNetDev_Packet_t * volatile *pPacketListHead =
        &PH_STBNETDEV_POOL_HEADER.packetListHeads[PH_STBNETDEV_ID_SELF];

    MSG_LPRINT(9, (KERN_NOTICE ">>StbNetDev_GetFullBuffer ...\n"));
    PH_STBNETDEV_POOL_LOCK;

    pkt = *pPacketListHead;
    if (pkt != NULL)
    {
        /* Update packet list (head and tail pointers). */
        phStbNetDev_Packet_t * volatile *pPacketListTail = &PH_STBNETDEV_POOL_HEADER.packetListTails[PH_STBNETDEV_ID_SELF];
        tmmlCacheInvalidate(pkt, PH_STBNETDEV_PACKET_HEADER_SIZE);
        tmmlCacheInvalidate(pkt, PH_STBNETDEV_PACKET_HEADER_SIZE + pkt->header.datalen);
        *pPacketListHead = pkt->header.next;
        if (pkt == (*pPacketListTail))
        {
            *pPacketListTail = NULL;
        }
        MSG_LPRINT(5, (KERN_NOTICE "<<StbNetDev_GetFullBuffer => seqNr=%lu %p\n", pkt->header.seqNr, pkt));
    }
    else
    {
        MSG_LPRINT(5, (KERN_NOTICE "<<StbNetDev_GetFullBuffer => NULL\n"));
    }
    PH_STBNETDEV_POOL_UNLOCK;
    return pkt;
}

/******************************************************************************/
/** Put a full buffer into the receive list for the peer cpu.
    The packet has its memory cache flushed.
    Interrupt the peer cpu.
*/
void phStbNetDev_PutFullBuffer(pphStbNetDev_Device_t dev, phStbNetDev_Packet_t *pkt)
{
    phStbNetDev_Packet_t * volatile *pPacketListHead =
        &PH_STBNETDEV_POOL_HEADER.packetListHeads[PH_STBNETDEV_ID_PEER];
    phStbNetDev_Packet_t * volatile *pPacketListTail =
        &PH_STBNETDEV_POOL_HEADER.packetListTails[PH_STBNETDEV_ID_PEER];
    bool was_empty;

    MSG_LPRINT(5, (KERN_NOTICE ">>phStbNetDev_PutFullBuffer seqNr=%lu %p\n", pkt->header.seqNr, pkt));

    PH_STBNETDEV_POOL_LOCK;
    was_empty = ((*pPacketListHead) == NULL);
    /* Put the packet at the END (tail) of the lists, but if it is empty
       then assign the head of the list. */
    if (was_empty)
    {
        *pPacketListHead = pkt;
    }
    else
    {
        (*pPacketListTail)->header.next = pkt;
        tmmlCacheFlush(*pPacketListTail, PH_STBNETDEV_PACKET_HEADER_SIZE);
    }
    /* Update the tail to point to this packet. */
    *pPacketListTail = pkt;

    tmmlCacheFlush(pkt, PH_STBNETDEV_PACKET_HEADER_SIZE + pkt->header.datalen);

    if (was_empty)
    {
        /* Interrupt the destination CPU. */
        MMIO(gphStbNetDev_MailboxBase + PH_STBNETDEV_MAILBOX_EVENT_SET) = 1;
    }

    PH_STBNETDEV_POOL_UNLOCK;

    MSG_LPRINT(9, (KERN_NOTICE "<<phStbNetDev_PutFullBuffer\n"));
}

/******************************************************************************/
/**
 * Enable and disable receive interrupts.
 */
void phStbNetDev_ReceiveInterruptsEnable(int enable)
{
    MSG_LPRINT(8, (KERN_NOTICE "phStbNetDev_ReceiveInterruptsEnable %s\n", enable ? "enable" : "disable"));
    MMIO(gphStbNetDev_MailboxBase + PH_STBNETDEV_MAILBOX_INT_ENABLE) = enable;
}

/******************************************************************************/
