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
|