/*****************************************************************************/
/* 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.                                                                      */
/*                                                                           */
/*****************************************************************************/
/*  Based on 'snull' example code from "Network Drivers" Chapter 17,
    in "Linux Device Drivers" book,
    3rd edition, ISBN 0-596-00590-3.

*//**
    @file   phStbNetDev_Module.c
    @brief  Linux kernel module for phStbNetDev component.

@b  Component:  phStbNetDev.

*//*

Rev Date       Author      Comments
--------------------------------------------------------------------------------
 10 20070413   batelaan    Use worker thread for real work after interrupt.
 11 20070501   batelaan    Use local lock to protect pool from other threads,
                           no need to protect from interrupts.
 12 20070920   batelaan    Refactor common things.
 13 20070921   batelaan    Minor code cleanup.
 14 20070925   batelaan    Use of new function phStbNetDev_CreatePool.
 15 20071029   batelaan    Correct mac addresses in packet header
 16 20071206   batelaan    Add more tracing.
 17 20071218   hutch       Renamed phStbNetDev_SetupPool.
 18 20080205   batelaan    Update file copyright/license text
 19 20080307   laird       Move to 2.6.24_nxp linux kernel.
 20 20080318   laird       Add a random mac addr.
 21 20080409   batelaan    Use netif_rx_ni since we are not in interrupt context.
 22 20080415   batelaan    Solve msg: BUG ipc0 code -12 qlen 0
 23 20080419   batelaan    Spinlock improvements.
 24 20080603   batelaan    Add param subnet and phStbNetDev_GetSubNet
 25 20080606   laird       Remove 2.6.19 support
 26 20080801   Robbe	   (CFR_BSS) Make ipc0 an OSAL task for control MIPS
 27 20081210   laird       Remove above fix.
 28 20090121   batelaan    Update author/license.
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

#include <linux/version.h>
#include <tmNxTypes.h>
#include <tmNxCompId.h>
#include <phStbNetDev_Internal.h>

/* Attempt at using tmosal task, instead of Linux kthread.
   Aborted since DSP does not have full osal functionality.
*/
//#define PH_STBNETDEV_USE_OSAL_TASK
#ifdef PH_STBNETDEV_USE_OSAL_TASK
#include <tmosal.h>
#endif

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

/** Default timeout period for a transmit, in jiffies. A timeout means
 * that StbNetDev_TransmitTimeOut() is called by the network layer,
 * where we start the queue again. */
#define PH_STBNETDEV_TIMEOUT 5

#ifdef PH_STBNETDEV_USE_OSAL_TASK
#define PH_STBNETDEV_RECEIVE_TASK_PRIORITY 30
#define PH_STBNETDEV_RECEIVE_TASK_STACK_SIZE 4096
#endif

#define THIS_MODULE_DESCRIPTION "Ethernet over Memory IPC Network device"
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION(THIS_MODULE_DESCRIPTION);
MODULE_AUTHOR("NXP B.V.");

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

static int __init StbNetDev_ModuleInit(void);
static void StbNetDev_TransmitTimeOut(struct net_device *dev);
static irqreturn_t StbNetDev_Interrupt(int irq, void *dev_id);

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

/**
 * This structure is private to our device.
 */
typedef struct phStbNetDev_Priv {
    /** Statistics for this device (errors, receive/transmitted packets/bytes, etc). */
    struct net_device_stats stats;
    /** Protection against interrupts. */
    spinlock_t lock;
    /** Sequence number of last packet received by this device. 0
     * means no packets received yet. Non-essential, for reliablity
     * checking only. */
    ulong seqNrLastReceived;
    /** Sequence number of last packet sent by this device.  Used to
     * ensure packets are not lost. 0 means no packets sent
     * yet. Non-essential, for reliablity checking only. */
    ulong seqNrLastSent;
} phStbNetDev_Priv_t;

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

module_init(StbNetDev_ModuleInit);

int gphStbNetDev_LocalLockFlag;

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

#if PH_STBNETDEV_DEBUG
/** Debug level.
    - 0: 0ff
    - 1: initialisation
    - 2: empty queue or packet share limit situations. Transmit timeouts.
    - 3: receive and transmit start/stop
    - 4: individual packet send/receive
    - 5: packet queues
    - 8: interrupt tracing
    - 9: additional stuff, like function entry/exit.
    The default level is 2.
    @see PH_STBNETDEV_DEBUG
*/
module_param_named(debug, gphStbNetDev_Debug, uint, S_IRUGO|S_IWUSR);
#endif /* # if PH_STBNETDEV_DEBUG */

