From: Quentin Deslandes Date: Mon, 16 Feb 2026 19:40:38 +0000 (+0100) Subject: udev/net: add IRQAffinity= option to filter eligible CPUs X-Git-Tag: v261-rc1~70^2~2 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=a9e821549bc757ed34b3f6dd7aee0997be126e1e;p=thirdparty%2Fsystemd.git udev/net: add IRQAffinity= option to filter eligible CPUs Add IRQAffinity= option to .link files that filters the set of CPUs eligible for IRQ placement. This works in conjunction with IRQAffinityPolicy= to constrain which CPUs receive network IRQs. When specified with spread policy, only the listed CPUs are considered for IRQ distribution. When specified with single policy, IRQs are pinned to the first CPU in the allowed set instead of CPU 0. --- diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf index 0b63a1e4462..90eebe120d1 100644 --- a/src/udev/net/link-config-gperf.gperf +++ b/src/udev/net/link-config-gperf.gperf @@ -134,6 +134,7 @@ Link.CoalescePacketRateSampleIntervalSec, config_parse_coalesce_sec, Link.ReceivePacketSteeringCPUMask, config_parse_rps_cpu_mask, 0, offsetof(LinkConfig, rps_cpu_mask) /* IRQ affinity settings */ Link.IRQAffinityPolicy, config_parse_irq_affinity_policy, 0, offsetof(LinkConfig, irq_affinity_policy) +Link.IRQAffinity, config_parse_cpu_set, 0, offsetof(LinkConfig, irq_affinity_cpus) /* SR-IOV settings */ Link.SR-IOVVirtualFunctions, config_parse_sr_iov_num_vfs, 0, offsetof(LinkConfig, sr_iov_num_vfs) SR-IOV.VirtualFunction, config_parse_sr_iov_uint32, 0, offsetof(LinkConfig, sr_iov_by_section) diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 6211081d79a..5fd59f9453a 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -83,6 +83,7 @@ static LinkConfig* link_config_free(LinkConfig *config) { free(config->wol_password_file); erase_and_free(config->wol_password); cpu_set_done(&config->rps_cpu_mask); + cpu_set_done(&config->irq_affinity_cpus); ordered_hashmap_free(config->sr_iov_by_section); @@ -1408,7 +1409,7 @@ static int set_irq_affinity(Link *link, unsigned irq, unsigned cpu) { return 0; } -static int link_apply_irq_affinity_spread(Link *link) { +static int link_apply_irq_affinity_spread(Link *link, const CPUSet *allowed_cpus) { _cleanup_closedir_ DIR *dir = NULL; _cleanup_free_ CPUTopology *topology = NULL; _cleanup_free_ unsigned *irqs = NULL; @@ -1450,7 +1451,30 @@ static int link_apply_irq_affinity_spread(Link *link) { if (r < 0) return log_link_error_errno(link, r, "Failed to discover CPU topology: %m"); - log_link_debug(link, "Discovered %zu CPUs, spreading %zu IRQs.", topology_count, irq_count); + /* Filter topology by allowed CPUs if specified */ + if (allowed_cpus && allowed_cpus->set) { + _cleanup_free_ CPUTopology *filtered_topology = new(CPUTopology, topology_count); + size_t filtered_count = 0; + + if (!filtered_topology) + return log_oom(); + + for (size_t i = 0; i < topology_count; i++) + if (CPU_ISSET_S(topology[i].cpu, allowed_cpus->allocated, allowed_cpus->set)) + filtered_topology[filtered_count++] = topology[i]; + + if (filtered_count == 0) { + log_link_warning(link, "IRQAffinity= filter excludes all CPUs, skipping spread."); + return 0; + } + + log_link_debug(link, "Filtered to %zu CPUs (from %zu) based on IRQAffinity=.", filtered_count, topology_count); + + free_and_replace(topology, filtered_topology); + topology_count = filtered_count; + } + + log_link_debug(link, "Spreading %zu IRQs across %zu CPUs.", irq_count, topology_count); /* Select CPUs using maximum distance algorithm */ r = select_spread_cpus(topology, topology_count, irq_count, &spread_cpus, &spread_count); @@ -1466,14 +1490,32 @@ static int link_apply_irq_affinity_spread(Link *link) { return 0; } -static int link_apply_irq_affinity_single(Link *link) { +static int link_apply_irq_affinity_single(Link *link, const CPUSet *allowed_cpus) { _cleanup_closedir_ DIR *dir = NULL; + unsigned target_cpu = 0; int r; assert(link); assert(link->config); assert(ASSERT_PTR(link->event)->dev); + /* If IRQAffinity= is specified, use the first allowed CPU instead of CPU 0 */ + if (allowed_cpus && allowed_cpus->set) { + bool found = false; + + for (unsigned cpu = 0; cpu < allowed_cpus->allocated * 8; cpu++) + if (CPU_ISSET_S(cpu, allowed_cpus->allocated, allowed_cpus->set)) { + target_cpu = cpu; + found = true; + break; + } + + if (!found) { + log_link_warning(link, "IRQAffinity= filter excludes all CPUs, skipping single."); + return 0; + } + } + r = device_opendir(link->event->dev, "device/msi_irqs", &dir); if (r < 0) { if (r != -ENOENT) @@ -1489,10 +1531,10 @@ static int link_apply_irq_affinity_single(Link *link) { if (r < 0) return log_link_error_errno(link, r, "Failed to convert parse IRQ number: %s", de->d_name); - (void) set_irq_affinity(link, irq, /* cpu= */ 0); + (void) set_irq_affinity(link, irq, target_cpu); } - log_link_info(link, "Applied IRQ affinity policy 'single' (pinning to CPU 0)."); + log_link_info(link, "Applied IRQ affinity policy 'single' (pinning to CPU %u).", target_cpu); return 0; } @@ -1520,9 +1562,9 @@ static int link_apply_irq_affinity(Link *link) { switch (link->config->irq_affinity_policy) { case IRQ_AFFINITY_POLICY_SINGLE: - return link_apply_irq_affinity_single(link); + return link_apply_irq_affinity_single(link, &link->config->irq_affinity_cpus); case IRQ_AFFINITY_POLICY_SPREAD: - return link_apply_irq_affinity_spread(link); + return link_apply_irq_affinity_spread(link, &link->config->irq_affinity_cpus); default: assert_not_reached(); } diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h index ff581d8b021..da28f569807 100644 --- a/src/udev/net/link-config.h +++ b/src/udev/net/link-config.h @@ -122,6 +122,7 @@ struct LinkConfig { /* IRQ affinity */ IRQAffinityPolicy irq_affinity_policy; + CPUSet irq_affinity_cpus; /* SR-IOV */ uint32_t sr_iov_num_vfs; diff --git a/test/units/TEST-17-UDEV.irq-affinity.sh b/test/units/TEST-17-UDEV.irq-affinity.sh index f9dde104fa6..7c3662243e2 100755 --- a/test/units/TEST-17-UDEV.irq-affinity.sh +++ b/test/units/TEST-17-UDEV.irq-affinity.sh @@ -126,6 +126,69 @@ EOF echo "Skipping spread verification (need >1 CPU and >1 IRQ)" fi + # Test 1c: Test IRQAffinity= CPU filtering with single policy + # Pin to CPU 1 instead of default CPU 0 + cat >/run/systemd/network/00-test-irq-affinity.link <1 CPU)" + fi + + # Test 1d: Test IRQAffinity= with spread policy (restrict to subset of CPUs) + if [[ "$n_cpus" -ge 4 ]] && [[ "$irq_count" -gt 1 ]]; then + cat >/run/systemd/network/00-test-irq-affinity.link <=4 CPUs and >1 IRQ)" + fi + # Cleanup rm -f /run/systemd/network/00-test-irq-affinity.link udevadm control --reload @@ -200,13 +263,39 @@ udevadm wait --settle --timeout=30 /sys/class/net/testirq2 output=$(udevadm info --query property /sys/class/net/testirq2) assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-empty.link" "$output" +# Test 5: IRQAffinity= config parsing +cat >/run/systemd/network/10-test-irq-affinity-cpus.link <&1) +assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-affinity-cpus.link" "$output" + # Cleanup ip link del dev testirq0 ip link del dev testirq1 ip link del dev testirq2 +ip link del dev testirq3 rm -f /run/systemd/network/10-test-irq.link rm -f /run/systemd/network/10-test-irq-invalid.link rm -f /run/systemd/network/10-test-irq-empty.link +rm -f /run/systemd/network/10-test-irq-affinity-cpus.link exit 0