LCOV - code coverage report
Current view: top level - disco/net/mlx5 - fd_mlx5_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 989 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 92 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE /* sendmmsg */
       2             : /* The mlx5 tile translates Ethernet frames between mlx5 work/completion
       3             :    queues and fd_tango traffic.
       4             : 
       5             :    An mlx5 queue pair (QP) contains a send queue (SQ) and a receive queue
       6             :    (RQ).  Work queue entries (WQEs) encode posted work.  Completion
       7             :    queue entries (CQEs) report completed work through completion queues
       8             :    (CQs).  The mlx5 tile uses separate CQs for TX and RX, one each. */
       9             : 
      10             : #include "../fd_net_tile.h"
      11             : #include "../fd_net_tile_private.h"
      12             : #include "fd_mlx5_private.h"
      13             : 
      14             : #include <errno.h>
      15             : #include <fcntl.h>
      16             : #include <net/if.h>
      17             : #include <stddef.h>
      18             : #include <stdlib.h>
      19             : #include <time.h>
      20             : #include <netinet/in.h>
      21             : #include <sys/socket.h>
      22             : 
      23             : #include "../../metrics/fd_metrics.h"
      24             : #include "../fd_net_router.h"
      25             : #include "../../topo/fd_topo.h"
      26             : #include "../../sleep/fd_sleep.h"
      27             : 
      28             : #include "../../../waltz/ip/fd_iproute.h"
      29             : #include "../../../util/net/fd_eth.h"
      30             : #include "../../../util/net/fd_gre.h"
      31             : #include "../../../util/net/fd_ip4.h"
      32             : #include "../../../util/net/fd_udp.h"
      33             : #include "../../../util/pod/fd_pod_format.h"
      34             : 
      35             : #include <unistd.h>
      36             : #include <sys/epoll.h>
      37             : #include <sys/ioctl.h>
      38             : #include <linux/rtnetlink.h>
      39             : #include <rdma/ib_user_verbs.h>
      40             : 
      41             : #include "generated/fd_mlx5_tile_seccomp.h"
      42             : 
      43             : 
      44           0 : #define FD_MLX5_TX_FLUSH_TIMEOUT_NS (20000L)  /* 20us */
      45           0 : #define FD_MLX5_LO_TX_TIMEOUT_NS    (500000L) /* 500us */
      46             : 
      47             : /* Recent RX packets or TX submissions defer RX CQ rearming and cause
      48             :    a timed repoll before the tile returns to epoll. */
      49           0 : #define FD_MLX5_REPOLL_TIMEOUT_NS (    200000L) /* 200us */
      50           0 : #define FD_MLX5_NS_PER_SEC        (1000000000L)
      51             : 
      52             : /* Linux names these UAR offsets MLX5_CQ_DOORBELL and MLX5_BF_OFFSET. */
      53           0 : #define FD_MLX5_UAR_CQ_DB_OFFSET (0x20UL)
      54           0 : #define FD_MLX5_UAR_SQ_DB_OFFSET (0x800UL)
      55             : 
      56             : /* epoll data of the async fd in the park set (the completion channel
      57             :    is tagged with its fd, the doorbell with FD_SLEEP_EPOLL_DOORBELL). */
      58           0 : #define FD_MLX5_EPOLL_ASYNC (ULONG_MAX-1UL)
      59             : 
      60             : /* mlx5 CQ arm doorbells use the event sequence in bits 28 and 29. */
      61           0 : #define FD_MLX5_CQ_ARM_SN_MASK  ( 3U)
      62           0 : #define FD_MLX5_CQ_ARM_SN_SHIFT (28U)
      63             : 
      64             : /* FD_MLX5_SQ_* are options in a SQ WQE to request certain NIC behaviour.
      65             :    SEND requests packet transmission.  CQ_UPDATE requests a CQE. */
      66           0 : #define FD_MLX5_SQ_SEND_PKT    (0x0aU) /* SEND */
      67           0 : #define FD_MLX5_SQ_REQUEST_CQE (0x08U) /* CQ_UPDATE */
      68             : 
      69             : /* FD_MLX5_CQE_OP_* are mlx5 CQE opcodes used to classify TX and RX completions */
      70           0 : #define FD_MLX5_CQE_OP_TX_OK   ( 0U) /* MLX5_CQE_REQ */
      71           0 : #define FD_MLX5_CQE_OP_RX_OK   ( 2U) /* MLX5_CQE_RESP_SEND */
      72           0 : #define FD_MLX5_CQE_OP_TX_ERR  (13U) /* MLX5_CQE_REQ_ERR */
      73           0 : #define FD_MLX5_CQE_OP_RX_ERR  (14U) /* MLX5_CQE_RESP_ERR */
      74           0 : #define FD_MLX5_CQE_OP_INVALID (15U)
      75           0 : #define FD_MLX5_CQE_OWNER_MASK ( 1U) /* MLX5_CQE_OWNER_MASK */
      76             : 
      77           0 : #define FD_MLX5_CQE_SYNDROME_LOCAL_LENGTH_ERR (0x01U) /* received packet's length exceeded FD_NET_MTU */
      78             : 
      79             : /* FD_MLX5_ASYNC_EVENT_* are Linux enum ib_event_type values delivered
      80             :    in ib_uverbs_async_event_desc.event_type. */
      81             : #define FD_MLX5_ASYNC_EVENT_CQ_ERR        ( 0U)
      82             : #define FD_MLX5_ASYNC_EVENT_QP_FATAL      ( 1U)
      83             : #define FD_MLX5_ASYNC_EVENT_QP_REQ_ERR    ( 2U)
      84             : #define FD_MLX5_ASYNC_EVENT_QP_ACCESS_ERR ( 3U)
      85           0 : #define FD_MLX5_ASYNC_EVENT_DEVICE_FATAL  ( 8U)
      86             : #define FD_MLX5_ASYNC_EVENT_WQ_FATAL      (19U)
      87             : 
      88             : /* The fd_mlx5_hw_* structs below define WQE and CQE formats in the
      89             :    mlx5 hardware interface.  The links show the corresponding definitions
      90             :    in the Linux mlx5 driver. */
      91             : 
      92             : /* https://elixir.bootlin.com/linux/v7.1.8/source/include/linux/mlx5/qp.h#L205 */
      93             : struct __attribute__((packed)) fd_mlx5_hw_wqe_ctrl_seg {
      94             :   /* mlx5 packs opmod, WQE index, opcode, QP number, and WQE segment
      95             :      count into two big-endian 32-bit words. */
      96             :   uint  opmod_idx_opcode;
      97             :   uint  qpn_ds;
      98             : 
      99             :   uchar signature;
     100             :   uchar reserved[2];
     101             :   uchar flags;
     102             :   uint  imm;
     103             : };
     104             : typedef struct fd_mlx5_hw_wqe_ctrl_seg fd_mlx5_hw_wqe_ctrl_seg_t;
     105             : 
     106             : /* https://elixir.bootlin.com/linux/v7.1.8/source/include/linux/mlx5/qp.h#L265 */
     107             : struct __attribute__((packed)) fd_mlx5_hw_wqe_eth_seg {
     108             :   uchar  reserved[12];
     109             :   ushort inline_hdr_sz;
     110             :   uchar  inline_hdr[2];
     111             : };
     112             : typedef struct fd_mlx5_hw_wqe_eth_seg fd_mlx5_hw_wqe_eth_seg_t;
     113             : 
     114             : /* https://elixir.bootlin.com/linux/v7.1.8/source/include/linux/mlx5/qp.h#L364 */
     115             : struct __attribute__((packed)) fd_mlx5_hw_wqe_data_seg {
     116             :   uint  byte_cnt;
     117             :   uint  lkey;
     118             :   ulong addr;
     119             : };
     120             : typedef struct fd_mlx5_hw_wqe_data_seg fd_mlx5_hw_wqe_data_seg_t;
     121             : 
     122             : /* https://elixir.bootlin.com/linux/v7.1.8/source/include/linux/mlx5/device.h#L824 */
     123             : struct __attribute__((packed,may_alias)) fd_mlx5_hw_cqe64 {
     124             :   uchar  reserved0[44];
     125             :   uint   byte_cnt;
     126             :   uchar  reserved1[6];
     127             :   uchar  vendor_err;
     128             :   uchar  syndrome;
     129             :   uint   sop_drop_qpn;
     130             :   ushort wqe_counter;
     131             :   uchar  signature;
     132             :   uchar  op_own;
     133             : };
     134             : typedef struct fd_mlx5_hw_cqe64 fd_mlx5_hw_cqe64_t;
     135             : 
     136             : struct fd_mlx5_tile_rx_comp {
     137             :   ulong chunk;
     138             :   uint  byte_len;
     139             :   uint  opcode;
     140             : };
     141             : typedef struct fd_mlx5_tile_rx_comp fd_mlx5_tile_rx_comp_t;
     142             : 
     143             : /* fd_mlx5_tile_t is private tile state */
     144             : struct __attribute__((aligned(64UL))) fd_mlx5_tile {
     145             :   fd_mlx5_rx_wq_t  rx_wq;
     146             :   uint             batch_size; /* used for both SQ/RQ and TX/RX CQ batching */
     147             :   fd_mlx5_cq_t     rx_cq;
     148             :   uint             lo_tx_cnt;
     149             :   uint             rq_pending_cnt;
     150             :   long             sq_flush_deadline_ticks;
     151             :   fd_mlx5_tx_qp_t  tx_qp;
     152             :   long             lo_tx_deadline_ticks;
     153             :   int              rx_comp_channel_fd;    /* completion channel of rx_cq, -1 if not efficient mode */
     154             :   uint             has_out_credit;
     155             :   long             repoll_deadline_ticks; /* LONG_MAX when no repoll is scheduled */
     156             :   fd_net_router_t  router; /* TX IP routing */
     157             : 
     158             :   long             sq_flush_timeout_ticks;
     159             :   long             lo_tx_timeout_ticks;
     160             :   int              lo_tx_sock;
     161             :   uint             lkey;
     162             :   uint             prepared;
     163             :   fd_uverbs_ctx_t  uverbs;
     164             :   fd_mlx5_cq_t     tx_cq;
     165             :   fd_mlx5_rss_qp_t outer_rss_qp;
     166             :   fd_mlx5_rss_qp_t gre_rss_qp;
     167             : 
     168             :   /* Packet buffer addressing */
     169             :   uint *  sq_wqe_buf_chunk; /* maps SQ WQEs to packet buffers */
     170             : 
     171             :   fd_net_tile_t net;
     172             : 
     173             :   /* RQ batching */
     174             :   uint rq_pending_chunk[ FD_MLX5_BATCH_SIZE ];
     175             : 
     176             :   /* Local socket TX, used only by mlx5:0. */
     177             :   struct mmsghdr     lo_tx_msg [ FD_MLX5_BATCH_SIZE ];
     178             :   struct iovec       lo_tx_iov [ FD_MLX5_BATCH_SIZE ];
     179             :   struct sockaddr_in lo_tx_addr[ FD_MLX5_BATCH_SIZE ];
     180             :   uchar              lo_tx_buf [ FD_MLX5_BATCH_SIZE ][ FD_NET_MTU ];
     181             : 
     182             :   /* Efficient mode only */
     183             :   int  epoll_fd;
     184             :   long repoll_timeout_ticks;
     185             : 
     186             :   ulong netdev_seq; /* netdev_shared seqlock value of the router.netdev_tbl copy */
     187             : 
     188             :   /* Metric tracking */
     189             :   struct {
     190             :     ulong tx_no_buffer_cnt;
     191             :   } metrics;
     192             : };
     193             : typedef struct fd_mlx5_tile fd_mlx5_tile_t;
     194             : 
     195             : static ulong
     196             : fd_mlx5_queue_footprint( uint rx_depth,
     197           0 :                          uint tx_depth ) {
     198           0 :   ulong layout = FD_LAYOUT_INIT;
     199           0 :   layout = FD_LAYOUT_APPEND( layout, FD_MLX5_PAGE_SZ, rx_depth*sizeof(fd_mlx5_cqe_t)    );
     200           0 :   layout = FD_LAYOUT_APPEND( layout, FD_MLX5_PAGE_SZ, tx_depth*sizeof(fd_mlx5_cqe_t)    );
     201           0 :   layout = FD_LAYOUT_APPEND( layout, FD_MLX5_PAGE_SZ, rx_depth*sizeof(fd_mlx5_rx_wqe_t) );
     202           0 :   layout = FD_LAYOUT_APPEND( layout, FD_MLX5_PAGE_SZ, tx_depth*sizeof(fd_mlx5_tx_wqe_t) );
     203           0 :   layout = FD_LAYOUT_APPEND( layout, FD_MLX5_PAGE_SZ, rx_depth*sizeof(uint)             );
     204           0 :   layout = FD_LAYOUT_APPEND( layout, FD_MLX5_PAGE_SZ, tx_depth*sizeof(uint)             );
     205           0 :   layout = FD_LAYOUT_APPEND( layout, FD_MLX5_PAGE_SZ, FD_MLX5_PAGE_SZ                   );
     206           0 :   return FD_LAYOUT_FINI( layout, FD_MLX5_PAGE_SZ );
     207           0 : }
     208             : 
     209             : static void
     210             : fd_mlx5_hw_invalidate_cqes( fd_mlx5_cqe_t * cqes,
     211           0 :                             uint            depth ) {
     212           0 :   for( uint i=0U; i<depth; i++ ) {
     213           0 :     fd_mlx5_hw_cqe64_t * hw_cqe = (fd_mlx5_hw_cqe64_t *)(cqes+i);
     214           0 :     hw_cqe->op_own = (uchar)(FD_MLX5_CQE_OP_INVALID<<4);
     215           0 :   }
     216           0 : }
     217             : 
     218             : static fd_mlx5_tile_t *
     219             : fd_mlx5_hw_join_queues( fd_mlx5_tile_t * ctx,
     220             :                         void *           queue_memory,
     221             :                         uint             rx_depth,
     222           0 :                         uint             tx_depth ) {
     223           0 :   ulong const queue_footprint = fd_mlx5_queue_footprint( rx_depth, tx_depth );
     224           0 :   FD_SCRATCH_ALLOC_INIT( queue, queue_memory );
     225           0 :   fd_mlx5_cqe_t *    rx_cqes          = FD_SCRATCH_ALLOC_APPEND( queue, FD_MLX5_PAGE_SZ, rx_depth*sizeof(fd_mlx5_cqe_t)    );
     226           0 :   fd_mlx5_cqe_t *    tx_cqes          = FD_SCRATCH_ALLOC_APPEND( queue, FD_MLX5_PAGE_SZ, tx_depth*sizeof(fd_mlx5_cqe_t)    );
     227           0 :   fd_mlx5_rx_wqe_t * rq               = FD_SCRATCH_ALLOC_APPEND( queue, FD_MLX5_PAGE_SZ, rx_depth*sizeof(fd_mlx5_rx_wqe_t) );
     228           0 :   fd_mlx5_tx_wqe_t * sq               = FD_SCRATCH_ALLOC_APPEND( queue, FD_MLX5_PAGE_SZ, tx_depth*sizeof(fd_mlx5_tx_wqe_t) );
     229           0 :   uint *             rq_wqe_buf_chunk = FD_SCRATCH_ALLOC_APPEND( queue, FD_MLX5_PAGE_SZ, rx_depth*sizeof(uint)             );
     230           0 :   uint *             sq_wqe_frame_sz  = FD_SCRATCH_ALLOC_APPEND( queue, FD_MLX5_PAGE_SZ, tx_depth*sizeof(uint)             );
     231           0 :   uchar *            control          = FD_SCRATCH_ALLOC_APPEND( queue, FD_MLX5_PAGE_SZ, FD_MLX5_PAGE_SZ                   );
     232           0 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( queue, FD_MLX5_PAGE_SZ )==(ulong)queue_memory+queue_footprint );
     233             : 
     234           0 :   ctx->rx_cq.entries = rx_cqes;
     235           0 :   ctx->rx_cq.control = (fd_mlx5_cq_control_t *)control;
     236           0 :   ctx->rx_cq.depth   = rx_depth;
     237             : 
     238           0 :   ctx->tx_cq.entries = tx_cqes;
     239           0 :   ctx->tx_cq.control = (fd_mlx5_cq_control_t *)(control+sizeof(fd_mlx5_cq_control_t));
     240           0 :   ctx->tx_cq.depth   = tx_depth;
     241             : 
     242           0 :   ctx->rx_wq.rq               = rq;
     243           0 :   ctx->rx_wq.rx_cq            = &ctx->rx_cq;
     244           0 :   ctx->rx_wq.rx_depth         = rx_depth;
     245           0 :   ctx->rx_wq.rq_wqe_buf_chunk = rq_wqe_buf_chunk;
     246           0 :   ctx->rx_wq.control          = (fd_mlx5_rx_wq_control_t *)(control+2UL*sizeof(fd_mlx5_cq_control_t));
     247             : 
     248           0 :   ctx->tx_qp.sq              = sq;
     249           0 :   ctx->tx_qp.tx_cq           = &ctx->tx_cq;
     250           0 :   ctx->tx_qp.tx_depth        = tx_depth;
     251           0 :   ctx->tx_qp.sq_wqe_frame_sz = sq_wqe_frame_sz;
     252           0 :   ctx->tx_qp.control         = (fd_mlx5_qp_control_t *)(control+2UL*sizeof(fd_mlx5_cq_control_t)+sizeof(fd_mlx5_rx_wq_control_t));
     253           0 :   return ctx;
     254           0 : }
     255             : 
     256             : static fd_mlx5_tile_t *
     257             : fd_mlx5_hw_init_queues( fd_mlx5_tile_t * ctx,
     258             :                         void *           queue_memory,
     259             :                         uint             rx_depth,
     260           0 :                         uint             tx_depth ) {
     261           0 :   fd_memset( queue_memory, 0, fd_mlx5_queue_footprint( rx_depth, tx_depth ) );
     262           0 :   FD_TEST( fd_mlx5_hw_join_queues( ctx, queue_memory, rx_depth, tx_depth ) );
     263           0 :   fd_mlx5_hw_invalidate_cqes( ctx->rx_cq.entries, rx_depth );
     264           0 :   fd_mlx5_hw_invalidate_cqes( ctx->tx_cq.entries, tx_depth );
     265           0 :   return ctx;
     266           0 : }
     267             : 
     268             : /* fd_mlx5_tile_tx_chunk returns the buffer paired with SQ WQE sq_idx.
     269             :    The buffer is reusable once sq_cons advances past that WQE. */
     270             : static inline uint
     271             : fd_mlx5_tile_tx_chunk( fd_mlx5_tile_t const * ctx,
     272           0 :                        uint                   sq_idx ) {
     273           0 :   return ctx->sq_wqe_buf_chunk[ sq_idx & (ctx->tx_qp.tx_depth-1U) ];
     274           0 : }
     275             : 
     276             : static inline void
     277           0 : fd_mlx5_hw_dma_to_device( void ) {
     278           0 : #if FD_HAS_X86
     279           0 :   FD_COMPILER_MFENCE();
     280             : #elif FD_HAS_ARM
     281             :   __asm__ __volatile__( "dmb oshst" ::: "memory" );
     282             : #elif FD_HAS_RISCV
     283             :   __asm__ __volatile__( "fence ow,ow" ::: "memory" );
     284             : #else
     285             :   FD_HW_MFENCE_ST();
     286             : #endif
     287           0 : }
     288             : 
     289             : static inline void
     290           0 : fd_mlx5_hw_dma_from_device( void ) {
     291           0 : #if FD_HAS_X86
     292           0 :   __asm__ __volatile__( "lfence" ::: "memory" );
     293             : #elif FD_HAS_ARM
     294             :   __asm__ __volatile__( "dmb oshld" ::: "memory" );
     295             : #elif FD_HAS_RISCV
     296             :   __asm__ __volatile__( "fence ir,ir" ::: "memory" );
     297             : #else
     298             :   FD_HW_MFENCE();
     299             : #endif
     300           0 : }
     301             : 
     302             : static inline int
     303             : fd_mlx5_hw_init_tx_wqe( fd_mlx5_tx_wqe_t * wqe,
     304             :                         uint               sq_idx,
     305             :                         uint               qpn,
     306             :                         void const *       frame,
     307             :                         ulong              frame_iova,
     308             :                         ulong              frame_sz,
     309             :                         uint               lkey,
     310           0 :                         ulong              inline_hdr_sz ) {
     311           0 :   if( FD_UNLIKELY( !frame_sz || frame_sz>UINT_MAX || qpn>0xffffffU ||
     312           0 :                    inline_hdr_sz>frame_sz || frame_iova>ULONG_MAX-inline_hdr_sz ) ) return 0;
     313           0 :   fd_memset( wqe, 0, sizeof(*wqe) );
     314           0 :   uint const  wqe_seg_cnt      = inline_hdr_sz ? 4U : 3U;
     315           0 :   ulong const wqe_data_seg_off = inline_hdr_sz ? 48UL : 32UL;
     316             : 
     317           0 :   fd_mlx5_hw_wqe_ctrl_seg_t * wqe_ctrl_seg = (fd_mlx5_hw_wqe_ctrl_seg_t *)wqe;
     318           0 :   fd_mlx5_hw_wqe_eth_seg_t *  wqe_eth_seg  = (fd_mlx5_hw_wqe_eth_seg_t *)(wqe->bytes+sizeof(*wqe_ctrl_seg));
     319           0 :   fd_mlx5_hw_wqe_data_seg_t * wqe_data_seg = (fd_mlx5_hw_wqe_data_seg_t *)(wqe->bytes+wqe_data_seg_off);
     320             : 
     321           0 :   wqe_ctrl_seg->opmod_idx_opcode = fd_uint_bswap( ((sq_idx & 0xffffU)<<8) | FD_MLX5_SQ_SEND_PKT );
     322           0 :   wqe_ctrl_seg->qpn_ds           = fd_uint_bswap( (qpn<<8) | wqe_seg_cnt );
     323             : 
     324           0 :   wqe_eth_seg->inline_hdr_sz = fd_ushort_bswap( (ushort)inline_hdr_sz );
     325           0 :   if( inline_hdr_sz ) fd_memcpy( wqe_eth_seg->inline_hdr, frame, inline_hdr_sz );
     326             : 
     327           0 :   wqe_data_seg->byte_cnt = fd_uint_bswap( (uint)(frame_sz-inline_hdr_sz) );
     328           0 :   wqe_data_seg->lkey     = fd_uint_bswap( lkey );
     329           0 :   wqe_data_seg->addr     = fd_ulong_bswap( frame_iova+inline_hdr_sz );
     330           0 :   return 1;
     331           0 : }
     332             : 
     333             : static inline void
     334             : fd_mlx5_hw_init_rx_wqe( fd_mlx5_rx_wqe_t * wqe,
     335             :                         ulong              frame_iova,
     336             :                         ulong              frame_sz,
     337           0 :                         uint               lkey ) {
     338           0 :   fd_mlx5_hw_wqe_data_seg_t * wqe_data_seg = (fd_mlx5_hw_wqe_data_seg_t *)wqe;
     339             : 
     340           0 :   wqe_data_seg->byte_cnt = fd_uint_bswap( (uint)frame_sz );
     341           0 :   wqe_data_seg->lkey     = fd_uint_bswap( lkey );
     342           0 :   wqe_data_seg->addr     = fd_ulong_bswap( frame_iova );
     343           0 : }
     344             : 
     345             : static inline void
     346             : fd_mlx5_hw_ring_sq( fd_mlx5_tx_qp_t *  tx_qp,
     347           0 :                     fd_mlx5_tx_wqe_t * wqe ) {
     348           0 :   fd_mlx5_hw_wqe_ctrl_seg_t * wqe_ctrl_seg = (fd_mlx5_hw_wqe_ctrl_seg_t *)wqe;
     349           0 :   wqe_ctrl_seg->flags                      = FD_MLX5_SQ_REQUEST_CQE;
     350             : 
     351           0 :   fd_mlx5_hw_dma_to_device();
     352           0 :   FD_VOLATILE( tx_qp->control->sq_prod ) = fd_uint_bswap( tx_qp->sq_prod & 0xffffU );
     353           0 :   fd_mlx5_hw_dma_to_device();
     354             : 
     355             :   /* Ring the SQ through the non-cached UAR.  The NIC fetches the complete
     356             :      WQE from write-back SQ memory. */
     357           0 :   volatile ulong * doorbell_reg = (volatile ulong *)tx_qp->sq_doorbell;
     358           0 :   doorbell_reg[0]               = FD_LOAD( ulong, wqe->bytes );
     359           0 :   FD_COMPILER_MFENCE();
     360           0 :   tx_qp->sq_posted = tx_qp->sq_prod;
     361           0 : }
     362             : 
     363             : static inline int
     364             : fd_mlx5_hw_sq_enqueue( fd_mlx5_tile_t * ctx,
     365           0 :                        uint             frame_sz ) {
     366           0 :   fd_mlx5_tx_qp_t * tx_qp = &ctx->tx_qp;
     367           0 :   uint const outstanding  = tx_qp->sq_prod-tx_qp->sq_cons;
     368           0 :   if( FD_UNLIKELY( outstanding>=tx_qp->tx_depth ) ) {
     369           0 :     errno = ENOSPC;
     370           0 :     return -1;
     371           0 :   }
     372             : 
     373           0 :   uint const  sq_idx     = tx_qp->sq_prod;
     374           0 :   uint const  chunk      = fd_mlx5_tile_tx_chunk( ctx, sq_idx );
     375           0 :   ulong const frame_iova = (ulong)chunk<<FD_CHUNK_LG_SZ;
     376           0 :   fd_mlx5_tx_wqe_t * wqe = tx_qp->sq+(sq_idx & (tx_qp->tx_depth-1U));
     377           0 :   if( FD_UNLIKELY( !fd_mlx5_hw_init_tx_wqe( wqe, sq_idx, tx_qp->qpn,
     378           0 :                                             fd_chunk_to_laddr( ctx->net.pkt_buf_wksp_base, chunk ), frame_iova, frame_sz,
     379           0 :                                             ctx->lkey, tx_qp->tx_inline_hdr_sz ) ) ) {
     380           0 :     errno = EINVAL;
     381           0 :     return -1;
     382           0 :   }
     383           0 :   tx_qp->sq_wqe_frame_sz[ sq_idx & (tx_qp->tx_depth-1U) ] = frame_sz;
     384           0 :   tx_qp->sq_prod++;
     385           0 :   return 0;
     386           0 : }
     387             : 
     388             : static inline int
     389             : fd_mlx5_hw_rq_enqueue( fd_mlx5_tile_t * ctx,
     390           0 :                        uint             recv_cnt ) {
     391           0 :   fd_mlx5_rx_wq_t * rx_wq = &ctx->rx_wq;
     392           0 :   uint const outstanding  = rx_wq->rq_prod-rx_wq->rq_cons;
     393           0 :   if( FD_UNLIKELY( outstanding>rx_wq->rx_depth || recv_cnt>rx_wq->rx_depth-outstanding ) ) {
     394           0 :     errno = ENOSPC;
     395           0 :     return -1;
     396           0 :   }
     397             : 
     398           0 :   uint const rq_prod = rx_wq->rq_prod;
     399           0 :   for( uint i=0U; i<recv_cnt; i++ ) {
     400           0 :     uint const chunk  = ctx->rq_pending_chunk[ i ];
     401           0 :     uint const rq_idx = rq_prod+i;
     402           0 :     fd_mlx5_hw_init_rx_wqe( rx_wq->rq+(rq_idx & (rx_wq->rx_depth-1U)),
     403           0 :                             (ulong)chunk<<FD_CHUNK_LG_SZ, FD_NET_MTU, ctx->lkey );
     404           0 :     rx_wq->rq_wqe_buf_chunk[ rq_idx & (rx_wq->rx_depth-1U) ] = chunk;
     405           0 :   }
     406           0 :   rx_wq->rq_prod += recv_cnt;
     407           0 :   fd_mlx5_hw_dma_to_device();
     408           0 :   FD_VOLATILE( rx_wq->control->rq_prod ) = fd_uint_bswap( rx_wq->rq_prod & 0xffffU );
     409           0 :   return 0;
     410           0 : }
     411             : 
     412             : static inline int
     413             : fd_mlx5_hw_rx_cqe_is_owned( fd_mlx5_cq_t const * rx_cq,
     414             :                             uint                 cq_cons_idx,
     415           0 :                             uchar                op_own ) {
     416           0 :   return (op_own>>4)!=FD_MLX5_CQE_OP_INVALID &&
     417           0 :          (op_own & FD_MLX5_CQE_OWNER_MASK)==!!(cq_cons_idx & rx_cq->depth);
     418           0 : }
     419             : 
     420             : static inline int
     421             : fd_mlx5_hw_poll_rx_cq( fd_mlx5_rx_wq_t *        rx_wq,
     422             :                        fd_mlx5_tile_rx_comp_t * comp,
     423           0 :                        uint                     comp_capacity ) {
     424           0 :   fd_mlx5_cq_t * rx_cq       = rx_wq->rx_cq;
     425           0 :   uint const     comp_limit  = fd_uint_min( comp_capacity, rx_cq->depth );
     426           0 :   uint           cq_cons_idx = rx_cq->cons_idx;
     427           0 :   uint           rq_cons_idx = rx_wq->rq_cons;
     428           0 :   uint const     rq_prod_idx = rx_wq->rq_prod;
     429           0 :   FD_TEST( rq_prod_idx-rq_cons_idx<=rx_wq->rx_depth );
     430             : 
     431           0 :   uint comp_cnt    = 0U;
     432           0 :   int  poll_failed = 0;
     433           0 :   while( comp_cnt<comp_limit ) {
     434           0 :     fd_mlx5_hw_cqe64_t const * rx_cqe = (fd_mlx5_hw_cqe64_t const *)(rx_cq->entries+(cq_cons_idx & (rx_cq->depth-1U)));
     435           0 :     uchar const op_own = FD_VOLATILE_CONST( rx_cqe->op_own );
     436           0 :     if( FD_UNLIKELY( !fd_mlx5_hw_rx_cqe_is_owned( rx_cq, cq_cons_idx, op_own ) ) ) break;
     437           0 :     fd_mlx5_hw_dma_from_device();
     438           0 :     uint const opcode = (uint)(op_own>>4);
     439             : 
     440           0 :     if( FD_UNLIKELY( opcode!=FD_MLX5_CQE_OP_RX_OK && opcode!=FD_MLX5_CQE_OP_RX_ERR ) ) {
     441           0 :       errno       = EINVAL;
     442           0 :       poll_failed = 1;
     443           0 :       break;
     444           0 :     }
     445           0 :     if( FD_UNLIKELY( opcode==FD_MLX5_CQE_OP_RX_ERR &&
     446           0 :                      rx_cqe->syndrome!=FD_MLX5_CQE_SYNDROME_LOCAL_LENGTH_ERR ) ) {
     447           0 :       FD_LOG_ERR(( "mlx5 RX CQE error (syndrome 0x%02x, vendor syndrome 0x%02x, "
     448           0 :                   "WQE opcode/QPN 0x%08x, WQE counter %u)",
     449           0 :                   (uint)rx_cqe->syndrome, (uint)rx_cqe->vendor_err, fd_uint_bswap( rx_cqe->sop_drop_qpn ),
     450           0 :                   (uint)fd_ushort_bswap( rx_cqe->wqe_counter ) ));
     451           0 :     }
     452             : 
     453           0 :     ushort const wqe_counter = fd_ushort_bswap( rx_cqe->wqe_counter );
     454           0 :     if( FD_UNLIKELY( rq_cons_idx==rq_prod_idx || wqe_counter!=(ushort)rq_cons_idx ) ) {
     455           0 :       errno       = EPROTO;
     456           0 :       poll_failed = 1;
     457           0 :       break;
     458           0 :     }
     459             : 
     460           0 :     comp[ comp_cnt++ ] = (fd_mlx5_tile_rx_comp_t) {
     461           0 :       .chunk    = rx_wq->rq_wqe_buf_chunk[ wqe_counter & (rx_wq->rx_depth-1U) ],
     462           0 :       .byte_len = fd_uint_bswap( rx_cqe->byte_cnt ),
     463           0 :       .opcode   = opcode
     464           0 :     };
     465           0 :     rq_cons_idx++;
     466           0 :     cq_cons_idx++;
     467           0 :   }
     468             : 
     469           0 :   if( comp_cnt ) {
     470           0 :     rx_wq->rq_cons  = rq_cons_idx;
     471           0 :     rx_cq->cons_idx = cq_cons_idx;
     472           0 :     FD_COMPILER_MFENCE();
     473           0 :     FD_VOLATILE( rx_cq->control->consumer_idx ) = fd_uint_bswap( cq_cons_idx & FD_MLX5_CQ_CONS_IDX_MASK );
     474           0 :   }
     475           0 :   return poll_failed ? -1 : (int)comp_cnt;
     476           0 : }
     477             : 
     478             : static inline void
     479           0 : fd_mlx5_hw_request_rx_notification( fd_mlx5_cq_t * rx_cq ) {
     480           0 :   uint const notification_request = ((rx_cq->comp_channel_event_seq & FD_MLX5_CQ_ARM_SN_MASK)<<FD_MLX5_CQ_ARM_SN_SHIFT) |
     481           0 :                                      (rx_cq->cons_idx & FD_MLX5_CQ_CONS_IDX_MASK);
     482           0 :   FD_VOLATILE( rx_cq->control->request_notification ) = fd_uint_bswap( notification_request );
     483           0 :   fd_mlx5_hw_dma_to_device();
     484             : 
     485           0 :   uint const doorbell[2] = { fd_uint_bswap( notification_request ), fd_uint_bswap( rx_cq->cqn ) };
     486           0 :   volatile ulong * doorbell_reg = (volatile ulong *)rx_cq->request_notification_doorbell;
     487           0 :   doorbell_reg[0] = FD_LOAD( ulong, doorbell );
     488           0 :   rx_cq->comp_channel_armed = 1U;
     489           0 : }
     490             : 
     491             : static inline int
     492             : fd_mlx5_hw_poll_tx_cq( fd_mlx5_tx_qp_t * tx_qp,
     493           0 :                        ulong *           comp_bytes ) {
     494           0 :   fd_mlx5_cq_t * tx_cq    = tx_qp->tx_cq;
     495           0 :   uint const cq_cons_idx  = tx_cq->cons_idx;
     496           0 :   uint const cq_entry_idx = cq_cons_idx & (tx_cq->depth-1U);
     497             : 
     498           0 :   fd_mlx5_hw_cqe64_t const * tx_cqe = (fd_mlx5_hw_cqe64_t const *)(tx_cq->entries+cq_entry_idx);
     499             : 
     500           0 :   uchar const op_own = FD_VOLATILE_CONST( tx_cqe->op_own );
     501           0 :   uint const opcode  = (uint)(op_own>>4);
     502             : 
     503           0 :   if( FD_LIKELY( opcode==FD_MLX5_CQE_OP_INVALID ||
     504           0 :                  (op_own & FD_MLX5_CQE_OWNER_MASK)!=!!(cq_cons_idx & tx_cq->depth) ) ) {
     505           0 :     return 0;
     506           0 :   }
     507           0 :   fd_mlx5_hw_dma_from_device();
     508             : 
     509           0 :   if( FD_UNLIKELY( opcode!=FD_MLX5_CQE_OP_TX_OK && opcode!=FD_MLX5_CQE_OP_TX_ERR ) ) {
     510           0 :     errno = EINVAL;
     511           0 :     return -1;
     512           0 :   }
     513             : 
     514           0 :   if( FD_UNLIKELY( opcode==FD_MLX5_CQE_OP_TX_ERR ) ) {
     515           0 :     FD_LOG_ERR(( "mlx5 TX CQE error (syndrome 0x%02x, vendor syndrome 0x%02x, "
     516           0 :                 "WQE opcode/QPN 0x%08x, WQE counter %u)",
     517           0 :                 (uint)tx_cqe->syndrome, (uint)tx_cqe->vendor_err, fd_uint_bswap( tx_cqe->sop_drop_qpn ),
     518           0 :                 (uint)fd_ushort_bswap( tx_cqe->wqe_counter ) ));
     519           0 :   }
     520             : 
     521           0 :   uint const sq_cons_idx = tx_qp->sq_cons;
     522           0 :   uint const wqe_counter = (uint)fd_ushort_bswap( tx_cqe->wqe_counter );
     523           0 :   uint const outstanding = tx_qp->sq_posted-sq_cons_idx;
     524           0 :   uint const comp_cnt    = (uint)(ushort)(wqe_counter-sq_cons_idx)+1U;
     525             : 
     526           0 :   if( FD_UNLIKELY( !outstanding || outstanding>tx_qp->tx_depth || comp_cnt>outstanding ) ) {
     527           0 :     errno = EPROTO;
     528           0 :     return -1;
     529           0 :   }
     530             : 
     531           0 :   *comp_bytes = 0UL;
     532           0 :   for( uint i=0U; i<comp_cnt; i++ ) {
     533           0 :     *comp_bytes += tx_qp->sq_wqe_frame_sz[ (sq_cons_idx+i) & (tx_qp->tx_depth-1U) ];
     534           0 :   }
     535           0 :   tx_qp->sq_cons  = sq_cons_idx+comp_cnt;
     536           0 :   tx_cq->cons_idx = cq_cons_idx+1U;
     537           0 :   FD_COMPILER_MFENCE();
     538           0 :   FD_VOLATILE( tx_cq->control->consumer_idx ) = fd_uint_bswap( (cq_cons_idx+1U) & FD_MLX5_CQ_CONS_IDX_MASK );
     539           0 :   return (int)comp_cnt;
     540           0 : }
     541             : 
     542             : static inline void
     543           0 : fd_mlx5_tile_sq_flush( fd_mlx5_tile_t * ctx ) {
     544           0 :   fd_mlx5_tx_qp_t * tx_qp = &ctx->tx_qp;
     545           0 :   if( FD_UNLIKELY( tx_qp->sq_posted==tx_qp->sq_prod ) ) return;
     546             : 
     547           0 :   uint const         last_sq_idx = tx_qp->sq_prod-1U;
     548           0 :   fd_mlx5_tx_wqe_t * last_wqe    = tx_qp->sq+(last_sq_idx & (tx_qp->tx_depth-1U));
     549           0 :   fd_mlx5_hw_ring_sq( tx_qp, last_wqe );
     550           0 : }
     551             : 
     552             : static inline void
     553             : fd_mlx5_tile_rx_recycle( fd_mlx5_tile_t * ctx,
     554           0 :                          ulong            chunk ) {
     555           0 :   ctx->rq_pending_chunk[ ctx->rq_pending_cnt++ ] = (uint)chunk;
     556             : 
     557             :   /* Hold partial recycle batches: at most batch_size-1 RQ buffers stay
     558             :      unposted, avoiding a device-ordering barrier and doorbell-record write
     559             :      per packet. */
     560           0 :   if( ctx->rq_pending_cnt==ctx->batch_size ) {
     561           0 :     FD_TEST( !fd_mlx5_hw_rq_enqueue( ctx, ctx->rq_pending_cnt ) );
     562           0 :     ctx->rq_pending_cnt = 0U;
     563           0 :   }
     564           0 : }
     565             : 
     566             : static inline int
     567             : fd_mlx5_tile_poll_rx( fd_mlx5_tile_t *    ctx,
     568           0 :                       fd_stem_context_t * stem ) {
     569           0 :   fd_mlx5_tile_rx_comp_t comp[ FD_MLX5_BATCH_SIZE ];
     570           0 :   int comp_cnt = fd_mlx5_hw_poll_rx_cq( &ctx->rx_wq, comp, ctx->batch_size );
     571             : 
     572           0 :   if( FD_UNLIKELY( comp_cnt<0 ) ) {
     573           0 :     FD_LOG_ERR(( "direct mlx5 RX CQ poll failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     574           0 :   }
     575           0 :   if( FD_UNLIKELY( !comp_cnt ) ) return 0;
     576             : 
     577           0 :   ulong const tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
     578             : 
     579           0 :   for( uint i=0U; i<(uint)comp_cnt; i++ ) {
     580           0 :     ulong const chunk = comp[ i ].chunk;
     581           0 :     if( FD_UNLIKELY( chunk<ctx->net.pkt_buf_chunk0 || chunk>ctx->net.pkt_buf_wmark ) ) {
     582           0 :       FD_LOG_CRIT(( "RX completion chunk %lu out of bounds [%lu,%lu]", chunk, ctx->net.pkt_buf_chunk0, ctx->net.pkt_buf_wmark ));
     583           0 :     }
     584           0 :     __builtin_prefetch( fd_chunk_to_laddr_const( ctx->net.pkt_buf_wksp_base, chunk ), 0, 3 );
     585           0 :   }
     586             : 
     587           0 :   for( uint i=0U; i<(uint)comp_cnt; i++ ) {
     588           0 :     ulong freed_chunk;
     589           0 :     if( FD_UNLIKELY( comp[ i ].opcode==FD_MLX5_CQE_OP_RX_ERR ) ) {
     590           0 :       ctx->net.metrics.rx_drop_cnt[ FD_METRICS_ENUM_PKT_RX_DROP_REASON_V_MALFORMED_IDX ]++;
     591           0 :       freed_chunk = comp[ i ].chunk;
     592           0 :     } else {
     593           0 :       fd_net_rx_pkt( &ctx->net, stem, comp[ i ].chunk, 0UL, comp[ i ].byte_len, tspub, &freed_chunk );
     594           0 :     }
     595           0 :     if( FD_UNLIKELY( !freed_chunk ) ) FD_LOG_CRIT(( "invalid chunk in mcache" ));
     596           0 :     fd_mlx5_tile_rx_recycle( ctx, freed_chunk );
     597           0 :   }
     598           0 :   return 1;
     599           0 : }
     600             : 
     601             : static inline int
     602           0 : fd_mlx5_tile_poll_tx( fd_mlx5_tile_t * ctx ) {
     603           0 :   ulong tx_pkt_cnt     = 0UL;
     604           0 :   ulong tx_bytes_total = 0UL;
     605             : 
     606           0 :   int busy = 0;
     607           0 :   for( uint poll_cnt=0U; poll_cnt<ctx->batch_size; poll_cnt++ ) {
     608           0 :     ulong tx_bytes;
     609             : 
     610           0 :     int comp_cnt = fd_mlx5_hw_poll_tx_cq( &ctx->tx_qp, &tx_bytes );
     611           0 :     if( FD_UNLIKELY( comp_cnt<0 ) ) {
     612           0 :       FD_LOG_ERR(( "direct mlx5 TX CQ poll failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     613           0 :     }
     614           0 :     if( FD_UNLIKELY( !comp_cnt ) ) break;
     615             : 
     616           0 :     busy = 1;
     617           0 :     tx_pkt_cnt     += (uint)comp_cnt;
     618           0 :     tx_bytes_total += tx_bytes;
     619           0 :   }
     620           0 :   ctx->net.metrics.tx_pkt_cnt     += tx_pkt_cnt;
     621           0 :   ctx->net.metrics.tx_bytes_total += tx_bytes_total;
     622             : 
     623           0 :   return busy;
     624           0 : }
     625             : 
     626             : /* fd_mlx5_tile_lo_tx_flush makes one nonblocking send attempt per batch,
     627             :    unsent packets are dropped. */
     628             : static void
     629           0 : fd_mlx5_tile_lo_tx_flush( fd_mlx5_tile_t * ctx ) {
     630           0 :   if( FD_UNLIKELY( !ctx->lo_tx_cnt ) ) return;
     631           0 :   int const send_cnt = sendmmsg( ctx->lo_tx_sock, ctx->lo_tx_msg, ctx->lo_tx_cnt, MSG_DONTWAIT );
     632           0 :   uint const sent_cnt = send_cnt<0 ? 0U : (uint)send_cnt;
     633           0 :   for( uint i=0U; i<sent_cnt; i++ ) ctx->net.metrics.tx_bytes_total += sizeof(fd_eth_hdr_t)+ctx->lo_tx_iov[ i ].iov_len;
     634           0 :   ctx->net.metrics.tx_pkt_cnt += sent_cnt;
     635           0 :   ctx->lo_tx_cnt = 0U;
     636           0 : }
     637             : 
     638             : static void
     639             : fd_mlx5_tile_lo_tx_enqueue( fd_mlx5_tile_t *      ctx,
     640             :                             fd_ip4_hdr_t const * ip4,
     641           0 :                             ulong                ip_sz ) {
     642           0 :   uint const batch_idx = ctx->lo_tx_cnt;
     643           0 :   struct mmsghdr *     msg = ctx->lo_tx_msg  + batch_idx;
     644           0 :   struct sockaddr_in * sa  = ctx->lo_tx_addr + batch_idx;
     645           0 :   struct iovec *       iov = ctx->lo_tx_iov  + batch_idx;
     646           0 :   uchar *              buf = ctx->lo_tx_buf[ batch_idx ];
     647             : 
     648           0 :   *iov = (struct iovec) {
     649           0 :     .iov_base = buf,
     650           0 :     .iov_len  = ip_sz,
     651           0 :   };
     652           0 :   sa->sin_family      = AF_INET;
     653           0 :   sa->sin_addr.s_addr = ip4->daddr;
     654           0 :   sa->sin_port        = 0; /* ignored */
     655             : 
     656           0 :   *msg = (struct mmsghdr) {
     657           0 :     .msg_hdr = {
     658           0 :       .msg_name    = sa,
     659           0 :       .msg_namelen = sizeof(struct sockaddr_in),
     660           0 :       .msg_iov     = iov,
     661           0 :       .msg_iovlen  = 1UL
     662           0 :     }
     663           0 :   };
     664             : 
     665           0 :   fd_memcpy( buf, ip4, ip_sz );
     666           0 :   ctx->lo_tx_cnt++;
     667           0 :   if( ctx->lo_tx_cnt==FD_MLX5_BATCH_SIZE ) fd_mlx5_tile_lo_tx_flush( ctx );
     668           0 :   else if( ctx->lo_tx_cnt==1U ) ctx->lo_tx_deadline_ticks = fd_tickcount()+ctx->lo_tx_timeout_ticks;
     669           0 : }
     670             : 
     671             : /* fd_mlx5_tile_drain_rx_comp_channel reads every pending rx_cq completion event.
     672             :    Each event consumes the CQ's arm, so comp_channel_event_seq advances. */
     673             : static void
     674           0 : fd_mlx5_tile_drain_rx_comp_channel( fd_mlx5_tile_t * ctx ) {
     675           0 :   for(;;) {
     676           0 :     struct ib_uverbs_comp_event_desc comp_event;
     677           0 :     ssize_t comp_event_read_sz = read( ctx->rx_comp_channel_fd, &comp_event, sizeof(comp_event) );
     678           0 :     if( FD_UNLIKELY( comp_event_read_sz<0 && errno==EINTR ) ) continue;
     679             : 
     680           0 :     if( FD_LIKELY( comp_event_read_sz<0 && (errno==EAGAIN || errno==EWOULDBLOCK) ) ) break;
     681           0 :     if( FD_UNLIKELY( comp_event_read_sz!=(ssize_t)sizeof(comp_event) ) ) {
     682           0 :       FD_LOG_ERR(( "mlx5 RX completion event read failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     683           0 :     }
     684           0 :     ctx->rx_cq.comp_channel_event_seq++;
     685           0 :     ctx->rx_cq.comp_channel_armed = 0U;
     686           0 :   }
     687           0 : }
     688             : 
     689             : static inline void
     690             : fd_mlx5_tile_repoll_deadline_update( fd_mlx5_tile_t * ctx,
     691             :                                      int              work_done,
     692           0 :                                      long             now ) {
     693           0 :   if( FD_LIKELY( work_done ) ) {
     694           0 :     ctx->repoll_deadline_ticks = now + ctx->repoll_timeout_ticks;
     695           0 :   } else if( FD_UNLIKELY( now>=ctx->repoll_deadline_ticks ) ) {
     696           0 :     ctx->repoll_deadline_ticks = LONG_MAX;
     697           0 :   }
     698           0 : }
     699             : 
     700             : static inline void
     701             : before_credit( fd_mlx5_tile_t *    ctx,
     702             :                fd_stem_context_t * stem,
     703           0 :                int *               charge_busy ) {
     704           0 :   ctx->has_out_credit = 0U;
     705           0 :   fd_net_router_solicit( &ctx->router, stem );
     706             : 
     707           0 :   if( FD_UNLIKELY( ctx->lo_tx_cnt && fd_tickcount()>=ctx->lo_tx_deadline_ticks ) ) {
     708           0 :     fd_mlx5_tile_lo_tx_flush( ctx );
     709           0 :     *charge_busy = 1;
     710           0 :   }
     711           0 :   fd_mlx5_tx_qp_t * tx_qp          = &ctx->tx_qp;
     712           0 :   uint const        sq_pending_cnt = tx_qp->sq_prod-tx_qp->sq_posted;
     713             : 
     714           0 :   if( FD_UNLIKELY( sq_pending_cnt && fd_tickcount()>=ctx->sq_flush_deadline_ticks ) ) {
     715           0 :     fd_mlx5_tile_sq_flush( ctx );
     716           0 :     *charge_busy = 1;
     717           0 :   }
     718           0 :   if( tx_qp->sq_cons!=tx_qp->sq_posted ) {
     719           0 :     *charge_busy |= fd_mlx5_tile_poll_tx( ctx );
     720           0 :   }
     721           0 : }
     722             : 
     723             : /* after_credit is called every run loop iteration, provided there is
     724             :    sufficient downstream credit for forwarding on all output links. */
     725             : static inline void
     726             : after_credit( fd_mlx5_tile_t *    ctx,
     727             :               fd_stem_context_t * stem,
     728             :               int *               poll_in,
     729           0 :               int *               charge_busy ) {
     730           0 :   (void)poll_in;
     731           0 :   ctx->has_out_credit = 1U;
     732           0 :   int rx_busy = fd_mlx5_tile_poll_rx( ctx, stem );
     733           0 :   if( FD_UNLIKELY( ctx->rx_comp_channel_fd>=0 ) ) {
     734           0 :     fd_mlx5_tile_repoll_deadline_update( ctx, rx_busy, fd_tickcount() );
     735           0 :   }
     736           0 :   *charge_busy |= rx_busy;
     737           0 : }
     738             : 
     739             : static void
     740           0 : fd_mlx5_tile_async_drain( fd_mlx5_tile_t * ctx ) {
     741           0 :   int const async_event_fd = ctx->uverbs.async_fd;
     742           0 :   for(;;) {
     743           0 :     struct ib_uverbs_async_event_desc async_event;
     744           0 :     ssize_t async_event_read_sz = read( async_event_fd, &async_event, sizeof(async_event) );
     745           0 :     if( FD_UNLIKELY( async_event_read_sz<0 && errno==EINTR ) ) continue;
     746             : 
     747           0 :     if( FD_LIKELY( async_event_read_sz<0 && (errno==EAGAIN || errno==EWOULDBLOCK) ) ) break;
     748           0 :     if( FD_UNLIKELY( async_event_read_sz!=(ssize_t)sizeof(async_event) ) ) {
     749           0 :       FD_LOG_ERR(( "mlx5 async event read failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     750           0 :     }
     751             : 
     752           0 :     uint const async_event_type = async_event.event_type;
     753           0 :     ulong const async_event_element = (ulong)async_event.element;
     754           0 :     if( FD_UNLIKELY( async_event_type==FD_MLX5_ASYNC_EVENT_CQ_ERR        ||
     755           0 :                      async_event_type==FD_MLX5_ASYNC_EVENT_QP_FATAL      ||
     756           0 :                      async_event_type==FD_MLX5_ASYNC_EVENT_QP_REQ_ERR    ||
     757           0 :                      async_event_type==FD_MLX5_ASYNC_EVENT_QP_ACCESS_ERR ||
     758           0 :                      async_event_type==FD_MLX5_ASYNC_EVENT_DEVICE_FATAL  ||
     759           0 :                      async_event_type==FD_MLX5_ASYNC_EVENT_WQ_FATAL ) ) {
     760           0 :       FD_LOG_ERR(( "fatal mlx5 async event %u on element %lu", async_event_type, async_event_element ));
     761           0 :     }
     762           0 :     FD_LOG_INFO(( "mlx5 async event %u on element %lu", async_event_type, async_event_element ));
     763           0 :   }
     764           0 : }
     765             : 
     766             : static inline int
     767           0 : prevent_park( fd_mlx5_tile_t * ctx ) {
     768           0 :   if( FD_UNLIKELY( ctx->tx_qp.sq_prod!=ctx->tx_qp.sq_posted ) ) fd_mlx5_tile_sq_flush( ctx );
     769           0 :   if( FD_UNLIKELY( ctx->lo_tx_cnt ) ) fd_mlx5_tile_lo_tx_flush( ctx );
     770           0 :   if( FD_UNLIKELY( !ctx->has_out_credit ) ) return 0;
     771           0 :   fd_mlx5_cq_t * rx_cq = &ctx->rx_cq;
     772           0 :   if( FD_UNLIKELY( !rx_cq->comp_channel_armed &&
     773           0 :                    ctx->repoll_deadline_ticks==LONG_MAX ) ) {
     774           0 :     fd_mlx5_hw_request_rx_notification( rx_cq );
     775           0 :   }
     776           0 :   uint const cq_cons_idx = rx_cq->cons_idx;
     777           0 :   fd_mlx5_hw_cqe64_t const * rx_cqe = (fd_mlx5_hw_cqe64_t const *)(rx_cq->entries+(cq_cons_idx & (rx_cq->depth-1U)));
     778           0 :   uchar const op_own = FD_VOLATILE_CONST( rx_cqe->op_own );
     779           0 :   return fd_mlx5_hw_rx_cqe_is_owned( rx_cq, cq_cons_idx, op_own );
     780           0 : }
     781             : 
     782             : static inline int
     783             : park_wait( fd_mlx5_tile_t * ctx,
     784             :            ulong *          word,
     785             :            long             deadline,
     786           0 :            double           tick_per_ns ) {
     787           0 :   if( FD_UNLIKELY( ctx->has_out_credit && ctx->repoll_deadline_ticks!=LONG_MAX ) ) {
     788           0 :     struct timespec timeout;
     789           0 :     clock_gettime( CLOCK_MONOTONIC, &timeout );
     790           0 :     long const remaining_ns = (long)((double)(ctx->repoll_deadline_ticks-fd_tickcount())/tick_per_ns);
     791           0 :     if( FD_UNLIKELY( remaining_ns<=0L ) ) return FD_SLEEP_UNPARK_DEADLINE;
     792           0 :     timeout.tv_sec  += remaining_ns/FD_MLX5_NS_PER_SEC;
     793           0 :     timeout.tv_nsec += remaining_ns%FD_MLX5_NS_PER_SEC;
     794           0 :     if( timeout.tv_nsec>=FD_MLX5_NS_PER_SEC ) {
     795           0 :       timeout.tv_sec++;
     796           0 :       timeout.tv_nsec -= FD_MLX5_NS_PER_SEC;
     797           0 :     }
     798           0 :     for(;;) {
     799           0 :       int const err = clock_nanosleep( CLOCK_MONOTONIC, TIMER_ABSTIME, &timeout, NULL );
     800           0 :       if( FD_LIKELY( !err ) ) return FD_SLEEP_UNPARK_DEADLINE;
     801           0 :       if( FD_UNLIKELY( err!=EINTR ) ) {
     802           0 :         FD_LOG_ERR(( "clock_nanosleep() failed (%i-%s)", err, fd_io_strerror( err ) ));
     803           0 :       }
     804           0 :     }
     805           0 :   }
     806             : 
     807           0 :   struct epoll_event evs[ 3 ];
     808           0 :   int ev_cnt;
     809           0 :   int cause = fd_sleep_park_wait_epoll( ctx->epoll_fd, word, evs, 3, &ev_cnt, deadline, tick_per_ns );
     810           0 :   for( int i=0; i<ev_cnt; i++ ) {
     811           0 :     if(      evs[ i ].data.u64==(ulong)ctx->rx_comp_channel_fd ) fd_mlx5_tile_drain_rx_comp_channel( ctx );
     812           0 :     else if( evs[ i ].data.u64==FD_MLX5_EPOLL_ASYNC            ) fd_mlx5_tile_async_drain( ctx );
     813           0 :   }
     814           0 :   return cause;
     815           0 : }
     816             : 
     817             : static inline long
     818           0 : next_deadline( fd_mlx5_tile_t * ctx ) {
     819           0 :   if( !ctx->has_out_credit ) return LONG_MAX; /* If no out credit then don't repoll */
     820             : 
     821           0 :   return ctx->repoll_deadline_ticks; /* LONG_MAX if no repoll */
     822           0 : }
     823             : 
     824             : /* before_frag resolves the TX route and checks SQ capacity */
     825             : static inline int
     826             : before_frag( fd_mlx5_tile_t * ctx,
     827             :              ulong            in_idx,
     828             :              ulong            seq,
     829           0 :              ulong            sig ) {
     830           0 :   (void)seq;
     831           0 :   if( FD_UNLIKELY( ctx->net.in_kind[ in_idx ]==FD_NET_IN_KIND_IPROUTE ) ) return 0;
     832             : 
     833             :   /* Resolve the TX route for outgoing packets */
     834           0 :   ulong dst_proto = fd_disco_netmux_sig_proto( sig );
     835           0 :   if( FD_UNLIKELY( dst_proto!=DST_PROTO_OUTGOING ) ) return 1;
     836             : 
     837           0 :   ulong const hash       = fd_disco_netmux_sig_hash( sig );
     838           0 :   ulong       target_idx = hash % ctx->net.tile_cnt;
     839           0 :   ulong const kind_id    = ctx->net.kind_id;
     840           0 :   if( kind_id!=0UL && kind_id!=target_idx ) return 1;
     841             : 
     842           0 :   uint dst_ip = fd_disco_netmux_sig_ip( sig );
     843           0 :   fd_net_tx_route_t * route = &ctx->net.tx_route;
     844           0 :   if( FD_UNLIKELY( !fd_net_tx_route( &ctx->router, &ctx->net, dst_ip, route ) ) ) return 1;
     845           0 :   if( FD_UNLIKELY( route->use_gre ) ) {
     846           0 :     fd_net_tx_route_t outer_route;
     847           0 :     uint const inner_src_ip = route->src_ip;
     848           0 :     uint const outer_src_ip = route->gre_outer_src_ip;
     849           0 :     uint const outer_dst_ip = route->gre_outer_dst_ip;
     850             : 
     851           0 :     if( FD_UNLIKELY( !inner_src_ip || !outer_dst_ip ) ) {
     852           0 :       ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_GRE_NO_ROUTE_IDX ]++;
     853           0 :       return 1;
     854           0 :     }
     855             :     /* fd_net_tx_route counts its own drop reason */
     856           0 :     if( FD_UNLIKELY( !fd_net_tx_route( &ctx->router, &ctx->net, outer_dst_ip, &outer_route ) ) ) return 1;
     857           0 :     if( FD_UNLIKELY( outer_route.use_gre || outer_route.use_loopback ) ) {
     858           0 :       ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_GRE_NO_ROUTE_IDX ]++;
     859           0 :       return 1;
     860           0 :     }
     861           0 :     *route                  = outer_route;
     862           0 :     route->src_ip           = inner_src_ip;
     863           0 :     route->gre_outer_src_ip = fd_uint_if( !outer_src_ip, outer_route.src_ip, outer_src_ip );
     864           0 :     route->gre_outer_dst_ip = outer_dst_ip;
     865           0 :     route->use_gre          = 1U;
     866           0 :   }
     867             : 
     868           0 :   if( route->use_loopback ) target_idx = 0UL;
     869           0 :   if( kind_id!=target_idx ) return 1;
     870             : 
     871             :   /* Drop the packet if no SQ WQE is available. */
     872           0 :   fd_mlx5_tx_qp_t const * tx_qp       = &ctx->tx_qp;
     873           0 :   uint const              outstanding = tx_qp->sq_prod-tx_qp->sq_cons;
     874           0 :   if( FD_UNLIKELY( outstanding>=tx_qp->tx_depth ) ) {
     875           0 :     ctx->metrics.tx_no_buffer_cnt++;
     876           0 :     return 1;
     877           0 :   }
     878             : 
     879           0 :   return 0; /* continue */
     880           0 : }
     881             : 
     882             : /* during_frag validates and stages the input packet or route update */
     883             : static inline void
     884             : during_frag( fd_mlx5_tile_t * ctx,
     885             :              ulong            in_idx,
     886             :              ulong            seq,
     887             :              ulong            sig,
     888             :              ulong            chunk,
     889             :              ulong            frame_sz,
     890           0 :              ulong            ctl ) {
     891           0 :   (void)seq; (void)sig; (void)ctl;
     892           0 :   fd_net_in_frag_validate( &ctx->net, in_idx, chunk, frame_sz );
     893             : 
     894           0 :   if( FD_UNLIKELY( fd_net_iproute_msg_stage( &ctx->net, in_idx, chunk, frame_sz ) ) ) return;
     895             : 
     896           0 :   ulong   dst_chunk = fd_mlx5_tile_tx_chunk( ctx, ctx->tx_qp.sq_prod );
     897           0 :   uchar * dst       = fd_chunk_to_laddr( ctx->net.pkt_buf_wksp_base, dst_chunk );
     898             :   /* Speculatively copy frame from in link into buffer */
     899           0 :   fd_net_tx_pkt_cpy( &ctx->net, dst, chunk, frame_sz, in_idx );
     900           0 : }
     901             : 
     902             : /* after_frag applies a route update or completes and submits the staged packet */
     903             : static void
     904             : after_frag( fd_mlx5_tile_t *    ctx,
     905             :             ulong               in_idx,
     906             :             ulong               seq,
     907             :             ulong               sig,
     908             :             ulong               frame_sz,
     909             :             ulong               tsorig,
     910             :             ulong               tspub,
     911           0 :             fd_stem_context_t * stem ) {
     912           0 :   (void)seq; (void)sig; (void)tsorig; (void)tspub;
     913             : 
     914           0 :   if( FD_UNLIKELY( ctx->net.in_kind[ in_idx ]==FD_NET_IN_KIND_IPROUTE ) ) {
     915           0 :     fd_iproute_msg_t const * msg = &ctx->net.iproute_msg;
     916           0 :     if( msg->op==FD_IPROUTE_OP_FLUSH ) {
     917           0 :       fd_fib4_clear( ctx->router.fib_local );
     918           0 :       fd_fib4_clear( ctx->router.fib_main );
     919           0 :       return;
     920           0 :     }
     921             : 
     922           0 :     fd_fib4_t * fib;
     923           0 :     if( msg->table_id==RT_TABLE_LOCAL )     fib = ctx->router.fib_local;
     924           0 :     else if( msg->table_id==RT_TABLE_MAIN ) fib = ctx->router.fib_main;
     925           0 :     else return;
     926             : 
     927           0 :     if( msg->op==FD_IPROUTE_OP_UPSERT && FD_UNLIKELY( !fd_fib4_insert( fib, msg->dst_addr, msg->prefix, msg->prio, &msg->hop ) ) ) {
     928           0 :       FD_LOG_WARNING(( "route update dropped: route table full (increase [net.max_routes] or [net.max_peer_routes])" ));
     929           0 :       fd_stem_publish( stem, ctx->router.netlnk_out_idx, FD_NETLINK_ROUTE4_SYNC_SIG, 0UL, 0UL, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
     930           0 :     } else if( msg->op==FD_IPROUTE_OP_DELETE ) {
     931           0 :       fd_fib4_remove( fib, msg->dst_addr, msg->prefix, msg->prio );
     932           0 :     }
     933           0 :     return;
     934           0 :   }
     935             : 
     936           0 :   ulong   chunk = fd_mlx5_tile_tx_chunk( ctx, ctx->tx_qp.sq_prod );
     937           0 :   uchar * frame = fd_chunk_to_laddr( ctx->net.pkt_buf_wksp_base, chunk );
     938             : 
     939           0 :   if( FD_UNLIKELY( !fd_net_tx_pkt_prep( &ctx->net, frame, &frame_sz, in_idx ) ) ) return;
     940             : 
     941           0 :   if( FD_UNLIKELY( ctx->net.tx_route.use_loopback ) ) {
     942           0 :     fd_eth_hdr_t const * eth_hdr = (fd_eth_hdr_t const *)frame;
     943           0 :     fd_ip4_hdr_t const * ip4 = (fd_ip4_hdr_t const *)(eth_hdr+1);
     944           0 :     ulong const ip_hdr_sz = FD_IP4_GET_LEN( *ip4 );
     945           0 :     ulong const ip_sz     = fd_ushort_bswap( ip4->net_tot_len );
     946           0 :     if( FD_UNLIKELY( ip4->protocol!=FD_IP4_HDR_PROTOCOL_UDP ||
     947           0 :                      ip4->daddr!=fd_disco_netmux_sig_ip( sig ) ||
     948           0 :                      (fd_ushort_bswap( ip4->net_frag_off ) & ~FD_IP4_HDR_FRAG_OFF_DF) ||
     949           0 :                      ip_sz<ip_hdr_sz+sizeof(fd_udp_hdr_t) ||
     950           0 :                      ip_sz>frame_sz-sizeof(fd_eth_hdr_t) ) ) {
     951           0 :       ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_INVALID_IDX ]++;
     952           0 :       return;
     953           0 :     }
     954           0 :     fd_udp_hdr_t const * udp = (fd_udp_hdr_t const *)((uchar const *)ip4+ip_hdr_sz);
     955           0 :     ulong const udp_sz = fd_ushort_bswap( udp->net_len );
     956           0 :     if( FD_UNLIKELY( udp_sz<sizeof(fd_udp_hdr_t) || udp_sz>ip_sz-ip_hdr_sz ) ) {
     957           0 :       ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_INVALID_IDX ]++;
     958           0 :       return;
     959           0 :     }
     960             : 
     961           0 :     int owned = 0;
     962           0 :     if( !ctx->router.bind_address || ctx->router.bind_address==ip4->daddr ) {
     963           0 :       ushort const dst_port = fd_ushort_bswap( udp->net_dport );
     964           0 :       for( ulong i=0UL; i<ctx->net.dst_port_cnt; i++ ) owned |= ctx->net.dst_ports[ i ]==dst_port;
     965           0 :     }
     966           0 :     if( !owned ) {
     967           0 :       fd_mlx5_tile_lo_tx_enqueue( ctx, ip4, ip_sz );
     968           0 :       return;
     969           0 :     }
     970             : 
     971           0 :     ulong freed_chunk;
     972           0 :     if( fd_net_rx_pkt( &ctx->net, stem, chunk, 0UL, frame_sz, (ulong)fd_frag_meta_ts_comp( fd_tickcount() ), &freed_chunk ) ) {
     973           0 :       ctx->sq_wqe_buf_chunk[ ctx->tx_qp.sq_prod & (ctx->tx_qp.tx_depth-1U) ] = (uint)freed_chunk;
     974           0 :     }
     975           0 :     ctx->net.metrics.tx_pkt_cnt++;
     976           0 :     ctx->net.metrics.tx_bytes_total += frame_sz;
     977           0 :     return;
     978           0 :   }
     979             : 
     980           0 :   FD_TEST( !fd_mlx5_hw_sq_enqueue( ctx, (uint)frame_sz ) );
     981             : 
     982           0 :   ctx->net.metrics.tx_gre_cnt += (ulong)ctx->net.tx_route.use_gre;
     983             : 
     984           0 :   fd_mlx5_tx_qp_t * tx_qp          = &ctx->tx_qp;
     985           0 :   uint const        sq_pending_cnt = tx_qp->sq_prod-tx_qp->sq_posted;
     986             : 
     987           0 :   if( sq_pending_cnt>=ctx->batch_size || tx_qp->sq_prod-tx_qp->sq_cons>=tx_qp->tx_depth ) {
     988           0 :     fd_mlx5_tile_sq_flush( ctx );
     989           0 :   } else if( sq_pending_cnt==1U ) {
     990           0 :     ctx->sq_flush_deadline_ticks = fd_tickcount()+ctx->sq_flush_timeout_ticks;
     991           0 :   }
     992           0 :   if( FD_UNLIKELY( ctx->rx_comp_channel_fd>=0 ) ) {
     993           0 :     fd_mlx5_tile_repoll_deadline_update( ctx, 1, fd_tickcount() );
     994           0 :   }
     995           0 : }
     996             : 
     997             : static inline void
     998           0 : metrics_write( fd_mlx5_tile_t * ctx ) {
     999           0 :   fd_mlx5_rx_wq_t const * rx_wq  = &ctx->rx_wq;
    1000           0 :   fd_mlx5_tx_qp_t const * tx_qp  = &ctx->tx_qp;
    1001           0 :   ulong const rx_buffer_idle_cnt = fd_ulong_min( (ulong)(rx_wq->rq_prod-rx_wq->rq_cons), rx_wq->rx_depth );
    1002           0 :   ulong const rx_buffer_busy_cnt = rx_wq->rx_depth-rx_buffer_idle_cnt;
    1003           0 :   ulong const tx_buffer_busy_cnt = fd_ulong_min( (ulong)(tx_qp->sq_prod-tx_qp->sq_cons), tx_qp->tx_depth );
    1004           0 :   ulong const tx_buffer_idle_cnt = tx_qp->tx_depth-tx_buffer_busy_cnt;
    1005             : 
    1006           0 :   FD_MCNT_SET(   MLX5, PKT_RX,             ctx->net.metrics.rx_pkt_cnt           );
    1007           0 :   FD_MCNT_SET(   MLX5, PKT_RX_BYTES,       ctx->net.metrics.rx_bytes_total       );
    1008           0 :   FD_MCNT_ENUM_COPY( MLX5, PKT_RX_DROPPED, ctx->net.metrics.rx_drop_cnt        );
    1009           0 :   FD_MCNT_SET(   MLX5, GRE_PKT_RX,         ctx->net.metrics.rx_gre_cnt           );
    1010           0 :   FD_MGAUGE_SET( MLX5, RX_BUFFER_USED,     rx_buffer_busy_cnt                    );
    1011           0 :   FD_MGAUGE_SET( MLX5, RX_BUFFER_FREE,     rx_buffer_idle_cnt                    );
    1012             : 
    1013           0 :   FD_MCNT_SET(   MLX5, PKT_TX_COMPLETED,       ctx->net.metrics.tx_pkt_cnt            );
    1014           0 :   FD_MCNT_SET(   MLX5, PKT_TX_COMPLETED_BYTES, ctx->net.metrics.tx_bytes_total        );
    1015           0 :   ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_NO_NEIGHBOR_IDX ] = ctx->router.metrics.tx_neigh_fail_cnt;
    1016           0 :   ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_RING_FULL_IDX   ] = ctx->metrics.tx_no_buffer_cnt;
    1017           0 :   FD_MCNT_ENUM_COPY( MLX5, PKT_TX_DROPPED,     ctx->net.metrics.tx_drop_cnt           );
    1018           0 :   FD_MCNT_SET(   MLX5, GRE_PKT_TX_SUBMITTED,   ctx->net.metrics.tx_gre_cnt            );
    1019           0 :   FD_MGAUGE_SET( MLX5, TX_BUFFER_USED,         tx_buffer_busy_cnt                     );
    1020           0 :   FD_MGAUGE_SET( MLX5, TX_BUFFER_FREE,         tx_buffer_idle_cnt                     );
    1021           0 : }
    1022             : 
    1023             : static inline void
    1024           0 : during_housekeeping( fd_mlx5_tile_t * ctx ) {
    1025           0 :   fd_mlx5_tile_async_drain( ctx );
    1026             : 
    1027           0 :   if( FD_UNLIKELY( fd_netdev_tbl_refresh( &ctx->router.netdev_tbl, &ctx->router.netdev_shared, &ctx->netdev_seq ) ) ) {
    1028           0 :     fd_net_gre_tunnels_refresh( &ctx->net, &ctx->router.netdev_tbl );
    1029           0 :   }
    1030           0 : }
    1031             : 
    1032             : static uint
    1033           0 : fd_mlx5_tile_if_ip4_addr( char const * if_name ) {
    1034           0 :   int sock_fd = socket( AF_INET, SOCK_DGRAM, 0 );
    1035           0 :   if( FD_UNLIKELY( sock_fd<0 ) ) {
    1036           0 :     FD_LOG_ERR(( "socket(AF_INET,SOCK_DGRAM) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1037           0 :   }
    1038             : 
    1039           0 :   struct ifreq ifr = { .ifr_addr.sa_family = AF_INET };
    1040           0 :   fd_cstr_ncpy( ifr.ifr_name, if_name, sizeof(ifr.ifr_name) );
    1041             : 
    1042           0 :   if( FD_UNLIKELY( ioctl( sock_fd, SIOCGIFADDR, &ifr ) ) ) {
    1043           0 :     FD_LOG_ERR(( "could not get IP address of interface `%s` (%i-%s)",
    1044           0 :                  if_name, errno, fd_io_strerror( errno ) ));
    1045           0 :   }
    1046           0 :   uint ip4_addr = ((struct sockaddr_in *)fd_type_pun( &ifr.ifr_addr ))->sin_addr.s_addr;
    1047           0 :   if( FD_UNLIKELY( close( sock_fd ) ) ) {
    1048           0 :     FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1049           0 :   }
    1050             : 
    1051           0 :   return ip4_addr;
    1052           0 : }
    1053             : 
    1054             : /* fd_mlx5_tile_lo_tx_socket uses IPPROTO_RAW for send only IP_HDRINCL
    1055             :    transmission, preserving the source address and UDP source port. */
    1056             : static int
    1057           0 : fd_mlx5_tile_lo_tx_socket( void ) {
    1058           0 :   int sock = socket( AF_INET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_RAW );
    1059           0 :   if( FD_UNLIKELY( sock<0 ) ) {
    1060           0 :     FD_LOG_ERR(( "socket(AF_INET,SOCK_RAW,IPPROTO_RAW) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1061           0 :   }
    1062           0 :   if( FD_UNLIKELY( 0!=setsockopt( sock, SOL_SOCKET, SO_BINDTODEVICE, "lo", sizeof("lo") ) ) ) {
    1063           0 :     FD_LOG_ERR(( "setsockopt(SO_BINDTODEVICE,lo) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1064           0 :   }
    1065           0 :   return sock;
    1066           0 : }
    1067             : 
    1068             : static ulong
    1069           0 : scratch_align( void ) {
    1070           0 :   ulong a = alignof( fd_mlx5_tile_t );
    1071           0 :   a = fd_ulong_max( a, FD_MLX5_PAGE_SZ );
    1072           0 :   a = fd_ulong_max( a, fd_netdev_tbl_align() );
    1073           0 :   a = fd_ulong_max( a, fd_fib4_align() );
    1074           0 :   return a;
    1075           0 : }
    1076             : 
    1077             : static ulong
    1078           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
    1079           0 :   ulong layout = FD_LAYOUT_INIT;
    1080           0 :   ulong const queue_footprint = fd_mlx5_queue_footprint( tile->mlx5.rx_queue_size,
    1081           0 :                                                          tile->mlx5.tx_queue_size );
    1082           0 :   if( FD_UNLIKELY( !queue_footprint ) ) return 0UL;
    1083             : 
    1084           0 :   layout = FD_LAYOUT_APPEND( layout, alignof(fd_mlx5_tile_t), sizeof(fd_mlx5_tile_t)                                               );
    1085           0 :   layout = FD_LAYOUT_APPEND( layout, FD_MLX5_PAGE_SZ,         queue_footprint                                                      );
    1086           0 :   layout = FD_LAYOUT_APPEND( layout, alignof(uint),           tile->mlx5.tx_queue_size*sizeof(uint)                                );
    1087           0 :   layout = FD_LAYOUT_APPEND( layout, fd_netdev_tbl_align(),   fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX )               );
    1088           0 :   layout = FD_LAYOUT_APPEND( layout, fd_fib4_align(),         fd_fib4_footprint( tile->mlx5.route_max, tile->mlx5.route_peer_max ) );
    1089           0 :   layout = FD_LAYOUT_APPEND( layout, fd_fib4_align(),         fd_fib4_footprint( tile->mlx5.route_max, tile->mlx5.route_peer_max ) );
    1090             : 
    1091           0 :   return FD_LAYOUT_FINI( layout, scratch_align() );
    1092           0 : }
    1093             : 
    1094             : fd_fib4_t *
    1095             : fd_mlx5_tile_fib4_join( fd_fib4_t *            out,
    1096             :                         fd_topo_t const *      topo,
    1097             :                         fd_topo_tile_t const * tile,
    1098           0 :                         int                    main_table ) {
    1099           0 :   FD_SCRATCH_ALLOC_INIT( scratch, fd_topo_obj_laddr( topo, tile->tile_obj_id ) );
    1100           0 :   (void)FD_SCRATCH_ALLOC_APPEND( scratch, alignof(fd_mlx5_tile_t), sizeof(fd_mlx5_tile_t) );
    1101           0 :   (void)FD_SCRATCH_ALLOC_APPEND( scratch, FD_MLX5_PAGE_SZ,         fd_mlx5_queue_footprint( tile->mlx5.rx_queue_size, tile->mlx5.tx_queue_size ) );
    1102           0 :   (void)FD_SCRATCH_ALLOC_APPEND( scratch, alignof(uint),           tile->mlx5.tx_queue_size*sizeof(uint) );
    1103           0 :   (void)FD_SCRATCH_ALLOC_APPEND( scratch, fd_netdev_tbl_align(),   fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX ) );
    1104           0 :   void * local_mem = FD_SCRATCH_ALLOC_APPEND( scratch, fd_fib4_align(), fd_fib4_footprint( tile->mlx5.route_max, tile->mlx5.route_peer_max ) );
    1105           0 :   void * main_mem  = FD_SCRATCH_ALLOC_APPEND( scratch, fd_fib4_align(), fd_fib4_footprint( tile->mlx5.route_max, tile->mlx5.route_peer_max ) );
    1106           0 :   return fd_fib4_join( out, main_table ? main_mem : local_mem );
    1107           0 : }
    1108             : 
    1109             : static void
    1110             : fd_mlx5_tile_packet_memory( fd_topo_t const *      topo,
    1111             :                             fd_topo_tile_t const * tile,
    1112             :                             fd_mlx5_tile_t *       ctx,
    1113             :                             void **                packet_memory,
    1114             :                             ulong *                packet_memory_sz,
    1115           0 :                             ulong *                packet_iova ) {
    1116           0 :   void * const packet_dcache_memory = fd_topo_obj_laddr( topo, tile->net.umem_dcache_obj_id );
    1117           0 :   void * const packet_dcache        = fd_dcache_join( packet_dcache_memory );
    1118           0 :   if( FD_UNLIKELY( !packet_dcache ) ) FD_LOG_ERR(( "Failed to join packet dcache" ));
    1119           0 :   ulong const packet_dcache_data_sz = fd_dcache_data_sz( packet_dcache );
    1120           0 :   ulong const frame_sz              = FD_NET_MTU;
    1121           0 :   ulong const frame_region_sz       = fd_ulong_align_dn( packet_dcache_data_sz, frame_sz );
    1122             : 
    1123           0 :   void * const workspace_base = fd_wksp_containing( packet_dcache_memory );
    1124           0 :   if( FD_UNLIKELY( !workspace_base ) ) FD_LOG_ERR(( "Packet dcache is not in a workspace" ));
    1125           0 :   ulong const pkt_buf_chunk0  = ((ulong)packet_dcache-(ulong)workspace_base)>>FD_CHUNK_LG_SZ;
    1126           0 :   ulong const pkt_buf_wmark   = pkt_buf_chunk0 + ((frame_region_sz-frame_sz)>>FD_CHUNK_LG_SZ);
    1127           0 :   if( FD_UNLIKELY( pkt_buf_chunk0>UINT_MAX || pkt_buf_wmark>UINT_MAX || pkt_buf_chunk0>pkt_buf_wmark ) ) {
    1128           0 :     FD_LOG_ERR(( "Calculated invalid packet buffer bounds [%lu,%lu]", pkt_buf_chunk0, pkt_buf_wmark ));
    1129           0 :   }
    1130             : 
    1131           0 :   ctx->net.pkt_buf_wksp_base = workspace_base;
    1132           0 :   ctx->net.pkt_buf_chunk0    = pkt_buf_chunk0;
    1133           0 :   ctx->net.pkt_buf_wmark     = pkt_buf_wmark;
    1134           0 :   if( packet_memory    ) *packet_memory    = packet_dcache;
    1135           0 :   if( packet_memory_sz ) *packet_memory_sz = frame_region_sz;
    1136           0 :   if( packet_iova      ) *packet_iova      = (ulong)packet_dcache-(ulong)workspace_base;
    1137           0 : }
    1138             : 
    1139             : FD_FN_UNUSED static void
    1140             : fd_mlx5_tile_join_obj_workspace( fd_topo_t * topo,
    1141           0 :                                  ulong       obj_id ) {
    1142           0 :   fd_topo_wksp_t * wksp = &topo->workspaces[ topo->objs[ obj_id ].wksp_id ];
    1143           0 :   if( FD_LIKELY( !wksp->wksp ) ) {
    1144           0 :     fd_topo_join_workspace( topo, wksp, FD_SHMEM_JOIN_MODE_READ_WRITE, 0 );
    1145           0 :   }
    1146           0 : }
    1147             : 
    1148             : #ifndef FD_TILE_TEST
    1149             : void
    1150             : fd_topo_install_mlx5( fd_topo_t *     topo,
    1151           0 :                       fd_mlx5_fds_t * fds ) {
    1152           0 :   ulong const tile_cnt = fd_topo_tile_name_cnt( topo, "mlx5" );
    1153           0 :   if( FD_UNLIKELY( 0==tile_cnt || tile_cnt>FD_MLX5_TILE_MAX ) ) {
    1154           0 :     FD_LOG_ERR(( "mlx5 tile count %lu must be in [1,%lu]", tile_cnt, FD_MLX5_TILE_MAX ));
    1155           0 :   }
    1156             : 
    1157           0 :   fd_mlx5_tile_t *       ctxs  [ FD_MLX5_TILE_MAX ];
    1158           0 :   fd_mlx5_uverbs_tile_t  queues[ FD_MLX5_TILE_MAX ];
    1159           0 :   fd_topo_tile_t const * first_tile = NULL;
    1160           0 :   char                   rdma_device_name[ FD_MLX5_RDMA_NAME_MAX ];
    1161           0 :   uint                   rdma_port_num = 0U;
    1162             : 
    1163           0 :   for( ulong i=0UL; i<tile_cnt; i++ ) {
    1164           0 :     ulong const tile_id = fd_topo_find_tile( topo, "mlx5", i );
    1165           0 :     FD_TEST( tile_id!=ULONG_MAX );
    1166           0 :     fd_topo_tile_t const * tile = topo->tiles+tile_id;
    1167           0 :     fd_mlx5_tile_join_obj_workspace( topo, tile->tile_obj_id            );
    1168           0 :     fd_mlx5_tile_join_obj_workspace( topo, tile->net.umem_dcache_obj_id );
    1169             : 
    1170           0 :     FD_SCRATCH_ALLOC_INIT( scratch, fd_topo_obj_laddr( topo, tile->tile_obj_id ) );
    1171           0 :     fd_mlx5_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( scratch, alignof(fd_mlx5_tile_t), sizeof(fd_mlx5_tile_t) );
    1172           0 :     ulong const queue_memory_sz = fd_mlx5_queue_footprint( tile->mlx5.rx_queue_size, tile->mlx5.tx_queue_size );
    1173           0 :     void * queue_memory = FD_SCRATCH_ALLOC_APPEND( scratch, FD_MLX5_PAGE_SZ, queue_memory_sz );
    1174           0 :     ctxs[ i ] = ctx;
    1175           0 :     fd_memset( ctx, 0, sizeof(*ctx) );
    1176           0 :     ctx->rx_comp_channel_fd = -1;
    1177           0 :     ctx->epoll_fd           = -1;
    1178           0 :     FD_TEST( fd_mlx5_hw_init_queues( ctx, queue_memory,
    1179           0 :                                      tile->mlx5.rx_queue_size,
    1180           0 :                                      tile->mlx5.tx_queue_size ) );
    1181             : 
    1182           0 :     void * packet_memory;
    1183           0 :     ulong  packet_memory_sz;
    1184           0 :     ulong  packet_iova;
    1185           0 :     fd_mlx5_tile_packet_memory( topo, tile, ctx, &packet_memory, &packet_memory_sz, &packet_iova );
    1186           0 :     queues[ i ] = (fd_mlx5_uverbs_tile_t) {
    1187           0 :       .rx_cq              = &ctx->rx_cq,
    1188           0 :       .tx_cq              = &ctx->tx_cq,
    1189           0 :       .rx_wq              = &ctx->rx_wq,
    1190           0 :       .tx_qp              = &ctx->tx_qp,
    1191           0 :       .lkey               = &ctx->lkey,
    1192           0 :       .rx_comp_channel_fd = tile->sleep_eventfd ? &ctx->rx_comp_channel_fd : NULL,
    1193           0 :       .packet_memory      = packet_memory,
    1194           0 :       .packet_memory_sz   = packet_memory_sz,
    1195           0 :       .packet_iova        = packet_iova,
    1196           0 :     };
    1197           0 :     fd_net_rx_dst_ports_init( &ctx->net, topo, tile );
    1198             : 
    1199           0 :     if( !i ) {
    1200           0 :       first_tile = tile;
    1201           0 :       if( FD_UNLIKELY( !fd_mlx5_rdma_dev_find( rdma_device_name, &rdma_port_num, tile->mlx5.if_name ) ) ) {
    1202           0 :         if( errno==EEXIST ) FD_LOG_ERR(( "multiple RDMA ports match interface `%s`", tile->mlx5.if_name ));
    1203           0 :         if( errno==ENOENT ) FD_LOG_ERR(( "RDMA device port for interface `%s` not found", tile->mlx5.if_name ));
    1204           0 :         FD_LOG_ERR(( "finding RDMA device port for interface `%s` failed (%i-%s)",
    1205           0 :                      tile->mlx5.if_name, errno, fd_io_strerror( errno ) ));
    1206           0 :       }
    1207           0 :     }
    1208           0 :   }
    1209             : 
    1210           0 :   fd_mlx5_tile_t * first = ctxs[ 0 ];
    1211           0 :   if( FD_UNLIKELY( !fd_mlx5_uverbs_avail() ) ) {
    1212           0 :     FD_LOG_ERR(( "failed to run mlx5 tile: uverbs interface unavailable. "
    1213           0 :                  "Run `sudo modprobe ib_uverbs` and retry. "
    1214           0 :                  "If it still fails, please set [net.provider] to `xdp`" ));
    1215           0 :   }
    1216           0 :   FD_LOG_INFO(( "Opening direct mlx5 device `%s` port %u for %lu tiles",
    1217           0 :                 rdma_device_name, rdma_port_num, tile_cnt ));
    1218           0 :   if( FD_UNLIKELY( !fd_uverbs_init( &first->uverbs, queues, tile_cnt,
    1219           0 :                                     &first->outer_rss_qp, &first->gre_rss_qp,
    1220           0 :                                     rdma_device_name, rdma_port_num ) ) ) {
    1221           0 :     FD_LOG_ERR(( "direct mlx5 setup failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1222           0 :   }
    1223             : 
    1224           0 :   int const async_event_fd    = first->uverbs.async_fd;
    1225           0 :   int const async_event_flags = fcntl( async_event_fd, F_GETFL );
    1226           0 :   if( FD_UNLIKELY( async_event_flags<0 ||
    1227           0 :                    fcntl( async_event_fd, F_SETFL, async_event_flags|O_NONBLOCK )<0 ) ) {
    1228           0 :     FD_LOG_ERR(( "making mlx5 async fd non-blocking failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1229           0 :   }
    1230             : 
    1231           0 :   for( ulong i=0UL; i<tile_cnt; i++ ) {
    1232           0 :     ctxs[ i ]->uverbs       = first->uverbs;
    1233           0 :     ctxs[ i ]->outer_rss_qp = first->outer_rss_qp;
    1234           0 :     ctxs[ i ]->gre_rss_qp   = first->gre_rss_qp;
    1235           0 :     ctxs[ i ]->prepared     = 1U;
    1236             : 
    1237           0 :     int const rx_comp_channel_fd = ctxs[ i ]->rx_comp_channel_fd;
    1238           0 :     if( rx_comp_channel_fd<0 ) continue;
    1239           0 :     int const rx_comp_channel_fd_flags = fcntl( rx_comp_channel_fd, F_GETFL );
    1240           0 :     if( FD_UNLIKELY( rx_comp_channel_fd_flags<0 ||
    1241           0 :                      fcntl( rx_comp_channel_fd, F_SETFL, rx_comp_channel_fd_flags|O_NONBLOCK )<0 ) ) {
    1242           0 :       FD_LOG_ERR(( "making mlx5 RX completion channel non-blocking failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1243           0 :     }
    1244           0 :   }
    1245             : 
    1246           0 :   for( ulong flow_idx=0UL; flow_idx<first->net.dst_port_cnt; flow_idx++ ) {
    1247           0 :     if( FD_UNLIKELY( fd_uverbs_create_udp_flow( &first->uverbs, &first->outer_rss_qp,
    1248           0 :                                                 first_tile->mlx5.net.bind_address,
    1249           0 :                                                 first->net.dst_ports[ flow_idx ] ) ) ) {
    1250           0 :       FD_LOG_ERR(( "fd_uverbs_create_udp_flow failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1251           0 :     }
    1252           0 :     if( FD_UNLIKELY( fd_uverbs_create_gre_udp_flow( &first->uverbs, &first->gre_rss_qp,
    1253           0 :                                                     first_tile->mlx5.net.bind_address,
    1254           0 :                                                     first->net.dst_ports[ flow_idx ] ) ) ) {
    1255           0 :       FD_LOG_ERR(( "fd_uverbs_create_gre_udp_flow failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1256           0 :     }
    1257           0 :   }
    1258           0 :   FD_LOG_INFO(( "Installed %lu direct mlx5 flow rules", 2UL*first->net.dst_port_cnt ));
    1259             : 
    1260           0 :   if( fds ) {
    1261           0 :     fds->cmd_fd                 = first->uverbs.cmd_fd;
    1262           0 :     fds->async_fd               = first->uverbs.async_fd;
    1263           0 :     fds->rx_comp_channel_fd_cnt = first->rx_comp_channel_fd>=0 ? tile_cnt : 0UL;
    1264           0 :     for( ulong i=0UL; i<fds->rx_comp_channel_fd_cnt; i++ )
    1265           0 :       fds->rx_comp_channel_fd[ i ] = ctxs[ i ]->rx_comp_channel_fd;
    1266           0 :   }
    1267           0 : }
    1268             : #endif
    1269             : 
    1270             : FD_FN_UNUSED static void
    1271             : privileged_init( fd_topo_t const *      topo,
    1272           0 :                  fd_topo_tile_t const * tile ) {
    1273           0 :   FD_SCRATCH_ALLOC_INIT( scratch, fd_topo_obj_laddr( topo, tile->tile_obj_id ) );
    1274           0 :   fd_mlx5_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( scratch, alignof(fd_mlx5_tile_t), sizeof(fd_mlx5_tile_t) );
    1275           0 :   ulong const queue_memory_sz = fd_mlx5_queue_footprint( tile->mlx5.rx_queue_size, tile->mlx5.tx_queue_size );
    1276           0 :   void * queue_memory = FD_SCRATCH_ALLOC_APPEND( scratch, FD_MLX5_PAGE_SZ, queue_memory_sz );
    1277           0 :   if( FD_UNLIKELY( !ctx->prepared ) ) {
    1278           0 :     if( FD_UNLIKELY( fd_topo_tile_name_cnt( topo, "mlx5" )!=1UL ) ) {
    1279           0 :       FD_LOG_ERR(( "multi-tile mlx5 setup must run before tile launch" ));
    1280           0 :     }
    1281           0 :     fd_topo_install_mlx5( (fd_topo_t *)topo, NULL );
    1282           0 :   }
    1283           0 :   FD_TEST( fd_mlx5_hw_join_queues( ctx, queue_memory,
    1284           0 :                                    tile->mlx5.rx_queue_size,
    1285           0 :                                    tile->mlx5.tx_queue_size ) );
    1286           0 :   volatile uchar * uar = fd_uverbs_map_uar( &ctx->uverbs, ctx->tx_qp.uar_mmap_offset );
    1287           0 :   if( FD_UNLIKELY( !uar ) ) {
    1288           0 :     FD_LOG_ERR(( "mapping mlx5 UAR failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1289           0 :   }
    1290           0 :   ctx->tx_qp.sq_doorbell                   = uar+FD_MLX5_UAR_SQ_DB_OFFSET;
    1291           0 :   ctx->rx_cq.request_notification_doorbell = uar+FD_MLX5_UAR_CQ_DB_OFFSET;
    1292             : 
    1293           0 :   ctx->batch_size       = tile->mlx5.batch_size;
    1294           0 :   ctx->sq_wqe_buf_chunk = FD_SCRATCH_ALLOC_APPEND( scratch, alignof(uint),
    1295           0 :                                                    tile->mlx5.tx_queue_size*sizeof(uint) );
    1296             : 
    1297           0 :   fd_mlx5_tile_packet_memory( topo, tile, ctx, NULL, NULL, NULL );
    1298             : 
    1299             :   /* Resolve the netdev. */
    1300           0 :   uint const interface_idx = if_nametoindex( tile->mlx5.if_name );
    1301           0 :   if( FD_UNLIKELY( !interface_idx ) ) {
    1302           0 :     FD_LOG_ERR(( "if_nametoindex(%s) failed (%i-%s)",
    1303           0 :                  tile->mlx5.if_name, errno, fd_io_strerror( errno ) ));
    1304           0 :   }
    1305           0 :   ctx->router.if_virt         = interface_idx;
    1306           0 :   ctx->router.default_address = fd_mlx5_tile_if_ip4_addr( tile->mlx5.if_name );
    1307           0 :   ctx->lo_tx_sock = tile->kind_id==0UL ? fd_mlx5_tile_lo_tx_socket() : -1;
    1308             : 
    1309           0 :   if( FD_UNLIKELY( tile->sleep_eventfd ) ) {
    1310           0 :     if( FD_UNLIKELY( ctx->rx_comp_channel_fd<0 ) ) {
    1311           0 :       FD_LOG_ERR(( "mlx5:%lu has no RX completion channel", tile->kind_id ));
    1312           0 :     }
    1313           0 :     ctx->epoll_fd = epoll_create1( EPOLL_CLOEXEC );
    1314           0 :     if( FD_UNLIKELY( ctx->epoll_fd<0 ) ) {
    1315           0 :       FD_LOG_ERR(( "epoll_create1() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1316           0 :     }
    1317           0 :     struct epoll_event doorbell_ev = { .events = EPOLLIN|EPOLLET, .data.u64 = FD_SLEEP_EPOLL_DOORBELL };
    1318           0 :     if( FD_UNLIKELY( -1==epoll_ctl( ctx->epoll_fd, EPOLL_CTL_ADD, FD_SLEEP_EVENTFD( tile->id ), &doorbell_ev ) ) ) {
    1319           0 :       FD_LOG_ERR(( "epoll_ctl(ADD,sleep_eventfd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1320           0 :     }
    1321           0 :     struct epoll_event comp_ev = { .events = EPOLLIN, .data.u64 = (ulong)ctx->rx_comp_channel_fd };
    1322           0 :     if( FD_UNLIKELY( -1==epoll_ctl( ctx->epoll_fd, EPOLL_CTL_ADD, ctx->rx_comp_channel_fd, &comp_ev ) ) ) {
    1323           0 :       FD_LOG_ERR(( "epoll_ctl(ADD,rx_comp_channel_fd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1324           0 :     }
    1325           0 :     struct epoll_event async_ev = { .events = EPOLLIN, .data.u64 = FD_MLX5_EPOLL_ASYNC };
    1326           0 :     if( FD_UNLIKELY( -1==epoll_ctl( ctx->epoll_fd, EPOLL_CTL_ADD, ctx->uverbs.async_fd, &async_ev ) ) ) {
    1327           0 :       FD_LOG_ERR(( "epoll_ctl(ADD,async_fd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1328           0 :     }
    1329           0 :   }
    1330           0 : }
    1331             : 
    1332             : FD_FN_UNUSED static void
    1333             : unprivileged_init( fd_topo_t const *      topo,
    1334           0 :                    fd_topo_tile_t const * tile ) {
    1335           0 :   FD_SCRATCH_ALLOC_INIT( scratch, fd_topo_obj_laddr( topo, tile->tile_obj_id ) );
    1336           0 :   fd_mlx5_tile_t * ctx  = FD_SCRATCH_ALLOC_APPEND( scratch, alignof(fd_mlx5_tile_t), sizeof(fd_mlx5_tile_t) );
    1337           0 :   ulong queue_footprint = fd_mlx5_queue_footprint( tile->mlx5.rx_queue_size, tile->mlx5.tx_queue_size );
    1338           0 :   (void)FD_SCRATCH_ALLOC_APPEND( scratch, FD_MLX5_PAGE_SZ, queue_footprint );
    1339             : 
    1340           0 :   ctx->sq_wqe_buf_chunk       = FD_SCRATCH_ALLOC_APPEND( scratch, alignof(uint), tile->mlx5.tx_queue_size*sizeof(uint) );
    1341           0 :   ctx->batch_size             = tile->mlx5.batch_size;
    1342           0 :   ctx->sq_flush_timeout_ticks = (long)( FD_MLX5_TX_FLUSH_TIMEOUT_NS*fd_tempo_tick_per_ns( NULL ) );
    1343           0 :   ctx->lo_tx_timeout_ticks    = (long)( FD_MLX5_LO_TX_TIMEOUT_NS*fd_tempo_tick_per_ns( NULL ) );
    1344             : 
    1345           0 :   ctx->repoll_timeout_ticks   = (long)( FD_MLX5_REPOLL_TIMEOUT_NS*fd_tempo_tick_per_ns( NULL ) );
    1346           0 :   ctx->repoll_deadline_ticks  = LONG_MAX;
    1347             : 
    1348           0 :   ctx->lo_tx_cnt              = 0U;
    1349           0 :   ctx->net.kind_id            = tile->kind_id;
    1350           0 :   ctx->net.tile_cnt           = fd_topo_tile_name_cnt( topo, tile->name );
    1351             : 
    1352           0 :   void * netdev_tbl_local = FD_SCRATCH_ALLOC_APPEND( scratch, fd_netdev_tbl_align(), fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX ) );
    1353           0 :   void * fib_local_mem    = FD_SCRATCH_ALLOC_APPEND( scratch, fd_fib4_align(), fd_fib4_footprint( tile->mlx5.route_max, tile->mlx5.route_peer_max ) );
    1354           0 :   void * fib_main_mem     = FD_SCRATCH_ALLOC_APPEND( scratch, fd_fib4_align(), fd_fib4_footprint( tile->mlx5.route_max, tile->mlx5.route_peer_max ) );
    1355             : 
    1356             :   /* chunk 0 is used as a sentinel value, so ensure actual chunk indices
    1357             :      do not use that value. */
    1358           0 :   FD_TEST( ctx->net.pkt_buf_chunk0>0UL );
    1359             : 
    1360           0 :   ctx->rq_pending_cnt = 0U;
    1361             : 
    1362             :   /* Post RQ WQEs */
    1363           0 :   ulong frame_chunks = FD_NET_MTU>>FD_CHUNK_LG_SZ;
    1364           0 :   ulong next_chunk   = ctx->net.pkt_buf_chunk0;
    1365             : 
    1366           0 :   FD_TEST( tile->mlx5.rx_queue_size>ctx->batch_size );
    1367             : 
    1368           0 :   ulong const rx_fill_cnt = tile->mlx5.rx_queue_size-ctx->batch_size;
    1369           0 :   for( ulong i=0UL; i<rx_fill_cnt; i++ ) {
    1370           0 :     fd_mlx5_tile_rx_recycle( ctx, next_chunk );
    1371           0 :     next_chunk += frame_chunks;
    1372           0 :   }
    1373             : 
    1374             :   /* Assign chunks to RX mcaches */
    1375           0 :   for( ulong i=0UL; i<(tile->out_cnt); i++ ) {
    1376           0 :     fd_frag_meta_t * mcache = topo->links[ tile->out_link_id[ i ] ].mcache;
    1377           0 :     ulong const      depth  = fd_mcache_depth( mcache );
    1378           0 :     for( ulong j=0UL; j<depth; j++ ) {
    1379           0 :       mcache[ j ].chunk = (uint)next_chunk;
    1380           0 :       mcache[ j ].seq   = fd_seq_dec( j, 1UL ); /* mark seq as invalid */
    1381           0 :       next_chunk += frame_chunks;
    1382           0 :     }
    1383           0 :   }
    1384             :   /* Assign one TX buffer to each SQ WQE */
    1385           0 :   for( ulong i=0UL; i<tile->mlx5.tx_queue_size; i++ ) {
    1386           0 :     ctx->sq_wqe_buf_chunk[ i ] = (uint)next_chunk;
    1387           0 :     next_chunk += frame_chunks;
    1388           0 :   }
    1389             :   /* Init TX */
    1390           0 :   if( FD_UNLIKELY( tile->in_cnt>FD_NET_IN_MAX ) ) {
    1391           0 :     FD_LOG_ERR(( "mlx5 tile in link count %lu exceeds max of %lu", tile->in_cnt, FD_NET_IN_MAX ));
    1392           0 :   }
    1393           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    1394           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    1395           0 :     if( !strcmp( link->name, "iproute_out" ) ) {
    1396           0 :       ctx->net.in_kind[ i ] = FD_NET_IN_KIND_IPROUTE;
    1397           0 :     } else {
    1398           0 :       ctx->net.in_kind[ i ] = FD_NET_IN_KIND_TX;
    1399           0 :       if( FD_UNLIKELY( link->mtu!=FD_NET_MTU ) ) FD_LOG_ERR(( "mlx5 tile in link does not have a normal MTU" ));
    1400           0 :     }
    1401             : 
    1402           0 :     ctx->net.in_dcache_ctx[ i ].wksp_base = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    1403           0 :     ctx->net.in_dcache_ctx[ i ].chunk0    = fd_dcache_compact_chunk0( ctx->net.in_dcache_ctx[ i ].wksp_base, link->dcache );
    1404           0 :     ctx->net.in_dcache_ctx[ i ].wmark     = fd_dcache_compact_wmark(  ctx->net.in_dcache_ctx[ i ].wksp_base, link->dcache, link->mtu );
    1405           0 :   }
    1406             : 
    1407             :   /* Join netbase objects */
    1408           0 :   FD_TEST( fd_fib4_join( ctx->router.fib_local, fd_fib4_new( fib_local_mem, tile->mlx5.route_max, tile->mlx5.route_peer_max, tile->mlx5.route_peer_seed ) ) );
    1409           0 :   FD_TEST( fd_fib4_join( ctx->router.fib_main,  fd_fib4_new( fib_main_mem,  tile->mlx5.route_max, tile->mlx5.route_peer_max, tile->mlx5.route_peer_seed ) ) );
    1410           0 :   FD_TEST( fd_netdev_tbl_join( &ctx->router.netdev_shared, fd_topo_obj_laddr( topo, tile->mlx5.netdev_tbl_obj_id ) )                                        );
    1411           0 :   FD_TEST( fd_netdev_tbl_new( netdev_tbl_local, NETDEV_MAX, BOND_MASTER_MAX )                                                                               );
    1412           0 :   FD_TEST( fd_netdev_tbl_join( &ctx->router.netdev_tbl, netdev_tbl_local )                                                                                  );
    1413             : 
    1414           0 :   ctx->netdev_seq = fd_netdev_tbl_copy( &ctx->router.netdev_tbl, &ctx->router.netdev_shared );
    1415           0 :   fd_net_gre_tunnels_refresh( &ctx->net, &ctx->router.netdev_tbl );
    1416           0 :   ctx->router.bind_address = tile->mlx5.net.bind_address;
    1417           0 :   ctx->net.bind_address    = tile->mlx5.net.bind_address;
    1418             : 
    1419           0 :   ulong neigh4_obj_id = tile->mlx5.neigh4_obj_id;
    1420           0 :   ulong ele_max   = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.ele_max",   neigh4_obj_id );
    1421           0 :   ulong probe_max = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.probe_max", neigh4_obj_id );
    1422           0 :   ulong seed      = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.seed",      neigh4_obj_id );
    1423           0 :   if( FD_UNLIKELY( (ele_max==ULONG_MAX) | (probe_max==ULONG_MAX) | (seed==ULONG_MAX) ) ) {
    1424           0 :     FD_LOG_ERR(( "neigh4 hmap properties not set" ));
    1425           0 :   }
    1426           0 :   if( FD_UNLIKELY( !fd_neigh4_hmap_join( ctx->router.neigh4, fd_topo_obj_laddr( topo, neigh4_obj_id ), ele_max, probe_max, seed ) ) ) {
    1427           0 :     FD_LOG_ERR(( "fd_neigh4_hmap_join failed" ));
    1428           0 :   }
    1429             : 
    1430           0 :   ctx->router.netlnk_out_idx = fd_topo_find_tile_out_link( topo, tile, "net_netlnk", tile->kind_id );
    1431           0 :   if( FD_UNLIKELY( ctx->router.netlnk_out_idx==ULONG_MAX ) ) FD_LOG_ERR(( "netlink request link not found" ));
    1432           0 :   ctx->router.solicit_ip = 0U;
    1433             : 
    1434             :   /* Check if all chunks are in bound */
    1435           0 :   if( FD_UNLIKELY( next_chunk>ctx->net.pkt_buf_wmark ) ) {
    1436           0 :     FD_LOG_ERR(( "dcache is too small (topology bug)" ));
    1437           0 :   }
    1438             : 
    1439           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( scratch, scratch_align() );
    1440           0 :   if( FD_UNLIKELY( scratch_top>(ulong)ctx+scratch_footprint( tile ) ) ) {
    1441           0 :     FD_LOG_ERR(( "scratch overflow" ));
    1442           0 :   }
    1443           0 : }
    1444             : 
    1445             : FD_FN_UNUSED static ulong
    1446             : populate_allowed_seccomp( fd_topo_t const *      topo,
    1447             :                           fd_topo_tile_t const * tile,
    1448             :                           ulong                  out_cnt,
    1449           0 :                           struct sock_filter *   out ) {
    1450           0 :   fd_mlx5_tile_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1451           0 :   populate_sock_filter_policy_fd_mlx5_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(),
    1452           0 :                                             (uint)ctx->uverbs.async_fd, (uint)ctx->lo_tx_sock,
    1453           0 :                                             (uint)ctx->rx_comp_channel_fd, (uint)ctx->epoll_fd );
    1454           0 :   return sock_filter_policy_fd_mlx5_tile_instr_cnt;
    1455           0 : }
    1456             : 
    1457             : FD_FN_UNUSED static ulong
    1458             : populate_allowed_fds( fd_topo_t const *      topo,
    1459             :                       fd_topo_tile_t const * tile,
    1460             :                       ulong                  out_fds_cnt,
    1461           0 :                       int *                  out_fds ) {
    1462           0 :   fd_mlx5_tile_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1463           0 :   if( FD_UNLIKELY( out_fds_cnt<8UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    1464           0 :   ulong out_cnt = 0UL;
    1465           0 :   out_fds[ out_cnt++ ] = 2;
    1466           0 :   if( FD_LIKELY( fd_log_private_logfile_fd()!=-1 ) ) out_fds[ out_cnt++ ] = fd_log_private_logfile_fd();
    1467           0 :   out_fds[ out_cnt++ ] = ctx->uverbs.cmd_fd;
    1468           0 :   out_fds[ out_cnt++ ] = ctx->uverbs.async_fd;
    1469           0 :   if( ctx->lo_tx_sock>=0 ) out_fds[ out_cnt++ ] = ctx->lo_tx_sock;
    1470           0 :   if( FD_UNLIKELY( tile->sleep_eventfd ) ) {
    1471           0 :     out_fds[ out_cnt++ ] = ctx->rx_comp_channel_fd;
    1472           0 :     out_fds[ out_cnt++ ] = ctx->epoll_fd;
    1473           0 :     out_fds[ out_cnt++ ] = FD_SLEEP_EVENTFD( tile->id );
    1474           0 :   }
    1475           0 :   return out_cnt;
    1476           0 : }
    1477             : 
    1478           0 : #define STEM_CALLBACK_CONTEXT_TYPE        fd_mlx5_tile_t
    1479           0 : #define STEM_CALLBACK_CONTEXT_ALIGN       alignof(fd_mlx5_tile_t)
    1480           0 : #define STEM_CALLBACK_BEFORE_CREDIT       before_credit
    1481           0 : #define STEM_CALLBACK_AFTER_CREDIT        after_credit
    1482           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    1483           0 : #define STEM_CALLBACK_DURING_FRAG         during_frag
    1484           0 : #define STEM_CALLBACK_AFTER_FRAG          after_frag
    1485           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    1486           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    1487             : #define STEM_CALLBACK_PREVENT_PARK        prevent_park
    1488           0 : #define STEM_CALLBACK_PARK_WAIT           park_wait
    1489           0 : #define STEM_CALLBACK_NEXT_DEADLINE       next_deadline
    1490           0 : #define STEM_BURST                        (FD_MLX5_BATCH_SIZE+1) /* +1 accounts for possible LB TX packet */
    1491           0 : #define STEM_LAZY                         270000UL /* 270us */
    1492             : #include "../../stem/fd_stem.c"
    1493             : 
    1494             : #ifndef FD_TILE_TEST
    1495             : fd_topo_run_tile_t fd_tile_mlx5 = {
    1496             :   .name                     = "mlx5",
    1497             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1498             :   .populate_allowed_fds     = populate_allowed_fds,
    1499             :   .scratch_align            = scratch_align,
    1500             :   .scratch_footprint        = scratch_footprint,
    1501             :   .privileged_init          = privileged_init,
    1502             :   .unprivileged_init        = unprivileged_init,
    1503             :   .run                      = stem_run,
    1504             : };
    1505             : #endif

Generated by: LCOV version 1.14