static int timeout = PH_STBNETDEV_TIMEOUT;
module_param(timeout, int, S_IRUGO);

static char *gphStbNetDev_SubNet = "?";
module_param_named(subnet, gphStbNetDev_SubNet, charp, S_IRUGO);

#if !PH_STBNETDEV_DSP
static size_t mtu = ETH_DATA_LEN;
module_param(mtu, uint, S_IRUGO);

static uint share = PH_STBNETDEV_SHARE_DEFAULT;
module_param(share, uint, S_IRUGO);

#else /* #if !PH_STBNETDEV_DSP */

/** Module parameter for the physical mapped (KSEG1) address of ::gpStbNetDev_Pool.
    If not given, the we allocate the pool ourselves.
*/
static ulong pool = 0;
module_param(pool, ulong, S_IRUGO);
#endif /* #if !PH_STBNETDEV_DSP */

/** The device.
*/
static struct net_device *gpStbNetDev_Dev;

/** Semaphore to signal a receive event to the receive worker thread. */
static struct semaphore gStbNetDev_RcvSema;

/** Lock to control access by current CPU to the pool. */
DEFINE_SPINLOCK(gphStbNetDev_PoolLock);

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

tmErrorCode_t phStbNetDev_GetSubNet(char *address)
{
    strcpy(address, gphStbNetDev_SubNet);
    return TM_OK;
}

/******************************************************************************/
/*
 * Set up the devices' packet pool ::gpStbNetDev_Pool.
 */
int phStbNetDev_SetupPool(void)
{
#if PH_STBNETDEV_DSP
    gpStbNetDev_PoolPhys = pool;
    gpStbNetDev_Pool = (phStbNetDev_Pool_t*)CAC_ADDR(gpStbNetDev_PoolPhys);
    if (gpStbNetDev_Pool->header.magic != PH_STBNETDEV_POOL_MAGIC)
    {
        printk(KERN_ERR "phStbDevNet: pool 0x%08lx has incorrect magic\n", pool);
        return -EINVAL;
    }
    MSG_LPRINT(1, (KERN_NOTICE "phStbNetDev: existing pool %p; %u packets\n", gpStbNetDev_Pool,
                 gpStbNetDev_Pool->header.numPackets));
    return 0;
#else
    return phStbNetDev_CreatePool(mtu, share);
#endif
}

/******************************************************************************/
/**
 * Open the device.
 */
static int StbNetDev_Open(struct net_device *dev)
{
    int ret;
    MSG_LPRINT(9, (KERN_NOTICE ">>%s\n", __FUNCTION__));

    ret = request_irq(
        PNX833X_PIC_AUDIO_DEC_INT,
        StbNetDev_Interrupt,
        0,
        dev->name,
        dev);
    if (ret != 0)
    {
        printk(KERN_ERR "StbNetDev_Open: %s: cannot get irq %d => %d\n",
               dev->name, PNX833X_PIC_AUDIO_DEC_INT, ret);
        return ret;
    }
    netif_start_queue(dev);
    phStbNetDev_ReceiveInterruptsEnable(1);		/* enable receive interrupts */
    MSG_LPRINT(9, (KERN_NOTICE "<<%s\n", __FUNCTION__));
    return 0;
}

/******************************************************************************/
/**
 * Close the device. Stops the transmit queue.
 */
static int StbNetDev_Release(struct net_device *dev)
{
    MSG_LPRINT(9, (KERN_NOTICE ">>%s\n", __FUNCTION__));
    netif_stop_queue(dev); /* can't transmit any more */
    free_irq(PNX833X_PIC_AUDIO_DEC_INT, dev);
    MSG_LPRINT(9, (KERN_NOTICE "<<%s\n", __FUNCTION__));
    return 0;
}

/******************************************************************************/
/**
 * Receive a packet: retrieve, encapsulate and pass over to upper levels
 */
