udev/net: implement IRQAffinityPolicy=spread with topology awareness

Implement the spread policy for IRQ affinity distribution using a
topology-aware algorithm. The algorithm:

1. Discovers CPU topology from sysfs (NUMA node, package, die/L3, core)
2. Groups CPUs by L3 cache domain (die) with equidistant ordering
3. Round-robins across dies, spreading IRQs across the system
4. Uses first hyperthread of each core before second hyperthreads
5. Applies IRQ affinity via /proc/irq/<n>/smp_affinity

When there are more IRQs than CPUs, queues wrap around using round-robin.
This commit is contained in:
Quentin Deslandes
2026-05-20 11:39:10 +02:00
parent 66892d877c
commit cf32bd9e7c
7 changed files with 626 additions and 0 deletions
+57
View File
@@ -154,6 +154,63 @@ static int numa_max_node(void) {
return max_node;
}
int numa_get_node_from_cpu(unsigned cpu, unsigned *ret) {
_cleanup_closedir_ DIR *d = NULL;
int r;
assert(ret);
d = opendir("/sys/devices/system/node");
if (!d)
return -errno;
FOREACH_DIRENT(de, d, break) {
char p[STRLEN("/sys/devices/system/node/node/cpulist") + DECIMAL_STR_MAX(unsigned) + 1];
_cleanup_(cpu_set_done) CPUSet cpus = {};
_cleanup_free_ char *cpulist = NULL;
const char *n;
unsigned node;
if (de->d_type != DT_DIR)
continue;
n = startswith(de->d_name, "node");
if (!n)
continue;
r = safe_atou(n, &node);
if (r < 0) {
log_debug_errno(r, "Failed to parse node number %s to unsigned, ignoring: %m", n);
continue;
}
xsprintf(p, "/sys/devices/system/node/node%u/cpulist", node);
r = read_virtual_file(p, SIZE_MAX, &cpulist, /* ret_size= */ NULL);
if (r < 0) {
log_debug_errno(r, "Failed to read %s, ignoring: %m", p);
continue;
}
r = parse_cpu_set(cpulist, &cpus);
if (r < 0) {
log_debug_errno(r, "Failed to parse cpu set %s, ignoring: %m", cpulist);
continue;
}
if (CPU_ISSET_S(cpu, cpus.allocated, cpus.set)) {
*ret = node;
return 0;
}
}
/* CPU not found in any NUMA node, assume node 0 */
log_debug("CPU %u not found in any NUMA node, assuming node 0.", cpu);
*ret = 0;
return 0;
}
int numa_mask_add_all(CPUSet *mask) {
int m;
+2
View File
@@ -31,6 +31,8 @@ static inline void numa_policy_reset(NUMAPolicy *p) {
int apply_numa_policy(const NUMAPolicy *policy);
int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret);
int numa_get_node_from_cpu(unsigned cpu, unsigned *ret);
int numa_mask_add_all(CPUSet *mask);
DECLARE_STRING_TABLE_LOOKUP(mpol, int);