/*
 * soclib_nic.c - SOCLIB_NIC (Network Interface Controler) driver implementation.
 *
 * Author     Alain Greiner (2016)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH.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; version 2.0 of the License.
 *
 * ALMOS-MKH.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 ALMOS-MKH.; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <hal_types.h>
#include <hal_remote.h>
#include <hal_special.h>
#include <chdev.h>
#include <dev_nic.h>
#include <spinlock.h>
#include <kmem.h>
#include <printk.h>
#include <memcpy.h>
#include <thread.h>
#include <soclib_nic.h>

///////////////////////////////////////
void soclib_nic_init( chdev_t * chdev )
{
    uint32_t    i;
    kmem_req_t  req;

    // set driver specific fields in chdev descriptor
    chdev->cmd = &soclib_nic_cmd;
    chdev->isr = &soclib_nic_isr;
    
    // get hardware device cluster and local pointer
    cxy_t      nic_cxy  = GET_CXY( chdev->base );
    uint32_t * nic_ptr  = (uint32_t *)GET_PTR( chdev->base );

    // initialize Soclib NIC global registers 
    hal_remote_sw( XPTR( nic_cxy , &nic_ptr + NIC_G_BC_ENABLE ) , 0 );
    hal_remote_sw( XPTR( nic_cxy , &nic_ptr + NIC_G_RUN       ) , 0 );

    // allocate memory for chbuf descriptor (one page)
    assert( (sizeof(nic_chbuf_t) <= CONFIG_PPM_PAGE_SIZE ) , __FUNCTION__ ,
            "chbuf descriptor exceeds one page" );

    req.type   = KMEM_PAGE;
    req.size   = 0;
    req.flags  = AF_KERNEL;

    nic_chbuf_t * chbuf = (nic_chbuf_t *)kmem_alloc( &req );

    assert( (chbuf != NULL) , __FUNCTION__ , 
             "cannot allocate chbuf descriptor" );

    // initialise chbuf state
    chbuf->cont_id  = 0;
    chbuf->pkt_id   = 0;
    chbuf->word_id  = 34;
    
    // allocate containers (one page per container)
    // and complete chbuf descriptor initialization
    assert( (CONFIG_PPM_PAGE_SIZE == 4096) , __FUNCTION__ ,
            "chbuf container must be 4 Kbytes" );

    for( i = 0 ; i < CONFIG_NIC_CHBUF_DEPTH ; i++ )
    {
        uint32_t * container = (uint32_t *)kmem_alloc( &req );   

        assert( (container != NULL) , __FUNCTION__ , 
                "cannot allocate container" );
        
        chbuf->cont[i] = container;
        chbuf->full[i] = (paddr_t)XPTR( local_cxy , container );
    }
} // end soclib_nic_init()


//////////////////////////////////////////////////////////////////
void __attribute__ ((noinline)) soclib_nic_cmd( xptr_t thread_xp )
{
    uint32_t       cmd;          // command type    
    char         * buffer;       // pointer on command buffer   
    uint32_t       length;       // Ethernet packet length
    xptr_t         dev_xp;       // extended pointer on NIC device
    nic_chbuf_t  * chbuf;        // pointer on chbuf descriptor
    uint32_t       cont_id;      // index of current container in chbuf
    uint32_t       pkt_id;       // index of current packet in container
    uint32_t       word_id;      // index of first word of current packet in container
    uint32_t     * container;    // pointer on container (array of uint32_t)
    uint16_t     * header;       // pointer on container header (array of uint16_t)
    uint32_t       npackets;     // number of packets in current container

    // get local pointer for client thread
    thread_t * thread_ptr = (thread_t *)GET_PTR( thread_xp );

    // get command arguments
    cmd    = thread_ptr->nic_cmd.cmd;
    buffer = thread_ptr->nic_cmd.buffer;
    length = thread_ptr->nic_cmd.length;
    dev_xp = thread_ptr->nic_cmd.dev_xp;

    // get local pointer for device
    chdev_t * dev_ptr = (chdev_t *)GET_PTR( dev_xp );

    // get chbuf descriptor pointer
    chbuf = (nic_chbuf_t *)dev_ptr->ext.nic.queue;

    // analyse command type
    switch( cmd )
    {
        /////////////////////////////////////////////////////////////////////////////
        case NIC_CMD_READ:   // transfer one packet from RX queue to command buffer
        {
            // get current container index
            cont_id = chbuf->cont_id;
            
            if( chbuf->full[cont_id] == 0 )  // container empty
            {
                printk("PANIC in %s : read an empty container\n", __FUNCTION__ );
                hal_core_sleep();
            }

            // get pointer on container and header 
            container = chbuf->cont[cont_id];
            header    = (uint16_t *)container;

            // get  expected packet index and first word index in container
            pkt_id  = chbuf->pkt_id;
            word_id = chbuf->word_id;

            // get packet length and number of packets from container header
            length    = header[pkt_id + 2];
            npackets  = header[0];

            if( pkt_id >= npackets )  // packet index too large
            {
                printk("PANIC in %s : read a non readable container\n", __FUNCTION__ );
                hal_core_sleep();
            }

            // move the packet from container to buffer
            memcpy( buffer , container + word_id , length );

            // update current packet index and first word index
            chbuf->pkt_id  = pkt_id + 1;
            if( length & 0x3 ) chbuf->word_id = word_id + (length>>2) + 1;
            else               chbuf->word_id = word_id + (length>>2);
        }
        break;    // end READ
            
        //////////////////////////////////////////////////////////////////////////
        case NIC_CMD_WRITE:  // move one packet from command buffer to TX queue
        {
            // get current TX container indexes
            cont_id = chbuf->cont_id;
            pkt_id  = chbuf->pkt_id;
            word_id = chbuf->word_id;

            if( chbuf->full[cont_id] != 0 )  // container full
            {
                printk("PANIC in %s : write to a full container\n", __FUNCTION__ );
                hal_core_sleep();
            }

            // get pointer on container and header 
            container = chbuf->cont[cont_id];
            header    = (uint16_t *)container;

            if( length > ((1024 - word_id)<<2) )  // packet length too large
            {
                printk("PANIC in %s : write to a non writable container\n", __FUNCTION__ );
                hal_core_sleep();
            }

            // update packet length in container header
            header[pkt_id + 2] = (uint16_t)length;

            // move the packet from buffer to container
            memcpy( container + word_id , buffer , length );

            // update current packet index and first word index
            chbuf->pkt_id  = pkt_id + 1;
            if( length & 0x3 ) chbuf->word_id = word_id + (length>>2) + 1;
            else               chbuf->word_id = word_id + (length>>2);
        }
        break;  // end WRITE

        ////////////////////////////////////////////////////////////////////////////
        case NIC_CMD_WRITABLE:  // analyse chbuf status / update status if required
        {
            // get current container state 
            cont_id = chbuf->cont_id;
            word_id = chbuf->word_id;

            // compute current container writable
            bool_t ok = ( chbuf->full[cont_id] == 0 ) &&
                        ( length <= ((1024 - word_id)<<2) );

            if( ok )                // current container writable
            {
                // return chbuf writable
                thread_ptr->nic_cmd.status = true;
            }
            else                    // current container not writable
            {
                // release current container
                chbuf->full[cont_id] = 1;

                // check next container 
                cont_id = (cont_id + 1) % CONFIG_NIC_CHBUF_DEPTH;

                if( chbuf->full[cont_id] == 0 ) // next container empty
                {
                    // update chbuf status
                    chbuf->word_id = 34;
                    chbuf->cont_id = cont_id;
                    chbuf->pkt_id  = 0;
                     
                    // return chbuf writable
                    thread_ptr->nic_cmd.status = true;
                }
                else                            // next container full     
                {
                    // return chbuf non writable
                    thread_ptr->nic_cmd.status = false;
                }
            }
        }
        break;  // end WRITABLE

        /////////////////////////////////////////////////////////////////////////////
        case NIC_CMD_READABLE:  // analyse chbuf status / update status if required
        {
            // get current container state
            cont_id  = chbuf->cont_id;
            pkt_id   = chbuf->pkt_id;
            npackets = chbuf->cont[cont_id][0] & 0x0000FFFF; 
            
            // compute current container readable
            bool_t ok = ( chbuf->full[cont_id] == 1 ) &&
                        ( pkt_id < npackets );

            if( ok )                    // current container readable
            {
                // return chbuf readable     
                thread_ptr->nic_cmd.status = true;
            }
            else                        // current container non readable
            {
                // release current container
                chbuf->full[cont_id] = 0;

                // check next container
                cont_id = (cont_id + 1) % CONFIG_NIC_CHBUF_DEPTH;

                if( chbuf->full[cont_id] == 1 ) // next container full 
                {
                    // update chbuf status
                    chbuf->word_id = 34;
                    chbuf->cont_id = cont_id;
                    chbuf->pkt_id  = 0;
                     
                    // return chbuf readable
                    thread_ptr->nic_cmd.status = true;
                }
                else                            // next container empty    
                {
                    // return chbuf non readable
                    thread_ptr->nic_cmd.status = false;
                }
            }
   
        }
        break;  // end READABLE
    } 
} // end soclib_nic_cmd()


/////////////////////////////////////////////////////////////////
void __attribute__ ((noinline)) soclib_nic_isr( chdev_t * chdev )
{
    // get base, size, channel, is_rx from NIC channel device NIC 
    xptr_t     base    = chdev->base;
    uint32_t   channel = chdev->channel;
    bool_t     is_rx   = chdev->is_rx;

    // get NIC peripheral cluster and local pointer
    cxy_t      cxy_nic = GET_CXY( base );
    uint32_t * ptr_nic = (uint32_t *)GET_PTR( base );

    // compute local pointer on status register 
    uint32_t * offset;
    if( is_rx ) offset = ptr_nic + (NIC_CHANNEL_SPAN * (channel + 1)) + NIC_RX_STATUS;
    else        offset = ptr_nic + (NIC_CHANNEL_SPAN * (channel + 1)) + NIC_TX_STATUS;

    // read NIC channel status and acknowledge IRQ
    uint32_t status = hal_remote_lw( XPTR( cxy_nic , offset ) );

    if( status != 0 ) hal_core_sleep("%s : illegal address\n", __FUNCTION__ );
    
    // unblock server thread 
    thread_t * server = chdev->server;
    thread_unblock( XPTR( local_cxy , server ) , THREAD_BLOCKED_IO );

} // end soclib_nic_isr()