static void StbNetDev_Receive(struct net_device *dev, phStbNetDev_Packet_t *pkt)
{
	struct sk_buff *skb;
	phStbNetDev_Priv_t *priv = netdev_priv(dev);

    MSG_LPRINT(4, (KERN_NOTICE "%s: receive; seqNr=%lu len=%u\n", dev->name, pkt->header.seqNr, pkt->header.datalen));
    if (pkt->header.seqNr != (priv->seqNrLastReceived + 1))
    {
        if (printk_ratelimit())
        {
            printk(KERN_ERR "StbNetDev_Receive: packet seqNr (%lu) != seqNrLastReceived (%lu)+1: packet dropped\n",
                   pkt->header.seqNr, priv->seqNrLastReceived);
        }
        priv->stats.rx_dropped++;
        goto out;
    }

    priv->seqNrLastReceived = pkt->header.seqNr;
	/*
	 * The packet has been retrieved from the transmission
	 * medium. Build an skb around it, so upper layers can handle it
	 */
	skb = dev_alloc_skb(pkt->header.datalen + 2);
	if (!skb) {
            if (printk_ratelimit())
            {
                printk(KERN_NOTICE "StbNetDev_Receive: low on mem: packet dropped\n");
            }
            priv->stats.rx_dropped++;
            goto out;
	}
	skb_reserve(skb, NET_IP_ALIGN); /* align socket data on correct boundary */
	memcpy(skb_put(skb, pkt->header.datalen), pkt->data, pkt->header.datalen);

	/* Write metadata, and then pass to the receive level */
	skb->dev = dev;
	skb->protocol = eth_type_trans(skb, dev);
	skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
	priv->stats.rx_packets++;
	priv->stats.rx_bytes += pkt->header.datalen;
	netif_rx_ni(skb);
  out:
    MSG_LPRINT(9, (KERN_NOTICE "<<%s\n", __FUNCTION__));
	return;
}


/******************************************************************************/
/*
 * The 'softirq' interrupt entry point. Receive a packet.
 */
static void StbNetDev_SoftInterrupt(void)
{
    phStbNetDev_Priv_t *priv;
    phStbNetDev_Packet_t *pkt = NULL;

    MSG_LPRINT(3, (KERN_NOTICE ">>%s\n", __FUNCTION__));

    priv = netdev_priv(gpStbNetDev_Dev);

    do {
        /* Lock the device */
        spin_lock(&priv->lock);

        pkt = phStbNetDev_GetFullBuffer(gpStbNetDev_Dev);
        if (pkt != NULL)
        {
            StbNetDev_Receive(gpStbNetDev_Dev, pkt);
        }
        /* Unlock the device and we are done */
        spin_unlock(&priv->lock);
        if (pkt != NULL)
        {
            phStbNetDev_PutFreeBuffer(gpStbNetDev_Dev, pkt); /* Do this outside the lock! TODO why? */
        }
        else
        {
            /* No packet received but there could be a new packet in the free list,
               put there by the peer CPU, so if our transmit queue is stopped
               then wake it up. */
            if (netif_queue_stopped(gpStbNetDev_Dev))
            {
                MSG_LPRINT(2, (KERN_NOTICE "StbNetDev_SoftInterrupt: netif_wake_queue\n"));
                netif_wake_queue(gpStbNetDev_Dev);
            }
        }
    } while (pkt != NULL);

    MSG_LPRINT(3, (KERN_NOTICE "<<%s\n\n", __FUNCTION__));
}

/******************************************************************************/
/*
 * The hard interrupt entry point. Raise a softirq via up'ing semaphore
 * ::gStbNetDev_RcvSema.
 */
static irqreturn_t StbNetDev_Interrupt(int irq, void *dev_id)
{
    (void)irq;
    (void)dev_id;
    MSG_LPRINT(8, ("StbNetDev_Interrupt: raising receive event\n"));
    MMIO(gphStbNetDev_MailboxBase + PH_STBNETDEV_MAILBOX_INT_CLEAR) = 1;
    up(&gStbNetDev_RcvSema);
    return IRQ_HANDLED;
}

/******************************************************************************/
/*
 * Transmit a packet (called by the kernel), our dev->hard_start_xmit field.
 * @return 0 if packet has been transmitted, !0 otherwise.
 */
static int StbNetDev_TransmitPacket(struct sk_buff *skb, struct net_device *dev)
{
    int ret = 0;
    phStbNetDev_Packet_t *tx_buffer;
    int len = skb->len;
    char *data = skb->data;
    phStbNetDev_Priv_t *priv = netdev_priv(dev);

    MSG_LPRINT(3, (KERN_NOTICE ">>%s\n", __FUNCTION__));

    /* Get a packet buffer in the packet pool, copy the data, and flush the cache. */
    tx_buffer = phStbNetDev_GetFreeBuffer(dev);
    if (tx_buffer == NULL)
    {
        ret = 1;
    }
    else
    {
        dev->trans_start = jiffies; /* save the timestamp */
        priv = netdev_priv(dev);
        tx_buffer->header.datalen = len;
        priv->seqNrLastSent++;
        tx_buffer->header.seqNr = priv->seqNrLastSent;
        memcpy(tx_buffer->data, data, len);
        MSG_LPRINT(4, (KERN_NOTICE "%s: transmit; seqNr=%lu len=%d\n", dev->name, tx_buffer->header.seqNr, len));
        phStbNetDev_PutFullBuffer(dev, tx_buffer);
        priv->stats.tx_packets++;
        priv->stats.tx_bytes += len;
        dev_kfree_skb_any(skb); /* Done with socket buffer. */
    }
    MSG_LPRINT(3, (KERN_NOTICE "<<%s DONE => %d\n", __FUNCTION__, ret));
    return ret;
}

