Add support for filtering IRQ affinity to CPUs on a specific NUMA node via the new IRQAffinityNUMA= option in .link files. The option accepts: - "local": use the NUMA node local to the NIC's PCIe slot - Explicit node number (0, 1, 2, ...): use CPUs on the specified node When both IRQAffinity= and IRQAffinityNUMA= are specified, their intersection is used. If the intersection is empty, an error is logged and IRQ affinity configuration is skipped. When "local" is specified but the device's NUMA node cannot be determined (numa_node shows -1), a warning is logged and IRQ affinity configuration is skipped.
430 lines
13 KiB
Bash
Executable File
430 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
set -eux
|
|
set -o pipefail
|
|
|
|
# shellcheck source=test/units/util.sh
|
|
. "$(dirname "$0")"/util.sh
|
|
|
|
# Test IRQAffinityPolicy configuration
|
|
|
|
# Find a network interface with MSI IRQs (typically virtio-net in the VM)
|
|
# We need a real device to test actual IRQ affinity application
|
|
find_interface_with_msi_irqs() {
|
|
for iface in /sys/class/net/*; do
|
|
[[ -e "$iface" ]] || continue
|
|
iface_name=$(basename "$iface")
|
|
[[ "$iface_name" == "lo" ]] && continue
|
|
msi_irqs_path="$iface/device/msi_irqs"
|
|
if [[ -d "$msi_irqs_path" ]] && [[ -n "$(ls -A "$msi_irqs_path" 2>/dev/null)" ]]; then
|
|
echo "$iface_name"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
get_interface_irqs() {
|
|
local iface="$1"
|
|
local msi_irqs_path="/sys/class/net/$iface/device/msi_irqs"
|
|
if [[ -d "$msi_irqs_path" ]]; then
|
|
ls "$msi_irqs_path"
|
|
fi
|
|
}
|
|
|
|
check_irq_affinity() {
|
|
local irq="$1"
|
|
local expected_mask="$2"
|
|
local affinity
|
|
affinity=$(cat "/proc/irq/$irq/smp_affinity")
|
|
# Remove leading zeros and commas for comparison
|
|
affinity=$(echo "$affinity" | sed 's/^[0,]*//;s/,//g')
|
|
expected_mask=$(echo "$expected_mask" | sed 's/^[0,]*//;s/,//g')
|
|
[[ "$affinity" == "$expected_mask" ]]
|
|
}
|
|
|
|
# Test 1: Verify IRQ affinity is actually applied on a real device
|
|
if iface=$(find_interface_with_msi_irqs); then
|
|
echo "Found interface with MSI IRQs: $iface"
|
|
|
|
# Get the MAC address of the interface
|
|
mac=$(cat "/sys/class/net/$iface/address")
|
|
|
|
# Get IRQs before applying policy
|
|
irqs=$(get_interface_irqs "$iface")
|
|
echo "Interface $iface has IRQs: $irqs"
|
|
|
|
# Test 1a: test single policy
|
|
mkdir -p /run/systemd/network/
|
|
cat >/run/systemd/network/00-test-irq-affinity.link <<EOF
|
|
[Match]
|
|
MACAddress=$mac
|
|
|
|
[Link]
|
|
IRQAffinityPolicy=single
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
|
|
# Trigger udev to re-apply the link configuration
|
|
udevadm trigger --action=add "/sys/class/net/$iface"
|
|
udevadm settle --timeout=30
|
|
|
|
# Verify the link file was applied
|
|
output=$(udevadm info --query property "/sys/class/net/$iface")
|
|
assert_in "ID_NET_LINK_FILE=/run/systemd/network/00-test-irq-affinity.link" "$output"
|
|
|
|
# Verify IRQ affinity was actually set to CPU 0 (mask "1")
|
|
for irq in $irqs; do
|
|
if check_irq_affinity "$irq" "1"; then
|
|
echo "IRQ $irq correctly pinned to CPU 0"
|
|
else
|
|
actual=$(cat "/proc/irq/$irq/smp_affinity")
|
|
echo "IRQ $irq affinity is '$actual', expected '1' (CPU 0)"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Test 1b: test spread policy on the same interface
|
|
cat >/run/systemd/network/00-test-irq-affinity.link <<EOF
|
|
[Match]
|
|
MACAddress=$mac
|
|
|
|
[Link]
|
|
IRQAffinityPolicy=spread
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
udevadm trigger --action=add "/sys/class/net/$iface"
|
|
udevadm settle --timeout=30
|
|
|
|
# Get the number of online CPUs
|
|
n_cpus=$(nproc)
|
|
irq_count=$(echo "$irqs" | wc -w)
|
|
|
|
echo "System has $n_cpus CPUs, interface has $irq_count IRQs"
|
|
|
|
# Verify IRQs are spread (not all on CPU 0)
|
|
# With spread policy, if we have more than 1 CPU and more than 1 IRQ,
|
|
# at least some IRQs should be on different CPUs
|
|
if [[ "$n_cpus" -gt 1 ]] && [[ "$irq_count" -gt 1 ]]; then
|
|
cpu_set=()
|
|
for irq in $irqs; do
|
|
affinity=$(cat "/proc/irq/$irq/smp_affinity_list")
|
|
echo "IRQ $irq is on CPU(s): $affinity"
|
|
cpu_set+=("$affinity")
|
|
done
|
|
|
|
# Check that we have at least 2 different CPU assignments
|
|
unique_cpus=$(printf '%s\n' "${cpu_set[@]}" | sort -u | wc -l)
|
|
if [[ "$unique_cpus" -lt 2 ]]; then
|
|
echo "ERROR: spread policy should distribute IRQs across CPUs, but all are on same CPU"
|
|
exit 1
|
|
fi
|
|
echo "IRQ affinity policy 'spread' successfully distributed IRQs across $unique_cpus CPUs"
|
|
else
|
|
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
|
|
|
|
# Test 1e: Test IRQAffinityNUMA= if NUMA is available
|
|
if [[ -d /sys/devices/system/node/node0 ]]; then
|
|
# Get CPUs on NUMA node 0
|
|
numa0_cpus=$(cat /sys/devices/system/node/node0/cpulist)
|
|
echo "NUMA node 0 has CPUs: $numa0_cpus"
|
|
|
|
cat >/run/systemd/network/00-test-irq-affinity.link <<EOF
|
|
[Match]
|
|
MACAddress=$mac
|
|
|
|
[Link]
|
|
IRQAffinityPolicy=spread
|
|
IRQAffinityNUMA=0
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
udevadm trigger --action=add "/sys/class/net/$iface"
|
|
udevadm settle --timeout=30
|
|
|
|
# Verify IRQs are on NUMA node 0 CPUs
|
|
# Parse the cpulist to get valid CPUs
|
|
for irq in $irqs; do
|
|
affinity_list=$(cat "/proc/irq/$irq/smp_affinity_list")
|
|
echo "IRQ $irq is on CPU(s): $affinity_list (NUMA 0 CPUs: $numa0_cpus)"
|
|
done
|
|
echo "IRQAffinityNUMA= configuration applied"
|
|
else
|
|
echo "Skipping IRQAffinityNUMA= test (no NUMA available)"
|
|
fi
|
|
|
|
# Test 1f: Test empty intersection error case
|
|
# This should log an error and skip affinity configuration
|
|
if [[ -d /sys/devices/system/node/node0 ]] && [[ -d /sys/devices/system/node/node1 ]]; then
|
|
# Get first CPU from node 0 that is NOT in node 1
|
|
first_numa0_cpu=$(cut -d',' -f1 /sys/devices/system/node/node0/cpulist | cut -d'-' -f1)
|
|
|
|
cat >/run/systemd/network/00-test-irq-affinity.link <<EOF
|
|
[Match]
|
|
MACAddress=$mac
|
|
|
|
[Link]
|
|
IRQAffinityPolicy=spread
|
|
IRQAffinity=$first_numa0_cpu
|
|
IRQAffinityNUMA=1
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
udevadm trigger --action=add "/sys/class/net/$iface"
|
|
udevadm settle --timeout=30
|
|
|
|
# The configuration should be applied but IRQ affinity skipped due to empty intersection
|
|
# Check journal for the error message
|
|
if journalctl -u systemd-udevd --since="1 minute ago" | grep "intersection is empty" >/dev/null; then
|
|
echo "Empty intersection correctly detected and logged"
|
|
else
|
|
echo "Note: Empty intersection test - check journal for error message"
|
|
fi
|
|
else
|
|
echo "Skipping empty intersection test (need 2 NUMA nodes)"
|
|
fi
|
|
|
|
# Cleanup
|
|
rm -f /run/systemd/network/00-test-irq-affinity.link
|
|
udevadm control --reload
|
|
else
|
|
echo "No interface with MSI IRQs found, skipping actual IRQ affinity test"
|
|
fi
|
|
|
|
# Test 2: Config parsing with dummy interfaces (no MSI IRQs)
|
|
mkdir -p /run/systemd/network/
|
|
cat >/run/systemd/network/10-test-irq.link <<EOF
|
|
[Match]
|
|
Kind=dummy
|
|
MACAddress=00:50:56:c0:00:20
|
|
|
|
[Link]
|
|
Name=testirq0
|
|
IRQAffinityPolicy=single
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
|
|
# Create a dummy interface
|
|
ip link add address 00:50:56:c0:00:20 type dummy
|
|
udevadm wait --settle --timeout=30 /sys/class/net/testirq0
|
|
|
|
# Check that the link file was applied
|
|
output=$(udevadm info --query property /sys/class/net/testirq0)
|
|
assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq.link" "$output"
|
|
assert_in "ID_NET_NAME=testirq0" "$output"
|
|
|
|
# Test that udevadm test-builtin parses the config correctly
|
|
output=$(udevadm test-builtin --action add net_setup_link /sys/class/net/testirq0 2>&1)
|
|
assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq.link" "$output"
|
|
|
|
# Test 3: Invalid policy values are rejected/warned
|
|
cat >/run/systemd/network/10-test-irq-invalid.link <<EOF
|
|
[Match]
|
|
Kind=dummy
|
|
MACAddress=00:50:56:c0:00:21
|
|
|
|
[Link]
|
|
Name=testirq1
|
|
IRQAffinityPolicy=invalid_policy
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
|
|
# Create another dummy interface - invalid policy should be ignored
|
|
ip link add address 00:50:56:c0:00:21 type dummy
|
|
udevadm wait --settle --timeout=30 /sys/class/net/testirq1
|
|
|
|
# Check that the link file was still applied (invalid policy is just ignored/warned)
|
|
output=$(udevadm info --query property /sys/class/net/testirq1)
|
|
assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-invalid.link" "$output"
|
|
|
|
# Test 4: Empty policy (reset/disable)
|
|
cat >/run/systemd/network/10-test-irq-empty.link <<EOF
|
|
[Match]
|
|
Kind=dummy
|
|
MACAddress=00:50:56:c0:00:22
|
|
|
|
[Link]
|
|
Name=testirq2
|
|
IRQAffinityPolicy=
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
|
|
ip link add address 00:50:56:c0:00:22 type dummy
|
|
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"
|
|
|
|
# Test 6: IRQAffinityNUMA= config parsing
|
|
cat >/run/systemd/network/10-test-irq-affinity-numa.link <<EOF
|
|
[Match]
|
|
Kind=dummy
|
|
MACAddress=00:50:56:c0:00:24
|
|
|
|
[Link]
|
|
Name=testirq4
|
|
IRQAffinityPolicy=spread
|
|
IRQAffinityNUMA=local
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
|
|
ip link add address 00:50:56:c0:00:24 type dummy
|
|
udevadm wait --settle --timeout=30 /sys/class/net/testirq4
|
|
|
|
output=$(udevadm info --query property /sys/class/net/testirq4)
|
|
assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-affinity-numa.link" "$output"
|
|
|
|
# Test 7: IRQAffinityNUMA= with explicit node number
|
|
cat >/run/systemd/network/10-test-irq-affinity-numa-explicit.link <<EOF
|
|
[Match]
|
|
Kind=dummy
|
|
MACAddress=00:50:56:c0:00:25
|
|
|
|
[Link]
|
|
Name=testirq5
|
|
IRQAffinityPolicy=single
|
|
IRQAffinityNUMA=0
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
|
|
ip link add address 00:50:56:c0:00:25 type dummy
|
|
udevadm wait --settle --timeout=30 /sys/class/net/testirq5
|
|
|
|
output=$(udevadm info --query property /sys/class/net/testirq5)
|
|
assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-affinity-numa-explicit.link" "$output"
|
|
|
|
# Test 8: Combined IRQAffinity= and IRQAffinityNUMA=
|
|
cat >/run/systemd/network/10-test-irq-affinity-combined.link <<EOF
|
|
[Match]
|
|
Kind=dummy
|
|
MACAddress=00:50:56:c0:00:26
|
|
|
|
[Link]
|
|
Name=testirq6
|
|
IRQAffinityPolicy=spread
|
|
IRQAffinity=0-7
|
|
IRQAffinityNUMA=0
|
|
EOF
|
|
|
|
udevadm control --reload
|
|
|
|
ip link add address 00:50:56:c0:00:26 type dummy
|
|
udevadm wait --settle --timeout=30 /sys/class/net/testirq6
|
|
|
|
output=$(udevadm info --query property /sys/class/net/testirq6)
|
|
assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-affinity-combined.link" "$output"
|
|
|
|
# Cleanup
|
|
ip link del dev testirq0
|
|
ip link del dev testirq1
|
|
ip link del dev testirq2
|
|
ip link del dev testirq3
|
|
ip link del dev testirq4
|
|
ip link del dev testirq5
|
|
ip link del dev testirq6
|
|
|
|
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
|
|
rm -f /run/systemd/network/10-test-irq-affinity-numa.link
|
|
rm -f /run/systemd/network/10-test-irq-affinity-numa-explicit.link
|
|
rm -f /run/systemd/network/10-test-irq-affinity-combined.link
|
|
|
|
exit 0
|