/*
 * soclib_nic.c - SOCLIB_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 : ptr %x / wid %d / rid %d *****\n",
    name, chbuf, chbuf->wid, chbuf->rid );

    for( i = 0 ; i < SOCLIB_NIC_CHBUF_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 / cont_pad [%x,%x] / plen %d\n",
            i, chbuf->cont_ptr[i],
            (uint32_t)(chbuf->cont_pad[i]>>32),
            (uint32_t)chbuf->cont_pad[i],
            container[510] );
        }
        else
        {
            nolock_printk(" - %d : EMPTY / cont_ptr %x / cont_pad [%x,%x]\n",
            i, chbuf->cont_ptr[i],
            (uint32_t)(chbuf->cont_pad[i]>>32),
            (uint32_t)chbuf->cont_pad[i] );
        }
    }

    // release TXT0 lock 
    remote_busylock_release( lock_xp );

}  // end soclib_nic_chbuf_display()

#endif

///////////////////////////////////////
void soclib_nic_init( chdev_t * chdev )
{
    uint32_t    i;
    kmem_req_t  req;
    ppn_t       ppn;
    uint64_t    padr;

    // 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 / base %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, channel, chdev, nic_ptr, cycle );
if( is_rx && DEBUG_HAL_NIC_RX < cycle )
printk("\n[%s] thread[%x,%x] enter : NIC_RX channel %d / chdev %x / base %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, channel, chdev, nic_ptr, cycle );
#endif

    // get number of channels from hardware
    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[PANIC] in %s illegal channel index\n", __FUNCTION__ );
        return;
    }

    // allocate memory for chbuf descriptor
    req.type   = KMEM_KCM;
    req.order  = bits_log2( sizeof(nic_chbuf_t) );
    req.flags  = AF_KERNEL;
    nic_chbuf_t * chbuf = kmem_alloc( &req );

    if( chbuf == NULL )
    {
        printk("\n[PANIC] 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 );
    
    // allocate containers and complete chbuf initialisation
    for( i = 0 ; i < SOCLIB_NIC_CHBUF_DEPTH ; i++ )
    {
        // 2048 bytes per container
        req.type   = KMEM_KCM;
        req.order  = 11;
        req.flags  = AF_KERNEL;
        uint32_t * container  = kmem_alloc( &req );

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

        // 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_SHIFT) |
               ((intptr_t)container & CONFIG_PPM_PAGE_MASK);

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

    // 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        = SOCLIB_NIC_CHBUF_DEPTH;
    
    // compute chbuf physical address 
    ppn  = ppm_base2ppn( XPTR( local_cxy , chbuf ) );
    padr = ((uint64_t)ppn  << CONFIG_PPM_PAGE_SHIFT) |
           ((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;

// check calling thread == client 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 / chdev %x / chbuf %x / len %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ptr, chbuf, length, cycle );
soclib_nic_chbuf_display( chbuf , dev_ptr->name );
#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] WRITE failure : NIC_TX[%d] queue full / cycle %d\n",
__FUNCTION__, this->process->pid , this->trdid , dev_ptr->channel , cycle );
soclib_nic_chbuf_display( chbuf , dev_ptr->name );
#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) % SOCLIB_NIC_CHBUF_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] WRITE success on NIC_TX[%d] / len %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ptr->channel , length, cycle );
soclib_nic_chbuf_display( chbuf , dev_ptr->name );
#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 / chdev %x / chbuf %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ptr, chbuf, cycle );
soclib_nic_chbuf_display( chbuf , dev_ptr->name );
#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] READ failure : NIC_RX[%d] queue empty / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ptr->channel , cycle );
soclib_nic_chbuf_display( chbuf , dev_ptr->name );
#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) % SOCLIB_NIC_CHBUF_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] READ success on NIC_RX[%d] queue / len %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid , dev_ptr->channel , length , cycle );
soclib_nic_chbuf_display( chbuf , dev_ptr->name );
#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 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 from NIC channel device NIC 
    xptr_t     base_xp = chdev->base;
    uint32_t   channel = chdev->channel;
    bool_t     is_rx   = chdev->is_rx;

    // 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( is_rx &&  (status != NIC_CHANNEL_STATUS_IDLE) )
printk("\n[PANIC] in %s : error reported by NIC_RX[%d]\n", __FUNCTION__, channel );
if( (is_rx == false) &&  (status != NIC_CHANNEL_STATUS_IDLE) )
printk("\n[PANIC] in %s : error reported by NIC_TX[%d]\n", __FUNCTION__, channel );

    // unblock server thread 
    thread_t * server = chdev->server;
    thread_unblock( XPTR( local_cxy , server ) , THREAD_BLOCKED_ISR );

#if (DEBUG_HAL_NIC_RX || DEBUG_HAL_NIC_TX) 
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( is_rx && DEBUG_HAL_NIC_RX < cycle )
printk("\n[%s] ISR unblocks NIC_RX[%d] server thread / cycle %d\n",
__FUNCTION__, channel, cycle );
if( (is_rx == false) && DEBUG_HAL_NIC_TX < cycle )
printk("\n[%s] ISR unblocks NIC_TX[%d] server thread / cycle %d\n",
__FUNCTION__, channel, cycle );
#endif

} // end soclib_nic_isr()