/******************************************************************************/
/**
 * Deal with a transmit timeout.
 * We just re-enable the device queue that was stopped in ::StbNetDev_TransmitPacket.
 */
static void StbNetDev_TransmitTimeOut (struct net_device *dev)
{
    phStbNetDev_Priv_t *priv = netdev_priv(dev);

    MSG_LPRINT(2, (KERN_NOTICE "%s: Transmit timeout at %ld, latency %ld (wake queue)\n",
                 dev->name, jiffies, jiffies - dev->trans_start));
    priv->stats.tx_errors++;
    dev->trans_start = jiffies;
    netif_wake_queue(dev);
}

/******************************************************************************/
/**
 * Return statistics (bytes and packets received/transmitted, errors etc) to the caller.
 * @return Device statistics.
 */
static struct net_device_stats *StbNetDev_Stats(struct net_device *dev)
{
    phStbNetDev_Priv_t *priv = netdev_priv(dev);
    return &priv->stats;
}

/******************************************************************************/
/**
 * Fill up an eth header, since arp is not available on the interface.
 */
static int StbNetDev_RebuildHeader(struct sk_buff *skb)
{
    struct ethhdr *eth = (struct ethhdr *) skb->data;

    memcpy(eth->h_source, PH_STBNETDEV_SOURCE_ADDR, ETH_ALEN);
    memcpy(eth->h_dest, PH_STBNETDEV_DEST_ADDR, ETH_ALEN);
    return 0;
}

/******************************************************************************/
/** Take header information provided by the kernel, and fill in the
 * eth header of the socket buffer.
 */
static int StbNetDev_Header(struct sk_buff *skb, struct net_device *dev,
                            unsigned short type, const void *daddr, const void *saddr,
                            unsigned int len)
{
    struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);

    eth->h_proto = htons(type);
    memcpy(eth->h_source, PH_STBNETDEV_SOURCE_ADDR, ETH_ALEN);
    memcpy(eth->h_dest, PH_STBNETDEV_DEST_ADDR, ETH_ALEN);
    return (int)(dev->hard_header_len);
}

/** New structure for 2.6.24 linux kernel.  Same behaviour.*/
static const struct header_ops StbNetDev_header_ops = {
	.create		= StbNetDev_Header,
	.rebuild	= StbNetDev_RebuildHeader,
};

/******************************************************************************/
/**
 * The init function (sometimes called probe).
 * It is invoked by register_netdev().
 * @todo No resource should be grabbed: this is should be done on open().
 */
static void StbNetDev_InitDev(struct net_device *dev)
{
    phStbNetDev_Priv_t *priv;
    MSG_LPRINT(9, (KERN_NOTICE ">>%s\n", __FUNCTION__));
    /*
     * Assign other fields in dev, using ether_setup() and some
     * hand assignments
     */
    ether_setup(dev); /* assign some of the fields */

    dev->open            = StbNetDev_Open;
    dev->stop            = StbNetDev_Release;
    dev->hard_start_xmit = StbNetDev_TransmitPacket;
    dev->get_stats       = StbNetDev_Stats;
    dev->header_ops      = &StbNetDev_header_ops;
    dev->tx_timeout      = StbNetDev_TransmitTimeOut;

    /* Limit the amount of pool packets to use to a few less than the amount of packets.
       This because it will then less likely get transmission timeout. */
    dev->tx_queue_len    = gpStbNetDev_Pool->header.packetsAllocation;
    dev->watchdog_timeo = timeout;
    /* keep the default flags, just add NOARP */
    dev->flags           |= IFF_NOARP;
    /* Do not use: dev->features |= NETIF_F_NO_CSUM since it has been shown
       that this prevents us telnet-ing/nfs-ing between host pc and dsp. */
    dev->mtu = gpStbNetDev_Pool->header.mtu;
    memcpy(dev->dev_addr, PH_STBNETDEV_SOURCE_ADDR, ETH_ALEN);

    /*
     * Then, initialize the priv field. This encloses the statistics
     * and a few private fields.
     */
    priv = netdev_priv(dev);
    memset(priv, 0, sizeof(phStbNetDev_Priv_t));
    spin_lock_init(&priv->lock);
    MSG_LPRINT(9, (KERN_NOTICE "<<%s\n", __FUNCTION__));
}

