/*
 * soclib_nic.c - VCI_MASTER_NIC (Network Interface Controler) driver implementation.
 *
 * Author     Alain Greiner (2016,2017,2018,2019,2020)
 *
 * 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_kernel_types.h>
#include <hal_remote.h>
#include <hal_special.h>
#include <chdev.h>
#include <dev_nic.h>
#include <kmem.h>
#include <printk.h>
#include <memcpy.h>
#include <thread.h>
#include <soclib_nic.h>


////////////////////////////////////////////////////////////////////////////////////////
// Extern global variables
////////////////////////////////////////////////////////////////////////////////////////

extern chdev_directory_t  chdev_dir;     // allocated in kernel_init.c

#if DEBUG_HAL_NIC_TX || DEBUG_HAL_NIC_RX

////////////////////////////////////////////////////////////////////////////////////////
//          static function used for SOCLIB_NIC driver debug 
////////////////////////////////////////////////////////////////////////////////////////
static void soclib_nic_chbuf_display( nic_chbuf_t * chbuf,
                                      char        * name )
{
    uint32_t i;

    // software L2/L3 cache coherence for chbuf WID & RID read
    if( chdev_dir.iob )  dev_mmc_inval( XPTR ( local_cxy , chbuf ) , 8 );

    // get pointers on TXT0 chdev
    xptr_t    txt0_xp  = chdev_dir.txt_tx[0];
    cxy_t     txt0_cxy = GET_CXY( txt0_xp );
    chdev_t * txt0_ptr = GET_PTR( txt0_xp );

    // get extended pointer on remote TXT0 chdev lock
    xptr_t  lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );

    // get TXT0 lock 
    remote_busylock_acquire( lock_xp );

    nolock_printk("\n***** chbuf %s : cxy %x / ptr %x / wid %d / rid %d *****\n",
    name, local_cxy , chbuf, chbuf->wid, chbuf->rid );

    for( i = 0 ; i < CONFIG_SOCK_QUEUES_DEPTH ; i++ )
    {
        uint32_t * container = chbuf->cont_ptr[i];

        // software L2/L3 cache coherence for container STS & PLEN read
        if( chdev_dir.iob ) dev_mmc_inval( XPTR( local_cxy , container + 510 ), 8 );

        if( container[511] )
        {
            nolock_printk(" - %d : FULL  / cont_ptr %x / plen %d\n",
            i, chbuf->cont_ptr[i], container[510] );
        }
        else
        {
            nolock_printk(" - %d : EMPTY / cont_ptr %x\n",
            i, chbuf->cont_ptr[i] ); 
        }
    }

    // release TXT0 lock 
    remote_busylock_release( lock_xp );

}  // end soclib_nic_chbuf_display()

#endif

///////////////////////////////////////
void soclib_nic_init( chdev_t * chdev )
{
    uint32_t * container;       // local pointer on one container
    uint32_t   cont_per_page;   // number of containers per page
    uint32_t   cont_gid;        // container global index (in chbuf)
    bool_t     cont_error;      // not enough memory for chbuf containers

    ppn_t      ppn;             // used for both the chbuf descriptor and the containers
    uint64_t   padr;            // used for both the chbuf descriptor and the containers

assert( __FUNCTION__ , (chdev->func == DEV_FUNC_NIC),
"bad func argument" );

assert( __FUNCTION__ , (sizeof(nic_cont_t) == 2048),
"container size must be 2048 bytes" );

assert( __FUNCTION__ , (CONFIG_PPM_PAGE_ORDER >= 11 ),
"page size cannot be smaller than container size" );

    // set driver specific fields in chdev descriptor
    chdev->cmd = &soclib_nic_cmd;
    chdev->isr = &soclib_nic_isr;

    // get chdev channel & direction
    bool_t   is_rx   = chdev->is_rx;
    uint32_t channel = chdev->channel;
    
    // get NIC device cluster and local pointer
    cxy_t      nic_cxy  = GET_CXY( chdev->base );
    uint32_t * nic_ptr  = GET_PTR( chdev->base );

#if DEBUG_HAL_NIC_TX || DEBUG_HAL_NIC_RX
thread_t * this  = CURRENT_THREAD;
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( (is_rx == false) && DEBUG_HAL_NIC_RX < cycle )
printk("\n[%s] thread[%x,%x] enter : NIC_TX channel %d / chdev %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, channel, chdev, cycle );
if( is_rx && DEBUG_HAL_NIC_RX < cycle )
printk("\n[%s] thread[%x,%x] enter : NIC_RX channel %d / chdev %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, channel, chdev, cycle );
#endif

    // get number of channels from NIC hardware register
    uint32_t channels = hal_remote_l32( XPTR( nic_cxy,
                        nic_ptr + NIC_GLOBAL_OFFSET + NIC_G_CHANNELS )); 

    // check value registered in cluster descriptor
    if( LOCAL_CLUSTER->nb_nic_channels != channels )
    {
        printk("\n[PANIC] in %s : channels[soft] (%d) != channels[hard] (%d)\n",
        __FUNCTION__, LOCAL_CLUSTER->nb_nic_channels, channels );
        return;
    }

    // check channel index 
    if( channel >= channels ) 
    {
        printk("\n[ERROR] in %s illegal channel index\n", __FUNCTION__ );
        return;
    }

    // allocate memory for chbuf descriptor
    nic_chbuf_t * chbuf = kmem_alloc( bits_log2( sizeof(nic_chbuf_t) ) , AF_KERNEL );

    if( chbuf == NULL )
    {
        printk("\n[ERROR] in %s : cannot allocate chbuf descriptor\n", __FUNCTION__ );
        return;
    }

    // initialise chbuf indexes
    chbuf->wid  = 0;
    chbuf->rid  = 0;
     
    // software L2/L3 cache coherence for chbuf WID & RID     
    if( chdev_dir.iob ) dev_mmc_sync( XPTR( local_cxy , chbuf ) , 8 );

    cont_error    = false;
    cont_gid      = 0;
    cont_per_page = 1 << (CONFIG_PPM_PAGE_ORDER - 11);

    // allocate containers & complete chbuf initialisation
    // depending on the PPM page size, we pack several 
    // 248 bytes containers in one single page.

    // lopp on containers
    while( cont_gid < CONFIG_SOCK_QUEUES_DEPTH )  
    {
        if( (cont_gid & (cont_per_page - 1)) == 0 )  // allocate one PPM page
        {
            container = kmem_alloc( CONFIG_PPM_PAGE_ORDER , AF_KERNEL );

            if( container == NULL )
            {
                cont_error = true;
                break;
            }
        }
        else           // increment container base address 
        { 
            container = container + 512;
        }

        // initialize container as empty
        container[511] = 0;

        // software L2/L3 cache coherence for container STS       
        if( chdev_dir.iob ) dev_mmc_sync( XPTR( local_cxy , &container[511] ) , 4 );

        // compute container physical address
        ppn  = ppm_base2ppn( XPTR( local_cxy , container ) );
        padr = ((uint64_t)ppn << CONFIG_PPM_PAGE_ORDER) |
               ((intptr_t)container & CONFIG_PPM_PAGE_MASK);

        // complete chbuf initialisation        
        chbuf->cont_ptr[cont_gid] = container;
        chbuf->cont_pad[cont_gid] = padr;

        // increment container index
        cont_gid++;
    }

    // release allocated containers and chbuf if not enough memory
    if( cont_error )
    {
        // loop on allocated containers
        while( cont_gid )
        {
            // release container when required
            if( (cont_gid & (cont_per_page - 1)) == 0 )
            kmem_free( chbuf->cont_ptr[cont_gid] , CONFIG_PPM_PAGE_ORDER );

            // decrement container index
            cont_gid--;
        }

        // release chbuf descriptor
        kmem_free( chbuf , bits_log2(sizeof(nic_chbuf_t)) );

        return;
    }

    // software L2/L3 cache coherence for chbuf descriptor
    if( chdev_dir.iob ) dev_mmc_sync( XPTR( local_cxy , chbuf ),
                                      sizeof(nic_chbuf_t) );

    // get NIC channel segment base and chbuf depth
    uint32_t * channel_base = nic_ptr + NIC_CHANNEL_SPAN * channel;
    uint32_t   nbufs        = CONFIG_SOCK_QUEUES_DEPTH;
    
    // compute chbuf physical address 
    ppn  = ppm_base2ppn( XPTR( local_cxy , chbuf ) );
    padr = ((uint64_t)ppn  << CONFIG_PPM_PAGE_ORDER) |
           ((intptr_t)chbuf & CONFIG_PPM_PAGE_MASK);

    uint32_t low  = (uint32_t)(padr); 
    uint32_t high = (uint32_t)(padr >> 32); 

    // initialize the NIC channel registers
    if( is_rx ) 
    {
        hal_remote_s32( XPTR( nic_cxy , channel_base + NIC_RX_CHBUF_DESC_LO ) , low );
        hal_remote_s32( XPTR( nic_cxy , channel_base + NIC_RX_CHBUF_DESC_HI ) , high );
        hal_remote_s32( XPTR( nic_cxy , channel_base + NIC_RX_CHBUF_NBUFS   ) , nbufs );

        hal_fence();

        hal_remote_s32( XPTR( nic_cxy , channel_base + NIC_RX_CHANNEL_RUN   ) , 1 );
    }
    else
    {
        hal_remote_s32( XPTR( nic_cxy , channel_base + NIC_TX_CHBUF_DESC_LO ) , low );
        hal_remote_s32( XPTR( nic_cxy , channel_base + NIC_TX_CHBUF_DESC_HI ) , high );
        hal_remote_s32( XPTR( nic_cxy , channel_base + NIC_TX_CHBUF_NBUFS   ) , nbufs );

        hal_fence();

        hal_remote_s32( XPTR( nic_cxy , channel_base + NIC_TX_CHANNEL_RUN   ) , 1 );
    }

    // register chbuf pointer in chdev descriptor extension
    chdev->ext.nic.queue = chbuf;
        
#if DEBUG_HAL_NIC_TX || DEBUG_HAL_NIC_RX
cycle = (uint32_t)hal_get_cycles();
if( (is_rx == false) && DEBUG_HAL_NIC_RX < cycle )
printk("\n[%s] thread[%x,%x] exit / NIC_TX channel %d / chbuf %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, channel, chbuf, cycle );
if( is_rx && DEBUG_HAL_NIC_RX < cycle )
printk("\n[%s] thread[%x,%x] exit / NIC_RX channel %d / chbuf %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, channel, chbuf, cycle );
soclib_nic_chbuf_display( chbuf , chdev->name );
#endif

} // end soclib_nic_init()

//////////////////////////////////////////////////////////////////
void __attribute__ ((noinline)) soclib_nic_cmd( xptr_t thread_xp )
{
    uint32_t       type;         // command type    
    uint8_t      * buffer;       // pointer on command buffer   
    uint32_t       length;       // Ethernet packet length
    xptr_t         dev_xp;       // extended pointer on NIC chdev
    chdev_t      * dev_ptr;      // local pointer on NIC chdev
    cxy_t          dev_cxy;      // NIC chdev cluster identifier
    nic_chbuf_t  * chbuf;        // pointer on chbuf descriptor
    uint32_t       index;        // index of current container in chbuf
    uint32_t     * container;    // pointer on container (array of uint32_t)

    thread_t * this = CURRENT_THREAD;

assert( __FUNCTION__, (thread_xp == XPTR( local_cxy , this )), 
"calling thread must be the client thread");
 
    // get command type
    type    = this->nic_cmd.type;

    // get chdev pointers for device
    dev_xp  = this->nic_cmd.dev_xp;
    dev_ptr = GET_PTR( dev_xp );
    dev_cxy = GET_CXY( dev_xp );

    // analyse command type
    switch( type )
    {
        //////////////////////////////////////////////////////////////////////////
        case NIC_CMD_WRITE:  // move one packet from command buffer to TX queue
        {

// check chdev is local
assert( __FUNCTION__, (dev_cxy == local_cxy),
"illegal cluster for a WRITE command");
            
            // get command arguments
            buffer = this->nic_cmd.buffer;
            length = this->nic_cmd.length;

// check packet length
assert( __FUNCTION__, (length <= 2040),
"packet length too large");

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

            // software L2/L3 cache coherence for chbuf WID read
            if( chdev_dir.iob )  dev_mmc_inval( XPTR ( local_cxy , chbuf ) , 8 );

            // get container write index
            index = chbuf->wid;

            // get pointer on container (no L2/L3 cache coherence required)
            container = chbuf->cont_ptr[index];

            // software L2/L3 cache coherence for container STS read
            if( chdev_dir.iob )  dev_mmc_inval( XPTR ( local_cxy , &container[511]) , 4 );

#if DEBUG_HAL_NIC_TX
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( DEBUG_HAL_NIC_TX < cycle )
printk("\n[%s] thread[%x,%x] enter / WRITE / %s / chbuf (%x,%x) / len %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ptr->name, local_cxy, chbuf, length, cycle );
#endif
            // check container STS
            if( container[511] != 0 )   // container full
            {
                // return failure
                this->nic_cmd.status = 0;
                this->nic_cmd.error  = 0;

#if DEBUG_HAL_NIC_TX
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_HAL_NIC_TX < cycle )
printk("\n[%s] thread[%x,%x] exit / WRITE failure : NIC_TX[%d] queue full / cycle %d\n",
__FUNCTION__, this->process->pid , this->trdid , dev_ptr->channel , cycle );
#endif
            }
            else                                       // container empty
            {
                // move the packet from buffer to container
                memcpy( container , buffer , length );

                // update packet length in container header
                container[510] = length;

                hal_fence();

                // update container STS
                container[511] = 1;

                // update current container WID
                chbuf->wid = (index + 1) % CONFIG_SOCK_QUEUES_DEPTH;

                // software L2/L3 cache coherence for container DATA write
                if( chdev_dir.iob ) dev_mmc_sync( XPTR( local_cxy , container ), length );

                // software L2/L3 cache coherence for container LENGTH and STS write
                if( chdev_dir.iob ) dev_mmc_sync( XPTR( local_cxy , &container[510] ) , 8 );
                
                // software L2/L3 cache coherence for chbuf WID write
                if( chdev_dir.iob ) dev_mmc_sync( XPTR( local_cxy , chbuf ) , 8 );

                // return success
                this->nic_cmd.status = length;
                this->nic_cmd.error  = 0;

#if DEBUG_HAL_NIC_TX
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_HAL_NIC_TX < cycle )
printk("\n[%s] thread[%x,%x] exit / WRITE success on NIC_TX[%d] / len %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ptr->channel , length, cycle );
if((DEBUG_HAL_NIC_TX < cycle) && (DEBUG_HAL_NIC_TX & 1))
putb( "64 first bytes moved to TX queue by NIC driver" , buffer , 64 );
#endif
            }
        }
        break;  // end WRITE

        /////////////////////////////////////////////////////////////////////////
        case NIC_CMD_READ:   // move one packet from RX queue to kernel buffer
        {

// check chdev is local
assert( __FUNCTION__, (dev_cxy == local_cxy), 
"illegal cluster for a READ command");
            
            // get target buffer
            buffer = this->nic_cmd.buffer;

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

            // software L2/L3 cache coherence for chbuf WID & RID read
            if( chdev_dir.iob )  dev_mmc_inval( XPTR ( local_cxy , chbuf ) , 8 );

            // get container read index
            index = chbuf->rid;
            
            // get pointer on container (no L2/L3 cache coherence required) 
            container = chbuf->cont_ptr[index];

            // software L2/L3 cache coherence for container STS & PLEN read
            if( chdev_dir.iob ) dev_mmc_inval( XPTR( local_cxy , container + 510 ), 8 );

#if DEBUG_HAL_NIC_RX 
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( DEBUG_HAL_NIC_RX < cycle )
printk("\n[%s] thread[%x,%x] enter / READ / %s / chbuf (%x,%x) / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ptr->name, local_cxy, chbuf, cycle );
#endif
            // check container state
            if( container[511] == 0 )   // container empty
            {
                // return failure
                this->nic_cmd.status = 0;
                this->nic_cmd.error  = 0;

#if DEBUG_HAL_NIC_RX
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_HAL_NIC_RX < cycle )
printk("\n[%s] thread[%x,%x] exit / READ failure : NIC_RX[%d] queue empty / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ptr->channel , cycle );
#endif
            }
            else                      // container full
            {
                // get packet length from container
                length = container[510];

                // software L2/L3 cache coherence for container DATA
                if( chdev_dir.iob ) dev_mmc_inval( XPTR( local_cxy , container) , length );

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

                hal_fence();

                // update container STS
                container[511] = 0;

                // update current container WID 
                chbuf->rid = (index + 1) % CONFIG_SOCK_QUEUES_DEPTH;

                // software L2/L3 cache coherence for container STS write
                if( chdev_dir.iob ) dev_mmc_sync( XPTR( local_cxy , &container[511] ), 4 );

                // software L2/L3 cache coherence for chbuf RID write
                if( chdev_dir.iob )  dev_mmc_sync( XPTR ( local_cxy , chbuf ) , 8 );

                // return success
                this->nic_cmd.status = length;
                this->nic_cmd.error  = 0;

#if DEBUG_HAL_NIC_RX 
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( DEBUG_HAL_NIC_RX < cycle )
printk("\n[%s] thread[%x,%x] exit / READ success on NIC_RX[%d] queue / len %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid , dev_ptr->channel , length , cycle );
if((DEBUG_HAL_NIC_RX < cycle) && (DEBUG_HAL_NIC_RX & 1))
putb("64 first bytes moved from RX queue by NIC driver" , buffer , 64 );
#endif
            }
        }
        break;    // end READ
            
        /////////////////////////////////////////////////////////////////////
        case NIC_CMD_GET_KEY:      // return channel from IP addr & port
        {
            // get number of NIC channels
            uint32_t channels = LOCAL_CLUSTER->nb_nic_channels;
 
            // get IP address and port from command in thread descriptor
            uint32_t addr = (intptr_t)this->nic_cmd.buffer;
            uint16_t port = (uint16_t)this->nic_cmd.length;
 
            // compute NIC channel index
            uint32_t key = ( ((addr     ) & 0xFF) +
                             ((addr > 8 ) & 0xFF) +
                             ((addr > 16) & 0xFF) +
                             ((addr > 24) & 0xFF) +
                             ((port     ) & 0xFF) +
                             ((port > 8 ) & 0xFF) ) % channels;

            // return key in "status" and return "error"
            this->nic_cmd.status = key;
            this->nic_cmd.error  = 0;
        }
        break;  // end GET_KEY

        /////////////////////////////////////////////////////////////////////
        case NIC_CMD_SET_RUN:       // activate/desactivate one NIC channel
        {
            // get pointers on NIC peripheral
            xptr_t     base_xp  = dev_ptr->base;
            uint32_t * base_ptr = GET_PTR( base_xp );
            cxy_t      base_cxy = GET_CXY( base_xp );

            // get "channel" and "run" arguments from the "length" and "status" arguments
            uint32_t channel = this->nic_cmd.length;
            uint32_t run     = this->nic_cmd.status;

            // build pointers on channel base
            uint32_t * channel_ptr = base_ptr + NIC_CHANNEL_SPAN * channel;

            // set new value in NIC_RX_CHANNEL_RUN & NIC_TX_CHANNEL_RUN registers
            hal_remote_s32( XPTR( base_cxy , channel_ptr + NIC_RX_CHANNEL_RUN ) , run );
            hal_remote_s32( XPTR( base_cxy , channel_ptr + NIC_TX_CHANNEL_RUN ) , run );

            // return "error"
            this->nic_cmd.error  = 0;
        }
        break;  // end SET_RUN

        /////////////////////////////////////////////////////////////////////
        case NIC_CMD_GET_INSTRU:     // diplay packets counters on TXT0
        {
            // get pointers on NIC peripheral
            xptr_t     base_xp  = dev_ptr->base;
            uint32_t * base_ptr = GET_PTR( base_xp );
            cxy_t      base_cxy = GET_CXY( base_xp );

            // build pointer on global register base 
            uint32_t * global_ptr = base_ptr + NIC_GLOBAL_OFFSET;

            uint32_t rx_g2s_received      = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_G2S_RECEIVED ));
            uint32_t rx_g2s_discarded     = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_G2S_DISCARDED ));
            uint32_t rx_des_success       = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_DES_SUCCESS ));
            uint32_t rx_des_too_small     = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_DES_TOO_SMALL ));
            uint32_t rx_des_too_big       = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_DES_TOO_BIG ));
            uint32_t rx_des_mfifo_full    = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_DES_MFIFO_FULL ));
            uint32_t rx_des_crc_fail      = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_DES_CRC_FAIL ));
            uint32_t rx_disp_received     = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_DISP_RECEIVED ));
            uint32_t rx_disp_dst_fail     = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_DISP_DST_FAIL ));
            uint32_t rx_disp_ch_full      = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_RX_DISP_CH_FULL ));

            uint32_t tx_disp_received     = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_TX_DISP_RECEIVED ));
            uint32_t tx_disp_too_small    = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_TX_DISP_TOO_SMALL ));
            uint32_t tx_disp_too_big      = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_TX_DISP_TOO_BIG ));
            uint32_t tx_disp_transmit     = hal_remote_l32( XPTR( base_cxy , 
                                            global_ptr + NIC_G_NPKT_TX_DISP_TRANSMIT ));

            printk("\n*** NIC device Instrumentation ***\n\n"
                   " - rx_g2s_received   = %d\n"
                   " - rx_g2s_discarded  = %d\n"
                   " - rx_des_success    = %d\n"
                   " - rx_des_too_small  = %d\n"
                   " - rx_des_too_big    = %d\n"
                   " - rx_des_mfifo_full = %d\n"
                   " - rx_des_crc_fail   = %d\n"
                   " - rx_disp_received  = %d\n"
                   " - rx_disp_dsp_fail  = %d\n"
                   " - rx_disp_ch_full   = %d\n\n"
                   " - tx_disp_received  = %d\n"
                   " - tx_disp_too_small = %d\n"
                   " - tx_disp_too_big   = %d\n"
                   " - tx_disp_transmit  = %d\n",
                   rx_g2s_received,
                   rx_g2s_discarded,
                   rx_des_success,
                   rx_des_too_small,
                   rx_des_too_big,
                   rx_des_mfifo_full,
                   rx_des_crc_fail,
                   rx_disp_received,
                   rx_disp_dst_fail, 
                   rx_disp_ch_full,
                   tx_disp_received,
                   tx_disp_too_small,
                   tx_disp_too_big, 
                   tx_disp_transmit );

            // return "error"
            this->nic_cmd.error  = 0;
        }
        break;  // end CLEAR_INSTRU

        /////////////////////////////////////////////////////////////////////
        case NIC_CMD_CLEAR_INSTRU:  // reset instrumentation registers 
        {
            // get pointers on NIC peripheral
            xptr_t     base_xp  = dev_ptr->base;
            uint32_t * base_ptr = GET_PTR( base_xp );
            cxy_t      base_cxy = GET_CXY( base_xp );

            // build pointer on relevant NIC register 
            uint32_t * reset_ptr = base_ptr + NIC_GLOBAL_OFFSET + NIC_G_NPKT_RESET;

            // reset all NIC instrumentation registers
            hal_remote_s32( XPTR( base_cxy , reset_ptr ) , 0 );

            // return "error"
            this->nic_cmd.error  = 0;
        }
        break;  // end GET_INSTRU

        default:
        {
            assert( __FUNCTION__, false, "Unknown command <%x>\n", type );
        }
    }
} // end soclib_nic_cmd()


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

    // get NIC peripheral cluster and local pointer
    cxy_t      nic_cxy = GET_CXY( base_xp );
    uint32_t * nic_ptr = GET_PTR( base_xp );

    // compute local pointer on state register 
    uint32_t  * ptr;
    if( is_rx ) ptr = nic_ptr + (NIC_CHANNEL_SPAN * channel) + NIC_RX_CHANNEL_STATE;
    else        ptr = nic_ptr + (NIC_CHANNEL_SPAN * channel) + NIC_TX_CHANNEL_STATE;

    // read NIC channel status and acknowledge IRQ
    uint32_t status = hal_remote_l32( XPTR( nic_cxy , ptr ) );

    // check status value
    if( status == NIC_CHANNEL_STATUS_ERROR )    // error reported 
    {

#if (DEBUG_HAL_NIC_RX || DEBUG_HAL_NIC_TX) 
uint32_t   cycle = (uint32_t)hal_get_cycles();
printk("\n[%s] error reported for %s / status %d / cycle %d\n",
 __FUNCTION__ , chdev->name , status , cycle );
#endif
        server->nic_cmd.error  = 1;
    }
    else if( status != NIC_CHANNEL_STATUS_IDLE)   // no error but DMA BUSY
    {

#if (DEBUG_HAL_NIC_RX || DEBUG_HAL_NIC_TX) 
uint32_t   cycle = (uint32_t)hal_get_cycles();
printk("\n[%s] warning reported for %s / status %d / cycle %d\n",
 __FUNCTION__ , chdev->name , status , cycle );
#endif
        server->nic_cmd.error  = 0;
    }
    else
    {

#if (DEBUG_HAL_NIC_RX || DEBUG_HAL_NIC_TX) 
uint32_t   cycle = (uint32_t)hal_get_cycles();
printk("\n[%s] irq reported for %s / status %d / cycle %d\n",
 __FUNCTION__ , chdev->name , status , cycle );
#endif
        server->nic_cmd.error  = 0;
    }

    // unblock server thread 
    server->nic_cmd.status = status;
    thread_unblock( XPTR( local_cxy , server ) , THREAD_BLOCKED_ISR );

} // end soclib_nic_isr()

