/* * 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 #include #include #include #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////////////////////// // 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()