/******************************************************************************/
/** Setup our device #PH_STBNETDEV_DEVNAME.
    @return 0 on success.
*/
static int StbNetDev_SetupDevice(void)
{
    int ret = -ENOMEM;
    char *name = PH_STBNETDEV_DEVNAME;
    phStbNetDev_Priv_t *priv;

    MSG_LPRINT(9, (KERN_NOTICE ">>%s\n", __FUNCTION__));

    /* Allocate the device */
    gpStbNetDev_Dev = alloc_netdev(sizeof(phStbNetDev_Priv_t), name,
                                   StbNetDev_InitDev);
    if (gpStbNetDev_Dev == NULL)
    {
        printk(KERN_ERR "StbNetDev: error alloc_netdev \"%s\"\n",
               name);
        return ret;
    }

    MSG_LPRINT(1, (KERN_NOTICE "StbNetDev: alloc_netdev => \"%s\"\n",
              gpStbNetDev_Dev->name));

    priv = netdev_priv(gpStbNetDev_Dev);

    ret = register_netdev(gpStbNetDev_Dev);
    if (ret != 0)
    {
        printk(KERN_ERR "StbNetDev: register_netdev \"%s\" => %d\n",
               gpStbNetDev_Dev->name, ret);
        return -ENODEV;
    }
    MSG_LPRINT(9, (KERN_NOTICE "<<%s => 0\n", __FUNCTION__));
    return 0;
}

#ifdef PH_STBNETDEV_USE_OSAL_TASK
void StbNetDev_ReceiveTaskHandler(void *arg)
#else
int  StbNetDev_ReceiveTaskHandler(void *arg)
#endif
{
    for (;;)
    {
        MSG_LPRINT(8, ("StbNetDev_ReceiveTaskHandler: waiting for event...\n"));
        down(&gStbNetDev_RcvSema);
        StbNetDev_SoftInterrupt();
    }
#ifndef PH_STBNETDEV_USE_OSAL_TASK
    return 0;
#endif
}

/******************************************************************************/
/** Module Creation function.

    @retval int 0 if module and the device initialised successfully.
*/
static Int __init StbNetDev_ModuleInit(void)
{
    int ret = -EINVAL;
#ifdef PH_STBNETDEV_USE_OSAL_TASK
    tmosalTaskHandle_t taskHandle;
#endif

    MSG_LPRINT(9, (KERN_NOTICE ">>%s\n", __FUNCTION__));
    spin_lock_init(&gphStbNetDev_PoolLock);

#if PH_STBNETDEV_DSP
    if (pool == 0)
    {
        printk(KERN_ERR "phStbNetDev: pool parameter should be specified and !NULL\n");
        goto ERROR_RETURN;
    }
#endif

    ret = phStbNetDev_SetupPool();
    if (ret != 0)
    {
        goto ERROR_RETURN;
    }
    init_MUTEX_LOCKED(&gStbNetDev_RcvSema);

    ret = StbNetDev_SetupDevice();
    if (ret != 0)
    {
        goto ERROR_RETURN;
    }

#ifdef PH_STBNETDEV_USE_OSAL_TASK
    (void)tmosalTaskCreate(
        &StbNetDev_ReceiveTaskHandler, NULL,
        PH_STBNETDEV_RECEIVE_TASK_PRIORITY, PH_STBNETDEV_RECEIVE_TASK_STACK_SIZE,
        &taskHandle, gpStbNetDev_Dev->name, tmosalTaskStarted);
#else
    (void)kthread_run(&StbNetDev_ReceiveTaskHandler, NULL, gpStbNetDev_Dev->name);
#endif

    printk("%s (%s-%s)\n", THIS_MODULE_DESCRIPTION, __DATE__, __TIME__);

    MSG_LPRINT(9, (KERN_NOTICE "<<%s => %d\n", __FUNCTION__, ret));
    return ret;

  ERROR_RETURN:
    printk(KERN_ERR "%s (%s-%s) => %d\n", THIS_MODULE_DESCRIPTION, __DATE__, __TIME__, ret);
    return ret;
}

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