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.
This commit is contained in:
Quentin Deslandes
2026-05-20 11:39:10 +02:00
parent cf32bd9e7c
commit a9e821549b
4 changed files with 140 additions and 7 deletions
+1
View File
@@ -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)
+49 -7
View File
@@ -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();
}
+1
View File
@@ -122,6 +122,7 @@ struct LinkConfig {
/* IRQ affinity */
IRQAffinityPolicy irq_affinity_policy;
CPUSet irq_affinity_cpus;
/* SR-IOV */
uint32_t sr_iov_num_vfs;
+89
View File
@@ -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 <<EOF
[Match]
MACAddress=$mac
[Link]
IRQAffinityPolicy=single
IRQAffinity=1
EOF
udevadm control --reload
udevadm trigger --action=add "/sys/class/net/$iface"
udevadm settle --timeout=30
if [[ "$n_cpus" -gt 1 ]]; then
for irq in $irqs; do
echo "Checking IRQ $irq affinity with IRQAffinity=1..."
if check_irq_affinity "$irq" "2"; then
echo "IRQ $irq correctly pinned to CPU 1"
else
actual=$(cat "/proc/irq/$irq/smp_affinity")
echo "IRQ $irq affinity is '$actual', expected '2' (CPU 1)"
exit 1
fi
done
echo "IRQAffinity= filtering with single policy works correctly"
else
echo "Skipping IRQAffinity= test (need >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 <<EOF
[Match]
MACAddress=$mac
[Link]
IRQAffinityPolicy=spread
IRQAffinity=0-1
EOF
udevadm control --reload
udevadm trigger --action=add "/sys/class/net/$iface"
udevadm settle --timeout=30
# Verify all IRQs are on CPU 0 or 1 only
for irq in $irqs; do
affinity_list=$(cat "/proc/irq/$irq/smp_affinity_list")
echo "IRQ $irq is on CPU(s): $affinity_list"
# Check that affinity is 0 or 1 (not higher CPUs)
if [[ "$affinity_list" =~ ^[01]$ ]]; then
echo "IRQ $irq correctly restricted to CPUs 0-1"
else
echo "ERROR: IRQ $irq is on CPU $affinity_list, expected 0 or 1"
exit 1
fi
done
echo "IRQAffinity= filtering with spread policy works correctly"
else
echo "Skipping IRQAffinity= spread test (need >=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 <<EOF
[Match]
Kind=dummy
MACAddress=00:50:56:c0:00:23
[Link]
Name=testirq3
IRQAffinityPolicy=spread
IRQAffinity=0-3,8-11
EOF
udevadm control --reload
ip link add address 00:50:56:c0:00:23 type dummy
udevadm wait --settle --timeout=30 /sys/class/net/testirq3
output=$(udevadm info --query property /sys/class/net/testirq3)
assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-affinity-cpus.link" "$output"
# Test that udevadm test-builtin parses the IRQAffinity= config correctly
output=$(udevadm test-builtin --action add net_setup_link /sys/class/net/testirq3 2>&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