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

Generated by: LCOV version 1.14