LCOV - code coverage report
Current view: top level - disco/net/iavf - fd_iavf.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 491 1504 32.6 %
Date: 2026-11-12 04:25:03 Functions: 31 93 33.3 %

          Line data    Source code
       1             : #define _GNU_SOURCE /* strcasestr */
       2             : #include "fd_iavf_private.h"
       3             : #include "../../../util/fd_util.h"
       4             : #include "../../../util/net/fd_eth.h"
       5             : #include "../fd_linux_bond.h"
       6             : 
       7             : #if FD_HAS_X86
       8             : #include <cpuid.h>
       9             : #include <sys/utsname.h>
      10             : #endif
      11             : #include <ctype.h>
      12             : #include <dirent.h>
      13             : #include <errno.h>
      14             : #include <stddef.h>
      15             : #include <fcntl.h>
      16             : #include <limits.h>
      17             : #include <stdio.h>
      18             : #include <stdlib.h>
      19             : #include <string.h>
      20             : #include <sys/ioctl.h>
      21             : #include <sys/mman.h>
      22             : #include <sys/stat.h>
      23             : #include <time.h>
      24             : #include <unistd.h>
      25             : 
      26             : #include <linux/vfio.h>
      27             : #include <linux/pci_regs.h>
      28             : 
      29           0 : #define FD_IAVF_PCI_SYSFS "/sys/bus/pci/devices"
      30             : 
      31             : #define FD_IAVF_PCI_RESOURCE_MEM      (0x00000200UL) /* IORESOURCE_MEM */
      32             : #define FD_IAVF_PCI_RESOURCE_UNUSABLE (0x30000000UL) /* IORESOURCE_UNSET | IORESOURCE_DISABLED */
      33             : 
      34             : /* A VF reset reinitializes the NIC state for one virtual PCI device.
      35             :    The PF reports here when the VF is ready for Admin Queue setup. */
      36          21 : #define FD_IAVF_VFGEN_RSTAT         (0x8800UL) /* IAVF_VFGEN_RSTAT */
      37          21 : #define FD_IAVF_VFGEN_RSTAT_STATE   (0x3U)     /* IAVF_VFGEN_RSTAT_VFR_STATE_MASK */
      38           0 : #define FD_IAVF_VFR_STATE_COMPLETED (1U)       /* VIRTCHNL_VFR_COMPLETED */
      39           0 : #define FD_IAVF_VFR_STATE_ACTIVE    (2U)       /* VIRTCHNL_VFR_VFACTIVE */
      40             : 
      41           0 : #define FD_IAVF_VFINT_DYN_CTL        (0x3800UL) /* IAVF_VFINT_DYN_CTLN1(0) */
      42           0 : #define FD_IAVF_VFINT_WB_ON_ITR      (1U<<30)   /* IAVF_VFINT_DYN_CTLN1_WB_ON_ITR_MASK */
      43           0 : #define FD_IAVF_VFINT_INTERVAL_SHIFT (5U)       /* IAVF_VFINT_DYN_CTLN1_INTERVAL_SHIFT */
      44             : 
      45             : /* The PF driver controls the VF's packet queues and receive filters.
      46             :    Admin Queues carry virtchnl requests to it through ATQ and replies or
      47             :    unsolicited events back through ARQ. */
      48           0 : #define FD_IAVF_VF_ARQBAH (0x6000UL) /* IAVF_VF_ARQBAH1 */
      49           0 : #define FD_IAVF_VF_ARQBAL (0x6c00UL) /* IAVF_VF_ARQBAL1 */
      50         666 : #define FD_IAVF_VF_ARQH   (0x7400UL) /* IAVF_VF_ARQH1 */
      51           0 : #define FD_IAVF_VF_ARQLEN (0x8000UL) /* IAVF_VF_ARQLEN1 */
      52          48 : #define FD_IAVF_VF_ARQT   (0x7000UL) /* IAVF_VF_ARQT1 */
      53           0 : #define FD_IAVF_VF_ATQBAH (0x7800UL) /* IAVF_VF_ATQBAH1 */
      54           0 : #define FD_IAVF_VF_ATQBAL (0x7c00UL) /* IAVF_VF_ATQBAL1 */
      55          24 : #define FD_IAVF_VF_ATQH   (0x6400UL) /* IAVF_VF_ATQH1 */
      56           0 : #define FD_IAVF_VF_ATQLEN (0x6800UL) /* IAVF_VF_ATQLEN1 */
      57          24 : #define FD_IAVF_VF_ATQT   (0x8400UL) /* IAVF_VF_ATQT1 */
      58           0 : #define FD_IAVF_ATQ_ENABLE (1U<<31) /* IAVF_VF_ATQLEN1_ATQENABLE_MASK */
      59           0 : #define FD_IAVF_ARQ_ENABLE (1U<<31) /* IAVF_VF_ARQLEN1_ARQENABLE_MASK */
      60          24 : #define FD_IAVF_ATQ_HEAD   (0x3ffU) /* IAVF_VF_ATQH1_ATQH_MASK */
      61         666 : #define FD_IAVF_ARQ_HEAD   (0x3ffU) /* IAVF_VF_ARQH1_ARQH_MASK */
      62             : 
      63          60 : #define FD_IAVF_POLL_INTERVAL_NS (1000000L)
      64           0 : #define FD_IAVF_RESET_RETRY_CNT  (10000UL)
      65          51 : #define FD_IAVF_ADMINQ_RETRY_CNT (2000UL)
      66         612 : #define FD_IAVF_LINK_RETRY_CNT   (100UL)
      67             : 
      68             : /* Keep both Admin Queue rings and their message buffers in one DMA mapping.
      69             :    Descriptors point to the buffers containing the virtchnl payloads. */
      70         291 : #define FD_IAVF_ADMINQ_DEPTH         (32UL)
      71         378 : #define FD_IAVF_ADMINQ_BUF_SZ        (4096UL)
      72          24 : #define FD_IAVF_AQ_LARGE_BUF         (512UL) /* IAVF_AQ_LARGE_BUF */
      73          24 : #define FD_IAVF_ADMINQ_SEND_DESC_OFF (0UL)
      74          96 : #define FD_IAVF_ADMINQ_RECV_DESC_OFF (4096UL)
      75         189 : #define FD_IAVF_ADMINQ_SEND_BUF_OFF  (8192UL)
      76         141 : #define FD_IAVF_ADMINQ_RECV_BUF_OFF  (FD_IAVF_ADMINQ_SEND_BUF_OFF + FD_IAVF_ADMINQ_DEPTH*FD_IAVF_ADMINQ_BUF_SZ)
      77          45 : #define FD_IAVF_ADMINQ_FOOTPRINT     (FD_IAVF_ADMINQ_RECV_BUF_OFF + FD_IAVF_ADMINQ_DEPTH*FD_IAVF_ADMINQ_BUF_SZ)
      78             : 
      79             : /* Linux PF validation requires one padding byte after the RSS key or LUT */
      80             : #define FD_IAVF_RSS_KEY_HEADER_SZ (4UL) /* sizeof(struct virtchnl_rss_key) */
      81             : #define FD_IAVF_RSS_KEY_LEGACY_SZ (6UL) /* virtchnl_rss_key_LEGACY_SIZEOF */
      82             : #define FD_IAVF_RSS_KEY_MAX_SZ    (FD_IAVF_ADMINQ_BUF_SZ-FD_IAVF_RSS_KEY_LEGACY_SZ)
      83             : #define FD_IAVF_RSS_LUT_HEADER_SZ (4UL) /* sizeof(struct virtchnl_rss_lut) */
      84           0 : #define FD_IAVF_RSS_LUT_LEGACY_SZ (6UL) /* virtchnl_rss_lut_LEGACY_SIZEOF */
      85             : #define FD_IAVF_RSS_LUT_MAX_SZ    (FD_IAVF_ADMINQ_BUF_SZ-FD_IAVF_RSS_LUT_LEGACY_SZ)
      86             : 
      87           0 : #define FD_IAVF_DEVICE_VF          (0x154cU) /* IAVF_DEV_ID_VF */
      88           0 : #define FD_IAVF_DEVICE_VF_HV       (0x1571U) /* IAVF_DEV_ID_VF_HV */
      89           0 : #define FD_IAVF_DEVICE_ADAPTIVE_VF (0x1889U) /* IAVF_DEV_ID_ADAPTIVE_VF */
      90           0 : #define FD_IAVF_DEVICE_X722_VF     (0x37cdU) /* IAVF_DEV_ID_X722_VF */
      91             : 
      92             : /* fd_iavf_aq_desc is the Linux libie_aq_desc hardware descriptor. */
      93             : struct fd_iavf_aq_desc {
      94             :   ushort flags;
      95             :   ushort opcode;
      96             :   ushort datalen;
      97             :   ushort retval;
      98             :   uint   cookie_high;
      99             :   uint   cookie_low;
     100             :   uint   param0;
     101             :   uint   param1;
     102             :   uint   addr_high;
     103             :   uint   addr_low;
     104             : };
     105             : 
     106             : typedef struct fd_iavf_aq_desc fd_iavf_aq_desc_t;
     107             : 
     108             : FD_STATIC_ASSERT( sizeof(fd_iavf_aq_desc_t)==32UL, iavf_aq_desc_sz );
     109             : 
     110             : #define FD_IAVF_AQ_FLAG_ERR (1U<<2)  /* LIBIE_AQ_FLAG_ERR */
     111          48 : #define FD_IAVF_AQ_FLAG_LB  (1U<<9)  /* LIBIE_AQ_FLAG_LB */
     112          24 : #define FD_IAVF_AQ_FLAG_RD  (1U<<10) /* LIBIE_AQ_FLAG_RD */
     113          72 : #define FD_IAVF_AQ_FLAG_BUF (1U<<12) /* LIBIE_AQ_FLAG_BUF */
     114          24 : #define FD_IAVF_AQ_FLAG_SI  (1U<<13) /* LIBIE_AQ_FLAG_SI */
     115             : 
     116             : /* Admin Queue opcodes select message transport, cookie_high carries the
     117             :    virtchnl operation and cookie_low carries the PF reply status */
     118          24 : #define FD_IAVF_AQ_SEND_MSG_TO_PF         (0x0801U) /* iavf_aqc_opc_send_msg_to_pf */
     119             : #define FD_IAVF_AQ_SEND_MSG_TO_VF         (0x0802U) /* iavf_aqc_opc_send_msg_to_vf */
     120           0 : #define FD_IAVF_VIRTCHNL_VERSION          (1U)      /* VIRTCHNL_OP_VERSION */
     121           0 : #define FD_IAVF_VIRTCHNL_GET_VF_RESOURCES (3U)      /* VIRTCHNL_OP_GET_VF_RESOURCES */
     122           0 : #define FD_IAVF_VIRTCHNL_CONFIG_QUEUES    (6U)      /* VIRTCHNL_OP_CONFIG_VSI_QUEUES */
     123           0 : #define FD_IAVF_VIRTCHNL_CONFIG_IRQ_MAP   (7U)      /* VIRTCHNL_OP_CONFIG_IRQ_MAP */
     124           0 : #define FD_IAVF_VIRTCHNL_ENABLE_QUEUES    (8U)      /* VIRTCHNL_OP_ENABLE_QUEUES */
     125           0 : #define FD_IAVF_VIRTCHNL_ADD_ETH_ADDR     (10U)     /* VIRTCHNL_OP_ADD_ETH_ADDR */
     126           0 : #define FD_IAVF_VIRTCHNL_CONFIG_RSS_KEY   (23U)     /* VIRTCHNL_OP_CONFIG_RSS_KEY */
     127           0 : #define FD_IAVF_VIRTCHNL_CONFIG_RSS_LUT   (24U)     /* VIRTCHNL_OP_CONFIG_RSS_LUT */
     128          27 : #define FD_IAVF_VIRTCHNL_EVENT            (17U)     /* VIRTCHNL_OP_EVENT */
     129             : 
     130          15 : #define FD_IAVF_VIRTCHNL_CAP_L2        (1U<<0)  /* VIRTCHNL_VF_OFFLOAD_L2 */
     131          30 : #define FD_IAVF_VIRTCHNL_CAP_WB_ON_ITR (1U<<5)  /* VIRTCHNL_VF_OFFLOAD_WB_ON_ITR */
     132          15 : #define FD_IAVF_VIRTCHNL_CAP_RSS_PF    (1U<<19) /* VIRTCHNL_VF_OFFLOAD_RSS_PF */
     133          15 : #define FD_IAVF_VIRTCHNL_VSI_SRIOV   (6)      /* VIRTCHNL_VSI_SRIOV */
     134           0 : #define FD_IAVF_VIRTCHNL_MAC_PRIMARY (1U)     /* VIRTCHNL_ETHER_ADDR_PRIMARY */
     135             : 
     136          21 : #define FD_IAVF_EVENT_LINK_CHANGE     (1) /* VIRTCHNL_EVENT_LINK_CHANGE */
     137             : #define FD_IAVF_EVENT_RESET_IMPENDING (2) /* VIRTCHNL_EVENT_RESET_IMPENDING */
     138             : #define FD_IAVF_EVENT_PF_DRIVER_CLOSE (3) /* VIRTCHNL_EVENT_PF_DRIVER_CLOSE */
     139             : 
     140             : /* fd_iavf_virtchnl_version is the Linux virtchnl_version_info VF/PF message. */
     141             : struct fd_iavf_virtchnl_version {
     142             :   uint major;
     143             :   uint minor;
     144             : };
     145             : 
     146             : typedef struct fd_iavf_virtchnl_version fd_iavf_virtchnl_version_t;
     147             : 
     148             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_version_t)==8UL, iavf_virtchnl_version_sz );
     149             : 
     150             : /* fd_iavf_virtchnl_vf_resource is the fixed Linux virtchnl_vf_resource header. */
     151             : struct fd_iavf_virtchnl_vf_resource {
     152             :   ushort num_vsis;
     153             :   ushort num_queue_pairs;
     154             :   ushort max_vectors;
     155             :   ushort max_mtu;
     156             :   uint   capability_flags;
     157             :   uint   rss_key_sz;
     158             :   uint   rss_lut_sz;
     159             : };
     160             : 
     161             : typedef struct fd_iavf_virtchnl_vf_resource fd_iavf_virtchnl_vf_resource_t;
     162             : 
     163             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_vf_resource_t)==20UL, iavf_virtchnl_vf_resource_sz );
     164             : 
     165             : /* fd_iavf_virtchnl_vsi_resource is the Linux virtchnl_vsi_resource VF/PF message entry. */
     166             : struct fd_iavf_virtchnl_vsi_resource {
     167             :   ushort vsi_id;
     168             :   ushort num_queue_pairs;
     169             :   int    vsi_type;
     170             :   ushort qset_handle;
     171             :   uchar  default_mac_addr[ 6 ];
     172             : };
     173             : 
     174             : typedef struct fd_iavf_virtchnl_vsi_resource fd_iavf_virtchnl_vsi_resource_t;
     175             : 
     176             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_vsi_resource_t)==16UL, iavf_virtchnl_vsi_resource_sz );
     177             : 
     178             : /* fd_iavf_virtchnl_event is the Linux virtchnl_pf_event PF-to-VF message. */
     179             : struct fd_iavf_virtchnl_event {
     180             :   int   event;
     181             :   /* Unused, link_up has the same offset in both Linux event formats. */
     182             :   uint  link_speed;
     183             :   uchar link_up;
     184             :   uchar pad[ 3 ];
     185             :   int   severity;
     186             : };
     187             : 
     188             : typedef struct fd_iavf_virtchnl_event fd_iavf_virtchnl_event_t;
     189             : 
     190             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_event_t)==16UL, iavf_virtchnl_event_sz );
     191             : 
     192             : /* fd_iavf_virtchnl_txq_info is the Linux virtchnl_txq_info VF/PF message entry. */
     193             : struct fd_iavf_virtchnl_txq_info {
     194             :   ushort vsi_id;
     195             :   ushort queue_id;
     196             :   ushort ring_len;
     197             :   ushort head_writeback_enabled;
     198             :   ulong  ring_iova;
     199             :   ulong  head_writeback_iova;
     200             : };
     201             : 
     202             : typedef struct fd_iavf_virtchnl_txq_info fd_iavf_virtchnl_txq_info_t;
     203             : 
     204             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_txq_info_t)==24UL, iavf_virtchnl_txq_info_sz );
     205             : 
     206             : /* fd_iavf_virtchnl_rxq_info is the Linux virtchnl_rxq_info VF/PF message entry. */
     207             : struct fd_iavf_virtchnl_rxq_info {
     208             :   ushort vsi_id;
     209             :   ushort queue_id;
     210             :   uint   ring_len;
     211             :   ushort header_buffer_sz;
     212             :   ushort split_header_enabled;
     213             :   uint   data_buffer_sz;
     214             :   uint   max_frame_sz;
     215             :   uchar  crc_disable;
     216             :   uchar  descriptor_id;
     217             :   uchar  flags;
     218             :   uchar  pad1;
     219             :   ulong  ring_iova;
     220             :   int    split_position;
     221             :   uint   pad2;
     222             : };
     223             : 
     224             : typedef struct fd_iavf_virtchnl_rxq_info fd_iavf_virtchnl_rxq_info_t;
     225             : 
     226             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_rxq_info_t)==40UL, iavf_virtchnl_rxq_info_sz );
     227             : 
     228             : /* fd_iavf_virtchnl_queue_pair is the Linux virtchnl_queue_pair_info message entry. */
     229             : struct fd_iavf_virtchnl_queue_pair {
     230             :   fd_iavf_virtchnl_txq_info_t tx;
     231             :   fd_iavf_virtchnl_rxq_info_t rx;
     232             : };
     233             : 
     234             : typedef struct fd_iavf_virtchnl_queue_pair fd_iavf_virtchnl_queue_pair_t;
     235             : 
     236             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_queue_pair_t)==64UL, iavf_virtchnl_queue_pair_sz );
     237             : 
     238             : /* fd_iavf_virtchnl_queue_config holds Linux virtchnl_vsi_queue_config_info and its queue pairs. */
     239             : struct fd_iavf_virtchnl_queue_config {
     240             :   ushort vsi_id;
     241             :   ushort queue_pair_cnt;
     242             :   uint   pad;
     243             :   /* Linux PF message validation requires space for one extra queue pair. */
     244             :   fd_iavf_virtchnl_queue_pair_t pairs[ FD_IAVF_TILE_MAX+1UL ];
     245             : };
     246             : 
     247             : typedef struct fd_iavf_virtchnl_queue_config fd_iavf_virtchnl_queue_config_t;
     248             : 
     249             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_queue_config_t)==8UL+64UL*(FD_IAVF_TILE_MAX+1UL), iavf_virtchnl_queue_config_sz );
     250             : 
     251             : /* fd_iavf_virtchnl_irq_map holds Linux virtchnl_irq_map_info and one virtchnl_vector_map. */
     252             : struct fd_iavf_virtchnl_irq_map {
     253             :   ushort vector_cnt;
     254             :   ushort vsi_id;
     255             :   ushort vector_id;
     256             :   ushort rx_queue_map;
     257             :   ushort tx_queue_map;
     258             :   ushort rx_itr_idx;
     259             :   ushort tx_itr_idx;
     260             :   /* Linux PF message validation requires space for one extra vector map. */
     261             :   uchar legacy_padding[ 12 ];
     262             : };
     263             : 
     264             : typedef struct fd_iavf_virtchnl_irq_map fd_iavf_virtchnl_irq_map_t;
     265             : 
     266             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_irq_map_t)==26UL, iavf_virtchnl_irq_map_sz );
     267             : 
     268             : /* fd_iavf_virtchnl_queue_select is the Linux virtchnl_queue_select VF/PF message. */
     269             : struct fd_iavf_virtchnl_queue_select {
     270             :   ushort vsi_id;
     271             :   ushort pad;
     272             :   uint   rx_queue_map;
     273             :   uint   tx_queue_map;
     274             : };
     275             : 
     276             : typedef struct fd_iavf_virtchnl_queue_select fd_iavf_virtchnl_queue_select_t;
     277             : 
     278             : FD_STATIC_ASSERT( sizeof(fd_iavf_virtchnl_queue_select_t)==12UL, iavf_virtchnl_queue_select_sz );
     279             : 
     280             : static int
     281           0 : fd_iavf_device_supported( ushort device_id ) {
     282           0 :   return device_id==FD_IAVF_DEVICE_VF          ||
     283           0 :          device_id==FD_IAVF_DEVICE_VF_HV       ||
     284           0 :          device_id==FD_IAVF_DEVICE_ADAPTIVE_VF ||
     285           0 :          device_id==FD_IAVF_DEVICE_X722_VF;
     286           0 : }
     287             : 
     288             : static int
     289             : fd_iavf_pci_addr_normalize( char         normalized[ FD_IAVF_PCI_ADDR_SZ ],
     290          42 :                             char const * pci_addr ) {
     291          42 :   if( FD_UNLIKELY( NULL==pci_addr || strnlen( pci_addr, FD_IAVF_PCI_ADDR_SZ )!=FD_IAVF_PCI_ADDR_SZ-1UL ) ) {
     292          12 :     errno = EINVAL;
     293          12 :     return -1;
     294          12 :   }
     295          30 :   if( FD_UNLIKELY( pci_addr[4]!=':' || pci_addr[7]!=':' || pci_addr[10]!='.' ) ) {
     296           3 :     errno = EINVAL;
     297           3 :     return -1;
     298           3 :   }
     299             : 
     300         342 :   for( ulong i=0UL; i<FD_IAVF_PCI_ADDR_SZ-1UL; i++ ) {
     301         318 :     if( i==4UL || i==7UL || i==10UL ) {
     302          78 :       normalized[i] = pci_addr[i];
     303          78 :       continue;
     304          78 :     }
     305         240 :     uchar c = (uchar)pci_addr[i];
     306         240 :     if( FD_UNLIKELY( !isxdigit( c ) ) ) {
     307           3 :       errno = EINVAL;
     308           3 :       return -1;
     309           3 :     }
     310         237 :     normalized[i] = (char)tolower( c );
     311         237 :   }
     312          24 :   normalized[ FD_IAVF_PCI_ADDR_SZ-1UL ] = '\0';
     313             : 
     314          24 :   ulong slot = strtoul( normalized+8, NULL, 16 );
     315          24 :   if( FD_UNLIKELY( slot>31UL ) ) {
     316           3 :     errno = EINVAL;
     317           3 :     return -1;
     318           3 :   }
     319          21 :   ulong function = strtoul( normalized+11, NULL, 16 );
     320          21 :   if( FD_UNLIKELY( function>7UL ) ) {
     321           3 :     errno = EINVAL;
     322           3 :     return -1;
     323           3 :   }
     324          18 :   return 0;
     325          21 : }
     326             : 
     327             : static void
     328             : fd_iavf_path_join( char         path[ PATH_MAX ],
     329             :                    char const * parent,
     330           0 :                    char const * name ) {
     331           0 :   int path_sz = snprintf( path, PATH_MAX, "%s/%s", parent, name );
     332           0 :   FD_TEST( path_sz>=0 && path_sz<PATH_MAX );
     333           0 : }
     334             : 
     335             : static int
     336             : fd_iavf_read_ulong( char const * path,
     337             :                     int          base,
     338          33 :                     ulong *      value ) {
     339          33 :   int fd = open( path, O_RDONLY|O_CLOEXEC );
     340          33 :   if( FD_UNLIKELY( -1==fd ) ) return -1;
     341             : 
     342          33 :   char buf[ 64 ];
     343          33 :   ssize_t read_sz = read( fd, buf, sizeof(buf)-1UL );
     344          33 :   int     err     = 0;
     345          33 :   if( FD_UNLIKELY( -1==read_sz ) )                        err = errno;
     346          33 :   else if( FD_UNLIKELY( (ulong)read_sz==sizeof(buf)-1UL ) ) err = EOVERFLOW;
     347          33 :   if( FD_UNLIKELY( -1==close( fd ) && 0==err ) )           err = errno;
     348          33 :   if( FD_UNLIKELY( 0!=err ) ) {
     349           0 :     errno = err;
     350           0 :     return -1;
     351           0 :   }
     352             : 
     353          66 :   while( 0!=read_sz && isspace( (uchar)buf[ read_sz-1L ] ) ) read_sz--;
     354          33 :   if( FD_UNLIKELY( 0==read_sz ) ) {
     355           0 :     errno = EPROTO;
     356           0 :     return -1;
     357           0 :   }
     358          33 :   buf[ read_sz ] = '\0';
     359             : 
     360          33 :   errno = 0;
     361          33 :   char * end;
     362          33 :   ulong parsed = strtoul( buf, &end, base );
     363          33 :   if( FD_UNLIKELY( 0!=errno || *end ) ) {
     364           0 :     if( 0==errno ) errno = EPROTO;
     365           0 :     return -1;
     366           0 :   }
     367          33 :   *value = parsed;
     368          33 :   return 0;
     369          33 : }
     370             : 
     371             : static int
     372             : fd_iavf_read_link_name( char *       name,
     373             :                         ulong        name_max,
     374             :                         char const * path,
     375          72 :                         int          optional ) {
     376          72 :   char target[ PATH_MAX ];
     377          72 :   ssize_t target_sz = readlink( path, target, sizeof(target)-1UL );
     378          72 :   if( FD_UNLIKELY( -1==target_sz ) ) {
     379          24 :     if( optional && errno==ENOENT ) {
     380          21 :       name[0] = '\0';
     381          21 :       return 0;
     382          21 :     }
     383           3 :     return -1;
     384          24 :   }
     385          48 :   if( FD_UNLIKELY( (ulong)target_sz==sizeof(target)-1UL ) ) {
     386           0 :     errno = ENAMETOOLONG;
     387           0 :     return -1;
     388           0 :   }
     389          48 :   target[ target_sz ] = '\0';
     390             : 
     391          48 :   char const * base = strrchr( target, '/' );
     392          48 :   base              = NULL!=base ? base+1 : target;
     393          48 :   ulong base_sz     = strlen( base );
     394          48 :   if( FD_UNLIKELY( 0==base_sz || base_sz>=name_max ) ) {
     395           0 :     errno = EPROTO;
     396           0 :     return -1;
     397           0 :   }
     398          48 :   fd_memcpy( name, base, base_sz+1UL );
     399          48 :   return 0;
     400          48 : }
     401             : 
     402             : int
     403           0 : fd_iavf_vfio_avail( void ) {
     404           0 :   struct stat st;
     405           0 :   return 0==stat( "/sys/bus/pci/drivers/vfio-pci", &st ) && S_ISDIR( st.st_mode );
     406           0 : }
     407             : 
     408             : int
     409           0 : fd_iavf_vfio_api_check( char const ** diagnostic ) {
     410           0 :   if( NULL!=diagnostic ) *diagnostic = NULL;
     411             : 
     412           0 :   char const * message = "Opening /dev/vfio/vfio failed";
     413           0 :   int fd = open( "/dev/vfio/vfio", O_RDWR|O_CLOEXEC );
     414           0 :   if( FD_UNLIKELY( -1==fd ) ) goto fail;
     415             : 
     416           0 :   message = "Reading the VFIO API version from /dev/vfio/vfio failed";
     417           0 :   int api_version = ioctl( fd, VFIO_GET_API_VERSION );
     418           0 :   if( FD_UNLIKELY( -1==api_version ) ) goto fail;
     419           0 :   if( FD_UNLIKELY( api_version!=VFIO_API_VERSION ) ) {
     420           0 :     message = "The kernel's VFIO API version is incompatible with the iavf networking provider";
     421           0 :     errno   = EPROTONOSUPPORT;
     422           0 :     goto fail;
     423           0 :   }
     424           0 :   message = "Checking VFIO TYPE1v2 IOMMU support through /dev/vfio/vfio failed";
     425           0 :   int type1v2_supported = ioctl( fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1v2_IOMMU );
     426           0 :   if( FD_UNLIKELY( -1==type1v2_supported ) ) goto fail;
     427           0 :   if( FD_UNLIKELY( !type1v2_supported ) ) {
     428           0 :     message = "VFIO TYPE1v2 IOMMU support is unavailable";
     429           0 :     errno   = EPROTONOSUPPORT;
     430           0 :     goto fail;
     431           0 :   }
     432           0 :   if( FD_UNLIKELY( -1==close( fd ) ) ) {
     433           0 :     if( NULL!=diagnostic ) *diagnostic = "Closing /dev/vfio/vfio failed";
     434           0 :     return -1;
     435           0 :   }
     436           0 :   return 0;
     437             : 
     438           0 : fail:
     439           0 :   {
     440           0 :     int err = errno;
     441           0 :     if( -1!=fd ) close( fd );
     442           0 :     if( NULL!=diagnostic ) *diagnostic = message;
     443           0 :     errno = err;
     444           0 :     return -1;
     445           0 :   }
     446           0 : }
     447             : 
     448             : int
     449             : fd_iavf_member_interfaces( char const * interface,
     450             :                            char         members[ FD_IAVF_MEMBER_MAX ][ IF_NAMESIZE ],
     451           9 :                            ulong *      member_cnt ) {
     452           9 :   if( NULL==interface || NULL==members || NULL==member_cnt || 0==interface[0] || strlen(interface)>=IF_NAMESIZE ) {
     453           0 :     errno = EINVAL;
     454           0 :     return -1;
     455           0 :   }
     456           9 :   *member_cnt = 0UL;
     457             :   /* Find PF interfaces, expanding 802.3ad bonds into their members */
     458           9 :   if( fd_bonding_is_master( interface ) ) {
     459           9 :     if( !fd_bonding_is_lacp( interface ) ) {
     460           0 :       errno = EOPNOTSUPP;
     461           0 :       return -1;
     462           0 :     }
     463           9 :     fd_bonding_slave_iter_t iter[1];
     464           9 :     for( fd_bonding_slave_iter_init( iter, interface );
     465          27 :          !fd_bonding_slave_iter_done( iter );
     466          18 :          fd_bonding_slave_iter_next( iter ) ) {
     467          18 :       char const * name = fd_bonding_slave_iter_ele( iter );
     468          18 :       if( *member_cnt>=FD_IAVF_MEMBER_MAX || strlen(name)>=IF_NAMESIZE ) {
     469           0 :         errno = E2BIG;
     470           0 :         return -1;
     471           0 :       }
     472          18 :       fd_cstr_ncpy( members[ (*member_cnt)++ ], name, IF_NAMESIZE );
     473          18 :     }
     474           9 :   } else {
     475           0 :     fd_cstr_ncpy( members[0], interface, IF_NAMESIZE );
     476           0 :     *member_cnt = 1UL;
     477           0 :   }
     478           9 :   if( 0==*member_cnt ) {
     479           0 :     errno = ENODEV;
     480           0 :     return -1;
     481           0 :   }
     482             :   /* Require ice or i40e on every PF */
     483           9 :   for( ulong i=0UL; i<*member_cnt; i++ ) {
     484           9 :     char path  [ PATH_MAX ];
     485           9 :     char driver[ FD_IAVF_DRIVER_NAME_MAX ];
     486           9 :     FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device/driver", members[i] ) );
     487           9 :     if( -1==fd_iavf_read_link_name( driver, sizeof(driver), path, 0 ) ) return -1;
     488           9 :     if( 0!=strcmp(driver,"ice") && 0!=strcmp(driver,"i40e") ) {
     489           9 :       errno = EPROTONOSUPPORT;
     490           9 :       return -1;
     491           9 :     }
     492           9 :   }
     493           0 :   return 0;
     494           9 : }
     495             : 
     496             : #if FD_HAS_X86
     497             : static int
     498           0 : fd_iavf_cpu_vendor( char vendor[ 13 ] ) {
     499           0 :   uint eax, ebx, ecx, edx;
     500           0 :   if( !__get_cpuid( 0U, &eax, &ebx, &ecx, &edx ) ) return 0;
     501             : 
     502             :   /* CPUID returns the vendor string in EBX, EDX, ECX order. */
     503           0 :   fd_memcpy( vendor,   &ebx, 4UL );
     504           0 :   fd_memcpy( vendor+4, &edx, 4UL );
     505           0 :   fd_memcpy( vendor+8, &ecx, 4UL );
     506           0 :   vendor[ 12 ] = '\0';
     507           0 :   return 1;
     508           0 : }
     509             : 
     510             : /* fd_iavf_cmdline_next splits one boot argument in place using Linux
     511             :    lib/cmdline.c next_arg quoting rules. Quotes group whitespace and
     512             :    backslashes are literal. Returns the remaining command line, or
     513             :    NULL for unclosed quotes. param and value point into cmdline, with
     514             :    value set to NULL when the argument has no value. */
     515             : static char *
     516             : fd_iavf_cmdline_next( char *  cmdline,
     517             :                       char ** param,
     518           0 :                       char ** value ) {
     519           0 :   while( fd_isspace( (int)(uchar)*cmdline ) ) cmdline++;
     520           0 :   int quoted = *cmdline=='"';
     521           0 :   char * arg = cmdline+quoted;
     522             : 
     523           0 :   char * equals = NULL;
     524           0 :   char * end    = arg;
     525           0 :   int in_quote  = quoted;
     526           0 :   for( ; *end; end++ ) {
     527           0 :     if( fd_isspace( (int)(uchar)*end ) && !in_quote ) break;
     528           0 :     if( *end=='=' && NULL==equals && end!=arg ) equals = end;
     529           0 :     if( *end=='"' ) in_quote = !in_quote;
     530           0 :   }
     531           0 :   if( in_quote ) return NULL;
     532             : 
     533           0 :   char * next = end;
     534           0 :   if( *next ) *next++ = '\0';
     535           0 :   *param = arg;
     536           0 :   *value = NULL;
     537           0 :   if( NULL!=equals ) {
     538           0 :     *equals = '\0';
     539           0 :     *value  = equals+1;
     540           0 :     if( **value=='"' ) {
     541           0 :       (*value)++;
     542           0 :       if( end>*value && end[-1]=='"' ) end[-1] = '\0';
     543           0 :     }
     544           0 :   }
     545           0 :   if( quoted && end>arg && end[-1]=='"' ) end[-1] = '\0';
     546           0 :   return next;
     547           0 : }
     548             : 
     549             : static int
     550           0 : fd_iavf_intel_iommu_default_off( void ) {
     551           0 :   struct utsname kernel;
     552           0 :   if( -1==uname( &kernel ) ) return 0;
     553             : 
     554           0 :   char config_path[ PATH_MAX ];
     555           0 :   if( !fd_cstr_printf_check( config_path, sizeof(config_path), NULL, "/boot/config-%s", kernel.release ) ) return 0;
     556             : 
     557           0 :   FILE * config_file = fopen( config_path, "r" );
     558           0 :   if( NULL==config_file ) return 0;
     559             : 
     560             :   /* Long CONFIG_CMDLINE values can span reads, so only match at line starts. */
     561           0 :   char line[ 256 ];
     562           0 :   int line_start     = 1;
     563           0 :   int driver_enabled = 0;
     564           0 :   int default_off    = 0;
     565           0 :   while( NULL!=fgets( line, sizeof(line), config_file ) ) {
     566           0 :     if( line_start ) {
     567           0 :       if( 0==strcmp( line, "CONFIG_INTEL_IOMMU=y\n" ) ) driver_enabled = 1;
     568           0 :       if( 0==strcmp( line, "# CONFIG_INTEL_IOMMU_DEFAULT_ON is not set\n" ) ) default_off = 1;
     569           0 :     }
     570           0 :     line_start = NULL!=strchr( line, '\n' );
     571           0 :   }
     572             : 
     573           0 :   int read_err  = ferror( config_file );
     574           0 :   int close_err = fclose( config_file );
     575           0 :   return driver_enabled && default_off && !read_err && 0==close_err;
     576           0 : }
     577             : #endif
     578             : 
     579             : char const *
     580           0 : fd_iavf_sriov_bios_guide( int vendor_only ) {
     581           0 :   char const * fallback = NULL;
     582           0 :   if( !vendor_only ) {
     583           0 :     fallback = "Enable SR-IOV and IOMMU in the system BIOS.\n"
     584           0 :                "  Set Virtualization Mode to SR-IOV in the NIC's BIOS settings";
     585           0 :   }
     586             : 
     587           0 : #if FD_HAS_X86
     588           0 :   char vendor[ 13 ];
     589           0 :   if( !fd_iavf_cpu_vendor( vendor ) ) return fallback;
     590           0 :   int is_intel = 0==strcmp( vendor, "GenuineIntel" );
     591           0 :   int is_amd   = 0==strcmp( vendor, "AuthenticAMD" );
     592             :   /* The board vendor can identify Supermicro in systems sold under another brand. */
     593           0 :   char const * const paths[] = {
     594           0 :     "/sys/class/dmi/id/board_vendor",
     595           0 :     "/sys/class/dmi/id/sys_vendor"
     596           0 :   };
     597           0 :   for( ulong i=0UL; i<sizeof(paths)/sizeof(paths[0]); i++ ) {
     598           0 :     int fd = open( paths[i], O_RDONLY|O_CLOEXEC );
     599           0 :     if( -1==fd ) continue;
     600             : 
     601           0 :     char manufacturer[ 128 ];
     602           0 :     ulong manufacturer_sz;
     603           0 :     int read_err  = fd_io_read( fd, manufacturer, sizeof(manufacturer), sizeof(manufacturer), &manufacturer_sz );
     604           0 :     int close_err = close( fd );
     605           0 :     if( read_err>=0 || -1==close_err || 0==manufacturer_sz ) continue;
     606           0 :     manufacturer[ manufacturer_sz ] = '\0';
     607             : 
     608           0 :     if( NULL!=strcasestr( manufacturer, "Supermicro" ) || NULL!=strcasestr( manufacturer, "Super Micro" ) ) {
     609           0 :       if( is_intel ) {
     610           0 :         return "Follow Supermicro's instructions at\n"
     611           0 :                "    https://www.supermicro.com/en/support/faqs/faq.php?faq=31936";
     612           0 :       }
     613           0 :       if( is_amd ) {
     614           0 :         return "Follow Supermicro's instructions at\n"
     615           0 :                "    https://www.supermicro.com/en/support/faqs/faq.php?faq=31883";
     616           0 :       }
     617           0 :     }
     618           0 :     if( NULL!=strcasestr( manufacturer, "Dell" ) ) {
     619           0 :       if( is_intel ) {
     620           0 :         return "This PowerEdge guide uses a Mellanox card, but the system and NIC BIOS\n"
     621           0 :                "  changes are roughly the same for Intel NICs:\n"
     622           0 :                "    https://enterprise-support.nvidia.com/s/article/howto-set-dell-poweredge-r730-bios-parameters-to-support-sr-iov";
     623           0 :       }
     624           0 :       if( is_amd ) {
     625           0 :         return "This PowerEdge guide uses an Intel CPU and a Mellanox card, but the\n"
     626           0 :                "  system and NIC BIOS changes are roughly the same for Intel NICs:\n"
     627           0 :                "    https://enterprise-support.nvidia.com/s/article/howto-set-dell-poweredge-r730-bios-parameters-to-support-sr-iov";
     628           0 :       }
     629           0 :       return "Follow NVIDIA's PowerEdge SR-IOV configuration guide at\n"
     630           0 :              "    https://enterprise-support.nvidia.com/s/article/howto-set-dell-poweredge-r730-bios-parameters-to-support-sr-iov";
     631           0 :     }
     632           0 :   }
     633           0 : #endif
     634           0 :   return fallback;
     635           0 : }
     636             : 
     637             : static char const *
     638           0 : fd_iavf_iommu_diagnose( void ) {
     639           0 :   char const * fallback = fd_iavf_sriov_bios_guide( 0 );
     640             : 
     641           0 : #if FD_HAS_X86
     642           0 :   char vendor[ 13 ];
     643           0 :   if( !fd_iavf_cpu_vendor( vendor ) ) return fallback;
     644           0 :   int is_intel = 0==strcmp( vendor, "GenuineIntel" );
     645           0 :   int is_amd   = 0==strcmp( vendor, "AuthenticAMD" );
     646             : 
     647           0 :   int cmdline_fd = open( "/proc/cmdline", O_RDONLY|O_CLOEXEC );
     648           0 :   if( -1==cmdline_fd ) return fallback;
     649             : 
     650             :   /* Read through EOF so a truncated command line cannot hide a later override. */
     651           0 :   char cmdline[ 4096 ];
     652           0 :   ulong cmdline_sz;
     653           0 :   int read_err  = fd_io_read( cmdline_fd, cmdline, sizeof(cmdline), sizeof(cmdline), &cmdline_sz );
     654           0 :   int close_err = close( cmdline_fd );
     655           0 :   if( read_err>=0 || -1==close_err || 0==cmdline_sz ) return fallback;
     656           0 :   cmdline[ cmdline_sz ] = '\0';
     657             : 
     658             :   /* Match Linux's intel_iommu_setup, iommu_setup and parse_amd_iommu_options,
     659             :      including prefix and substring matches. Intel's last on/off option wins. */
     660           0 :   int iommu_off  = 0;
     661           0 :   int intel_mode = -1;
     662           0 :   int amd_off    = 0;
     663           0 :   for( char * next=cmdline; *next; ) {
     664           0 :     char * param;
     665           0 :     char * value;
     666           0 :     next = fd_iavf_cmdline_next( next, &param, &value );
     667           0 :     if( NULL==next ) return fallback;
     668           0 :     if( NULL==value ) {
     669           0 :       if( 0==strcmp( param, "--" ) ) break;
     670           0 :       continue;
     671           0 :     }
     672             : 
     673           0 :     if( is_amd && 0==strcmp( param, "amd_iommu" ) && NULL!=strstr( value, "off" ) ) amd_off = 1;
     674           0 :     char * save;
     675           0 :     for( char * opt=strtok_r( value, ",", &save ); NULL!=opt; opt=strtok_r( NULL, ",", &save ) ) {
     676           0 :       if( 0==strcmp( param, "iommu" ) ) {
     677           0 :         if( 0==strncmp( opt, "off", 3UL ) ) iommu_off = 1;
     678           0 :         if( 0==strncmp( opt, "usedac", 6UL ) ) break;
     679           0 :       } else if( is_intel && 0==strcmp( param, "intel_iommu" ) ) {
     680           0 :         if(      0==strncmp( opt, "on",  2UL ) ) intel_mode = 1;
     681           0 :         else if( 0==strncmp( opt, "off", 3UL ) ) intel_mode = 0;
     682           0 :       }
     683           0 :     }
     684           0 :   }
     685             : 
     686           0 :   if( iommu_off ) {
     687           0 :     return "The iommu=off kernel boot argument disables IOMMU support, so remove it "
     688           0 :            "from the boot arguments and reboot the machine";
     689           0 :   }
     690             : 
     691           0 :   if( is_intel && 0==intel_mode ) {
     692           0 :     return "The intel_iommu=off kernel boot argument disables Intel IOMMU support, "
     693           0 :            "so replace it with intel_iommu=on and reboot the machine";
     694           0 :   }
     695             : 
     696           0 :   if( is_amd && amd_off ) {
     697           0 :     return "The amd_iommu=off kernel boot argument disables AMD IOMMU support, "
     698           0 :            "so remove it from the boot arguments and reboot the machine";
     699           0 :   }
     700             : 
     701           0 :   if( is_intel && intel_mode<0 && fd_iavf_intel_iommu_default_off() ) {
     702           0 :     return "Add intel_iommu=on to the kernel boot arguments and reboot the machine "
     703           0 :            "to enable Intel IOMMU support";
     704           0 :   }
     705           0 : #endif
     706           0 :   return fallback;
     707           0 : }
     708             : 
     709             : int
     710             : fd_iavf_iommu_check( char const *  pf_if,
     711           0 :                      char const ** diagnostic ) {
     712           0 :   if( NULL!=diagnostic ) *diagnostic = NULL;
     713             : 
     714           0 :   char path[ PATH_MAX ];
     715           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device/iommu_group", pf_if ) );
     716           0 :   char resolved[ PATH_MAX ];
     717           0 :   if( NULL!=realpath( path, resolved ) ) return 0;
     718             : 
     719           0 :   int err = errno;
     720           0 :   if( NULL!=diagnostic ) {
     721           0 :     *diagnostic = err==ENOENT ? fd_iavf_iommu_diagnose()
     722           0 :                              : "Check that the PF's iommu_group sysfs link is accessible "
     723           0 :                                "so its IOMMU status can be determined";
     724           0 :   }
     725           0 :   errno = err;
     726           0 :   return -1;
     727           0 : }
     728             : 
     729             : int
     730           0 : fd_iavf_ari_check( char const * pf_if ) {
     731           0 :   char path[ PATH_MAX ];
     732           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device/ari_enabled", pf_if ) );
     733           0 :   ulong enabled;
     734           0 :   if( FD_UNLIKELY( -1==fd_iavf_read_ulong( path, 10, &enabled ) ) ) return -1;
     735           0 :   if( FD_UNLIKELY( enabled!=1UL ) ) {
     736           0 :     errno = enabled==0UL ? EOPNOTSUPP : EPROTO;
     737           0 :     return -1;
     738           0 :   }
     739           0 :   return 0;
     740           0 : }
     741             : 
     742             : int
     743             : fd_iavf_numvfs_get( char const * pf_if,
     744          33 :                     uint *       numvfs ) {
     745          33 :   char path[ PATH_MAX ];
     746          33 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device/sriov_numvfs", pf_if ) );
     747          33 :   ulong value;
     748          33 :   if( FD_UNLIKELY( -1==fd_iavf_read_ulong( path, 10, &value ) ) ) return -1;
     749          33 :   if( FD_UNLIKELY( value>UINT_MAX ) ) {
     750           0 :     errno = EOVERFLOW;
     751           0 :     return -1;
     752           0 :   }
     753          33 :   *numvfs = (uint)value;
     754          33 :   return 0;
     755          33 : }
     756             : 
     757             : static int
     758           0 : fd_iavf_sriov_bus_check( char const * pf_if ) {
     759           0 :   char path[ PATH_MAX ];
     760           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device/sriov_offset", pf_if ) );
     761           0 :   ulong vf_offset;
     762           0 :   if( FD_UNLIKELY( -1==fd_iavf_read_ulong( path, 10, &vf_offset ) ) ) return -1;
     763           0 :   if( FD_UNLIKELY( 0==vf_offset || vf_offset>USHRT_MAX ) ) {
     764           0 :     errno = EPROTO;
     765           0 :     return -1;
     766           0 :   }
     767             : 
     768           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device", pf_if ) );
     769           0 :   char pci_addr[ FD_IAVF_PCI_ADDR_SZ ];
     770           0 :   if( FD_UNLIKELY( -1==fd_iavf_read_link_name( pci_addr, sizeof(pci_addr), path, 0 ) ||
     771           0 :                    -1==fd_iavf_pci_addr_normalize( pci_addr, pci_addr ) ) ) return -1;
     772             : 
     773           0 :   ulong pf_bus      = strtoul( pci_addr+5,  NULL, 16 );
     774           0 :   ulong pf_device   = strtoul( pci_addr+8,  NULL, 16 );
     775           0 :   ulong pf_function = strtoul( pci_addr+11, NULL, 16 );
     776             :   /* VF 0's function offset can carry into a higher PCI bus number */
     777           0 :   ulong vf_bus = pf_bus+((pf_device*8UL+pf_function+vf_offset)>>8);
     778           0 :   if( vf_bus==pf_bus ) return 0;
     779             : 
     780             :   /* Check VF 0's bus against the parent bridge's assigned bus range */
     781           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device/../config", pf_if ) );
     782           0 :   int fd = open( path, O_RDONLY|O_CLOEXEC );
     783           0 :   if( FD_UNLIKELY( -1==fd ) ) return -1;
     784           0 :   uchar config[ PCI_SUBORDINATE_BUS+1 ];
     785           0 :   ssize_t read_sz = pread( fd, config, sizeof(config), 0 );
     786           0 :   int     err     = 0;
     787           0 :   if( FD_UNLIKELY( -1==read_sz ) )                         err = errno;
     788           0 :   else if( FD_UNLIKELY( (ulong)read_sz!=sizeof(config) ) ) err = EPROTO;
     789           0 :   if( FD_UNLIKELY( -1==close( fd ) && 0==err ) )           err = errno;
     790           0 :   if( FD_UNLIKELY( 0!=err ) ) {
     791           0 :     errno = err;
     792           0 :     return -1;
     793           0 :   }
     794           0 :   if( FD_UNLIKELY( (config[ PCI_HEADER_TYPE ] & 0x7fU)!=PCI_HEADER_TYPE_BRIDGE ||
     795           0 :                    config[ PCI_SECONDARY_BUS ]!=pf_bus ) ) {
     796           0 :     errno = EPROTO;
     797           0 :     return -1;
     798           0 :   }
     799           0 :   if( FD_UNLIKELY( vf_bus>config[ PCI_SUBORDINATE_BUS ] ) ) {
     800           0 :     errno = ENOSPC;
     801           0 :     return -1;
     802           0 :   }
     803           0 :   return 0;
     804           0 : }
     805             : 
     806             : int
     807           0 : fd_iavf_sriov_resources_check( char const * pf_if ) {
     808           0 :   if( FD_UNLIKELY( -1==fd_iavf_sriov_bus_check( pf_if ) ) ) return -1;
     809             : 
     810           0 :   char path[ PATH_MAX ];
     811           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device/resource", pf_if ) );
     812           0 :   FILE * file = fopen( path, "re" );
     813           0 :   if( FD_UNLIKELY( NULL==file ) ) return -1;
     814             : 
     815           0 :   int err = 0;
     816             :   /* Linux lists VF BAR0 and BAR3 at resource indices 7 and 10 */
     817           0 :   for( ulong resource_idx=0UL; resource_idx<13UL; resource_idx++ ) {
     818           0 :     ulong start, end, flags;
     819           0 :     errno = 0;
     820           0 :     if( FD_UNLIKELY( fscanf( file, "%lx %lx %lx", &start, &end, &flags )!=3 ) ) {
     821           0 :       err = ferror( file ) ? (0!=errno ? errno : EIO) : EPROTO;
     822           0 :       break;
     823           0 :     }
     824           0 :     if( resource_idx!=7UL && resource_idx!=10UL ) continue;
     825           0 :     if( FD_UNLIKELY( 0==start || end<start || !(flags & FD_IAVF_PCI_RESOURCE_MEM) ||
     826           0 :                      (flags & FD_IAVF_PCI_RESOURCE_UNUSABLE) ) ) {
     827           0 :       err = ENOMEM;
     828           0 :       break;
     829           0 :     }
     830           0 :   }
     831           0 :   if( FD_UNLIKELY( EOF==fclose( file ) && 0==err ) ) err = errno;
     832           0 :   if( FD_UNLIKELY( 0!=err ) ) {
     833           0 :     errno = err;
     834           0 :     return -1;
     835           0 :   }
     836           0 :   return 0;
     837           0 : }
     838             : 
     839             : int
     840             : fd_iavf_get_last_vf( char const * pf_if,
     841             :                      uint *       vf_idx,
     842             :                      char         vf_pci_addr[ FD_IAVF_PCI_ADDR_SZ ],
     843          33 :                      int          vfio_only ) {
     844          33 :   uint numvfs;
     845          33 :   if( FD_UNLIKELY( -1==fd_iavf_numvfs_get( pf_if, &numvfs ) ) ) return -1;
     846             : 
     847          63 :   for( uint candidate_idx=numvfs; candidate_idx; ) {
     848          51 :     candidate_idx--;
     849          51 :     char path[ PATH_MAX ];
     850          51 :     if( vfio_only ) {
     851          45 :       FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device/virtfn%u/driver", pf_if, candidate_idx ) );
     852          45 :       char driver[ FD_IAVF_DRIVER_NAME_MAX ];
     853          45 :       if( FD_UNLIKELY( -1==fd_iavf_read_link_name( driver, sizeof(driver), path, 1 ) ) ) return -1;
     854          42 :       if( 0!=strcmp( driver, "vfio-pci" ) ) continue;
     855          42 :     }
     856             : 
     857          18 :     FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/class/net/%s/device/virtfn%u", pf_if, candidate_idx ) );
     858          18 :     char pci_addr[ FD_IAVF_PCI_ADDR_SZ ];
     859          18 :     if( FD_UNLIKELY( -1==fd_iavf_read_link_name( pci_addr, sizeof(pci_addr), path, 0 ) ) ) return -1;
     860          18 :     if( FD_UNLIKELY( -1==fd_iavf_pci_addr_normalize( vf_pci_addr, pci_addr ) ) ) return -1;
     861          18 :     if( NULL!=vf_idx ) *vf_idx = candidate_idx;
     862          18 :     return 0;
     863          18 :   }
     864          33 :   errno = ENOENT;
     865          12 :   return -1;
     866          33 : }
     867             : 
     868             : static int
     869             : fd_iavf_iommu_group_check( char const * pci_addr,
     870           0 :                            uint         iommu_group ) {
     871           0 :   char devices_path[ PATH_MAX ];
     872           0 :   int path_sz = snprintf( devices_path, sizeof(devices_path),
     873           0 :                           "/sys/kernel/iommu_groups/%u/devices", iommu_group );
     874           0 :   FD_TEST( path_sz>=0 && (ulong)path_sz<sizeof(devices_path) );
     875             : 
     876           0 :   DIR * devices = opendir( devices_path );
     877           0 :   if( FD_UNLIKELY( NULL==devices ) ) return -1;
     878             : 
     879           0 :   ulong device_cnt = 0UL;
     880           0 :   ulong match_cnt  = 0UL;
     881           0 :   int   err        = 0;
     882           0 :   for(;;) {
     883           0 :     errno                 = 0;
     884           0 :     struct dirent * entry = readdir( devices );
     885           0 :     if( NULL==entry ) {
     886           0 :       err = errno;
     887           0 :       break;
     888           0 :     }
     889           0 :     if( entry->d_name[0]=='.' ) continue;
     890           0 :     device_cnt++;
     891           0 :     match_cnt += (ulong)(0==strcmp( entry->d_name, pci_addr ));
     892           0 :   }
     893           0 :   if( FD_UNLIKELY( -1==closedir( devices ) && 0==err ) ) err = errno;
     894           0 :   if( FD_UNLIKELY( 0!=err ) ) {
     895           0 :     errno = err;
     896           0 :     return -1;
     897           0 :   }
     898           0 :   if( FD_UNLIKELY( device_cnt!=1UL || match_cnt!=1UL ) ) {
     899           0 :     FD_LOG_WARNING(( "PCI %s is not alone in IOMMU group %u (%i-%s)", pci_addr, iommu_group, EBUSY, fd_io_strerror( EBUSY ) ));
     900           0 :     errno = EBUSY;
     901           0 :     return -1;
     902           0 :   }
     903           0 :   return 0;
     904           0 : }
     905             : 
     906             : int
     907             : fd_iavf_pci_probe( fd_iavf_pci_info_t * info,
     908          27 :                    char const *         pci_addr ) {
     909          27 :   if( FD_UNLIKELY( NULL==info ) ) {
     910           3 :     errno = EINVAL;
     911           3 :     return -1;
     912           3 :   }
     913          24 :   fd_memset( info, 0, sizeof(*info) );
     914             : 
     915          24 :   fd_iavf_pci_info_t probed[1];
     916          24 :   fd_memset( probed, 0, sizeof(probed) );
     917          24 :   if( FD_UNLIKELY( -1==fd_iavf_pci_addr_normalize( probed->pci_addr, pci_addr ) ) ) return -1;
     918             : 
     919           0 :   char device_path[ PATH_MAX ];
     920           0 :   fd_iavf_path_join( device_path, FD_IAVF_PCI_SYSFS, probed->pci_addr );
     921             : 
     922             :   /* Check Intel Ethernet VF identity */
     923           0 :   char  path[ PATH_MAX ];
     924           0 :   ulong value;
     925           0 :   fd_iavf_path_join( path, device_path, "vendor" );
     926           0 :   if( FD_UNLIKELY( -1==fd_iavf_read_ulong( path, 0, &value ) ) ) return -1;
     927           0 :   if( FD_UNLIKELY( value!=0x8086UL ) ) {
     928           0 :     FD_LOG_WARNING(( "PCI %s vendor %#lx is not Intel (%i-%s)", pci_addr, value, ENODEV, fd_io_strerror( ENODEV ) ));
     929           0 :     errno = ENODEV;
     930           0 :     return -1;
     931           0 :   }
     932             : 
     933           0 :   fd_iavf_path_join( path, device_path, "class" );
     934           0 :   if( FD_UNLIKELY( -1==fd_iavf_read_ulong( path, 0, &value ) ) ) return -1;
     935           0 :   if( FD_UNLIKELY( value!=0x020000UL ) ) {
     936           0 :     FD_LOG_WARNING(( "PCI %s class %#lx is not Ethernet (%i-%s)", pci_addr, value, ENODEV, fd_io_strerror( ENODEV ) ));
     937           0 :     errno = ENODEV;
     938           0 :     return -1;
     939           0 :   }
     940             : 
     941           0 :   fd_iavf_path_join( path, device_path, "device" );
     942           0 :   if( FD_UNLIKELY( -1==fd_iavf_read_ulong( path, 0, &value ) ) ) return -1;
     943           0 :   if( FD_UNLIKELY( value>USHRT_MAX ) ) {
     944           0 :     FD_LOG_WARNING(( "PCI %s device ID %#lx exceeds 16 bits (%i-%s)", pci_addr, value, EPROTO, fd_io_strerror( EPROTO ) ));
     945           0 :     errno = EPROTO;
     946           0 :     return -1;
     947           0 :   }
     948           0 :   probed->device_id = (ushort)value;
     949           0 :   if( FD_UNLIKELY( !fd_iavf_device_supported( probed->device_id ) ) ) {
     950           0 :     FD_LOG_WARNING(( "unsupported Intel VF device %#x (%i-%s)", (uint)probed->device_id, ENODEV, fd_io_strerror( ENODEV ) ));
     951           0 :     errno = ENODEV;
     952           0 :     return -1;
     953           0 :   }
     954             : 
     955             :   /* Confirm the VF's parent PF and IOMMU isolation */
     956           0 :   char pf_addr[ FD_IAVF_PCI_ADDR_SZ ];
     957           0 :   fd_iavf_path_join( path, device_path, "physfn" );
     958           0 :   if( FD_UNLIKELY( -1==fd_iavf_read_link_name( pf_addr, sizeof(pf_addr), path, 0 ) ||
     959           0 :                    -1==fd_iavf_pci_addr_normalize( probed->pf_pci_addr, pf_addr ) ) ) {
     960           0 :     if( errno==ENOENT ) errno = ENODEV;
     961           0 :     return -1;
     962           0 :   }
     963             : 
     964           0 :   char iommu_group_name[ 32 ];
     965           0 :   fd_iavf_path_join( path, device_path, "iommu_group" );
     966           0 :   if( FD_UNLIKELY( -1==fd_iavf_read_link_name( iommu_group_name, sizeof(iommu_group_name), path, 0 ) ) ) return -1;
     967           0 :   errno = 0;
     968           0 :   char * end;
     969           0 :   ulong iommu_group = strtoul( iommu_group_name, &end, 10 );
     970           0 :   if( FD_UNLIKELY( 0!=errno || *end || iommu_group>UINT_MAX ) ) {
     971           0 :     int err = 0!=errno ? errno : EPROTO;
     972           0 :     FD_LOG_WARNING(( "invalid IOMMU group for PCI %s (%i-%s)", pci_addr, err, fd_io_strerror( err ) ));
     973           0 :     errno = err;
     974           0 :     return -1;
     975           0 :   }
     976           0 :   probed->iommu_group = (uint)iommu_group;
     977           0 :   if( FD_UNLIKELY( -1==fd_iavf_iommu_group_check( probed->pci_addr, probed->iommu_group ) ) ) return -1;
     978             : 
     979           0 :   fd_iavf_path_join( path, device_path, "driver" );
     980           0 :   if( FD_UNLIKELY( -1==fd_iavf_read_link_name( probed->driver, sizeof(probed->driver), path, 1 ) ) ) return -1;
     981             : 
     982           0 :   *info = *probed;
     983           0 :   return 0;
     984           0 : }
     985             : 
     986             : static void
     987             : fd_iavf_vfio_cleanup( fd_iavf_vfio_t * vfio,
     988           0 :                       int              container_set ) {
     989           0 :   if( NULL!=vfio->bar0 ) munmap( (void *)vfio->bar0, FD_IAVF_BAR0_MAP_SZ );
     990           0 :   if( vfio->device_fd>=0 ) close( vfio->device_fd );
     991           0 :   if( container_set ) ioctl( vfio->group_fd, VFIO_GROUP_UNSET_CONTAINER );
     992           0 :   if( vfio->group_fd>=0 )     close( vfio->group_fd );
     993           0 :   if( vfio->container_fd>=0 ) close( vfio->container_fd );
     994           0 :   vfio->bar0         = NULL;
     995           0 :   vfio->device_fd    = -1;
     996           0 :   vfio->group_fd     = -1;
     997           0 :   vfio->container_fd = -1;
     998           0 : }
     999             : 
    1000             : static int
    1001           0 : fd_iavf_vfio_wait_reset( fd_iavf_vfio_t * vfio ) {
    1002           0 :   volatile uint const * reset_reg = (volatile uint const *)(vfio->bar0 + FD_IAVF_VFGEN_RSTAT);
    1003           0 :   struct timespec       delay     = { .tv_sec=0L, .tv_nsec=FD_IAVF_POLL_INTERVAL_NS };
    1004           0 :   for( ulong retry=0UL; retry<FD_IAVF_RESET_RETRY_CNT; retry++ ) {
    1005           0 :     uint reset_state = *reset_reg & FD_IAVF_VFGEN_RSTAT_STATE;
    1006           0 :     if( reset_state==FD_IAVF_VFR_STATE_COMPLETED ||
    1007           0 :         reset_state==FD_IAVF_VFR_STATE_ACTIVE ) {
    1008           0 :       return 0;
    1009           0 :     }
    1010           0 :     if( FD_UNLIKELY( -1==nanosleep( &delay, NULL ) && errno!=EINTR ) ) return -1;
    1011           0 :   }
    1012           0 :   errno = ETIMEDOUT;
    1013           0 :   return -1;
    1014           0 : }
    1015             : 
    1016             : static int
    1017           0 : fd_iavf_vfio_enable_pci( fd_iavf_vfio_t * vfio ) {
    1018           0 :   struct vfio_region_info config = {
    1019           0 :     .argsz = sizeof(config),
    1020           0 :     .index = VFIO_PCI_CONFIG_REGION_INDEX
    1021           0 :   };
    1022           0 :   if( FD_UNLIKELY( -1==ioctl( vfio->device_fd, VFIO_DEVICE_GET_REGION_INFO, &config ) ) ) return -1;
    1023           0 :   if( FD_UNLIKELY( config.size<PCI_COMMAND+sizeof(ushort) || config.offset>(ulong)LLONG_MAX-PCI_COMMAND ) ) {
    1024           0 :     errno = EPROTO;
    1025           0 :     return -1;
    1026           0 :   }
    1027             : 
    1028           0 :   off_t command_off = (off_t)(config.offset+PCI_COMMAND);
    1029           0 :   ushort command;
    1030           0 :   errno = 0;
    1031           0 :   if( FD_UNLIKELY( pread( vfio->device_fd, &command, sizeof(command), command_off )!=(ssize_t)sizeof(command) ) ) {
    1032           0 :     if( 0==errno ) errno = EIO;
    1033           0 :     return -1;
    1034           0 :   }
    1035           0 :   command = (ushort)(command | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
    1036           0 :   errno   = 0;
    1037           0 :   if( FD_UNLIKELY( pwrite( vfio->device_fd, &command, sizeof(command), command_off )!=(ssize_t)sizeof(command) ) ) {
    1038           0 :     if( 0==errno ) errno = EIO;
    1039           0 :     return -1;
    1040           0 :   }
    1041           0 :   ushort enabled;
    1042           0 :   errno = 0;
    1043           0 :   if( FD_UNLIKELY( pread( vfio->device_fd, &enabled, sizeof(enabled), command_off )!=(ssize_t)sizeof(enabled) ) ) {
    1044           0 :     if( 0==errno ) errno = EIO;
    1045           0 :     return -1;
    1046           0 :   }
    1047           0 :   if( FD_UNLIKELY( (enabled & (PCI_COMMAND_MEMORY|PCI_COMMAND_MASTER))!=(PCI_COMMAND_MEMORY|PCI_COMMAND_MASTER) ) ) {
    1048           0 :     errno = EIO;
    1049           0 :     return -1;
    1050           0 :   }
    1051           0 :   return 0;
    1052           0 : }
    1053             : 
    1054             : int
    1055           0 : fd_iavf_vfio_map_bar( fd_iavf_vfio_t * vfio ) {
    1056           0 :   if( FD_UNLIKELY( NULL==vfio || vfio->device_fd<0 ) ) {
    1057           0 :     errno = EINVAL;
    1058           0 :     return -1;
    1059           0 :   }
    1060           0 :   struct vfio_region_info bar0 = {
    1061           0 :     .argsz = sizeof(bar0),
    1062           0 :     .index = VFIO_PCI_BAR0_REGION_INDEX
    1063           0 :   };
    1064           0 :   if( FD_UNLIKELY( -1==ioctl( vfio->device_fd, VFIO_DEVICE_GET_REGION_INFO, &bar0 ) ) ) return -1;
    1065           0 :   if( FD_UNLIKELY( bar0.size<FD_IAVF_BAR0_MAP_SZ || bar0.offset>(ulong)LLONG_MAX ||
    1066           0 :                     !(bar0.flags & VFIO_REGION_INFO_FLAG_READ) ||
    1067           0 :                     !(bar0.flags & VFIO_REGION_INFO_FLAG_WRITE) ||
    1068           0 :                     !(bar0.flags & VFIO_REGION_INFO_FLAG_MMAP) ) ) {
    1069           0 :     errno = EOPNOTSUPP;
    1070           0 :     return -1;
    1071           0 :   }
    1072             : 
    1073           0 :   void * bar0_map = mmap( NULL, FD_IAVF_BAR0_MAP_SZ, PROT_READ|PROT_WRITE,
    1074           0 :                           MAP_SHARED, vfio->device_fd, (off_t)bar0.offset );
    1075           0 :   if( FD_UNLIKELY( bar0_map==MAP_FAILED ) ) return -1;
    1076           0 :   vfio->bar0 = (volatile uchar *)bar0_map;
    1077             : 
    1078           0 :   return 0;
    1079           0 : }
    1080             : 
    1081             : int
    1082           0 : fd_iavf_vfio_unmap_bar( fd_iavf_vfio_t * vfio ) {
    1083           0 :   if( FD_UNLIKELY( -1==munmap( (void *)vfio->bar0, FD_IAVF_BAR0_MAP_SZ ) ) ) return -1;
    1084           0 :   vfio->bar0 = NULL;
    1085           0 :   return 0;
    1086           0 : }
    1087             : 
    1088             : static int
    1089             : fd_iavf_vfio_init( fd_iavf_vfio_t *           vfio,
    1090           0 :                    fd_iavf_pci_info_t const * info ) {
    1091           0 :   if( FD_UNLIKELY( 0!=strcmp( info->driver, "vfio-pci" ) ) ) {
    1092           0 :     errno = EINVAL;
    1093           0 :     return -1;
    1094           0 :   }
    1095             : 
    1096           0 :   *vfio = (fd_iavf_vfio_t) {
    1097           0 :     .container_fd = -1,
    1098           0 :     .group_fd     = -1,
    1099           0 :     .device_fd    = -1
    1100           0 :   };
    1101           0 :   int          container_set = 0;
    1102           0 :   char const * operation     = "open(/dev/vfio/vfio)";
    1103             : 
    1104             :   /* Check VFIO API and IOMMU support */
    1105           0 :   vfio->container_fd = open( "/dev/vfio/vfio", O_RDWR|O_CLOEXEC );
    1106           0 :   if( FD_UNLIKELY( -1==vfio->container_fd ) ) goto fail;
    1107           0 :   operation       = "VFIO_GET_API_VERSION";
    1108           0 :   int api_version = ioctl( vfio->container_fd, VFIO_GET_API_VERSION );
    1109           0 :   if( FD_UNLIKELY( -1==api_version ) ) goto fail;
    1110           0 :   if( FD_UNLIKELY( api_version!=VFIO_API_VERSION ) ) {
    1111           0 :     operation = "unsupported VFIO API version";
    1112           0 :     errno     = EPROTONOSUPPORT;
    1113           0 :     goto fail;
    1114           0 :   }
    1115           0 :   operation             = "VFIO_CHECK_EXTENSION(TYPE1v2)";
    1116           0 :   int type1v2_supported = ioctl( vfio->container_fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1v2_IOMMU );
    1117           0 :   if( FD_UNLIKELY( -1==type1v2_supported ) ) goto fail;
    1118           0 :   if( FD_UNLIKELY( !type1v2_supported ) ) {
    1119           0 :     operation = "VFIO TYPE1v2 IOMMU unsupported";
    1120           0 :     errno     = EPROTONOSUPPORT;
    1121           0 :     goto fail;
    1122           0 :   }
    1123             : 
    1124             :   /* Attach the VF's IOMMU group to this container */
    1125           0 :   char group_path[ 64 ];
    1126           0 :   int group_path_sz = snprintf( group_path, sizeof(group_path), "/dev/vfio/%u", info->iommu_group );
    1127           0 :   FD_TEST( group_path_sz>=0 && (ulong)group_path_sz<sizeof(group_path) );
    1128           0 :   operation      = "open VFIO group";
    1129           0 :   vfio->group_fd = open( group_path, O_RDWR|O_CLOEXEC );
    1130           0 :   if( FD_UNLIKELY( -1==vfio->group_fd ) ) goto fail;
    1131             : 
    1132           0 :   operation                             = "VFIO_GROUP_GET_STATUS";
    1133           0 :   struct vfio_group_status group_status = { .argsz=sizeof(group_status) };
    1134           0 :   if( FD_UNLIKELY( -1==ioctl( vfio->group_fd, VFIO_GROUP_GET_STATUS, &group_status ) ) ) goto fail;
    1135           0 :   if( FD_UNLIKELY( !(group_status.flags & VFIO_GROUP_FLAGS_VIABLE) ) ) {
    1136           0 :     operation = "VFIO group not viable";
    1137           0 :     errno     = EBUSY;
    1138           0 :     goto fail;
    1139           0 :   }
    1140           0 :   if( FD_UNLIKELY( (group_status.flags & VFIO_GROUP_FLAGS_CONTAINER_SET) ) ) {
    1141           0 :     operation = "VFIO group already has a container";
    1142           0 :     errno     = EBUSY;
    1143           0 :     goto fail;
    1144           0 :   }
    1145           0 :   operation = "VFIO_GROUP_SET_CONTAINER";
    1146           0 :   if( FD_UNLIKELY( -1==ioctl( vfio->group_fd, VFIO_GROUP_SET_CONTAINER, &vfio->container_fd ) ) ) goto fail;
    1147           0 :   container_set = 1;
    1148           0 :   operation     = "VFIO_SET_IOMMU";
    1149           0 :   if( FD_UNLIKELY( -1==ioctl( vfio->container_fd, VFIO_SET_IOMMU, VFIO_TYPE1v2_IOMMU ) ) ) goto fail;
    1150             : 
    1151             :   /* Query page sizes for DMA mappings */
    1152           0 :   operation                               = "VFIO_IOMMU_GET_INFO";
    1153           0 :   struct vfio_iommu_type1_info iommu_info = { .argsz=sizeof(iommu_info) };
    1154           0 :   if( FD_UNLIKELY( -1==ioctl( vfio->container_fd, VFIO_IOMMU_GET_INFO, &iommu_info ) ) ) goto fail;
    1155           0 :   if( FD_UNLIKELY( !(iommu_info.flags & VFIO_IOMMU_INFO_PGSIZES) || 0==iommu_info.iova_pgsizes ) ) {
    1156           0 :     operation = "VFIO IOMMU page sizes missing";
    1157           0 :     errno     = EPROTO;
    1158           0 :     goto fail;
    1159           0 :   }
    1160           0 :   vfio->iova_pgsizes = (ulong)iommu_info.iova_pgsizes;
    1161             : 
    1162             :   /* Open the VF and map its registers */
    1163           0 :   operation       = "VFIO_GROUP_GET_DEVICE_FD";
    1164           0 :   vfio->device_fd = ioctl( vfio->group_fd, VFIO_GROUP_GET_DEVICE_FD, info->pci_addr );
    1165           0 :   if( FD_UNLIKELY( -1==vfio->device_fd ) ) goto fail;
    1166             : 
    1167           0 :   operation                           = "VFIO_DEVICE_GET_INFO";
    1168           0 :   struct vfio_device_info device_info = { .argsz=sizeof(device_info) };
    1169           0 :   if( FD_UNLIKELY( -1==ioctl( vfio->device_fd, VFIO_DEVICE_GET_INFO, &device_info ) ) ) goto fail;
    1170           0 :   if( FD_UNLIKELY( !(device_info.flags & VFIO_DEVICE_FLAGS_PCI) ||
    1171           0 :                     !(device_info.flags & VFIO_DEVICE_FLAGS_RESET) ) ) {
    1172           0 :     operation = "VFIO device lacks PCI or reset support";
    1173           0 :     errno     = EOPNOTSUPP;
    1174           0 :     goto fail;
    1175           0 :   }
    1176           0 :   if( FD_UNLIKELY( device_info.num_regions<=VFIO_PCI_BAR0_REGION_INDEX ) ) {
    1177           0 :     operation = "VFIO BAR0 region missing";
    1178           0 :     errno     = EPROTO;
    1179           0 :     goto fail;
    1180           0 :   }
    1181             : 
    1182           0 :   operation = "VFIO BAR0 mapping";
    1183           0 :   if( FD_UNLIKELY( -1==fd_iavf_vfio_map_bar( vfio ) ) ) goto fail;
    1184             : 
    1185             :   /* Reset the VF and wait for the PF to report it ready */
    1186           0 :   operation = "VFIO_DEVICE_RESET";
    1187           0 :   if( FD_UNLIKELY( -1==ioctl( vfio->device_fd, VFIO_DEVICE_RESET ) ) ) goto fail;
    1188           0 :   operation = "VF reset wait";
    1189           0 :   if( FD_UNLIKELY( -1==fd_iavf_vfio_wait_reset( vfio ) ) ) goto fail;
    1190           0 :   operation = "PCI memory and bus mastering enable";
    1191           0 :   if( FD_UNLIKELY( -1==fd_iavf_vfio_enable_pci( vfio ) ) ) goto fail;
    1192           0 :   return 0;
    1193             : 
    1194           0 : fail:
    1195           0 :   {
    1196           0 :     int err = errno;
    1197           0 :     FD_LOG_WARNING(( "%s failed for PCI %s (%i-%s)", operation, info->pci_addr, err, fd_io_strerror( err ) ));
    1198           0 :     fd_iavf_vfio_cleanup( vfio, container_set );
    1199           0 :     errno = err;
    1200           0 :     return -1;
    1201           0 :   }
    1202           0 : }
    1203             : 
    1204             : int
    1205             : fd_iavf_vfio_dma_map( fd_iavf_vfio_t * vfio,
    1206             :                       void *           memory,
    1207             :                       ulong            memory_sz,
    1208          18 :                       ulong            iova ) {
    1209          18 :   if( FD_UNLIKELY( NULL==vfio || vfio->container_fd<0 || NULL==memory || 0==memory_sz || 0==vfio->iova_pgsizes ) ) {
    1210           6 :     errno = EINVAL;
    1211           6 :     return -1;
    1212           6 :   }
    1213             : 
    1214             :   /* The smallest supported IOMMU page size sets mapping alignment */
    1215          12 :   ulong page_sz = vfio->iova_pgsizes & -vfio->iova_pgsizes;
    1216          12 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)memory, page_sz ) ||
    1217          12 :                    !fd_ulong_is_aligned( memory_sz,     page_sz ) ||
    1218          12 :                    !fd_ulong_is_aligned( iova,          page_sz ) ||
    1219          12 :                    (ulong)memory>ULONG_MAX-memory_sz ||
    1220          12 :                    memory_sz>ULONG_MAX-iova ) ) {
    1221           9 :     FD_LOG_WARNING(( "invalid DMA mapping bounds or alignment, memory %p, size %lu, IOVA %#lx, required alignment %lu", memory, memory_sz, iova, page_sz ));
    1222           9 :     errno = EINVAL;
    1223           9 :     return -1;
    1224           9 :   }
    1225             : 
    1226           3 :   struct vfio_iommu_type1_dma_map dma_map = {
    1227           3 :     .argsz = sizeof(dma_map),
    1228           3 :     .flags = VFIO_DMA_MAP_FLAG_READ|VFIO_DMA_MAP_FLAG_WRITE,
    1229           3 :     .vaddr = (ulong)memory,
    1230           3 :     .iova  = iova,
    1231           3 :     .size  = memory_sz
    1232           3 :   };
    1233           3 :   if( FD_UNLIKELY( -1==ioctl( vfio->container_fd, VFIO_IOMMU_MAP_DMA, &dma_map ) ) ) {
    1234           3 :     int err = errno;
    1235           3 :     FD_LOG_WARNING(( "VFIO_IOMMU_MAP_DMA failed, size %lu, IOVA %#lx (%i-%s)", memory_sz, iova, err, fd_io_strerror( err ) ));
    1236           3 :     errno = err;
    1237           3 :     return -1;
    1238           3 :   }
    1239           0 :   return 0;
    1240           3 : }
    1241             : 
    1242             : static inline uint
    1243             : fd_iavf_mmio_read( fd_iavf_vfio_t const * vfio,
    1244         753 :                    ulong                  reg ) {
    1245         753 :   volatile uint const * ptr   = (volatile uint const *)(vfio->bar0 + reg);
    1246         753 :   uint                  value = *ptr;
    1247         753 :   FD_COMPILER_MFENCE();
    1248         753 :   return value;
    1249         753 : }
    1250             : 
    1251             : static inline void
    1252             : fd_iavf_mmio_write( fd_iavf_vfio_t * vfio,
    1253             :                     ulong            reg,
    1254          72 :                     uint             value ) {
    1255          72 :   FD_COMPILER_MFENCE();
    1256          72 :   volatile uint * ptr = (volatile uint *)(vfio->bar0 + reg);
    1257          72 :   *ptr                = value;
    1258          72 :   FD_COMPILER_MFENCE();
    1259          72 : }
    1260             : 
    1261             : static int
    1262             : fd_iavf_mmio_verify( fd_iavf_vfio_t const * vfio,
    1263             :                      ulong                  reg,
    1264           0 :                      uint                   expected ) {
    1265           0 :   uint const actual = fd_iavf_mmio_read( vfio, reg );
    1266           0 :   if( FD_UNLIKELY( actual!=expected ) ) {
    1267           0 :     FD_LOG_WARNING(( "register %#lx read %#x, expected %#x (%i-%s)", reg, actual, expected, EIO, fd_io_strerror( EIO ) ));
    1268           0 :     errno = EIO;
    1269           0 :     return -1;
    1270           0 :   }
    1271           0 :   return 0;
    1272           0 : }
    1273             : 
    1274             : static inline fd_iavf_aq_desc_t *
    1275             : fd_iavf_adminq_atq_desc( fd_iavf_adminq_t * adminq,
    1276          24 :                          uint               idx ) {
    1277          24 :   return (fd_iavf_aq_desc_t *)((uchar *)adminq->dma_memory + FD_IAVF_ADMINQ_SEND_DESC_OFF) + idx;
    1278          24 : }
    1279             : 
    1280             : static inline fd_iavf_aq_desc_t *
    1281             : fd_iavf_adminq_arq_desc( fd_iavf_adminq_t * adminq,
    1282          96 :                          uint               idx ) {
    1283          96 :   return (fd_iavf_aq_desc_t *)((uchar *)adminq->dma_memory + FD_IAVF_ADMINQ_RECV_DESC_OFF) + idx;
    1284          96 : }
    1285             : 
    1286             : static inline uchar *
    1287             : fd_iavf_adminq_atq_buf( fd_iavf_adminq_t * adminq,
    1288          24 :                         uint               idx ) {
    1289          24 :   return (uchar *)adminq->dma_memory + FD_IAVF_ADMINQ_SEND_BUF_OFF + (ulong)idx*FD_IAVF_ADMINQ_BUF_SZ;
    1290          24 : }
    1291             : 
    1292             : static inline uchar *
    1293             : fd_iavf_adminq_arq_buf( fd_iavf_adminq_t * adminq,
    1294          48 :                         uint               idx ) {
    1295          48 :   return (uchar *)adminq->dma_memory + FD_IAVF_ADMINQ_RECV_BUF_OFF + (ulong)idx*FD_IAVF_ADMINQ_BUF_SZ;
    1296          48 : }
    1297             : 
    1298             : static inline ulong
    1299             : fd_iavf_adminq_atq_buf_iova( fd_iavf_adminq_t const * adminq,
    1300          24 :                              uint                     idx ) {
    1301          24 :   return adminq->dma_iova + FD_IAVF_ADMINQ_SEND_BUF_OFF + (ulong)idx*FD_IAVF_ADMINQ_BUF_SZ;
    1302          24 : }
    1303             : 
    1304             : static inline ulong
    1305             : fd_iavf_adminq_arq_buf_iova( fd_iavf_adminq_t const * adminq,
    1306          48 :                              uint                     idx ) {
    1307          48 :   return adminq->dma_iova + FD_IAVF_ADMINQ_RECV_BUF_OFF + (ulong)idx*FD_IAVF_ADMINQ_BUF_SZ;
    1308          48 : }
    1309             : 
    1310             : static inline void
    1311             : fd_iavf_adminq_desc_set_addr( fd_iavf_aq_desc_t * desc,
    1312          72 :                               ulong               iova ) {
    1313          72 :   desc->addr_high = (uint)(iova>>32);
    1314          72 :   desc->addr_low  = (uint)iova;
    1315          72 : }
    1316             : 
    1317             : static void
    1318             : fd_iavf_adminq_arq_post( fd_iavf_adminq_t * adminq,
    1319          48 :                          uint               idx ) {
    1320          48 :   fd_iavf_aq_desc_t * desc = fd_iavf_adminq_arq_desc( adminq, idx );
    1321          48 :   fd_memset( desc, 0, sizeof(*desc) );
    1322          48 :   desc->flags   = FD_IAVF_AQ_FLAG_BUF|FD_IAVF_AQ_FLAG_LB;
    1323          48 :   desc->datalen = FD_IAVF_ADMINQ_BUF_SZ;
    1324          48 :   fd_iavf_adminq_desc_set_addr( desc, fd_iavf_adminq_arq_buf_iova( adminq, idx ) );
    1325          48 : }
    1326             : 
    1327             : ulong
    1328          45 : fd_iavf_adminq_footprint( void ) {
    1329          45 :   return FD_IAVF_ADMINQ_FOOTPRINT;
    1330          45 : }
    1331             : 
    1332             : static int
    1333             : fd_iavf_adminq_init( fd_iavf_vfio_t *   vfio,
    1334             :                      fd_iavf_adminq_t * adminq,
    1335             :                      void *             dma_memory,
    1336           0 :                      ulong              dma_iova ) {
    1337           0 :   if( FD_UNLIKELY( NULL==dma_memory ||
    1338           0 :                    !fd_ulong_is_aligned( (ulong)dma_memory, FD_IAVF_PAGE_SZ ) ||
    1339           0 :                    !fd_ulong_is_aligned( dma_iova,          FD_IAVF_PAGE_SZ ) ||
    1340           0 :                    0==(vfio->iova_pgsizes & FD_IAVF_PAGE_SZ) ) ) {
    1341           0 :     errno = EINVAL;
    1342           0 :     return -1;
    1343           0 :   }
    1344             : 
    1345             :   /* Map Admin Queue rings and prepare receive buffers */
    1346           0 :   fd_memset( dma_memory, 0, FD_IAVF_ADMINQ_FOOTPRINT );
    1347           0 :   if( FD_UNLIKELY( -1==fd_iavf_vfio_dma_map( vfio, dma_memory, FD_IAVF_ADMINQ_FOOTPRINT, dma_iova ) ) ) return -1;
    1348             : 
    1349           0 :   *adminq = (fd_iavf_adminq_t) {
    1350           0 :     .dma_memory = dma_memory,
    1351           0 :     .dma_iova   = dma_iova
    1352           0 :   };
    1353           0 :   for( uint idx=0U; idx<(uint)FD_IAVF_ADMINQ_DEPTH; idx++ ) fd_iavf_adminq_arq_post( adminq, idx );
    1354           0 :   fd_iavf_hw_dma_to_device();
    1355             : 
    1356             :   /* Enable ATQ for outgoing PF requests */
    1357           0 :   ulong atq_iova = dma_iova + FD_IAVF_ADMINQ_SEND_DESC_OFF;
    1358           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ATQH,   0U );
    1359           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ATQT,   0U );
    1360           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ATQLEN, (uint)FD_IAVF_ADMINQ_DEPTH|FD_IAVF_ATQ_ENABLE );
    1361           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ATQBAL, (uint)atq_iova );
    1362           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ATQBAH, (uint)(atq_iova>>32) );
    1363             : 
    1364             :   /* Enable ARQ and post buffers for PF replies and events */
    1365           0 :   ulong arq_iova = dma_iova + FD_IAVF_ADMINQ_RECV_DESC_OFF;
    1366           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ARQH,   0U );
    1367           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ARQT,   0U );
    1368           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ARQLEN, (uint)FD_IAVF_ADMINQ_DEPTH|FD_IAVF_ARQ_ENABLE );
    1369           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ARQBAL, (uint)arq_iova );
    1370           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ARQBAH, (uint)(arq_iova>>32) );
    1371           0 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ARQT,   (uint)FD_IAVF_ADMINQ_DEPTH-1U );
    1372             : 
    1373           0 :   if( FD_UNLIKELY( -1==fd_iavf_mmio_verify( vfio, FD_IAVF_VF_ATQBAL, (uint)atq_iova ) ||
    1374           0 :                    -1==fd_iavf_mmio_verify( vfio, FD_IAVF_VF_ATQBAH, (uint)(atq_iova>>32) ) ||
    1375           0 :                    -1==fd_iavf_mmio_verify( vfio, FD_IAVF_VF_ARQBAL, (uint)arq_iova ) ||
    1376           0 :                    -1==fd_iavf_mmio_verify( vfio, FD_IAVF_VF_ARQBAH, (uint)(arq_iova>>32) ) ||
    1377           0 :                    -1==fd_iavf_mmio_verify( vfio, FD_IAVF_VF_ATQLEN, (uint)FD_IAVF_ADMINQ_DEPTH|FD_IAVF_ATQ_ENABLE ) ||
    1378           0 :                    -1==fd_iavf_mmio_verify( vfio, FD_IAVF_VF_ARQLEN, (uint)FD_IAVF_ADMINQ_DEPTH|FD_IAVF_ARQ_ENABLE ) ) ) return -1;
    1379           0 :   return 0;
    1380           0 : }
    1381             : 
    1382             : static char const *
    1383           0 : fd_iavf_virtchnl_op_name( uint op ) {
    1384           0 :   switch( op ) {
    1385           0 :   case FD_IAVF_VIRTCHNL_VERSION:          return "version exchange";
    1386           0 :   case FD_IAVF_VIRTCHNL_GET_VF_RESOURCES: return "VF resource discovery";
    1387           0 :   case FD_IAVF_VIRTCHNL_CONFIG_QUEUES:    return "queue configuration";
    1388           0 :   case FD_IAVF_VIRTCHNL_CONFIG_IRQ_MAP:   return "IRQ mapping";
    1389           0 :   case FD_IAVF_VIRTCHNL_ENABLE_QUEUES:    return "queue enable";
    1390           0 :   case FD_IAVF_VIRTCHNL_ADD_ETH_ADDR:     return "MAC registration";
    1391           0 :   case FD_IAVF_VIRTCHNL_CONFIG_RSS_KEY:   return "RSS key configuration";
    1392           0 :   case FD_IAVF_VIRTCHNL_CONFIG_RSS_LUT:   return "RSS table configuration";
    1393           0 :   default:                                return "virtchnl request";
    1394           0 :   }
    1395           0 : }
    1396             : 
    1397             : static int
    1398             : fd_iavf_adminq_send( fd_iavf_vfio_t *   vfio,
    1399             :                      fd_iavf_adminq_t * adminq,
    1400             :                      uint               virtchnl_op,
    1401             :                      void const *       message,
    1402          24 :                      ulong              message_sz ) {
    1403          24 :   uint                idx  = adminq->atq_prod;
    1404          24 :   uint                next = (idx+1U) & ((uint)FD_IAVF_ADMINQ_DEPTH-1U);
    1405          24 :   fd_iavf_aq_desc_t * desc = fd_iavf_adminq_atq_desc( adminq, idx );
    1406          24 :   fd_memset( desc, 0, sizeof(*desc) );
    1407          24 :   desc->flags       = FD_IAVF_AQ_FLAG_SI;
    1408          24 :   desc->opcode      = FD_IAVF_AQ_SEND_MSG_TO_PF;
    1409          24 :   desc->cookie_high = virtchnl_op;
    1410          24 :   if( 0!=message_sz ) {
    1411          24 :     fd_memcpy( fd_iavf_adminq_atq_buf( adminq, idx ), message, message_sz );
    1412          24 :     desc->flags   |= FD_IAVF_AQ_FLAG_BUF|FD_IAVF_AQ_FLAG_RD;
    1413             :     /* Match Linux IAVF's descriptor flags for large requests. */
    1414          24 :     if( message_sz>FD_IAVF_AQ_LARGE_BUF ) desc->flags |= FD_IAVF_AQ_FLAG_LB;
    1415          24 :     desc->datalen = (ushort)message_sz;
    1416          24 :     fd_iavf_adminq_desc_set_addr( desc, fd_iavf_adminq_atq_buf_iova( adminq, idx ) );
    1417          24 :   }
    1418             :   /* Publish the descriptor and payload before advancing ATQ tail */
    1419          24 :   fd_iavf_hw_dma_to_device();
    1420          24 :   adminq->atq_prod = next;
    1421          24 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ATQT, next );
    1422             : 
    1423             :   /* Wait for send completion, the PF's reply arrives separately on ARQ */
    1424          24 :   struct timespec delay = { .tv_sec=0L, .tv_nsec=FD_IAVF_POLL_INTERVAL_NS };
    1425          24 :   for( ulong retry=0UL; retry<FD_IAVF_ADMINQ_RETRY_CNT; retry++ ) {
    1426          24 :     if( (fd_iavf_mmio_read( vfio, FD_IAVF_VF_ATQH ) & FD_IAVF_ATQ_HEAD)==next ) {
    1427          24 :       fd_iavf_hw_dma_from_device();
    1428          24 :       if( FD_UNLIKELY( (desc->flags & FD_IAVF_AQ_FLAG_ERR) || 0!=desc->retval ) ) {
    1429           0 :         FD_LOG_WARNING(( "%s Admin Queue send failed, status %hu (%i-%s)", fd_iavf_virtchnl_op_name( virtchnl_op ), desc->retval, EIO, fd_io_strerror( EIO ) ));
    1430           0 :         errno = EIO;
    1431           0 :         return -1;
    1432           0 :       }
    1433          24 :       return 0;
    1434          24 :     }
    1435           0 :     if( FD_UNLIKELY( -1==nanosleep( &delay, NULL ) && errno!=EINTR ) ) {
    1436           0 :       int err = errno;
    1437           0 :       FD_LOG_WARNING(( "%s send wait failed (%i-%s)", fd_iavf_virtchnl_op_name( virtchnl_op ), err, fd_io_strerror( err ) ));
    1438           0 :       errno = err;
    1439           0 :       return -1;
    1440           0 :     }
    1441           0 :   }
    1442           0 :   FD_LOG_WARNING(( "%s Admin Queue send timed out (%i-%s)", fd_iavf_virtchnl_op_name( virtchnl_op ), ETIMEDOUT, fd_io_strerror( ETIMEDOUT ) ));
    1443           0 :   errno = ETIMEDOUT;
    1444           0 :   return -1;
    1445          24 : }
    1446             : 
    1447             : static int
    1448             : fd_iavf_adminq_recv( fd_iavf_vfio_t *   vfio,
    1449             :                      fd_iavf_adminq_t * adminq,
    1450             :                      uint *             virtchnl_op,
    1451             :                      int *              virtchnl_status,
    1452             :                      void *             message,
    1453         666 :                      ulong *            message_sz ) {
    1454         666 :   uint head = fd_iavf_mmio_read( vfio, FD_IAVF_VF_ARQH ) & FD_IAVF_ARQ_HEAD;
    1455         666 :   uint idx  = adminq->arq_cons;
    1456         666 :   if( head==idx ) return 0;
    1457             : 
    1458             :   /* Read the message before returning its buffer to the NIC */
    1459          48 :   fd_iavf_hw_dma_from_device();
    1460          48 :   fd_iavf_aq_desc_t * desc      = fd_iavf_adminq_arq_desc( adminq, idx );
    1461          48 :   fd_iavf_aq_desc_t   comp_desc = *desc;
    1462          48 :   ulong               comp_sz   = comp_desc.datalen;
    1463          48 :   int                 err       = 0;
    1464          48 :   if( FD_UNLIKELY( comp_desc.opcode!=FD_IAVF_AQ_SEND_MSG_TO_VF ||
    1465          48 :                    (comp_desc.flags & FD_IAVF_AQ_FLAG_ERR) || 0!=comp_desc.retval ) ) err = EIO;
    1466          48 :   else if( FD_UNLIKELY( comp_sz>*message_sz ) ) err = EMSGSIZE;
    1467          48 :   else if( 0!=comp_sz ) fd_memcpy( message, fd_iavf_adminq_arq_buf( adminq, idx ), comp_sz );
    1468             : 
    1469             :   /* ARQ tail names the last returned descriptor */
    1470          48 :   fd_iavf_adminq_arq_post( adminq, idx );
    1471          48 :   fd_iavf_hw_dma_to_device();
    1472          48 :   fd_iavf_mmio_write( vfio, FD_IAVF_VF_ARQT, idx );
    1473          48 :   adminq->arq_cons = (idx+1U) & ((uint)FD_IAVF_ADMINQ_DEPTH-1U);
    1474             : 
    1475          48 :   if( FD_UNLIKELY( 0!=err ) ) {
    1476           0 :     errno = err;
    1477           0 :     return -1;
    1478           0 :   }
    1479          48 :   *virtchnl_op     = comp_desc.cookie_high;
    1480          48 :   *virtchnl_status = (int)comp_desc.cookie_low;
    1481          48 :   *message_sz      = comp_sz;
    1482          48 :   return 1;
    1483          48 : }
    1484             : 
    1485             : static int
    1486             : fd_iavf_virtchnl_cache_event( fd_iavf_adminq_t * adminq,
    1487             :                               uint               op,
    1488             :                               int                status,
    1489             :                               void const *       message,
    1490          27 :                               ulong              message_sz ) {
    1491          27 :   if( FD_UNLIKELY( op!=FD_IAVF_VIRTCHNL_EVENT || 0!=status ||
    1492          27 :                    message_sz!=sizeof(fd_iavf_virtchnl_event_t) ) ) {
    1493           0 :     FD_LOG_WARNING(( "invalid PF event, operation %u, status %i, size %lu, expected %lu (%i-%s)",
    1494           0 :                      op, status, message_sz, sizeof(fd_iavf_virtchnl_event_t), EPROTO, fd_io_strerror( EPROTO ) ));
    1495           0 :     errno = EPROTO;
    1496           0 :     return -1;
    1497           0 :   }
    1498          27 :   fd_iavf_virtchnl_event_t event;
    1499          27 :   fd_memcpy( &event, message, sizeof(event) );
    1500          27 :   if( FD_UNLIKELY( event.event==FD_IAVF_EVENT_RESET_IMPENDING ) ) {
    1501           3 :     FD_LOG_WARNING(( "PF reset impending (%i-%s)", ECONNRESET, fd_io_strerror( ECONNRESET ) ));
    1502           3 :     errno = ECONNRESET;
    1503           3 :     return -1;
    1504           3 :   }
    1505          24 :   if( FD_UNLIKELY( event.event==FD_IAVF_EVENT_PF_DRIVER_CLOSE ) ) {
    1506           3 :     FD_LOG_WARNING(( "PF driver closed (%i-%s)", ESHUTDOWN, fd_io_strerror( ESHUTDOWN ) ));
    1507           3 :     errno = ESHUTDOWN;
    1508           3 :     return -1;
    1509           3 :   }
    1510             :   /* Keep early link reports until resource discovery can apply them */
    1511          21 :   fd_memcpy( adminq->pending_event, message, message_sz );
    1512          21 :   adminq->pending_event_sz = message_sz;
    1513          21 :   return 0;
    1514          24 : }
    1515             : 
    1516             : static int
    1517             : fd_iavf_virtchnl_request( fd_iavf_vfio_t *   vfio,
    1518             :                           fd_iavf_adminq_t * adminq,
    1519             :                           uint               virtchnl_op,
    1520             :                           void const *       request,
    1521             :                           ulong              request_sz,
    1522             :                           void *             response,
    1523          24 :                           ulong *            response_sz ) {
    1524          24 :   ulong response_capacity = *response_sz;
    1525          24 :   *response_sz            = 0UL;
    1526          24 :   if( FD_UNLIKELY( -1==fd_iavf_adminq_send( vfio, adminq, virtchnl_op, request, request_sz ) ) ) return -1;
    1527             : 
    1528          24 :   struct timespec delay = { .tv_sec=0L, .tv_nsec=FD_IAVF_POLL_INTERVAL_NS };
    1529          27 :   for( ulong retry=0UL; retry<FD_IAVF_ADMINQ_RETRY_CNT; retry++ ) {
    1530          27 :     uchar message[ FD_IAVF_ADMINQ_BUF_SZ ];
    1531          27 :     ulong message_sz = sizeof(message);
    1532          27 :     uint received_op;
    1533          27 :     int  received_status;
    1534          27 :     int received = fd_iavf_adminq_recv( vfio, adminq, &received_op, &received_status,
    1535          27 :                                         message, &message_sz );
    1536          27 :     if( FD_UNLIKELY( -1==received ) ) goto fail;
    1537          27 :     if( 0==received ) {
    1538           0 :       if( FD_UNLIKELY( -1==nanosleep( &delay, NULL ) && errno!=EINTR ) ) goto fail;
    1539           0 :       continue;
    1540           0 :     }
    1541             :     /* PF events can arrive before the request's reply */
    1542          27 :     if( received_op==FD_IAVF_VIRTCHNL_EVENT ) {
    1543           6 :       if( FD_UNLIKELY( -1==fd_iavf_virtchnl_cache_event( adminq, received_op, received_status,
    1544           6 :                                                          message, message_sz ) ) ) return -1;
    1545           3 :       continue;
    1546           6 :     }
    1547          21 :     if( FD_UNLIKELY( received_op!=virtchnl_op ) ) {
    1548           0 :       FD_LOG_WARNING(( "unexpected PF operation %u during %s (%i-%s)", received_op, fd_iavf_virtchnl_op_name( virtchnl_op ), EPROTO, fd_io_strerror( EPROTO ) ));
    1549           0 :       errno = EPROTO;
    1550           0 :       return -1;
    1551           0 :     }
    1552          21 :     if( FD_UNLIKELY( 0!=received_status ) ) {
    1553           0 :       FD_LOG_WARNING(( "%s rejected, PF status %i (%i-%s)", fd_iavf_virtchnl_op_name( virtchnl_op ), received_status, EIO, fd_io_strerror( EIO ) ));
    1554           0 :       errno = EIO;
    1555           0 :       return -1;
    1556           0 :     }
    1557          21 :     if( FD_UNLIKELY( message_sz>response_capacity ) ) {
    1558           0 :       FD_LOG_WARNING(( "%s response size %lu exceeds %lu (%i-%s)", fd_iavf_virtchnl_op_name( virtchnl_op ), message_sz, response_capacity, EMSGSIZE, fd_io_strerror( EMSGSIZE ) ));
    1559           0 :       errno = EMSGSIZE;
    1560           0 :       return -1;
    1561           0 :     }
    1562          21 :     if( 0!=message_sz ) fd_memcpy( response, message, message_sz );
    1563          21 :     *response_sz = message_sz;
    1564          21 :     return 0;
    1565          21 :   }
    1566          24 :   errno = ETIMEDOUT;
    1567           0 : fail:
    1568           0 :   {
    1569           0 :     int err = errno;
    1570           0 :     FD_LOG_WARNING(( "%s PF reply failed (%i-%s)", fd_iavf_virtchnl_op_name( virtchnl_op ), err, fd_io_strerror( err ) ));
    1571           0 :     errno = err;
    1572           0 :     return -1;
    1573           0 :   }
    1574           0 : }
    1575             : 
    1576             : int
    1577             : fd_iavf_virtchnl_version( fd_iavf_vfio_t *   vfio,
    1578           9 :                           fd_iavf_adminq_t * adminq ) {
    1579           9 :   fd_iavf_virtchnl_version_t requested = { .major=1U, .minor=1U };
    1580           9 :   fd_iavf_virtchnl_version_t response;
    1581           9 :   ulong response_sz = sizeof(response);
    1582           9 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_request( vfio, adminq, FD_IAVF_VIRTCHNL_VERSION,
    1583           9 :                                                  &requested, sizeof(requested),
    1584           9 :                                                  &response, &response_sz ) ) ) return -1;
    1585           6 :   if( FD_UNLIKELY( response_sz!=sizeof(response) ) ) {
    1586           0 :     FD_LOG_WARNING(( "unsupported virtchnl version reply, size %lu (%i-%s)", response_sz, EPROTONOSUPPORT, fd_io_strerror( EPROTONOSUPPORT ) ));
    1587           0 :     errno = EPROTONOSUPPORT;
    1588           0 :     return -1;
    1589           0 :   }
    1590           6 :   if( FD_UNLIKELY( response.major!=1U || response.minor!=1U ) ) {
    1591           3 :     FD_LOG_WARNING(( "unsupported virtchnl version %u.%u (%i-%s)", response.major, response.minor, EPROTONOSUPPORT, fd_io_strerror( EPROTONOSUPPORT ) ));
    1592           3 :     errno = EPROTONOSUPPORT;
    1593           3 :     return -1;
    1594           3 :   }
    1595           3 :   return 0;
    1596           6 : }
    1597             : 
    1598             : /* fd_iavf_virtchnl_apply_event returns 1 for a link report, including down */
    1599             : static int
    1600             : fd_iavf_virtchnl_apply_event( fd_iavf_adminq_t *  adminq,
    1601          54 :                              fd_iavf_vf_info_t * info ) {
    1602          54 :   if( 0==adminq->pending_event_sz ) return 0;
    1603          21 :   fd_iavf_virtchnl_event_t event;
    1604          21 :   fd_memcpy( &event, adminq->pending_event, sizeof(event) );
    1605          21 :   adminq->pending_event_sz = 0UL;
    1606          21 :   if( event.event==FD_IAVF_EVENT_LINK_CHANGE ) {
    1607          21 :     info->link_usable = !!event.link_up;
    1608          21 :     return 1;
    1609          21 :   }
    1610           0 :   return 0;
    1611          21 : }
    1612             : 
    1613             : /* fd_iavf_virtchnl_wait_link_event accepts an initial down or unknown link
    1614             :    Tiles exclude the VF from packet traffic until an up report arrives */
    1615             : static int
    1616             : fd_iavf_virtchnl_wait_link_event( fd_iavf_vfio_t *    vfio,
    1617             :                                  fd_iavf_adminq_t *  adminq,
    1618          15 :                                  fd_iavf_vf_info_t * info ) {
    1619          15 :   if( fd_iavf_virtchnl_apply_event( adminq, info ) ) return 0;
    1620             : 
    1621          12 :   struct timespec delay = { .tv_sec=0L, .tv_nsec=FD_IAVF_POLL_INTERVAL_NS };
    1622         612 :   for( ulong retry=0UL; retry<FD_IAVF_LINK_RETRY_CNT; retry++ ) {
    1623         606 :     uchar message[ FD_IAVF_ADMINQ_BUF_SZ ];
    1624         606 :     ulong message_sz = sizeof(message);
    1625         606 :     uint virtchnl_op;
    1626         606 :     int  virtchnl_status;
    1627         606 :     int received = fd_iavf_adminq_recv( vfio, adminq, &virtchnl_op, &virtchnl_status,
    1628         606 :                                         message, &message_sz );
    1629         606 :     if( FD_UNLIKELY( -1==received ) ) {
    1630           0 :       int err = errno;
    1631           0 :       FD_LOG_WARNING(( "link event receive failed (%i-%s)", err, fd_io_strerror( err ) ));
    1632           0 :       errno = err;
    1633           0 :       return -1;
    1634           0 :     }
    1635         606 :     if( 0==received ) {
    1636         600 :       if( FD_UNLIKELY( -1==nanosleep( &delay, NULL ) && errno!=EINTR ) ) {
    1637           0 :         int err = errno;
    1638           0 :         FD_LOG_WARNING(( "link event wait failed (%i-%s)", err, fd_io_strerror( err ) ));
    1639           0 :         errno = err;
    1640           0 :         return -1;
    1641           0 :       }
    1642         600 :       continue;
    1643         600 :     }
    1644           6 :     if( FD_UNLIKELY( -1==fd_iavf_virtchnl_cache_event( adminq, virtchnl_op, virtchnl_status,
    1645           6 :                                                        message, message_sz ) ) ) return -1;
    1646           6 :     if( fd_iavf_virtchnl_apply_event( adminq, info ) ) return 0;
    1647           6 :   }
    1648           6 :   return 0;
    1649          12 : }
    1650             : 
    1651             : int
    1652             : fd_iavf_virtchnl_get_resources( fd_iavf_vfio_t *    vfio,
    1653             :                                 fd_iavf_adminq_t *  adminq,
    1654          15 :                                 fd_iavf_vf_info_t * info ) {
    1655          15 :   fd_memset( info, 0, sizeof(*info) );
    1656             : 
    1657             :   /* Request Ethernet, timed writeback and PF-managed RSS */
    1658          15 :   uint requested_caps = FD_IAVF_VIRTCHNL_CAP_L2 | FD_IAVF_VIRTCHNL_CAP_WB_ON_ITR | FD_IAVF_VIRTCHNL_CAP_RSS_PF;
    1659          15 :   uchar response[ FD_IAVF_ADMINQ_BUF_SZ ];
    1660          15 :   ulong response_sz = sizeof(response);
    1661          15 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_request( vfio, adminq, FD_IAVF_VIRTCHNL_GET_VF_RESOURCES,
    1662          15 :                                                  &requested_caps, sizeof(requested_caps),
    1663          15 :                                                  response, &response_sz ) ) ) return -1;
    1664          15 :   if( FD_UNLIKELY( response_sz<sizeof(fd_iavf_virtchnl_vf_resource_t) ) ) {
    1665           0 :     FD_LOG_WARNING(( "VF resource reply too short, %lu bytes (%i-%s)", response_sz, EPROTO, fd_io_strerror( EPROTO ) ));
    1666           0 :     errno = EPROTO;
    1667           0 :     return -1;
    1668           0 :   }
    1669             : 
    1670             :   /* Validate reply length and required capabilities */
    1671          15 :   fd_iavf_virtchnl_vf_resource_t resources;
    1672          15 :   fd_memcpy( &resources, response, sizeof(resources) );
    1673          15 :   ulong expected_sz = sizeof(resources) + (ulong)resources.num_vsis*sizeof(fd_iavf_virtchnl_vsi_resource_t);
    1674             :   /* Linux i40e includes one extra VSI entry in the reply length. */
    1675          15 :   ulong legacy_sz = expected_sz + sizeof(fd_iavf_virtchnl_vsi_resource_t);
    1676          15 :   if( FD_UNLIKELY( 0==resources.num_vsis || 0==resources.num_queue_pairs ||
    1677          15 :                    !(resources.capability_flags & FD_IAVF_VIRTCHNL_CAP_L2) ) ) {
    1678           0 :     FD_LOG_WARNING(( "invalid VF resources, VSIs %hu, queue pairs %hu, capabilities %#x (L2 required) (%i-%s)",
    1679           0 :                      resources.num_vsis, resources.num_queue_pairs, resources.capability_flags, EPROTO, fd_io_strerror( EPROTO ) ));
    1680           0 :     errno = EPROTO;
    1681           0 :     return -1;
    1682           0 :   }
    1683          15 :   if( FD_UNLIKELY( response_sz!=expected_sz && response_sz!=legacy_sz ) ) {
    1684           0 :     FD_LOG_WARNING(( "VF resource reply size %lu, expected %lu or %lu for %hu VSIs (%i-%s)",
    1685           0 :                      response_sz, expected_sz, legacy_sz, resources.num_vsis, EPROTO, fd_io_strerror( EPROTO ) ));
    1686           0 :     errno = EPROTO;
    1687           0 :     return -1;
    1688           0 :   }
    1689          15 :   if( FD_UNLIKELY( !(resources.capability_flags & FD_IAVF_VIRTCHNL_CAP_RSS_PF) ) ) {
    1690           0 :     FD_LOG_WARNING(( "PF does not support RSS configuration (%i-%s)", EPROTONOSUPPORT, fd_io_strerror( EPROTONOSUPPORT ) ));
    1691           0 :     errno = EPROTONOSUPPORT;
    1692           0 :     return -1;
    1693           0 :   }
    1694             : 
    1695             :   /* Select the SR-IOV VSI used for packet queues */
    1696          15 :   fd_iavf_virtchnl_vsi_resource_t selected     = {0};
    1697          15 :   ulong                           selected_cnt = 0UL;
    1698          30 :   for( ulong vsi_idx=0UL; vsi_idx<(ulong)resources.num_vsis; vsi_idx++ ) {
    1699          15 :     fd_iavf_virtchnl_vsi_resource_t vsi;
    1700          15 :     fd_memcpy( &vsi, response+sizeof(resources)+vsi_idx*sizeof(vsi), sizeof(vsi) );
    1701          15 :     if( vsi.vsi_type==FD_IAVF_VIRTCHNL_VSI_SRIOV ) {
    1702          15 :       selected = vsi;
    1703          15 :       selected_cnt++;
    1704          15 :     }
    1705          15 :   }
    1706          15 :   if( FD_UNLIKELY( selected_cnt!=1UL ) ) {
    1707           0 :     FD_LOG_WARNING(( "VF resources contain %lu SR-IOV VSIs, expected 1 (%i-%s)", selected_cnt, EPROTO, fd_io_strerror( EPROTO ) ));
    1708           0 :     errno = EPROTO;
    1709           0 :     return -1;
    1710           0 :   }
    1711          15 :   if( FD_UNLIKELY( 0==selected.num_queue_pairs ) ) {
    1712           0 :     FD_LOG_WARNING(( "SR-IOV VSI %hu has no queue pairs (%i-%s)", selected.vsi_id, EPROTO, fd_io_strerror( EPROTO ) ));
    1713           0 :     errno = EPROTO;
    1714           0 :     return -1;
    1715           0 :   }
    1716          15 :   if( FD_UNLIKELY( 0!=(selected.default_mac_addr[0] & 1U) ) ) {
    1717           0 :     FD_LOG_WARNING(( "SR-IOV VSI %hu MAC " FD_ETH_MAC_FMT " is multicast (%i-%s)",
    1718           0 :                      selected.vsi_id, FD_ETH_MAC_FMT_ARGS( selected.default_mac_addr ), EPROTO, fd_io_strerror( EPROTO ) ));
    1719           0 :     errno = EPROTO;
    1720           0 :     return -1;
    1721           0 :   }
    1722          15 :   ulong mac_bits = 0UL;
    1723          15 :   fd_memcpy( &mac_bits, selected.default_mac_addr, sizeof(selected.default_mac_addr) );
    1724          15 :   if( FD_UNLIKELY( 0==mac_bits ) ) {
    1725           0 :     FD_LOG_WARNING(( "PF returned a zero VF MAC address (%i-%s)", EPROTO, fd_io_strerror( EPROTO ) ));
    1726           0 :     errno = EPROTO;
    1727           0 :     return -1;
    1728           0 :   }
    1729             : 
    1730             :   /* Retain PF limits and the assigned MAC */
    1731          15 :   info->vsi_id         = selected.vsi_id;
    1732          15 :   info->queue_pair_cnt = fd_ushort_min( resources.num_queue_pairs, selected.num_queue_pairs );
    1733          15 :   info->vector_cnt     = resources.max_vectors;
    1734          15 :   info->max_mtu        = resources.max_mtu;
    1735          15 :   info->rss_key_sz     = resources.rss_key_sz;
    1736          15 :   info->rss_lut_sz     = resources.rss_lut_sz;
    1737          15 :   info->wb_on_itr      = !!(resources.capability_flags & FD_IAVF_VIRTCHNL_CAP_WB_ON_ITR);
    1738          15 :   if( FD_UNLIKELY( 0==info->rss_key_sz || info->rss_key_sz>FD_IAVF_RSS_KEY_MAX_SZ ||
    1739          15 :                    0==info->rss_lut_sz || info->rss_lut_sz>FD_IAVF_RSS_LUT_MAX_SZ ) ) {
    1740           0 :     FD_LOG_WARNING(( "invalid RSS sizes, key %u must be in [1,%lu], LUT %u must be in [1,%lu] (%i-%s)",
    1741           0 :                      info->rss_key_sz, FD_IAVF_RSS_KEY_MAX_SZ, info->rss_lut_sz, FD_IAVF_RSS_LUT_MAX_SZ, EPROTO, fd_io_strerror( EPROTO ) ));
    1742           0 :     errno = EPROTO;
    1743           0 :     return -1;
    1744           0 :   }
    1745          15 :   fd_memcpy( info->mac_addr, selected.default_mac_addr, sizeof(info->mac_addr) );
    1746          15 :   return fd_iavf_virtchnl_wait_link_event( vfio, adminq, info );
    1747          15 : }
    1748             : 
    1749             : int
    1750             : fd_iavf_virtchnl_poll_link( fd_iavf_vfio_t *    vfio,
    1751             :                             fd_iavf_adminq_t *  adminq,
    1752             :                             fd_iavf_vf_info_t * info,
    1753          21 :                             int *               changed ) {
    1754          21 :   if( FD_UNLIKELY( NULL==vfio || NULL==adminq || NULL==info || NULL==changed ) ) {
    1755           0 :     errno = EINVAL;
    1756           0 :     return -1;
    1757           0 :   }
    1758             : 
    1759          21 :   int const  old_usable  = info->link_usable;
    1760             :   /* Detect resets even when no PF event was received */
    1761          21 :   uint       reset_state = fd_iavf_mmio_read( vfio, FD_IAVF_VFGEN_RSTAT ) & FD_IAVF_VFGEN_RSTAT_STATE;
    1762          21 :   if( FD_UNLIKELY( (reset_state!=FD_IAVF_VFR_STATE_COMPLETED && reset_state!=FD_IAVF_VFR_STATE_ACTIVE) ||
    1763          21 :                    !(fd_iavf_mmio_read( vfio, FD_IAVF_VF_ATQLEN ) & FD_IAVF_ATQ_ENABLE) ||
    1764          21 :                    !(fd_iavf_mmio_read( vfio, FD_IAVF_VF_ARQLEN ) & FD_IAVF_ARQ_ENABLE) ) ) {
    1765           0 :     errno = ECONNRESET;
    1766           0 :     return -1;
    1767           0 :   }
    1768          21 :   fd_iavf_virtchnl_apply_event( adminq, info );
    1769             : 
    1770             :   /* Bound event processing so housekeeping can return */
    1771          33 :   for( ulong i=0UL; i<FD_IAVF_ADMINQ_DEPTH; i++ ) {
    1772          33 :     uchar message[ FD_IAVF_ADMINQ_BUF_SZ ];
    1773          33 :     ulong message_sz = sizeof(message);
    1774          33 :     uint virtchnl_op;
    1775          33 :     int  virtchnl_status;
    1776          33 :     int const received = fd_iavf_adminq_recv( vfio, adminq, &virtchnl_op, &virtchnl_status,
    1777          33 :                                               message, &message_sz );
    1778          33 :     if( FD_UNLIKELY( -1==received ) ) {
    1779           0 :       int err = errno;
    1780           0 :       FD_LOG_WARNING(( "link event receive failed (%i-%s)", err, fd_io_strerror( err ) ));
    1781           0 :       errno = err;
    1782           0 :       return -1;
    1783           0 :     }
    1784          33 :     if( 0==received ) break;
    1785          15 :     if( FD_UNLIKELY( -1==fd_iavf_virtchnl_cache_event( adminq, virtchnl_op, virtchnl_status,
    1786          15 :                                                        message, message_sz ) ) ) return -1;
    1787          12 :     fd_iavf_virtchnl_apply_event( adminq, info );
    1788          12 :   }
    1789             : 
    1790          18 :   *changed = old_usable!=info->link_usable;
    1791          18 :   return 0;
    1792          21 : }
    1793             : 
    1794             : ulong
    1795             : fd_iavf_queue_footprint( uint tx_depth,
    1796          66 :                          uint rx_depth ) {
    1797          66 :   if( FD_UNLIKELY( tx_depth<FD_IAVF_QUEUE_DEPTH_MIN || tx_depth>FD_IAVF_QUEUE_DEPTH_MAX || !fd_uint_is_pow2( tx_depth ) ||
    1798          66 :                    rx_depth<FD_IAVF_QUEUE_DEPTH_MIN || rx_depth>FD_IAVF_QUEUE_DEPTH_MAX || !fd_uint_is_pow2( rx_depth ) ) ) return 0UL;
    1799             :   /* TX and RX descriptor rings followed by software TX completion tracking */
    1800          54 :   ulong tx_ring_sz = fd_ulong_align_up( (ulong)tx_depth*sizeof(fd_iavf_tx_desc_t), FD_IAVF_PAGE_SZ );
    1801          54 :   ulong rx_ring_sz = fd_ulong_align_up( (ulong)rx_depth*sizeof(fd_iavf_hw_rx_desc_t), FD_IAVF_PAGE_SZ );
    1802          54 :   ulong tx_comp_sz = fd_ulong_align_up( (ulong)tx_depth*sizeof(ulong), FD_IAVF_PAGE_SZ );
    1803          54 :   return tx_ring_sz + rx_ring_sz + tx_comp_sz;
    1804          66 : }
    1805             : 
    1806             : int
    1807             : fd_iavf_virtchnl_configure_queues( fd_iavf_vfio_t *            vfio,
    1808             :                                    fd_iavf_adminq_t *          adminq,
    1809             :                                    fd_iavf_vf_info_t const *   info,
    1810             :                                    fd_iavf_vfio_tile_t const * tiles,
    1811             :                                    uint                        queue_cnt,
    1812             :                                    ulong                       dma_iova,
    1813             :                                    uint                        rx_buffer_sz,
    1814           3 :                                    uint                        max_frame_sz ) {
    1815             :   /* Check VF queue and frame limits */
    1816           3 :   if( FD_UNLIKELY( queue_cnt>info->queue_pair_cnt ) ) {
    1817           0 :     FD_LOG_WARNING(( "VF has %hu queue pairs but %u net tiles are configured. Please decrease [layout.net_tile_count] to %hu or less",
    1818           0 :                      info->queue_pair_cnt, queue_cnt, info->queue_pair_cnt ));
    1819           0 :     errno = EINVAL;
    1820           0 :     return -1;
    1821           0 :   }
    1822           3 :   if( FD_UNLIKELY( info->vector_cnt<2U ) ) {
    1823           0 :     FD_LOG_WARNING(( "VF has %hu interrupt vectors, queue setup requires at least 2", info->vector_cnt ));
    1824           0 :     errno = EINVAL;
    1825           0 :     return -1;
    1826           0 :   }
    1827           3 :   if( FD_UNLIKELY( 0==rx_buffer_sz || 0!=(rx_buffer_sz & 127U) ) ) {
    1828           0 :     FD_LOG_WARNING(( "RX buffer size %u must be a nonzero multiple of 128", rx_buffer_sz ));
    1829           0 :     errno = EINVAL;
    1830           0 :     return -1;
    1831           0 :   }
    1832           3 :   if( FD_UNLIKELY( max_frame_sz<64U || max_frame_sz>=16384U || max_frame_sz>rx_buffer_sz ||
    1833           3 :                    (0!=info->max_mtu && max_frame_sz>info->max_mtu) ) ) {
    1834           0 :     FD_LOG_WARNING(( "frame size %u must be in [64,16383], fit RX buffer %u and not exceed nonzero VF MTU limit %hu", max_frame_sz, rx_buffer_sz, info->max_mtu ));
    1835           0 :     errno = EINVAL;
    1836           0 :     return -1;
    1837           0 :   }
    1838             : 
    1839             :   /* Check every tile's queue memory before mapping */
    1840           3 :   ulong queue_iova = dma_iova;
    1841           6 :   for( uint i=0U; i<queue_cnt; i++ ) {
    1842           3 :     fd_iavf_vfio_tile_t const * tile = &tiles[i];
    1843           3 :     if( FD_UNLIKELY( NULL==tile->tx_queue || NULL==tile->rx_queue || tile->tx_queue==tile->rx_queue || NULL==tile->queue_memory ) ) {
    1844           0 :       FD_LOG_WARNING(( "queue pair %u requires distinct non-NULL TX/RX queue state and non-NULL DMA memory", i ));
    1845           0 :       errno = EINVAL;
    1846           0 :       return -1;
    1847           0 :     }
    1848           3 :     uint  tx_depth  = tile->tx_queue->depth;
    1849           3 :     uint  rx_depth  = tile->rx_queue->depth;
    1850           3 :     ulong footprint = fd_iavf_queue_footprint( tx_depth, rx_depth );
    1851           3 :     if( FD_UNLIKELY( 0==footprint ) ) {
    1852           0 :       FD_LOG_WARNING(( "queue %u depths TX %u, RX %u must be powers of two in [%u,%u]", i, tx_depth, rx_depth, FD_IAVF_QUEUE_DEPTH_MIN, FD_IAVF_QUEUE_DEPTH_MAX ));
    1853           0 :       errno = EINVAL;
    1854           0 :       return -1;
    1855           0 :     }
    1856           3 :     if( FD_UNLIKELY( tile->queue_memory_sz<footprint || footprint>ULONG_MAX-queue_iova ) ) {
    1857           0 :       FD_LOG_WARNING(( "invalid queue %u DMA bounds, memory size %lu, required %lu, IOVA %#lx", i, tile->queue_memory_sz, footprint, queue_iova ));
    1858           0 :       errno = EINVAL;
    1859           0 :       return -1;
    1860           0 :     }
    1861           3 :     queue_iova += footprint;
    1862           3 :   }
    1863             : 
    1864             :   /* Map tile queues at consecutive IOVAs, queue IDs match tile indices */
    1865           3 :   fd_iavf_virtchnl_queue_config_t config = {
    1866           3 :     .vsi_id         = info->vsi_id,
    1867           3 :     .queue_pair_cnt = (ushort)queue_cnt
    1868           3 :   };
    1869           3 :   queue_iova = dma_iova;
    1870           3 :   for( uint i=0U; i<queue_cnt; i++ ) {
    1871           3 :     fd_iavf_vfio_tile_t const * tile = &tiles[i];
    1872           3 :     uint  tx_depth   = tile->tx_queue->depth;
    1873           3 :     uint  rx_depth   = tile->rx_queue->depth;
    1874           3 :     ulong tx_ring_sz = fd_ulong_align_up( (ulong)tx_depth*sizeof(fd_iavf_tx_desc_t), FD_IAVF_PAGE_SZ );
    1875           3 :     ulong rx_ring_sz = fd_ulong_align_up( (ulong)rx_depth*sizeof(fd_iavf_hw_rx_desc_t), FD_IAVF_PAGE_SZ );
    1876           3 :     ulong tx_comp_sz = fd_ulong_align_up( (ulong)tx_depth*sizeof(ulong), FD_IAVF_PAGE_SZ );
    1877           3 :     ulong footprint  = tx_ring_sz + rx_ring_sz + tx_comp_sz;
    1878           3 :     if( FD_UNLIKELY( -1==fd_iavf_vfio_dma_map( vfio, tile->queue_memory, footprint, queue_iova ) ) ) return -1;
    1879           0 :     fd_memset( tile->queue_memory, 0, footprint );
    1880           0 :     *tile->tx_queue = (fd_iavf_queue_t) {
    1881           0 :       .ring      = tile->queue_memory,
    1882           0 :       .depth     = tx_depth,
    1883           0 :       .tail      = (volatile uint *)(vfio->bar0 + FD_IAVF_TX_TAIL( i )),
    1884           0 :       .comp_ring = (ulong *)((uchar *)tile->queue_memory + tx_ring_sz + rx_ring_sz)
    1885           0 :     };
    1886           0 :     *tile->rx_queue = (fd_iavf_queue_t) {
    1887           0 :       .ring  = (uchar *)tile->queue_memory + tx_ring_sz,
    1888           0 :       .depth = rx_depth,
    1889           0 :       .tail  = (volatile uint *)(vfio->bar0 + FD_IAVF_RX_TAIL( i ))
    1890           0 :     };
    1891           0 :     config.pairs[i].tx = (fd_iavf_virtchnl_txq_info_t) {
    1892           0 :       .vsi_id    = info->vsi_id,
    1893           0 :       .queue_id  = (ushort)i,
    1894           0 :       .ring_len  = (ushort)tx_depth,
    1895           0 :       .ring_iova = queue_iova
    1896           0 :     };
    1897             :     /* Without flexible RX descriptor negotiation, the PF uses the 32-byte base format */
    1898           0 :     config.pairs[i].rx = (fd_iavf_virtchnl_rxq_info_t) {
    1899           0 :       .vsi_id         = info->vsi_id,
    1900           0 :       .queue_id       = (ushort)i,
    1901           0 :       .ring_len       = rx_depth,
    1902           0 :       .data_buffer_sz = rx_buffer_sz,
    1903           0 :       .max_frame_sz   = max_frame_sz,
    1904           0 :       .ring_iova      = queue_iova + tx_ring_sz
    1905           0 :     };
    1906           0 :     queue_iova += footprint;
    1907           0 :   }
    1908             : 
    1909             :   /* Include the unused queue-pair entry required by Linux PF validation */
    1910           0 :   ulong config_sz = offsetof(fd_iavf_virtchnl_queue_config_t, pairs) +
    1911           0 :                     ((ulong)queue_cnt+1UL)*sizeof(fd_iavf_virtchnl_queue_pair_t);
    1912           0 :   ulong response_sz = 0UL;
    1913           0 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_request( vfio, adminq, FD_IAVF_VIRTCHNL_CONFIG_QUEUES,
    1914           0 :                                                  &config, config_sz, NULL, &response_sz ) ) ) return -1;
    1915             : 
    1916             :   /* Virtchnl requires queue interrupt mappings even when polling
    1917             :      Vector 0 is reserved for Admin Queue events */
    1918           0 :   ushort queue_map = (ushort)((1U<<queue_cnt)-1U);
    1919           0 :   fd_iavf_virtchnl_irq_map_t irq_map = {
    1920           0 :     .vector_cnt   = 1U,
    1921           0 :     .vsi_id       = info->vsi_id,
    1922           0 :     .vector_id    = 1U,
    1923           0 :     .rx_queue_map = queue_map,
    1924           0 :     .tx_queue_map = queue_map,
    1925           0 :     .rx_itr_idx   = 0U,
    1926           0 :     .tx_itr_idx   = 0U
    1927           0 :   };
    1928           0 :   response_sz = 0UL;
    1929           0 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_request( vfio, adminq, FD_IAVF_VIRTCHNL_CONFIG_IRQ_MAP,
    1930           0 :                                                  &irq_map, sizeof(irq_map), NULL, &response_sz ) ) ) return -1;
    1931             : 
    1932           0 :   if( info->wb_on_itr ) {
    1933             :     /* WB_ON_ITR flushes sparse RX completions without interrupts.
    1934             :        Use DPDK's interval of 2 hardware timer counts. */
    1935           0 :     fd_iavf_mmio_write( vfio, FD_IAVF_VFINT_DYN_CTL,
    1936           0 :                         FD_IAVF_VFINT_WB_ON_ITR | (2U<<FD_IAVF_VFINT_INTERVAL_SHIFT) );
    1937           0 :     (void)fd_iavf_mmio_read( vfio, FD_IAVF_VFGEN_RSTAT );
    1938           0 :   }
    1939           0 :   return 0;
    1940           0 : }
    1941             : 
    1942             : int
    1943             : fd_iavf_virtchnl_enable_queues( fd_iavf_vfio_t *            vfio,
    1944             :                                 fd_iavf_adminq_t *          adminq,
    1945             :                                 fd_iavf_vf_info_t *         info,
    1946             :                                 fd_iavf_vfio_tile_t const * tiles,
    1947           0 :                                 uint                        queue_cnt ) {
    1948           0 :   if( FD_UNLIKELY( NULL==vfio || NULL==adminq || NULL==info || NULL==tiles ||
    1949           0 :                    0==queue_cnt || queue_cnt>FD_IAVF_TILE_MAX || queue_cnt>info->queue_pair_cnt ) ) {
    1950           0 :     errno = EINVAL;
    1951           0 :     return -1;
    1952           0 :   }
    1953             :   /* Post RX buffers before enabling reception on the VF */
    1954           0 :   for( uint i=0U; i<queue_cnt; i++ ) {
    1955           0 :     fd_iavf_queue_t const * tx_queue = tiles[i].tx_queue;
    1956           0 :     fd_iavf_queue_t const * rx_queue = tiles[i].rx_queue;
    1957           0 :     if( FD_UNLIKELY( NULL==tx_queue || NULL==rx_queue || tx_queue==rx_queue ||
    1958           0 :                      tx_queue->enabled || rx_queue->enabled || 0==rx_queue->posted ||
    1959           0 :                      rx_queue->posted!=rx_queue->prod ||
    1960           0 :                      rx_queue->prod-rx_queue->cons>=rx_queue->depth ) ) {
    1961           0 :       FD_LOG_WARNING(( "queue pair %u must have distinct disabled TX/RX queues and posted RX buffers below ring capacity", i ));
    1962           0 :       errno = EINVAL;
    1963           0 :       return -1;
    1964           0 :     }
    1965           0 :   }
    1966           0 :   uint queue_map = (1U<<queue_cnt)-1U;
    1967           0 :   fd_iavf_virtchnl_queue_select_t select = {
    1968           0 :     .vsi_id       = info->vsi_id,
    1969           0 :     .rx_queue_map = queue_map,
    1970           0 :     .tx_queue_map = queue_map
    1971           0 :   };
    1972           0 :   ulong response_sz = 0UL;
    1973           0 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_request( vfio, adminq, FD_IAVF_VIRTCHNL_ENABLE_QUEUES,
    1974           0 :                                                  &select, sizeof(select), NULL, &response_sz ) ) ) return -1;
    1975           0 :   for( uint i=0U; i<queue_cnt; i++ ) {
    1976           0 :     tiles[i].tx_queue->enabled = 1;
    1977           0 :     tiles[i].rx_queue->enabled = 1;
    1978           0 :   }
    1979             : 
    1980             :   /* Queue enable triggers a new PF link report */
    1981           0 :   info->link_usable = 0;
    1982           0 :   return fd_iavf_virtchnl_wait_link_event( vfio, adminq, info );
    1983           0 : }
    1984             : 
    1985             : static int
    1986             : fd_iavf_virtchnl_add_mac( fd_iavf_vfio_t *          vfio,
    1987             :                           fd_iavf_adminq_t *        adminq,
    1988           0 :                           fd_iavf_vf_info_t const * info ) {
    1989             :   /* Linux virtchnl_ether_addr_list with one primary address. PF message
    1990             :      validation requires space for one extra virtchnl_ether_addr entry. */
    1991           0 :   struct {
    1992           0 :     ushort vsi_id;
    1993           0 :     ushort count;
    1994           0 :     uchar  mac[ 6 ];
    1995           0 :     uchar  type;
    1996           0 :     uchar  pad;
    1997           0 :     uchar  legacy_padding[ 8 ];
    1998           0 :   } request = { .vsi_id=info->vsi_id, .count=1U, .type=FD_IAVF_VIRTCHNL_MAC_PRIMARY };
    1999           0 :   FD_STATIC_ASSERT( sizeof(request)==20UL, iavf_mac_request_sz );
    2000           0 :   fd_memcpy( request.mac, info->mac_addr, sizeof(request.mac) );
    2001           0 :   ulong response_sz = 0UL;
    2002           0 :   return fd_iavf_virtchnl_request( vfio, adminq, FD_IAVF_VIRTCHNL_ADD_ETH_ADDR,
    2003           0 :                                    &request, sizeof(request), NULL, &response_sz );
    2004           0 : }
    2005             : 
    2006             : static int
    2007             : fd_iavf_virtchnl_configure_rss( fd_iavf_vfio_t *          vfio,
    2008             :                                 fd_iavf_adminq_t *        adminq,
    2009             :                                 fd_iavf_vf_info_t const * info,
    2010           0 :                                 uint                      queue_cnt ) {
    2011             :   /* Configure the PF-managed RSS hash key */
    2012           0 :   struct {
    2013           0 :     ushort vsi_id;
    2014           0 :     ushort count;
    2015           0 :     uchar  bytes[ FD_IAVF_ADMINQ_BUF_SZ-FD_IAVF_RSS_KEY_HEADER_SZ ];
    2016           0 :   } request = { .vsi_id=info->vsi_id, .count=(ushort)info->rss_key_sz };
    2017           0 :   FD_STATIC_ASSERT( offsetof(__typeof__(request), bytes)==FD_IAVF_RSS_KEY_HEADER_SZ, iavf_rss_key_header_sz );
    2018           0 :   FD_STATIC_ASSERT( offsetof(__typeof__(request), bytes)==FD_IAVF_RSS_LUT_HEADER_SZ, iavf_rss_lut_header_sz );
    2019           0 :   FD_STATIC_ASSERT( sizeof(request)==FD_IAVF_ADMINQ_BUF_SZ, iavf_rss_request_sz );
    2020           0 :   if( FD_UNLIKELY( NULL==fd_rng_secure( request.bytes, info->rss_key_sz ) ) ) return -1;
    2021           0 :   ulong response_sz = 0UL;
    2022           0 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_request( vfio, adminq, FD_IAVF_VIRTCHNL_CONFIG_RSS_KEY,
    2023           0 :                                                  &request, FD_IAVF_RSS_KEY_LEGACY_SZ+(ulong)info->rss_key_sz-1UL,
    2024           0 :                                                  NULL, &response_sz ) ) ) return -1;
    2025             : 
    2026             :   /* Cycle queue IDs through the LUT, tile count need not be a power of two */
    2027           0 :   fd_memset( request.bytes, 0, sizeof(request.bytes) );
    2028           0 :   request.count = (ushort)info->rss_lut_sz;
    2029           0 :   for( uint i=0U; i<info->rss_lut_sz; i++ ) request.bytes[i] = (uchar)(i%queue_cnt);
    2030           0 :   response_sz = 0UL;
    2031           0 :   return fd_iavf_virtchnl_request( vfio, adminq, FD_IAVF_VIRTCHNL_CONFIG_RSS_LUT,
    2032           0 :                                    &request, FD_IAVF_RSS_LUT_LEGACY_SZ+(ulong)info->rss_lut_sz-1UL,
    2033           0 :                                    NULL, &response_sz );
    2034           0 : }
    2035             : 
    2036             : int
    2037             : fd_iavf_init( fd_iavf_vfio_t *            vfio,
    2038             :               fd_iavf_adminq_t *          adminq,
    2039             :               fd_iavf_vf_info_t *         info,
    2040             :               fd_iavf_vfio_tile_t const * tiles,
    2041             :               uint                        tile_cnt,
    2042             :               char const *                pci_addr,
    2043             :               uint                        rx_buffer_sz,
    2044           0 :               uint                        max_frame_sz ) {
    2045           0 :   if( FD_UNLIKELY( NULL==vfio || NULL==adminq || NULL==info || NULL==tiles || 0==tile_cnt || tile_cnt>FD_IAVF_TILE_MAX ) ) {
    2046           0 :     errno = EINVAL;
    2047           0 :     return -1;
    2048           0 :   }
    2049             : 
    2050             :   /* Place Admin Queue and queue IOVAs after packet mappings to avoid overlap */
    2051           0 :   ulong adminq_iova = 0UL;
    2052           0 :   for( uint tile_idx=0U; tile_idx<tile_cnt; tile_idx++ ) {
    2053           0 :     fd_iavf_vfio_tile_t const * tile = &tiles[tile_idx];
    2054           0 :     if( FD_UNLIKELY( tile->packet_memory_sz>ULONG_MAX-tile->packet_iova ) ) {
    2055           0 :       errno = EINVAL;
    2056           0 :       return -1;
    2057           0 :     }
    2058           0 :     adminq_iova = fd_ulong_max( adminq_iova, tile->packet_iova+tile->packet_memory_sz );
    2059           0 :   }
    2060           0 :   ulong const adminq_memory_sz = fd_iavf_adminq_footprint();
    2061           0 :   if( FD_UNLIKELY( adminq_memory_sz>ULONG_MAX-adminq_iova ) ) {
    2062           0 :     errno = EINVAL;
    2063           0 :     return -1;
    2064           0 :   }
    2065           0 :   ulong const queue_iova = adminq_iova+adminq_memory_sz;
    2066             : 
    2067             :   /* Open and reset the VF */
    2068           0 :   fd_iavf_pci_info_t pci;
    2069           0 :   if( FD_UNLIKELY( -1==fd_iavf_pci_probe( &pci, pci_addr ) ) ) return -1;
    2070           0 :   if( FD_UNLIKELY( -1==fd_iavf_vfio_init( vfio, &pci ) ) ) return -1;
    2071             : 
    2072             :   /* Establish PF communication and discover VF resources */
    2073           0 :   if( FD_UNLIKELY( -1==fd_iavf_adminq_init( vfio, adminq, adminq->dma_memory, adminq_iova ) ) ) return -1;
    2074           0 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_version( vfio, adminq ) ) ) return -1;
    2075           0 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_get_resources( vfio, adminq, info ) ) ) return -1;
    2076             : 
    2077             :   /* Configure tile queues and receive delivery */
    2078           0 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_configure_queues( vfio, adminq, info, tiles, tile_cnt,
    2079           0 :                                                           queue_iova, rx_buffer_sz, max_frame_sz ) ) ) return -1;
    2080           0 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_add_mac( vfio, adminq, info ) ) ) return -1;
    2081           0 :   if( FD_UNLIKELY( -1==fd_iavf_virtchnl_configure_rss( vfio, adminq, info, tile_cnt ) ) ) return -1;
    2082             :   /* Map packet buffers before the caller posts RX buffers and enables queues */
    2083           0 :   for( uint tile_idx=0U; tile_idx<tile_cnt; tile_idx++ ) {
    2084           0 :     fd_iavf_vfio_tile_t const * tile = &tiles[tile_idx];
    2085           0 :     if( FD_UNLIKELY( -1==fd_iavf_vfio_dma_map( vfio, tile->packet_memory, tile->packet_memory_sz, tile->packet_iova ) ) ) return -1;
    2086           0 :   }
    2087           0 :   return 0;
    2088           0 : }

Generated by: LCOV version 1.14