LCOV - code coverage report
Current view: top level - app/shared/commands/configure - irq-affinity.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 235 0.0 %
Date: 2026-09-17 04:28:31 Functions: 0 13 0.0 %

          Line data    Source code
       1             : /* The irq-affinity stage attempts to minimize the amount of CPU time
       2             :    stolen by device interrupts from Firedancer fixed tiles.
       3             : 
       4             :    Configuring interrupt affinity is an annoying problem, and doing so
       5             :    blindly using a default policy is very annoying.  This wall of text
       6             :    documents why the code was written like it is.
       7             : 
       8             :    The problem is that default Linux system configuration dispatches
       9             :    IRQs to arbitrary CPUs.  Since socket-based networking heavily relies
      10             :    on IRQs, heavy incoming traffic can possibly starve a Firedancer tile
      11             :    from running. Tiles cannot evacuate to other CPUs since they are
      12             :    pinned to one CPU each.
      13             : 
      14             :    The goal is to prevent interrupt requests from being delivered to
      15             :    CPUs that Firedancer tiles are spinning on.  The kernel exposes the
      16             :    /proc/irq/N/smp_affinity API for this purpose.
      17             : 
      18             :    ### isolcpus
      19             : 
      20             :    isolcpus is the ideal way to achieve CPU isolation from interrupts
      21             :    and lots of other undesired activity.  At the time of writing,
      22             :    isolcpus can only be configured at boot as a kernel command line
      23             :    parameter, though.
      24             : 
      25             :    ### procfs smp_affinity
      26             : 
      27             :    On a typical Intel/AMD system (with an x2APIC interrupt controller),
      28             :    the kernel binds an IRQ to one CPU core.  That CPU core is picked out
      29             :    of the smp_affinity mask (unless the mask is impossible to satisfy).
      30             :    This CPU can be found in /proc/irq/N/effective_affinity_list.
      31             :    (Technically, IRQ load balancing can be done dynamically at the
      32             :    hardware level with x2APIC, but this is rare...)
      33             : 
      34             :    The kernel picks the effective CPU index out of smp_affinity using
      35             :    roughly these rules:
      36             :    - ignore offline CPUs
      37             :    - ignore isolated CPUs (isolcpus kernel boot parameter)
      38             :    - ignore CPUs on a different NUMA than the device
      39             :    - pick the CPU with the fewest IRQs (kernel/irq/matrix.c)
      40             : 
      41             :    This load balancing policy is decent but does not take into account
      42             :    how busy different IRQs are.  On an unlucky system startup, one CPU
      43             :    might have multiple busy IRQs while another CPU barely gets any
      44             :    interrupts.
      45             : 
      46             :    ### irqbalance
      47             : 
      48             :    The irqbalance userland daemon was created to achieve better load
      49             :    balancing than the kernel's static mapping.
      50             :    It periodically rewrites all /proc/irq/N/smp_affinity files to
      51             :    dynamically rebalance IRQs, reacting to high CPU usage, thermal
      52             :    events, etc.
      53             : 
      54             :    One can ban irqbalance from using certain CPUs either via a config
      55             :    file or unix domain sockets.  The latter is ephemeral in nature,
      56             :    config written via UDS is auto-reset on restart.
      57             : 
      58             :    ### Firedancer smp_affinity interop
      59             : 
      60             :    Firedancer also manually updates the smp_affinity list.  Some IRQs
      61             :    cannot be removed from a CPU (e.g. hardware timer or NVMe managed
      62             :    interrupts), so Firedancer should ignore them.  Unfortunately, the
      63             :    kernel provides no method to tell which IRQs can be moved.
      64             : 
      65             :    Thus, irq-affinity is implemented as follows:
      66             :    - 'check' (which looks for misconfigured IRQs) writes back the
      67             :      existing smp_affinity mask, if the mask includes tile CPUs.  If
      68             :      this results in a permission error, the interrupt likely cannot be
      69             :      reconfigured.
      70             :    - 'init' does the actual reconfiguration (any CPUs that are not
      71             :      Firedancer tiles are allowed)
      72             :    - 'fini' re-admits fixed tile CPUs into the smp_affinity mask
      73             :      (attempts to keep CPUs excluded that were already excluded before
      74             :      Firedancer)
      75             : 
      76             :    Another quirk is that the kernel leaves the effective CPU of an IRQ
      77             :    unchanged if the smp_affinity mask is narrowed, but the effective CPU
      78             :    stays in the mask.  Due to this, 'check' is truly a no-op (ignoring
      79             :    TOCTOU races), and 'fini' fails to restore effective affinity masks.
      80             : 
      81             :    ### Firedancer irqbalance interop
      82             : 
      83             :    If irqbalance is available, Firedancer uses that unix domain socket
      84             :    API to ban tile CPUs.  Since irqbalance forgets UDS config on restart
      85             :    we would ideally periodically re-apply the config.  Unfortunately,
      86             :    irqbalance creates the UDS socket path on each startup.  Opening
      87             :    arbitrary files does not play well with Firedancer's sandbox.
      88             : 
      89             :    Thus, Firedancer only configures the irqbalance daemon on startup
      90             :    using this configure stage.
      91             : 
      92             :    Manual procfs smp_affinity is the lesser evil, so Firedancer logs a
      93             :    warning if it finds irqbalance.
      94             : 
      95             :    ### Firedancer network stack
      96             : 
      97             :    Firedancer code avoids producing interrupts/softirq where possible,
      98             :    instead opting for busy polling.  But at the time of writing, XDP
      99             :    driver code in the Linux kernel is so botched that preferred busy
     100             :    polling cannot be reliably enabled.
     101             : 
     102             :    Thus, unfortunately, IRQs for Firedancer RX XDP traffic will be
     103             :    handled by remote CPU cores. */
     104             : 
     105             : #define _DEFAULT_SOURCE
     106             : #include "configure.h"
     107             : #include "fd_cpu_isolation.h"
     108             : #include "fd_irqbalance_client.h"
     109             : #include "../../../../util/tile/fd_tile_private.h"
     110             : #include <fcntl.h>
     111             : #include <sys/types.h>
     112             : #include <dirent.h>
     113             : #include <ctype.h>
     114             : #include <errno.h>
     115             : #include <stdlib.h>
     116             : #include <unistd.h>
     117             : 
     118             : /* FD_IRQ_AFFINITY_CHECK_TIGHT, when non-zero, makes check() also flag
     119             :    IRQs whose affinity mask is a strict subset of the allowed CPUs (but
     120             :    that do not overlap any tile CPU).  Such IRQs do not steal time from
     121             :    tiles, and irqbalance routinely narrows IRQs this way, which would
     122             :    make check() never pass while it runs.  Disabled for now. */
     123             : #ifndef FD_IRQ_AFFINITY_CHECK_TIGHT
     124             : #define FD_IRQ_AFFINITY_CHECK_TIGHT 0
     125             : #endif
     126             : 
     127             : /* smp_affinity file format is 4a4a4a4a,fcfcfcfc,...
     128             :    So, 9 bytes per 32 bits ~ 3.56 bits per byte. */
     129             : #define SMP_AFFINITY_STR_LEN (FD_TILE_MAX/3)
     130             : #define MISMATCH_SAMPLE_MAX  (16UL)
     131             : #define MISMATCH_STR_LEN     (128UL)
     132             : #define MISMATCH_TILE_STR_LEN (128UL)
     133             : 
     134             : static char *
     135             : append_ulong_list_sample( char *        buf,
     136             :                           ulong         buf_sz,
     137             :                           ulong const * vals,
     138             :                           ulong         val_cnt,
     139           0 :                           ulong         total_cnt ) {
     140           0 :   char * p = fd_cstr_init( buf );
     141           0 :   for( ulong i=0UL; i<val_cnt; i++ ) {
     142           0 :     if( FD_LIKELY( i ) ) p = fd_cstr_append_char( p, ',' );
     143           0 :     if( FD_UNLIKELY( (ulong)(p-buf)+32UL >= buf_sz ) ) break;
     144           0 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, vals[ i ], fd_ulong_base10_dig_cnt( vals[ i ] ) );
     145           0 :   }
     146           0 :   if( FD_UNLIKELY( total_cnt>val_cnt && (ulong)(p-buf)+32UL<buf_sz ) ) {
     147           0 :     p = fd_cstr_append_cstr( p, ",+" );
     148           0 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, total_cnt-val_cnt, fd_ulong_base10_dig_cnt( total_cnt-val_cnt ) );
     149           0 :     p = fd_cstr_append_cstr( p, " more" );
     150           0 :   }
     151           0 :   fd_cstr_fini( p );
     152           0 :   return buf;
     153           0 : }
     154             : 
     155             : static char *
     156             : tile_list_sample( char *            buf,
     157             :                   ulong             buf_sz,
     158             :                   fd_topo_t const * topo,
     159             :                   ulong const *     tile_idxs,
     160             :                   ulong             tile_cnt,
     161           0 :                   ulong             total_cnt ) {
     162           0 :   char * p = fd_cstr_init( buf );
     163           0 :   for( ulong i=0UL; i<tile_cnt; i++ ) {
     164           0 :     fd_topo_tile_t const * tile = &topo->tiles[ tile_idxs[ i ] ];
     165           0 :     if( FD_LIKELY( i ) ) p = fd_cstr_append_char( p, ',' );
     166           0 :     if( FD_UNLIKELY( (ulong)(p-buf)+32UL >= buf_sz ) ) break;
     167           0 :     p = fd_cstr_append_cstr( p, tile->name );
     168           0 :     p = fd_cstr_append_char( p, ':' );
     169           0 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, tile->kind_id, fd_ulong_base10_dig_cnt( tile->kind_id ) );
     170           0 :   }
     171           0 :   if( FD_UNLIKELY( total_cnt>tile_cnt && (ulong)(p-buf)+32UL<buf_sz ) ) {
     172           0 :     p = fd_cstr_append_cstr( p, ",+" );
     173           0 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, total_cnt-tile_cnt, fd_ulong_base10_dig_cnt( total_cnt-tile_cnt ) );
     174           0 :     p = fd_cstr_append_cstr( p, " more" );
     175           0 :   }
     176           0 :   fd_cstr_fini( p );
     177           0 :   return buf;
     178           0 : }
     179             : 
     180             : static int
     181           0 : irq_dirent_is_irq( char const * name ) {
     182           0 :   if( FD_UNLIKELY( !name[0] ) ) return 0;
     183           0 :   for( char const * p=name; *p; p++ ) {
     184           0 :     if( FD_UNLIKELY( !isdigit( (uchar)*p ) ) ) return 0;
     185           0 :   }
     186           0 :   return 1;
     187           0 : }
     188             : 
     189             : static int
     190             : read_irq_smp_affinity( char const * irq,
     191           0 :                        fd_cpuset_t * cpuset ) {
     192           0 :   char path[ PATH_MAX ];
     193           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/proc/irq/%s/smp_affinity", irq ) );
     194             : 
     195           0 :   int fd = open( path, O_RDONLY );
     196           0 :   if( FD_UNLIKELY( fd<0 ) ) return 0;
     197             : 
     198           0 :   char affinity[ SMP_AFFINITY_STR_LEN+64UL ];
     199           0 :   long affinity_len = read( fd, affinity, sizeof(affinity)-1UL );
     200           0 :   int err = errno;
     201           0 :   if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
     202           0 :   if( FD_UNLIKELY( affinity_len<0 ) ) {
     203           0 :     errno = err;
     204           0 :     return 0;
     205           0 :   }
     206             : 
     207           0 :   affinity[ affinity_len ] = '\0';
     208           0 :   return fd_cpu_isolation_parse_mask( cpuset, affinity );
     209           0 : }
     210             : 
     211             : static int
     212             : write_irq_smp_affinity( char const *        irq,
     213             :                         fd_cpuset_t const * cpuset,
     214           0 :                         int                 warn ) {
     215           0 :   char path[ PATH_MAX ];
     216           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/proc/irq/%s/smp_affinity", irq ) );
     217             : 
     218           0 :   char affinity[ SMP_AFFINITY_STR_LEN+64UL ];
     219           0 :   fd_cpu_isolation_format_mask( affinity, sizeof(affinity), cpuset );
     220           0 :   ulong affinity_len = strlen( affinity );
     221             : 
     222           0 :   int fd = open( path, O_WRONLY );
     223           0 :   if( FD_UNLIKELY( fd<0 ) ) {
     224           0 :     if( FD_LIKELY( warn ) ) FD_LOG_WARNING(( "open(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
     225           0 :     return 0;
     226           0 :   }
     227             : 
     228           0 :   int ok = 1;
     229           0 :   if( FD_UNLIKELY( write( fd, affinity, affinity_len )!=(long)affinity_len ) ) {
     230           0 :     int err = errno;
     231           0 :     if( FD_LIKELY( warn ) ) FD_LOG_WARNING(( "write(%s) failed (%i-%s)", path, err, fd_io_strerror( err ) ));
     232           0 :     errno = err;
     233           0 :     ok = 0;
     234           0 :   }
     235             : 
     236           0 :   if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
     237           0 :   return ok;
     238           0 : }
     239             : 
     240             : static void
     241             : update_irq_smp_affinities( fd_cpuset_t const * add_cpus,
     242           0 :                            fd_cpuset_t const * remove_cpus ) {
     243           0 :   DIR * dir = opendir( "/proc/irq" );
     244           0 :   if( FD_UNLIKELY( !dir ) ) {
     245           0 :     FD_LOG_WARNING(( "opendir(/proc/irq) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     246           0 :     return;
     247           0 :   }
     248             : 
     249           0 :   FD_CPUSET_DECL( fallback );
     250           0 :   if( FD_LIKELY( remove_cpus ) ) fd_cpuset_subtract( fallback, fd_cpu_isolation_host_cpus( fallback ), remove_cpus );
     251             : 
     252           0 :   struct dirent * entry;
     253           0 :   while( (entry = readdir( dir )) ) {
     254           0 :     if( FD_UNLIKELY( !irq_dirent_is_irq( entry->d_name ) ) ) continue;
     255             : 
     256           0 :     FD_CPUSET_DECL( current );
     257           0 :     if( FD_UNLIKELY( !read_irq_smp_affinity( entry->d_name, current ) ) ) continue;
     258             : 
     259           0 :     FD_CPUSET_DECL( next );
     260           0 :     if( FD_LIKELY( remove_cpus ) ) fd_cpuset_subtract( next, current, remove_cpus );
     261           0 :     else                           fd_cpuset_copy    ( next, current );
     262           0 :     if( FD_UNLIKELY( !fd_cpuset_cnt( next ) && remove_cpus ) ) fd_cpuset_copy( next, fallback );
     263           0 :     if( FD_LIKELY( add_cpus ) ) fd_cpuset_union( next, next, add_cpus );
     264             : 
     265           0 :     if( FD_LIKELY( fd_cpuset_eq( current, next ) ) ) continue;
     266           0 :     if( FD_UNLIKELY( !write_irq_smp_affinity( entry->d_name, next, 0 ) && errno!=EPERM && errno!=EIO ) ) {
     267           0 :       int err = errno;
     268           0 :       FD_LOG_WARNING(( "write(/proc/irq/%s/smp_affinity) failed (%i-%s)", entry->d_name, err, fd_io_strerror( err ) ));
     269           0 :     }
     270           0 :   }
     271             : 
     272           0 :   if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "closedir(/proc/irq) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     273           0 : }
     274             : 
     275             : static void
     276           0 : set_irq_smp_affinities( fd_cpuset_t const * desired ) {
     277           0 :   DIR * dir = opendir( "/proc/irq" );
     278           0 :   if( FD_UNLIKELY( !dir ) ) {
     279           0 :     FD_LOG_WARNING(( "opendir(/proc/irq) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     280           0 :     return;
     281           0 :   }
     282             : 
     283           0 :   struct dirent * entry;
     284           0 :   while( (entry = readdir( dir )) ) {
     285           0 :     if( FD_UNLIKELY( !irq_dirent_is_irq( entry->d_name ) ) ) continue;
     286             : 
     287           0 :     FD_CPUSET_DECL( current );
     288           0 :     if( FD_UNLIKELY( !read_irq_smp_affinity( entry->d_name, current ) ) ) continue;
     289             : 
     290           0 :     if( FD_LIKELY( fd_cpuset_eq( current, desired ) ) ) continue;
     291           0 :     if( FD_UNLIKELY( !write_irq_smp_affinity( entry->d_name, desired, 0 ) && errno!=EPERM && errno!=EIO ) ) {
     292           0 :       int err = errno;
     293           0 :       FD_LOG_WARNING(( "write(/proc/irq/%s/smp_affinity) failed (%i-%s)", entry->d_name, err, fd_io_strerror( err ) ));
     294           0 :     }
     295           0 :   }
     296             : 
     297           0 :   if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "closedir(/proc/irq) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     298           0 : }
     299             : 
     300             : /* topo_banned_cpus returns the set of CPUs that should not handle
     301             :    interrupts in *cpuset. */
     302             : 
     303             : static fd_cpuset_t *
     304             : topo_banned_cpus( fd_cpuset_t cpuset[ static fd_cpuset_word_cnt ],
     305           0 :                   fd_topo_t const * topo ) {
     306           0 :   fd_cpuset_new( cpuset );
     307           0 :   ulong cpu_cnt = fd_shmem_cpu_cnt();
     308           0 :   for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     309           0 :     fd_topo_tile_t const * tile = &topo->tiles[ i ];
     310           0 :     if( tile->cpu_idx < cpu_cnt ) fd_cpuset_insert( cpuset, tile->cpu_idx );
     311           0 :   }
     312           0 :   return cpuset;
     313           0 : }
     314             : 
     315             : static void
     316             : init_perm( fd_cap_chk_t *   chk,
     317           0 :            config_t const * config FD_PARAM_UNUSED ) {
     318           0 :   fd_cap_chk_root( chk, "irq-affinity", "modify `/proc/irq/*/smp_affinity`" );
     319           0 : }
     320             : 
     321             : static void
     322             : fini_perm( fd_cap_chk_t *   chk,
     323           0 :            config_t const * config FD_PARAM_UNUSED ) {
     324           0 :   fd_cap_chk_root( chk, "irq-affinity", "modify `/proc/irq/*/smp_affinity`" );
     325           0 : }
     326             : 
     327             : static void
     328           0 : init( config_t const * config ) {
     329           0 :   FD_CPUSET_DECL( banned );
     330           0 :   topo_banned_cpus( banned, &config->topo );
     331             : 
     332           0 :   FD_CPUSET_DECL( allowed );
     333           0 :   fd_cpuset_subtract( allowed, fd_cpu_isolation_host_cpus( allowed ), banned );
     334             : 
     335           0 :   if( FD_UNLIKELY( !fd_cpuset_cnt( allowed ) ) ) {
     336           0 :     FD_LOG_ERR(( "all host CPUs are assigned to Firedancer tiles; cannot reserve any CPU for device interrupts" ));
     337           0 :   }
     338             : 
     339           0 :   set_irq_smp_affinities( allowed );
     340           0 : }
     341             : 
     342             : static int
     343             : fini( config_t const * config,
     344           0 :       int              pre_init ) {
     345           0 :   (void)pre_init;
     346             : 
     347           0 :   FD_CPUSET_DECL( banned );
     348           0 :   topo_banned_cpus( banned, &config->topo );
     349             : 
     350           0 :   if( FD_UNLIKELY( fd_cpuset_is_null( banned ) ) ) return 0;
     351             : 
     352           0 :   update_irq_smp_affinities( banned, NULL );
     353           0 :   return 1;
     354           0 : }
     355             : 
     356             : static configure_result_t
     357             : check( config_t const * config,
     358           0 :        int              check_type ) {
     359           0 :   FD_CPUSET_DECL( banned );
     360           0 :   topo_banned_cpus( banned, &config->topo );
     361             : 
     362           0 :   if( FD_UNLIKELY( fd_cpuset_is_null( banned ) ) ) {
     363           0 :     FD_CPUSET_DECL( daemon_banned );
     364           0 :     if( FD_LIKELY( -1!=fd_irqbalance_ban_cpus_get( daemon_banned ) ) ) CONFIGURE_OK();
     365           0 :   }
     366             : 
     367           0 :   FD_CPUSET_DECL( allowed );
     368           0 :   fd_cpuset_subtract( allowed, fd_cpu_isolation_host_cpus( allowed ), banned );
     369             : 
     370           0 :   DIR * dir = opendir( "/proc/irq" );
     371           0 :   if( FD_UNLIKELY( !dir ) ) FD_LOG_ERR(( "opendir(/proc/irq) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     372             : 
     373           0 :   ulong irq_cnt        = 0UL;
     374           0 :   ulong misconfigured  = 0UL;
     375           0 :   ulong unprobeable    = 0UL;
     376           0 :   ulong overlap_irq_sample[ MISMATCH_SAMPLE_MAX ];
     377           0 :   ulong overlap_irq_sample_cnt = 0UL;
     378           0 :   ulong overlap_irq_cnt = 0UL;
     379             : #if FD_IRQ_AFFINITY_CHECK_TIGHT
     380             :   ulong tight_irq_sample[ MISMATCH_SAMPLE_MAX ];
     381             :   ulong tight_irq_sample_cnt = 0UL;
     382             :   ulong tight_irq_cnt = 0UL;
     383             : #endif
     384           0 :   ulong tile_sample[ MISMATCH_SAMPLE_MAX ];
     385           0 :   ulong tile_sample_cnt = 0UL;
     386           0 :   ulong tile_overlap_cnt = 0UL;
     387           0 :   uchar tile_seen[ FD_TOPO_MAX_TILES ] = {0};
     388           0 :   struct dirent * entry;
     389           0 :   while( (entry = readdir( dir )) ) {
     390           0 :     if( FD_UNLIKELY( !irq_dirent_is_irq( entry->d_name ) ) ) continue;
     391           0 :     irq_cnt++;
     392             : 
     393           0 :     FD_CPUSET_DECL( current );
     394           0 :     if( FD_UNLIKELY( !read_irq_smp_affinity( entry->d_name, current ) ) ) {
     395           0 :       unprobeable++;
     396           0 :       continue;
     397           0 :     }
     398             : 
     399           0 :     if( FD_LIKELY( fd_cpuset_eq( current, allowed ) ) ) continue;
     400             : 
     401             :     /* Some IRQs cannot be moved.  Per the stage comment above, writing
     402             :        the same mask back lets us distinguish those from configurable IRQs. */
     403           0 :     if( FD_UNLIKELY( !write_irq_smp_affinity( entry->d_name, current, 0 ) ) ) {
     404           0 :       if( FD_LIKELY( errno==EPERM || errno==EIO || errno==ENOENT || errno==ENODEV || errno==ENXIO ) ) continue;
     405           0 :       else if( FD_LIKELY( errno==EACCES ) ) {
     406           0 :         char irq_name[ NAME_MAX+1UL ];
     407           0 :         FD_TEST( fd_cstr_printf_check( irq_name, sizeof(irq_name), NULL, "%s", entry->d_name ) );
     408           0 :         if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "closedir(/proc/irq) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     409           0 :         if( check_type==FD_CONFIGURE_CHECK_TYPE_FINI_PERM ) CONFIGURE_OK();
     410           0 :         NOT_CONFIGURED( "insufficient permissions to write /proc/irq/%s/smp_affinity", irq_name );
     411           0 :       } else {
     412           0 :         FD_LOG_ERR(( "write(/proc/irq/%s/smp_affinity) failed (%i-%s)", entry->d_name, errno, fd_io_strerror( errno ) ));
     413           0 :       }
     414           0 :     }
     415             : 
     416           0 :     FD_CPUSET_DECL( overlap );
     417           0 :     fd_cpuset_intersect( overlap, current, banned );
     418           0 :     ulong irq = strtoul( entry->d_name, NULL, 10 );
     419           0 :     if( FD_UNLIKELY( !fd_cpuset_is_null( overlap ) || fd_cpuset_is_null( banned ) ) ) {
     420           0 :       misconfigured++;
     421           0 :       if( FD_LIKELY( overlap_irq_sample_cnt<MISMATCH_SAMPLE_MAX ) ) overlap_irq_sample[ overlap_irq_sample_cnt++ ] = irq;
     422           0 :       overlap_irq_cnt++;
     423           0 :       for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
     424           0 :         fd_topo_tile_t const * tile = &config->topo.tiles[ i ];
     425           0 :         if( FD_UNLIKELY( tile->cpu_idx>=FD_TILE_MAX || !fd_cpuset_test( overlap, tile->cpu_idx ) || tile_seen[ i ] ) ) continue;
     426           0 :         tile_seen[ i ] = 1;
     427           0 :         if( FD_LIKELY( tile_sample_cnt<MISMATCH_SAMPLE_MAX ) ) tile_sample[ tile_sample_cnt++ ] = i;
     428           0 :         tile_overlap_cnt++;
     429           0 :       }
     430           0 :     }
     431             : #if FD_IRQ_AFFINITY_CHECK_TIGHT
     432             :     /* An IRQ pinned to a strict subset of the allowed CPUs (but no tile
     433             :        CPUs) does not steal time from any tile, so it does not violate the
     434             :        stage's goal.  irqbalance routinely narrows IRQs this way, which
     435             :        would make check() never pass while it runs, so the tight check is
     436             :        disabled for now. */
     437             :     else if( FD_UNLIKELY( fd_cpuset_subset( current, allowed ) ) ) {
     438             :       misconfigured++;
     439             :       if( FD_LIKELY( tight_irq_sample_cnt<MISMATCH_SAMPLE_MAX ) ) tight_irq_sample[ tight_irq_sample_cnt++ ] = irq;
     440             :       tight_irq_cnt++;
     441             :     }
     442             : #endif
     443           0 :   }
     444             : 
     445           0 :   if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "closedir(/proc/irq) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     446             : 
     447           0 :   if( FD_UNLIKELY( unprobeable==irq_cnt ) ) PARTIALLY_CONFIGURED( "could not read any IRQ affinity masks from /proc/irq" );
     448           0 :   if( FD_UNLIKELY( misconfigured ) ) {
     449           0 :     if( FD_UNLIKELY( fd_cpuset_is_null( banned ) ) ) {
     450           0 :       char irq_str[ MISMATCH_STR_LEN ];
     451           0 :       append_ulong_list_sample( irq_str, sizeof(irq_str), overlap_irq_sample, overlap_irq_sample_cnt, overlap_irq_cnt );
     452           0 :       NOT_CONFIGURED( "found IRQ affinity masks excluding host CPUs but no tiles are pinned (IRQs: %s)", irq_str );
     453           0 :     }
     454           0 :     if( FD_LIKELY( overlap_irq_cnt ) ) {
     455           0 :       char irq_str[ MISMATCH_STR_LEN ];
     456           0 :       char tile_str[ MISMATCH_TILE_STR_LEN ];
     457           0 :       append_ulong_list_sample( irq_str, sizeof(irq_str), overlap_irq_sample, overlap_irq_sample_cnt, overlap_irq_cnt );
     458           0 :       tile_list_sample( tile_str, sizeof(tile_str), &config->topo, tile_sample, tile_sample_cnt, tile_overlap_cnt );
     459           0 :       NOT_CONFIGURED( "found IRQs overlapping with Firedancer tile CPUs (IRQs: %s; tiles: %s)",
     460           0 :                       irq_str,
     461           0 :                       tile_str );
     462           0 :     }
     463             : #if FD_IRQ_AFFINITY_CHECK_TIGHT
     464             :     if( FD_UNLIKELY( tight_irq_cnt ) ) {
     465             :       char tight_irq_str[ MISMATCH_STR_LEN ];
     466             :       append_ulong_list_sample( tight_irq_str, sizeof(tight_irq_str), tight_irq_sample, tight_irq_sample_cnt, tight_irq_cnt );
     467             :       NOT_CONFIGURED( "found IRQ affinity masks excluding allowed CPUs (IRQs: %s)", tight_irq_str );
     468             :     }
     469             : #endif
     470           0 :     NOT_CONFIGURED( "%lu configurable IRQ affinity masks do not match Firedancer CPU policy", misconfigured );
     471           0 :   }
     472           0 :   CONFIGURE_OK();
     473           0 : }
     474             : 
     475             : configure_stage_t fd_cfg_stage_irq_affinity = {
     476             :   .name            = "irq-affinity",
     477             :   .init_perm       = init_perm,
     478             :   .fini_perm       = fini_perm,
     479             :   .init            = init,
     480             :   .fini            = fini,
     481             :   .check           = check
     482             : };
     483             : 
     484             : #undef FD_IRQ_AFFINITY_CHECK_TIGHT
     485             : #undef SMP_AFFINITY_STR_LEN
     486             : #undef MISMATCH_SAMPLE_MAX
     487             : #undef MISMATCH_STR_LEN
     488             : #undef MISMATCH_TILE_STR_LEN

Generated by: LCOV version 1.14