diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf index 0b63a1e446..90eebe120d 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 6211081d79..5fd59f9453 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 ff581d8b02..da28f56980 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 f9dde104fa..7c3662243e 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