* [PATCH net-next v2 3/7] selftests: rds: Fix more pylint errors
From: Allison Henderson @ 2026-04-28 22:27 UTC (permalink / raw)
To: netdev, pabeni, edumazet, kuba, horms, linux-rdma, achender,
linux-kselftest, shuah
In-Reply-To: <20260428222716.2960871-1-achender@kernel.org>
This patch fixes a few pylint errors in test.py. Remove unused exception
variables from except blocks, and disable warnings for imports that cannot
appear at the start of the module. Also disable warnings for the
tcpdump processes. The suggestion to use a with block does not apply
here since the process needs to outlive the parent to collect the dumps.
Lastly add the module docstring at the top of the module.
Signed-off-by: Allison Henderson <achender@kernel.org>
---
tools/testing/selftests/net/rds/test.py | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/net/rds/test.py b/tools/testing/selftests/net/rds/test.py
index 93e23e8b256c..4b6ffbb3a81c 100755
--- a/tools/testing/selftests/net/rds/test.py
+++ b/tools/testing/selftests/net/rds/test.py
@@ -1,5 +1,8 @@
#! /usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
+"""
+This module provides functional testing for the net/rds component.
+"""
import argparse
import ctypes
@@ -17,7 +20,8 @@ import shutil
# Allow utils module to be imported from different directory
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(this_dir, "../"))
-from lib.py.utils import ip
+# pylint: disable-next=wrong-import-position,import-error,no-name-in-module
+from lib.py.utils import ip # noqa: E402
libc = ctypes.cdll.LoadLibrary('libc.so.6')
setns = libc.setns
@@ -129,6 +133,7 @@ tcpdump_procs = []
for net in [NET0, NET1]:
pcap = logdir+'/'+net+'.pcap'
fd, pcap_tmp = tempfile.mkstemp(suffix=".pcap", prefix=f"{net}-", dir="/tmp")
+ # pylint: disable-next=consider-using-with
p = subprocess.Popen(
['ip', 'netns', 'exec', net,
'/usr/sbin/tcpdump', '-i', 'any', '-w', pcap_tmp])
@@ -192,7 +197,7 @@ while nr_send < NUM_PACKETS:
send_hashes.setdefault((sender.fileno(), receiver.fileno()),
hashlib.sha256()).update(f'<{send_data}>'.encode('utf-8'))
nr_send = nr_send + 1
- except BlockingIOError as e:
+ except BlockingIOError:
break
except OSError as e:
if e.errno in [errno.ENOBUFS, errno.ECONNRESET, errno.EPIPE]:
@@ -214,7 +219,7 @@ while nr_send < NUM_PACKETS:
receiver.fileno()), hashlib.sha256()).update(
f'<{recv_data}>'.encode('utf-8'))
nr_recv = nr_recv + 1
- except BlockingIOError as e:
+ except BlockingIOError:
break
# exercise net/rds/tcp.c:rds_tcp_sysctl_reset()
--
2.25.1
^ permalink raw reply related
* [PATCH net-next v2 4/7] selftests: rds: Add timeout flag to run.sh
From: Allison Henderson @ 2026-04-28 22:27 UTC (permalink / raw)
To: netdev, pabeni, edumazet, kuba, horms, linux-rdma, achender,
linux-kselftest, shuah
In-Reply-To: <20260428222716.2960871-1-achender@kernel.org>
Add a -t flag to run.sh to optionally override the default
timeout. The --timeout flag is already supported in test.py,
so just add the shorthand -t flag
Signed-off-by: Allison Henderson <achender@kernel.org>
---
tools/testing/selftests/net/rds/run.sh | 11 ++++++++---
tools/testing/selftests/net/rds/test.py | 2 +-
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/net/rds/run.sh b/tools/testing/selftests/net/rds/run.sh
index 73a9b986b0ef..bc2e53126aab 100755
--- a/tools/testing/selftests/net/rds/run.sh
+++ b/tools/testing/selftests/net/rds/run.sh
@@ -154,8 +154,9 @@ LOG_DIR="$current_dir"/rds_logs
PLOSS=0
PCORRUPT=0
PDUP=0
+TIMEOUT=$timeout
GENERATE_GCOV_REPORT=1
-while getopts "d:l:c:u:" opt; do
+while getopts "d:l:c:u:t:" opt; do
case ${opt} in
d)
LOG_DIR=${OPTARG}
@@ -166,12 +167,15 @@ while getopts "d:l:c:u:" opt; do
c)
PCORRUPT=${OPTARG}
;;
+ t)
+ TIMEOUT=${OPTARG}
+ ;;
u)
PDUP=${OPTARG}
;;
:)
echo "USAGE: run.sh [-d logdir] [-l packet_loss] [-c packet_corruption]" \
- "[-u packet_duplicate]"
+ "[-u packet_duplicate] [-t timeout]"
exit 1
;;
?)
@@ -198,7 +202,8 @@ echo running RDS tests...
echo Traces will be logged to "$TRACE_FILE"
rm -f "$TRACE_FILE"
strace -T -tt -o "$TRACE_FILE" python3 "$(dirname "$0")/test.py" \
- --timeout "$timeout" -d "$LOG_DIR" -l "$PLOSS" -c "$PCORRUPT" -u "$PDUP"
+ -t "$TIMEOUT" -d "$LOG_DIR" -l "$PLOSS" -c "$PCORRUPT" \
+ -u "$PDUP"
test_rc=$?
dmesg > "${LOG_DIR}/dmesg.out"
diff --git a/tools/testing/selftests/net/rds/test.py b/tools/testing/selftests/net/rds/test.py
index 4b6ffbb3a81c..d48533505f0f 100755
--- a/tools/testing/selftests/net/rds/test.py
+++ b/tools/testing/selftests/net/rds/test.py
@@ -83,7 +83,7 @@ parser = argparse.ArgumentParser(description="init script args",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--logdir", action="store",
help="directory to store logs", default="/tmp")
-parser.add_argument('--timeout', help="timeout to terminate hung test",
+parser.add_argument('-t', '--timeout', help="timeout to terminate hung test",
type=int, default=0)
parser.add_argument('-l', '--loss', help="Simulate tcp packet loss",
type=int, default=0)
--
2.25.1
^ permalink raw reply related
* [PATCH net-next v2 5/7] selftests: rds: Fix gcov and pcap collection
From: Allison Henderson @ 2026-04-28 22:27 UTC (permalink / raw)
To: netdev, pabeni, edumazet, kuba, horms, linux-rdma, achender,
linux-kselftest, shuah
In-Reply-To: <20260428222716.2960871-1-achender@kernel.org>
The vng guest shares the host filesystem via 9p and runs a minimal
systemd inherited from the host's /lib/systemd/system/. As a result,
which filesystems get auto-mounted in the guest depends on the host OS
and its systemd version.
The tcpdump pcaps are initially saved to /tmp because 9p does not support
chown which tcpdump requires. But whether /tmp is already a tmpfs depends
on the host's systemd configuration and may still sometimes fail if /tmp
is not mounted by default. Fix this by mounting tmpfs on /tmp in run.sh
when it is not already a separately mounted filesystem.
A similar dependency exists for gcov. debugfs is not mounted automatically
in vng guest, so the gcov data copy from /sys/kernel/debug/gcov/
silently finds nothing depending on whether debugfs is mounted by default
on the host OS. Fix this by mounting debugfs in run.sh before copying the
gcda files.
Finally, when invoked through the kselftest runner, the working directory
is the test directory rather than the kernel source root. gcovr defaults
--root to the current working directory, causing it to filter out all
coverage data for files under net/rds/ since they are not under the test
directory. Fix this by passing --root to gcovr explicitly.
Signed-off-by: Allison Henderson <achender@kernel.org>
---
tools/testing/selftests/net/rds/run.sh | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/rds/run.sh b/tools/testing/selftests/net/rds/run.sh
index bc2e53126aab..3fc116d23410 100755
--- a/tools/testing/selftests/net/rds/run.sh
+++ b/tools/testing/selftests/net/rds/run.sh
@@ -197,6 +197,13 @@ COVR_DIR="${LOG_DIR}/coverage/"
mkdir -p "$LOG_DIR"
mkdir -p "$COVR_DIR"
+# tcpdump saves pcaps to /tmp because it requires chown to save the
+# pcap but chown is not supported by 9p. Mount tmpfs on /tmp if it is
+# not already a separate filesystem
+if ! mountpoint -q /tmp 2>/dev/null; then
+ mount -t tmpfs tmpfs /tmp
+fi
+
set +e
echo running RDS tests...
echo Traces will be logged to "$TRACE_FILE"
@@ -210,6 +217,12 @@ dmesg > "${LOG_DIR}/dmesg.out"
if [ "$GENERATE_GCOV_REPORT" -eq 1 ]; then
echo saving coverage data...
+
+ # Ensure debugfs is mounted
+ if ! test -d /sys/kernel/debug/gcov; then
+ mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null || true
+ fi
+
(set +x; cd /sys/kernel/debug/gcov; find ./* -name '*.gcda' | \
while read -r f
do
@@ -218,7 +231,7 @@ if [ "$GENERATE_GCOV_REPORT" -eq 1 ]; then
echo running gcovr...
gcovr -s --html-details --gcov-executable "$GCOV_CMD" --gcov-ignore-parse-errors \
- -o "${COVR_DIR}/gcovr" "${ksrc_dir}/net/rds/"
+ --root "${ksrc_dir}" -o "${COVR_DIR}/gcovr" "${ksrc_dir}/net/rds/"
else
echo "Coverage report will be skipped"
fi
--
2.25.1
^ permalink raw reply related
* [PATCH net-next v2 6/7] selftests: rds: Collect pcaps on timeout
From: Allison Henderson @ 2026-04-28 22:27 UTC (permalink / raw)
To: netdev, pabeni, edumazet, kuba, horms, linux-rdma, achender,
linux-kselftest, shuah
In-Reply-To: <20260428222716.2960871-1-achender@kernel.org>
The timeout signal handler for the rds selftests currently just
exits when the time limit is exceeded, and forgets to collect the
network dumps. Which can be valueable for discerning why the test
timed out in the first place. Fix this by hoisting the network
dump collection into a helper function, and call it from the
signal handler before exiting
Signed-off-by: Allison Henderson <achender@kernel.org>
---
tools/testing/selftests/net/rds/test.py | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/tools/testing/selftests/net/rds/test.py b/tools/testing/selftests/net/rds/test.py
index d48533505f0f..1c7aebddeb61 100755
--- a/tools/testing/selftests/net/rds/test.py
+++ b/tools/testing/selftests/net/rds/test.py
@@ -70,11 +70,21 @@ def netns_socket(netns, *sock_args):
u1.close()
return socket.fromfd(fds[0], *sock_args)
+def collect_pcaps():
+ """Stop tcpdump processes and move their pcaps into the log dir."""
+ print("Stopping network packet captures")
+ for proc, tmp_path, dest_path, fno in tcpdump_procs:
+ proc.terminate()
+ proc.wait()
+ os.close(fno)
+ shutil.move(tmp_path, dest_path)
+
def signal_handler(_sig, _frame):
"""
Test timed out signal handler
"""
print('Test timed out')
+ collect_pcaps()
sys.exit(1)
#Parse out command line arguments. We take an optional
@@ -251,13 +261,7 @@ for s in sockets:
pass
print(f"getsockopt(): {nr_success}/{nr_error}")
-
-print("Stopping network packet captures")
-for p, pcap_tmp, pcap, fd in tcpdump_procs:
- p.terminate()
- p.wait()
- os.close(fd)
- shutil.move(pcap_tmp, pcap)
+collect_pcaps()
# We're done sending and receiving stuff, now let's check if what
# we received is what we sent.
--
2.25.1
^ permalink raw reply related
* [PATCH net-next v2 7/7] selftests: rds: Make rds selftests TAP compliant
From: Allison Henderson @ 2026-04-28 22:27 UTC (permalink / raw)
To: netdev, pabeni, edumazet, kuba, horms, linux-rdma, achender,
linux-kselftest, shuah
In-Reply-To: <20260428222716.2960871-1-achender@kernel.org>
This patch updates the rds selftests output to be TAP compliant.
Use ksft_pr() to mark debug output with a leading '# ' so that TAP
parsers treat it as commentary, and convert all informational print()
calls to use ksft_pr(). sys.exit(0) is changed to os._exit(0) to
avoid duplicate prints from the buffered TAP output. The console
output from the tcpdump subprocess is silenced, and the gcov console
output is redirected to a gcovr.log.
Finally adjust the exit path so that the hash check loop sets a
return code instead exiting directly. Then print the TAP results
and totals lines before exiting.
Signed-off-by: Allison Henderson <achender@kernel.org>
---
tools/testing/selftests/net/rds/run.sh | 18 +++++----
tools/testing/selftests/net/rds/test.py | 54 ++++++++++++++++---------
2 files changed, 45 insertions(+), 27 deletions(-)
diff --git a/tools/testing/selftests/net/rds/run.sh b/tools/testing/selftests/net/rds/run.sh
index 3fc116d23410..805cd0915585 100755
--- a/tools/testing/selftests/net/rds/run.sh
+++ b/tools/testing/selftests/net/rds/run.sh
@@ -205,8 +205,8 @@ if ! mountpoint -q /tmp 2>/dev/null; then
fi
set +e
-echo running RDS tests...
-echo Traces will be logged to "$TRACE_FILE"
+echo "# running RDS tests..."
+echo "# Traces will be logged to $TRACE_FILE"
rm -f "$TRACE_FILE"
strace -T -tt -o "$TRACE_FILE" python3 "$(dirname "$0")/test.py" \
-t "$TIMEOUT" -d "$LOG_DIR" -l "$PLOSS" -c "$PCORRUPT" \
@@ -216,7 +216,7 @@ test_rc=$?
dmesg > "${LOG_DIR}/dmesg.out"
if [ "$GENERATE_GCOV_REPORT" -eq 1 ]; then
- echo saving coverage data...
+ echo "# saving coverage data..."
# Ensure debugfs is mounted
if ! test -d /sys/kernel/debug/gcov; then
@@ -229,17 +229,19 @@ if [ "$GENERATE_GCOV_REPORT" -eq 1 ]; then
cat < "/sys/kernel/debug/gcov/$f" > "/$f"
done)
- echo running gcovr...
+ echo "# running gcovr..."
gcovr -s --html-details --gcov-executable "$GCOV_CMD" --gcov-ignore-parse-errors \
- --root "${ksrc_dir}" -o "${COVR_DIR}/gcovr" "${ksrc_dir}/net/rds/"
+ --root "${ksrc_dir}" -o "${COVR_DIR}/gcovr" "${ksrc_dir}/net/rds/" \
+ > "${LOG_DIR}/gcovr.log" 2>&1
+ echo "# gcovr log: ${LOG_DIR}/gcovr.log"
else
- echo "Coverage report will be skipped"
+ echo "# Coverage report will be skipped"
fi
if [ "$test_rc" -eq 0 ]; then
- echo "PASS: Test completed successfully"
+ echo "# PASS: Test completed successfully"
else
- echo "FAIL: Test failed"
+ echo "# FAIL: Test failed"
fi
exit "$test_rc"
diff --git a/tools/testing/selftests/net/rds/test.py b/tools/testing/selftests/net/rds/test.py
index 1c7aebddeb61..530ad9f9acba 100755
--- a/tools/testing/selftests/net/rds/test.py
+++ b/tools/testing/selftests/net/rds/test.py
@@ -22,6 +22,8 @@ this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(this_dir, "../"))
# pylint: disable-next=wrong-import-position,import-error,no-name-in-module
from lib.py.utils import ip # noqa: E402
+# pylint: disable-next=wrong-import-position,import-error,no-name-in-module
+from lib.py.ksft import ksft_pr # noqa: E402
libc = ctypes.cdll.LoadLibrary('libc.so.6')
setns = libc.setns
@@ -61,7 +63,7 @@ def netns_socket(netns, *sock_args):
# send resulting socket to parent
socket.send_fds(u0, [], [sock.fileno()])
- sys.exit(0)
+ os._exit(0)
# receive socket from child
_, fds, _, _ = socket.recv_fds(u1, 0, 1)
@@ -72,7 +74,7 @@ def netns_socket(netns, *sock_args):
def collect_pcaps():
"""Stop tcpdump processes and move their pcaps into the log dir."""
- print("Stopping network packet captures")
+ ksft_pr("Stopping network packet captures")
for proc, tmp_path, dest_path, fno in tcpdump_procs:
proc.terminate()
proc.wait()
@@ -83,8 +85,9 @@ def signal_handler(_sig, _frame):
"""
Test timed out signal handler
"""
- print('Test timed out')
+ ksft_pr("Test timed out")
collect_pcaps()
+ print("not ok 1 rds selftest")
sys.exit(1)
#Parse out command line arguments. We take an optional
@@ -146,7 +149,8 @@ for net in [NET0, NET1]:
# pylint: disable-next=consider-using-with
p = subprocess.Popen(
['ip', 'netns', 'exec', net,
- '/usr/sbin/tcpdump', '-i', 'any', '-w', pcap_tmp])
+ '/usr/sbin/tcpdump', '-i', 'any', '-w', pcap_tmp],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
tcpdump_procs.append((p, pcap_tmp, pcap, fd))
# simulate packet loss, duplication and corruption
@@ -155,6 +159,9 @@ for net, iface in [(NET0, VETH0), (NET1, VETH1)]:
corrupt {PACKET_CORRUPTION} loss {PACKET_LOSS} duplicate \
{PACKET_DUPLICATE}")
+print("TAP version 13")
+print("1..1")
+
# add a timeout
if args.timeout > 0:
signal.alarm(args.timeout)
@@ -193,7 +200,7 @@ nr_recv = 0
while nr_send < NUM_PACKETS:
# Send as much as we can without blocking
- print("sending...", nr_send, nr_recv)
+ ksft_pr("sending...", nr_send, nr_recv)
while nr_send < NUM_PACKETS:
send_data = hashlib.sha256(
f'packet {nr_send}'.encode('utf-8')).hexdigest().encode('utf-8')
@@ -215,7 +222,7 @@ while nr_send < NUM_PACKETS:
raise
# Receive as much as we can without blocking
- print("receiving...", nr_send, nr_recv)
+ ksft_pr("receiving...", nr_send, nr_recv)
while nr_recv < nr_send:
for fileno, eventmask in ep.poll():
receiver = fileno_to_socket[fileno]
@@ -237,7 +244,7 @@ while nr_send < NUM_PACKETS:
ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_rcvbuf=10000")
ip(f"netns exec {net} /usr/sbin/sysctl net.rds.tcp.rds_tcp_sndbuf=10000")
-print("done", nr_send, nr_recv)
+ksft_pr("done", nr_send, nr_recv)
# the Python socket module doesn't know these
RDS_INFO_FIRST = 10000
@@ -260,25 +267,34 @@ for s in sockets:
# ignore
pass
-print(f"getsockopt(): {nr_success}/{nr_error}")
+ksft_pr(f"getsockopt(): {nr_success}/{nr_error}")
collect_pcaps()
# We're done sending and receiving stuff, now let's check if what
# we received is what we sent.
+ret = 0
for (sender, receiver), send_hash in send_hashes.items():
recv_hash = recv_hashes.get((sender, receiver))
if recv_hash is None:
- print("FAIL: No data received")
- sys.exit(1)
+ ksft_pr("FAIL: No data received")
+ ret = 1
+ break
if send_hash.hexdigest() != recv_hash.hexdigest():
- print("FAIL: Send/recv mismatch")
- print("hash expected:", send_hash.hexdigest())
- print("hash received:", recv_hash.hexdigest())
- sys.exit(1)
-
- print(f"{sender}/{receiver}: ok")
-
-print("Success")
-sys.exit(0)
+ ksft_pr("FAIL: Send/recv mismatch")
+ ksft_pr("hash expected:", send_hash.hexdigest())
+ ksft_pr("hash received:", recv_hash.hexdigest())
+ ret = 1
+ break
+
+ ksft_pr(f"{sender}/{receiver}: ok")
+
+if ret == 0:
+ ksft_pr("Success")
+ print("ok 1 rds selftest")
+else:
+ print("not ok 1 rds selftest")
+
+ksft_pr(f"Totals: pass:{1-ret} fail:{ret} skip:0")
+sys.exit(ret)
--
2.25.1
^ permalink raw reply related
* [PATCH net-next] net: gianfar: use alloc_ethdev_mqs
From: Rosen Penev @ 2026-04-28 22:30 UTC (permalink / raw)
To: netdev
Cc: Claudiu Manoil, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, open list
From looking at git history, mqs was introduced after mq and after this
code was written. Having said that, mqs can be used as there is already
an RX queue variable in place. Not only that, mqs already sets the
num_xx_queues members. No need to open code this.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/ethernet/freescale/gianfar.c | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/freescale/gianfar.c b/drivers/net/ethernet/freescale/gianfar.c
index 3271de5844f8..7b47c7c49c08 100644
--- a/drivers/net/ethernet/freescale/gianfar.c
+++ b/drivers/net/ethernet/freescale/gianfar.c
@@ -669,7 +669,7 @@ static int gfar_of_init(struct platform_device *ofdev, struct net_device **pdev)
return -EINVAL;
}
- *pdev = alloc_etherdev_mq(sizeof(*priv), num_tx_qs);
+ *pdev = alloc_etherdev_mqs(sizeof(*priv), num_tx_qs, num_rx_qs);
dev = *pdev;
if (NULL == dev)
return -ENOMEM;
@@ -679,10 +679,6 @@ static int gfar_of_init(struct platform_device *ofdev, struct net_device **pdev)
priv->mode = mode;
- priv->num_tx_queues = num_tx_qs;
- netif_set_real_num_rx_queues(dev, num_rx_qs);
- priv->num_rx_queues = num_rx_qs;
-
err = gfar_alloc_tx_queues(priv);
if (err)
goto tx_alloc_failed;
--
2.54.0
^ permalink raw reply related
* [PATCH net-next 00/11] net: devmem: support devmem with netkit devices
From: Bobby Eshleman @ 2026-04-28 22:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
This series enables TCP devmem TX through netkit devices.
Netkit now supports queue leasing. A physical NIC's RX queue can be
leased to a netkit guest interface inside a container namespace. This
gives the container a devmem-capable data path on the RX side (bind-rx,
etc...). On the TX side, the container process binds to its netkit guest
interface and sends traffic that netkit redirects (via BPF or ip
forwarding) to the physical NIC for DMA.
Two things in the existing devmem TX path prevent this from working:
1. validate_xmit_unreadable_skb() requires dev->netmem_tx before it will
forward a dmabuf-backed (unreadable) skb. This protects skbs from
landing on devices that don't have the IOMMU mappings for the backing
dmabuf or that don't speak netmem. Netkit, however, does not support
DMA, doesn't attempt to read unreadable skb pages and so doesn't
break netmem (it is pure skb routing and redirection). It is
functionally capable of routing unreadable skbs, but there is no way
for the TX validation pathway to distinguish between a device that
will actually attempt DMA-ing the skb and another device
(like netkit) that does not DMA but also does not break
netmem.
2. bind_tx_doit uses the bound device as the DMA device. When the user
binds devmem TX to the netkit guest, the bind handler attempts to
create DMA mappings against netkit, which has no DMA capability and
no IOMMU mappings.
This series solves these problems as follows:
1. Extend netmem_tx to two bits, assigned to one of three values:
NETMEM_TX_NONE - netmem not supported
NETMEM_TX_DMA - netmem supported and performs DMA
NETMEM_TX_NO_DMA - netmem supported, but does not DMA
With these bits, phys devices can set NETMEM_TX_DMA and devices like
netkit set NETMEM_TX_NO_DMA. The validation TX path ensures that any
DMA-capable netdev exactly matches the bound device, guarantee the
correct mapping of the bound dmabuf. The validation TX path also
allows devices with NETMEM_TX_NO_DMA to pass, knowing these devices
will not misuse netmem or run into IOMMU faults. After redirection or
routing and the skb finally makes its way through the stack to a
physical device's TX path, the above NETMEM_TX_DMA check is performed
again to guarantee the device has the appropriate binding/mappings.
2. On TX bind, the bind handler recognizes NETMEM_TX_NO_DMA devices and
finds the phys TX device and binds to that instead. For the netkit
case, if it has been leased a queue from a DMA-capable device
already, then the bind action is performed on the DMA-capable device
instead and the dmabuf is mapped correctly.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Bobby Eshleman (11):
net: add netmem_tx modes that indicate dma capability
net: bnxt: convert netmem_tx from bool to NETMEM_TX_DMA enum
gve: convert netmem_tx from bool to NETMEM_TX_DMA enum
net/mlx5e: convert netmem_tx from bool to NETMEM_TX_DMA enum
eth: fbnic: convert netmem_tx from bool to NETMEM_TX_DMA enum
netkit: set NETMEM_TX_NO_DMA for unreadable skb passthrough
net: devmem: support TX over NETMEM_TX_NO_DMA devices
selftests: drv-net: ncdevmem: add -n flag to skip NIC configuration
selftests: drv-net: refactor devmem command builders into lib module
selftests: drv-net: add primary_rx_redirect support to NetDrvContEnv
selftests: drv-net: add netkit devmem tests
.../networking/net_cachelines/net_device.rst | 2 +-
Documentation/networking/netmem.rst | 8 +-
.../translations/zh_CN/networking/netmem.rst | 7 +-
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 2 +-
drivers/net/ethernet/google/gve/gve_main.c | 2 +-
drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 2 +-
drivers/net/ethernet/meta/fbnic/fbnic_netdev.c | 2 +-
drivers/net/netkit.c | 1 +
include/linux/netdevice.h | 11 +-
net/core/dev.c | 24 ++-
net/core/devmem.c | 6 +-
net/core/devmem.h | 9 +-
net/core/netdev-genl.c | 53 ++++-
tools/testing/selftests/drivers/net/hw/devmem.py | 73 +------
.../selftests/drivers/net/hw/lib/py/devmem.py | 215 +++++++++++++++++++++
tools/testing/selftests/drivers/net/hw/ncdevmem.c | 58 +++---
.../testing/selftests/drivers/net/hw/nk_devmem.py | 40 ++++
.../drivers/net/hw/nk_primary_rx_redirect.bpf.c | 41 ++++
tools/testing/selftests/drivers/net/lib/py/env.py | 67 +++++--
19 files changed, 498 insertions(+), 125 deletions(-)
---
base-commit: 790ead9394860e7d70c5e0e50a35b243e909a618
change-id: 20260423-tcp-dm-netkit-2bd78b638d30
Best regards,
--
Bobby Eshleman <bobbyeshleman@meta.com>
^ permalink raw reply
* [PATCH net-next 01/11] net: add netmem_tx modes that indicate dma capability
From: Bobby Eshleman @ 2026-04-28 22:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
Devices that support netmem TX previously set dev->netmem_tx = true.
This was checked in validate_xmit_unreadable_skb() to drop unreadable
skbs (skbs with dmabuf-backed frags) before they reach drivers that
would mishandle them or devices that would not have the iommu mappings
for them.
Some virtual devices like netkit (or ifb) never DMA and never touch frag
contents, as they essentially just forward the skb to another device.
They are unable to forward unreadable skbs, however, because they fail
to pass TX validation checks on dev->netmem_tx. This single bit flag
doesn't give the TX validator enough information to differentiate
devices that will attempt DMA on the unreadable skb and those that will
simply route it untouched.
This patch fixes this issue by adding an additional bit to netmem_tx, so
that drivers can indicate 1) if they have netmem support, and 2) if they
do, are they DMA-capable or not?
Replace the boolean with a 2-bit enum:
NETMEM_TX_NONE - no netmem TX support (drop unreadable skbs)
NETMEM_TX_DMA - full support, device does DMA
NETMEM_TX_NO_DMA - pass-through, device never DMAs
Subsequent patches update netmem drivers to the new flags.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Documentation/networking/net_cachelines/net_device.rst | 2 +-
Documentation/networking/netmem.rst | 8 +++++++-
Documentation/translations/zh_CN/networking/netmem.rst | 7 ++++++-
include/linux/netdevice.h | 11 +++++++++--
4 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst
index 1c19bb7705df..c85784259544 100644
--- a/Documentation/networking/net_cachelines/net_device.rst
+++ b/Documentation/networking/net_cachelines/net_device.rst
@@ -10,7 +10,7 @@ Type Name fastpath_tx_acce
=================================== =========================== =================== =================== ===================================================================================
unsigned_long:32 priv_flags read_mostly __dev_queue_xmit(tx)
unsigned_long:1 lltx read_mostly HARD_TX_LOCK,HARD_TX_TRYLOCK,HARD_TX_UNLOCK(tx)
-unsigned long:1 netmem_tx:1; read_mostly
+unsigned long:2 netmem_tx:2; read_mostly
char name[16]
struct netdev_name_node* name_node
struct dev_ifalias* ifalias
diff --git a/Documentation/networking/netmem.rst b/Documentation/networking/netmem.rst
index b63aded46337..217869d1108d 100644
--- a/Documentation/networking/netmem.rst
+++ b/Documentation/networking/netmem.rst
@@ -95,4 +95,10 @@ Driver TX Requirements
netdev@, or reach out to the maintainers and/or almasrymina@google.com for
help adding the netmem API.
-2. Driver should declare support by setting `netdev->netmem_tx = true`
+2. Driver should declare support by setting `netdev->netmem_tx` to the
+ appropriate mode:
+
+ - `NETMEM_TX_DMA`: for physical devices that perform DMA.
+
+ - `NETMEM_TX_NO_DMA`: for virtual or passthrough devices that do
+ not DMA, but still support handling of netmem-backed skbs.
diff --git a/Documentation/translations/zh_CN/networking/netmem.rst b/Documentation/translations/zh_CN/networking/netmem.rst
index fe351a240f02..320f3eacf51b 100644
--- a/Documentation/translations/zh_CN/networking/netmem.rst
+++ b/Documentation/translations/zh_CN/networking/netmem.rst
@@ -89,4 +89,9 @@ dma-mapping API 去处理。
使用某个还不存在的 netmem API,你可以自行添加并提交到 netdev@,也可以联系维护
人员或者发送邮件至 almasrymina@google.com 寻求帮助。
-2. 驱动程序应通过设置 netdev->netmem_tx = true 来表明自身支持 netmem 功能。
+2. 驱动程序应将 `netdev->netmem_tx` 设置为适当的模式:
+
+ - `NETMEM_TX_DMA`:适用于执行 DMA 的物理设备。
+
+ - `NETMEM_TX_NO_DMA`:适用于不执行 DMA 的虚拟或透传设备,但仍支持
+ 处理 netmem 支持的 skb。
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 0e1e581efc5a..11d68e75eb4f 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1788,6 +1788,12 @@ enum netdev_stat_type {
NETDEV_PCPU_STAT_DSTATS, /* struct pcpu_dstats */
};
+enum netmem_tx_mode {
+ NETMEM_TX_NONE, /* no netmem TX support */
+ NETMEM_TX_DMA, /* DMA-capable netmem TX (real HW) */
+ NETMEM_TX_NO_DMA, /* no DMA, e.g. passthrough for virtual devs */
+};
+
enum netdev_reg_state {
NETREG_UNINITIALIZED = 0,
NETREG_REGISTERED, /* completed register_netdevice */
@@ -1809,7 +1815,8 @@ enum netdev_reg_state {
* @lltx: device supports lockless Tx. Deprecated for real HW
* drivers. Mainly used by logical interfaces, such as
* bonding and tunnels
- * @netmem_tx: device support netmem_tx.
+ * @netmem_tx: device netmem TX mode (NETMEM_TX_NONE, NETMEM_TX_DMA,
+ * or NETMEM_TX_NO_DMA).
*
* @name: This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
@@ -2132,7 +2139,7 @@ struct net_device {
struct_group(priv_flags_fast,
unsigned long priv_flags:32;
unsigned long lltx:1;
- unsigned long netmem_tx:1;
+ unsigned long netmem_tx:2;
);
const struct net_device_ops *netdev_ops;
const struct header_ops *header_ops;
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 02/11] net: bnxt: convert netmem_tx from bool to NETMEM_TX_DMA enum
From: Bobby Eshleman @ 2026-04-28 22:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
Now that netmem_tx is a multi-mode enum (NETMEM_TX_NONE, NETMEM_TX_DMA,
NETMEM_TX_NO_DMA), set it to NETMEM_TX_DMA to indicate this driver
supports DMA-capable netmem TX.
No functional change.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index 8c55874f44ca..ed9c22dc4a5a 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -17120,7 +17120,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
dev->queue_mgmt_ops = &bnxt_queue_mgmt_ops_unsupp;
if (BNXT_SUPPORTS_QUEUE_API(bp))
dev->queue_mgmt_ops = &bnxt_queue_mgmt_ops;
- dev->netmem_tx = true;
+ dev->netmem_tx = NETMEM_TX_DMA;
rc = register_netdev(dev);
if (rc)
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 03/11] gve: convert netmem_tx from bool to NETMEM_TX_DMA enum
From: Bobby Eshleman @ 2026-04-28 22:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
Now that netmem_tx is a multi-mode enum (NETMEM_TX_NONE, NETMEM_TX_DMA,
NETMEM_TX_NO_DMA), set it to NETMEM_TX_DMA to indicate this driver
supports DMA-capable netmem TX.
No functional change.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
drivers/net/ethernet/google/gve/gve_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c
index 424d973c97f2..dd2b8f087163 100644
--- a/drivers/net/ethernet/google/gve/gve_main.c
+++ b/drivers/net/ethernet/google/gve/gve_main.c
@@ -2894,7 +2894,7 @@ static int gve_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
goto abort_with_wq;
if (!gve_is_gqi(priv) && !gve_is_qpl(priv))
- dev->netmem_tx = true;
+ dev->netmem_tx = NETMEM_TX_DMA;
err = register_netdev(dev);
if (err)
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 04/11] net/mlx5e: convert netmem_tx from bool to NETMEM_TX_DMA enum
From: Bobby Eshleman @ 2026-04-28 22:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
Now that netmem_tx is a multi-mode enum (NETMEM_TX_NONE, NETMEM_TX_DMA,
NETMEM_TX_NO_DMA), set it to NETMEM_TX_DMA to indicate this driver
supports DMA-capable netmem TX.
No functional change.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
index 5a46870c4b74..fc49aae38807 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
@@ -5924,7 +5924,7 @@ static void mlx5e_build_nic_netdev(struct net_device *netdev)
netdev->priv_flags |= IFF_UNICAST_FLT;
- netdev->netmem_tx = true;
+ netdev->netmem_tx = NETMEM_TX_DMA;
netif_set_tso_max_size(netdev, GSO_MAX_SIZE);
mlx5e_set_xdp_feature(priv);
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 05/11] eth: fbnic: convert netmem_tx from bool to NETMEM_TX_DMA enum
From: Bobby Eshleman @ 2026-04-28 22:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
Now that netmem_tx is a multi-mode enum (NETMEM_TX_NONE, NETMEM_TX_DMA,
NETMEM_TX_NO_DMA), set it to NETMEM_TX_DMA to indicate this driver
supports DMA-capable netmem TX.
No functional change.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
drivers/net/ethernet/meta/fbnic/fbnic_netdev.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
index c406a3b56b37..138e522ef9b9 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
@@ -752,7 +752,7 @@ struct net_device *fbnic_netdev_alloc(struct fbnic_dev *fbd)
netdev->netdev_ops = &fbnic_netdev_ops;
netdev->stat_ops = &fbnic_stat_ops;
netdev->queue_mgmt_ops = &fbnic_queue_mgmt_ops;
- netdev->netmem_tx = true;
+ netdev->netmem_tx = NETMEM_TX_DMA;
fbnic_set_ethtool_ops(netdev);
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 06/11] netkit: set NETMEM_TX_NO_DMA for unreadable skb passthrough
From: Bobby Eshleman @ 2026-04-28 22:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
Netkit never DMAs and it does not break netmem (it never touches frag
contents, it just forwards skbs between peers). Mark it as
NETMEM_TX_NO_DMA so unreadable (dmabuf-backed) skbs can pass through
without being dropped by validate_xmit_unreadable_skb().
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
drivers/net/netkit.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
index 5e2eecc3165d..0ad6a806d7d5 100644
--- a/drivers/net/netkit.c
+++ b/drivers/net/netkit.c
@@ -466,6 +466,7 @@ static void netkit_setup(struct net_device *dev)
dev->priv_flags |= IFF_NO_QUEUE;
dev->priv_flags |= IFF_DISABLE_NETPOLL;
dev->lltx = true;
+ dev->netmem_tx = NETMEM_TX_NO_DMA;
dev->netdev_ops = &netkit_netdev_ops;
dev->ethtool_ops = &netkit_ethtool_ops;
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 07/11] net: devmem: support TX over NETMEM_TX_NO_DMA devices
From: Bobby Eshleman @ 2026-04-28 22:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
When a netkit virtual device leases queues from a physical NIC, devmem
TX bindings created on the netkit device must still result in the dmabuf
being mapped for dma by the physical device. This patch accomplishes
this by teaching the bind handler to search for the underlying
DMA-capable device by looking it up via leased rx queues. The function
netdev_find_netmem_tx_dev(), used for finding the underlying DMA-capable
device, can be extended to support other non-netkit NETMEM_TX_NO_DMA
devices in the future if needed.
Additionally, this patch extends validate_xmit_unreadable_skb() to
support the netkit case, where the skb is validated twice: once on the
netkit guest device and again on the physical NIC after BPF redirect or
ip forwarding.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
net/core/dev.c | 24 ++++++++++++++++-------
net/core/devmem.c | 6 ++++--
net/core/devmem.h | 9 +++++++--
net/core/netdev-genl.c | 53 +++++++++++++++++++++++++++++++++++++++++++++-----
4 files changed, 76 insertions(+), 16 deletions(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index 06c195906231..f6575cf48287 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3990,22 +3990,32 @@ static struct sk_buff *sk_validate_xmit_skb(struct sk_buff *skb,
static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
struct net_device *dev)
{
+ struct net_devmem_dmabuf_binding *binding;
struct skb_shared_info *shinfo;
struct net_iov *niov;
if (likely(skb_frags_readable(skb)))
goto out;
- if (!dev->netmem_tx)
- goto out_free;
-
shinfo = skb_shinfo(skb);
+ if (shinfo->nr_frags == 0)
+ goto out;
- if (shinfo->nr_frags > 0) {
- niov = netmem_to_net_iov(skb_frag_netmem(&shinfo->frags[0]));
- if (net_is_devmem_iov(niov) &&
- READ_ONCE(net_devmem_iov_binding(niov)->dev) != dev)
+ niov = netmem_to_net_iov(skb_frag_netmem(&shinfo->frags[0]));
+ if (!net_is_devmem_iov(niov))
+ goto out;
+
+ binding = net_devmem_iov_binding(niov);
+
+ switch (dev->netmem_tx) {
+ case NETMEM_TX_DMA:
+ if (READ_ONCE(binding->dev) != dev)
goto out_free;
+ break;
+ case NETMEM_TX_NO_DMA:
+ break;
+ default: /* NETMEM_TX_NONE */
+ goto out_free;
}
out:
diff --git a/net/core/devmem.c b/net/core/devmem.c
index cde4c89bc146..644c286b778f 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -181,7 +181,7 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
}
struct net_devmem_dmabuf_binding *
-net_devmem_bind_dmabuf(struct net_device *dev,
+net_devmem_bind_dmabuf(struct net_device *dev, struct net_device *vdev,
struct device *dma_dev,
enum dma_data_direction direction,
unsigned int dmabuf_fd, struct netdev_nl_sock *priv,
@@ -212,6 +212,7 @@ net_devmem_bind_dmabuf(struct net_device *dev,
}
binding->dev = dev;
+ binding->vdev = vdev;
xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC);
err = percpu_ref_init(&binding->ref,
@@ -397,7 +398,8 @@ struct net_devmem_dmabuf_binding *net_devmem_get_binding(struct sock *sk,
*/
dst_dev = dst_dev_rcu(dst);
if (unlikely(!dst_dev) ||
- unlikely(dst_dev != READ_ONCE(binding->dev))) {
+ unlikely(dst_dev != READ_ONCE(binding->dev) &&
+ dst_dev != READ_ONCE(binding->vdev))) {
err = -ENODEV;
goto out_unlock;
}
diff --git a/net/core/devmem.h b/net/core/devmem.h
index 1c5c18581fcb..f399632b3c4b 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -19,7 +19,12 @@ struct net_devmem_dmabuf_binding {
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
struct sg_table *sgt;
+ /* Physical NIC that does the actual DMA for this binding. */
struct net_device *dev;
+ /* Virtual device (e.g. netkit) the user called bind-tx on. Must be
+ * NETMEM_TX_NO_DMA.
+ */
+ struct net_device *vdev;
struct gen_pool *chunk_pool;
/* Protect dev */
struct mutex lock;
@@ -84,7 +89,7 @@ struct dmabuf_genpool_chunk_owner {
void __net_devmem_dmabuf_binding_free(struct work_struct *wq);
struct net_devmem_dmabuf_binding *
-net_devmem_bind_dmabuf(struct net_device *dev,
+net_devmem_bind_dmabuf(struct net_device *dev, struct net_device *vdev,
struct device *dma_dev,
enum dma_data_direction direction,
unsigned int dmabuf_fd, struct netdev_nl_sock *priv,
@@ -165,7 +170,7 @@ static inline void net_devmem_put_net_iov(struct net_iov *niov)
}
static inline struct net_devmem_dmabuf_binding *
-net_devmem_bind_dmabuf(struct net_device *dev,
+net_devmem_bind_dmabuf(struct net_device *dev, struct net_device *vdev,
struct device *dma_dev,
enum dma_data_direction direction,
unsigned int dmabuf_fd,
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index b8f6076d8007..bc6057aee98e 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -1077,7 +1077,7 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
goto err_rxq_bitmap;
}
- binding = net_devmem_bind_dmabuf(netdev, dma_dev, DMA_FROM_DEVICE,
+ binding = net_devmem_bind_dmabuf(netdev, NULL, dma_dev, DMA_FROM_DEVICE,
dmabuf_fd, priv, info->extack);
if (IS_ERR(binding)) {
err = PTR_ERR(binding);
@@ -1119,9 +1119,42 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info)
return err;
}
+/* Find the DMA-capable device for netmem TX binding.
+ * For NETMEM_TX_DMA devices, returns the device itself.
+ * For NETMEM_TX_NO_DMA devices (e.g. netkit), walks leased queues
+ * to find the underlying physical device.
+ * Returns NULL if no suitable device is found.
+ */
+static struct net_device *netdev_find_netmem_tx_dev(struct net_device *dev)
+{
+ struct netdev_rx_queue *lease_rxq;
+ struct net_device *phys_dev;
+ int i;
+
+ if (dev->netmem_tx == NETMEM_TX_DMA)
+ return dev;
+
+ if (dev->netmem_tx != NETMEM_TX_NO_DMA)
+ return NULL;
+
+ for (i = 0; i < dev->real_num_rx_queues; i++) {
+ lease_rxq = READ_ONCE(__netif_get_rx_queue(dev, i)->lease);
+ if (!lease_rxq)
+ continue;
+
+ phys_dev = lease_rxq->dev;
+ if (netif_device_present(phys_dev) &&
+ phys_dev->netmem_tx == NETMEM_TX_DMA)
+ return phys_dev;
+ }
+
+ return NULL;
+}
+
int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
{
struct net_devmem_dmabuf_binding *binding;
+ struct net_device *bind_dev;
struct netdev_nl_sock *priv;
struct net_device *netdev;
struct device *dma_dev;
@@ -1164,16 +1197,26 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
goto err_unlock_netdev;
}
- if (!netdev->netmem_tx) {
+ if (netdev->netmem_tx == NETMEM_TX_NONE) {
err = -EOPNOTSUPP;
NL_SET_ERR_MSG(info->extack,
"Driver does not support netmem TX");
goto err_unlock_netdev;
}
- dma_dev = netdev_queue_get_dma_dev(netdev, 0, NETDEV_QUEUE_TYPE_TX);
- binding = net_devmem_bind_dmabuf(netdev, dma_dev, DMA_TO_DEVICE,
- dmabuf_fd, priv, info->extack);
+ bind_dev = netdev_find_netmem_tx_dev(netdev);
+ if (!bind_dev) {
+ err = -EOPNOTSUPP;
+ NL_SET_ERR_MSG(info->extack,
+ "No DMA-capable device found for netmem TX");
+ goto err_unlock_netdev;
+ }
+
+ dma_dev = netdev_queue_get_dma_dev(bind_dev, 0, NETDEV_QUEUE_TYPE_TX);
+ binding = net_devmem_bind_dmabuf(bind_dev,
+ bind_dev != netdev ? netdev : NULL,
+ dma_dev, DMA_TO_DEVICE, dmabuf_fd,
+ priv, info->extack);
if (IS_ERR(binding)) {
err = PTR_ERR(binding);
goto err_unlock_netdev;
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 08/11] selftests: drv-net: ncdevmem: add -n flag to skip NIC configuration
From: Bobby Eshleman @ 2026-04-28 22:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
Add a -n (skip_config) flag that causes ncdevmem to skip NIC
configuration when operating as an RX server. When -n is passed,
ncdevmem skips configuring header split, RSS, and flow steering, as well
as their teardown on exit.
This allows ksft tests to pre-configure the NIC in the host namespace
before launching ncdevmem in the guest namespace. This is needed for
netkit devmem tests where the test harness namespace has direct access
to the NIC and the ncdevmem namespace does not.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
tools/testing/selftests/drivers/net/hw/ncdevmem.c | 58 +++++++++++++----------
1 file changed, 34 insertions(+), 24 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
index e098d6534c3c..d96e8a3b5a65 100644
--- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c
+++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
@@ -93,6 +93,7 @@ static char *port;
static size_t do_validation;
static int start_queue = -1;
static int num_queues = -1;
+static int skip_config;
static char *ifname;
static unsigned int ifindex;
static unsigned int dmabuf_id;
@@ -828,7 +829,7 @@ static struct netdev_queue_id *create_queues(void)
static int do_server(struct memory_buffer *mem)
{
- struct ethtool_rings_get_rsp *ring_config;
+ struct ethtool_rings_get_rsp *ring_config = NULL;
char ctrl_data[sizeof(int) * 20000];
size_t non_page_aligned_frags = 0;
struct sockaddr_in6 client_addr;
@@ -851,27 +852,29 @@ static int do_server(struct memory_buffer *mem)
return -1;
}
- ring_config = get_ring_config();
- if (!ring_config) {
- pr_err("Failed to get current ring configuration");
- return -1;
- }
+ if (!skip_config) {
+ ring_config = get_ring_config();
+ if (!ring_config) {
+ pr_err("Failed to get current ring configuration");
+ return -1;
+ }
- if (configure_headersplit(ring_config, 1)) {
- pr_err("Failed to enable TCP header split");
- goto err_free_ring_config;
- }
+ if (configure_headersplit(ring_config, 1)) {
+ pr_err("Failed to enable TCP header split");
+ goto err_free_ring_config;
+ }
- /* Configure RSS to divert all traffic from our devmem queues */
- if (configure_rss()) {
- pr_err("Failed to configure rss");
- goto err_reset_headersplit;
- }
+ /* Configure RSS to divert all traffic from our devmem queues */
+ if (configure_rss()) {
+ pr_err("Failed to configure rss");
+ goto err_reset_headersplit;
+ }
- /* Flow steer our devmem flows to start_queue */
- if (configure_flow_steering(&server_sin)) {
- pr_err("Failed to configure flow steering");
- goto err_reset_rss;
+ /* Flow steer our devmem flows to start_queue */
+ if (configure_flow_steering(&server_sin)) {
+ pr_err("Failed to configure flow steering");
+ goto err_reset_rss;
+ }
}
if (bind_rx_queue(ifindex, mem->fd, create_queues(), num_queues, &ys)) {
@@ -1052,13 +1055,17 @@ static int do_server(struct memory_buffer *mem)
err_unbind:
ynl_sock_destroy(ys);
err_reset_flow_steering:
- reset_flow_steering();
+ if (!skip_config)
+ reset_flow_steering();
err_reset_rss:
- reset_rss();
+ if (!skip_config)
+ reset_rss();
err_reset_headersplit:
- restore_ring_config(ring_config);
+ if (!skip_config)
+ restore_ring_config(ring_config);
err_free_ring_config:
- ethtool_rings_get_rsp_free(ring_config);
+ if (!skip_config)
+ ethtool_rings_get_rsp_free(ring_config);
return err;
}
@@ -1404,7 +1411,7 @@ int main(int argc, char *argv[])
int is_server = 0, opt;
int ret, err = 1;
- while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:")) != -1) {
+ while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:n")) != -1) {
switch (opt) {
case 'L':
fail_on_linear = true;
@@ -1436,6 +1443,9 @@ int main(int argc, char *argv[])
case 'z':
max_chunk = atoi(optarg);
break;
+ case 'n':
+ skip_config = 1;
+ break;
case '?':
fprintf(stderr, "unknown option: %c\n", optopt);
break;
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 09/11] selftests: drv-net: refactor devmem command builders into lib module
From: Bobby Eshleman @ 2026-04-28 22:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
Adding netkit-based devmem tests is a straight-forward copy of devmem
test commands plus some args for the nk cases, so this patch breaks out
these command builders into helpers used by both.
Though we tried to avoid libraries to avoid increasing the barrier of
entry/complexity (see selftests/drivers/net/README.md, section "Avoid
libraries and frameworks"), factoring out these functions seemed like
the lesser of two evils in this case of using the same commands, just
with slightly different args per environment.
I experimented with just having all of the tests in the same file to
avoid having helpers in a library file, but because ksft_run() is
limited to a single call per file, and the new tests will require
different environments (NetDrvContEnv/NetDrvEpEnv), it would have been
necessary to have each test set up its own environment instead of
sharing one for the entire ksft_run() run. This came at the cost of
ballooning the test time (from under 5s to 30s on my test system), so to
strike a balance these tests were placed in separate files so they could
keep a shared environment across a single ksft_run() run shared across
all tests using the same env type (introduced in subsequent patches).
The helpers work transparently with both plain and netkit environments
by inspecting cfg for netkit-specific attributes (netns, nk_queue,
etc...).
No functional change to the existing devmem.py tests.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
tools/testing/selftests/drivers/net/hw/devmem.py | 73 +------
.../selftests/drivers/net/hw/lib/py/devmem.py | 215 +++++++++++++++++++++
2 files changed, 224 insertions(+), 64 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/hw/devmem.py b/tools/testing/selftests/drivers/net/hw/devmem.py
index ee863e90d1e0..33648e39577a 100755
--- a/tools/testing/selftests/drivers/net/hw/devmem.py
+++ b/tools/testing/selftests/drivers/net/hw/devmem.py
@@ -1,92 +1,37 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
+"""Test devmem TCP."""
from os import path
-from lib.py import ksft_run, ksft_exit
-from lib.py import ksft_eq, KsftSkipEx
+from lib.py import ksft_run, ksft_exit, ksft_disruptive
from lib.py import NetDrvEpEnv
-from lib.py import bkg, cmd, rand_port, wait_port_listen
-from lib.py import ksft_disruptive
-
-
-def require_devmem(cfg):
- if not hasattr(cfg, "_devmem_probed"):
- probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
- cfg._devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0
- cfg._devmem_probed = True
-
- if not cfg._devmem_supported:
- raise KsftSkipEx("Test requires devmem support")
+from lib.py.devmem import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds
@ksft_disruptive
def check_rx(cfg) -> None:
- require_devmem(cfg)
-
- port = rand_port()
- socat = f"socat -u - TCP{cfg.addr_ipver}:{cfg.baddr}:{port},bind={cfg.remote_baddr}:{port}"
- listen_cmd = f"{cfg.bin_local} -l -f {cfg.ifname} -s {cfg.addr} -p {port} -c {cfg.remote_addr} -v 7"
-
- with bkg(listen_cmd, exit_wait=True) as ncdevmem:
- wait_port_listen(port)
- cmd(f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | \
- head -c 1K | {socat}", host=cfg.remote, shell=True)
-
- ksft_eq(ncdevmem.ret, 0)
+ run_rx(cfg)
@ksft_disruptive
def check_tx(cfg) -> None:
- require_devmem(cfg)
-
- port = rand_port()
- listen_cmd = f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
-
- with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
- wait_port_listen(port, host=cfg.remote)
- cmd(f"echo -e \"hello\\nworld\"| {cfg.bin_local} -f {cfg.ifname} -s {cfg.remote_addr} -p {port}", shell=True)
-
- ksft_eq(socat.stdout.strip(), "hello\nworld")
+ run_tx(cfg)
@ksft_disruptive
def check_tx_chunks(cfg) -> None:
- require_devmem(cfg)
-
- port = rand_port()
- listen_cmd = f"socat -U - TCP{cfg.addr_ipver}-LISTEN:{port}"
-
- with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
- wait_port_listen(port, host=cfg.remote)
- cmd(f"echo -e \"hello\\nworld\"| {cfg.bin_local} -f {cfg.ifname} -s {cfg.remote_addr} -p {port} -z 3", shell=True)
-
- ksft_eq(socat.stdout.strip(), "hello\nworld")
+ run_tx_chunks(cfg)
def check_rx_hds(cfg) -> None:
- """Test HDS splitting across payload sizes."""
- require_devmem(cfg)
-
- for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]:
- port = rand_port()
- listen_cmd = f"{cfg.bin_local} -L -l -f {cfg.ifname} -s {cfg.addr} -p {port}"
-
- with bkg(listen_cmd, exit_wait=True) as ncdevmem:
- wait_port_listen(port)
- cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | " +
- f"socat -b {size} -u - TCP{cfg.addr_ipver}:{cfg.baddr}:{port},nodelay",
- host=cfg.remote, shell=True)
-
- ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}")
+ run_rx_hds(cfg)
def main() -> None:
with NetDrvEpEnv(__file__) as cfg:
- cfg.bin_local = path.abspath(path.dirname(__file__) + "/ncdevmem")
- cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
-
+ setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem"))
ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds],
- args=(cfg, ))
+ args=(cfg,))
ksft_exit()
diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py b/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py
new file mode 100644
index 000000000000..e95fc38337fa
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/lib/py/devmem.py
@@ -0,0 +1,215 @@
+# SPDX-License-Identifier: GPL-2.0
+"""Shared helpers for devmem TCP selftests."""
+
+import re
+
+from net.lib.py import (bkg, cmd, defer, ethtool, rand_port, wait_port_listen,
+ ksft_eq, KsftSkipEx, NetNSEnter, EthtoolFamily,
+ NetdevFamily)
+
+
+def require_devmem(cfg):
+ if not hasattr(cfg, "_devmem_probed"):
+ probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
+ cfg._devmem_supported = cmd(probe_command, fail=False, shell=True).ret == 0
+ cfg._devmem_probed = True
+
+ if not cfg._devmem_supported:
+ raise KsftSkipEx("Test requires devmem support")
+
+
+def configure_nic(cfg):
+ """Channels, rings, RSS, queue lease for netkit devmem.
+
+ Rings and RSS are re-applied each call because per-test defers restore
+ them after every test case. The queue lease is created only once.
+ """
+ cfg.require_ipver('6')
+ ethnl = EthtoolFamily()
+
+ channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
+ channels = channels['combined-count']
+ if channels < 2:
+ raise KsftSkipEx(
+ 'Test requires NETIF with at least 2 combined channels'
+ )
+
+ rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
+ rx_rings = rings['rx']
+ hds_thresh = rings.get('hds-thresh', 0)
+ orig_data_split = rings.get('tcp-data-split', 'unknown')
+
+ ethnl.rings_set({'header': {'dev-index': cfg.ifindex},
+ 'tcp-data-split': 'enabled',
+ 'hds-thresh': 0,
+ 'rx': min(64, rx_rings)})
+ defer(ethnl.rings_set, {'header': {'dev-index': cfg.ifindex},
+ 'tcp-data-split': orig_data_split,
+ 'hds-thresh': hds_thresh,
+ 'rx': rx_rings})
+
+ cfg.src_queue = channels - 1
+ ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}")
+ defer(ethtool, f"-X {cfg.ifname} default")
+
+ if not hasattr(cfg, 'nk_queue'):
+ with NetNSEnter(str(cfg.netns)):
+ netdevnl = NetdevFamily()
+ lease_result = netdevnl.queue_create({
+ "ifindex": cfg.nk_guest_ifindex,
+ "type": "rx",
+ "lease": {
+ "ifindex": cfg.ifindex,
+ "queue": {"id": cfg.src_queue, "type": "rx"},
+ "netns-id": 0,
+ },
+ })
+ cfg.nk_queue = lease_result['id']
+
+
+def set_flow_rule(cfg, port):
+ output = ethtool(
+ f"-N {cfg.ifname} flow-type tcp6 dst-port {port}"
+ f" action {cfg.src_queue}"
+ ).stdout
+ return int(re.search(r'ID (\d+)', output).group(1))
+
+
+def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False):
+ if hasattr(cfg, 'netns'):
+ flow_rule_id = set_flow_rule(cfg, port)
+ defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}")
+
+ ifname = cfg._nk_guest_ifname
+ addr = cfg.nk_guest_ipv6
+ extras = f" -t {cfg.nk_queue} -q 1 -n"
+ if verify:
+ extras += " -v 7"
+ else:
+ ifname = cfg.ifname
+ addr = cfg.addr
+ extras = ""
+
+ if fail_on_linear:
+ extras += " -L"
+
+ return f"{cfg.bin_local} -l -f {ifname} -s {addr} -p {port} {extras}"
+
+
+def ncdevmem_tx(cfg, port, chunk_size=0):
+ """ncdevmem TX send command (without stdin pipe)."""
+ if hasattr(cfg, 'netns'):
+ ifname = cfg._nk_guest_ifname
+ addr = cfg.remote_addr_v['6']
+ nk_args = "-t 0 -q 1 -n"
+ else:
+ ifname = cfg.ifname
+ addr = cfg.remote_addr
+ nk_args = ""
+
+ chunk = f"-z {chunk_size}" if chunk_size else ""
+
+ return (f"{cfg.bin_local} -f {ifname} -s {addr} -p {port}"
+ f" {nk_args} {chunk}").rstrip()
+
+
+def socat_send(cfg, port, buf_size=0, nodelay=False, bind=False):
+ """Socat command for sending to the devmem listener."""
+ proto = f"TCP{cfg.addr_ipver}"
+
+ if hasattr(cfg, 'netns'):
+ addr = f"[{cfg.nk_guest_ipv6}]"
+ else:
+ addr = cfg.baddr
+
+ buf = f"-b {buf_size} " if buf_size else ""
+
+ suffix = ""
+ if nodelay:
+ suffix += ",nodelay"
+ # Match the 5-tuple flow rule ncdevmem installs when given -c.
+ if bind:
+ suffix += f",bind={cfg.remote_baddr}:{port}"
+
+ return f"socat {buf}-u - {proto}:{addr}:{port}{suffix}"
+
+
+def socat_listen(cfg, port):
+ """Socat listen command for TX tests."""
+ proto = f"TCP{cfg.addr_ipver}"
+
+ if hasattr(cfg, 'netns'):
+ opts = ",reuseaddr"
+ else:
+ opts = ""
+
+ return f"socat -U - {proto}-LISTEN:{port}{opts}"
+
+
+def setup_test(cfg, bin_local):
+ cfg.bin_local = bin_local
+ cfg.bin_remote = cfg.remote.deploy(cfg.bin_local)
+ cfg.listen_ns = getattr(cfg, 'netns', None)
+ require_devmem(cfg)
+
+
+def run_rx(cfg):
+ if hasattr(cfg, 'netns'):
+ configure_nic(cfg)
+ port = rand_port()
+ socat = socat_send(cfg, port)
+ data_pipe = (f"yes $(echo -e \x01\x02\x03\x04\x05\x06) | head -c 1K"
+ f" | {socat}")
+ ns = getattr(cfg, "netns", None)
+
+ listen_cmd = ncdevmem_rx(cfg, port)
+ with bkg(listen_cmd, exit_wait=True, ns=ns) as ncdevmem:
+ wait_port_listen(port, proto="tcp", ns=ns)
+ cmd(data_pipe, host=cfg.remote, shell=True)
+ ksft_eq(ncdevmem.ret, 0)
+
+
+def run_tx(cfg):
+ if hasattr(cfg, 'netns'):
+ configure_nic(cfg)
+ ns = getattr(cfg, "netns", None)
+ port = rand_port()
+ tx = ncdevmem_tx(cfg, port)
+ listen_cmd = socat_listen(cfg, port)
+
+ with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
+ wait_port_listen(port, host=cfg.remote)
+ cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx}'", ns=ns, shell=True)
+ ksft_eq(socat.stdout.strip(), "hello\nworld")
+
+
+def run_tx_chunks(cfg):
+ if hasattr(cfg, 'netns'):
+ configure_nic(cfg)
+ ns = getattr(cfg, "netns", None)
+ port = rand_port()
+ tx = ncdevmem_tx(cfg, port, chunk_size=3)
+ listen_cmd = socat_listen(cfg, port)
+
+ with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
+ wait_port_listen(port, host=cfg.remote)
+ cmd(f"bash -c 'echo -e \"hello\\nworld\" | {tx}'", ns=ns, shell=True)
+ ksft_eq(socat.stdout.strip(), "hello\nworld")
+
+
+def run_rx_hds(cfg):
+ if hasattr(cfg, 'netns'):
+ configure_nic(cfg)
+ ns = getattr(cfg, "netns", None)
+
+ for size in [1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]:
+ port = rand_port()
+
+ listen_cmd = ncdevmem_rx(cfg, port, verify=False, fail_on_linear=True)
+ socat = socat_send(cfg, port, buf_size=size, nodelay=True)
+
+ with bkg(listen_cmd, exit_wait=True, ns=ns) as ncdevmem:
+ wait_port_listen(port, proto="tcp", ns=ns)
+ cmd(f"dd if=/dev/zero bs={size} count=1 2>/dev/null | "
+ f"{socat}", host=cfg.remote, shell=True)
+ ksft_eq(ncdevmem.ret, 0, f"HDS failed for payload size {size}")
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 10/11] selftests: drv-net: add primary_rx_redirect support to NetDrvContEnv
From: Bobby Eshleman @ 2026-04-28 22:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
When sending from a namespace that has access to a netkit device with a
leased queue, the nk primary in the host namespace needs to redirect its
RX to the physical device. This patch adds that redirection bpf program
and teaches the harness to install it.
Add primary_rx_redirect=False parameter to NetDrvContEnv.__init__().
When enabled, _attach_primary_rx_redirect_bpf() attaches a new BPF TC
program (nk_primary_rx_redirect.bpf.c) to the primary (host-side) netkit
interface. The program redirects non-ICMPv6 IPv6 packets to the
physical NIC via bpf_redirect_neigh(), with the physical ifindex
configured via the .bss map.
Extract _find_bss_map_id() from _attach_bpf() into a reusable helper so
other BPF attachment methods can use it.
Also add an IPv6 host route on the remote endpoint for the netkit guest
IP via the physical NIC address, so the remote can send packets that
traverse the redirect path to the guest.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
.../drivers/net/hw/nk_primary_rx_redirect.bpf.c | 41 +++++++++++++
tools/testing/selftests/drivers/net/lib/py/env.py | 67 ++++++++++++++++++----
2 files changed, 96 insertions(+), 12 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
new file mode 100644
index 000000000000..fe3c127a4fd0
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_primary_rx_redirect.bpf.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ipv6.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#define TC_ACT_OK 0
+#define ETH_P_IPV6 0x86DD
+#define IPPROTO_ICMPV6 58
+
+#define ctx_ptr(field) ((void *)(long)(field))
+
+volatile __u32 phys_ifindex;
+
+SEC("tc/ingress")
+int nk_primary_rx_redirect(struct __sk_buff *skb)
+{
+ void *data_end = ctx_ptr(skb->data_end);
+ void *data = ctx_ptr(skb->data);
+ struct ethhdr *eth;
+ struct ipv6hdr *ip6h;
+
+ eth = data;
+ if ((void *)(eth + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (eth->h_proto != bpf_htons(ETH_P_IPV6))
+ return TC_ACT_OK;
+
+ ip6h = data + sizeof(struct ethhdr);
+ if ((void *)(ip6h + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (ip6h->nexthdr == IPPROTO_ICMPV6)
+ return TC_ACT_OK;
+
+ return bpf_redirect_neigh(phys_ifindex, NULL, 0, 0);
+}
+
+char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index 24ce122abd9c..d569d01ef791 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -336,15 +336,17 @@ class NetDrvContEnv(NetDrvEpEnv):
+---------------+
"""
- def __init__(self, src_path, rxqueues=1, **kwargs):
+ def __init__(self, src_path, rxqueues=1, primary_rx_redirect=False, **kwargs):
self.netns = None
self._nk_host_ifname = None
self._nk_guest_ifname = None
self._tc_clsact_added = False
self._tc_attached = False
+ self._primary_rx_redirect_attached = False
self._bpf_prog_pref = None
self._bpf_prog_id = None
self._init_ns_attached = False
+ self._remote_route_added = False
self._old_fwd = None
self._old_accept_ra = None
@@ -396,8 +398,14 @@ class NetDrvContEnv(NetDrvEpEnv):
self._setup_ns()
self._attach_bpf()
+ if primary_rx_redirect:
+ self._attach_primary_rx_redirect_bpf()
def __del__(self):
+ if self._primary_rx_redirect_attached:
+ cmd(f"tc qdisc del dev {self._nk_host_ifname} clsact", fail=False)
+ self._primary_rx_redirect_attached = False
+
if self._tc_attached:
cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}")
self._tc_attached = False
@@ -406,6 +414,11 @@ class NetDrvContEnv(NetDrvEpEnv):
cmd(f"tc qdisc del dev {self.ifname} clsact")
self._tc_clsact_added = False
+ if self._remote_route_added:
+ cmd(f"ip -6 route del {self.nk_guest_ipv6}/128",
+ host=self.remote, fail=False)
+ self._remote_route_added = False
+
if self._nk_host_ifname:
cmd(f"ip link del dev {self._nk_host_ifname}")
self._nk_host_ifname = None
@@ -459,6 +472,9 @@ class NetDrvContEnv(NetDrvEpEnv):
ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self._nk_guest_ifname} nodad", ns=self.netns)
ip(f"-6 route add default via fe80::1 dev {self._nk_guest_ifname}", ns=self.netns)
+ ip(f"-6 route add {self.nk_guest_ipv6}/128 via {self.addr_v['6']}", host=self.remote)
+ self._remote_route_added = True
+
def _tc_ensure_clsact(self):
qdisc = json.loads(cmd(f"tc -j qdisc show dev {self.ifname}").stdout)
for q in qdisc:
@@ -476,6 +492,15 @@ class NetDrvContEnv(NetDrvEpEnv):
return (bpf['pref'], bpf['options']['prog']['id'])
raise Exception("Failed to get BPF prog ID")
+ def _find_bss_map_id(self, prog_id):
+ """Find the .bss map ID for a loaded BPF program."""
+ prog_info = bpftool(f"prog show id {prog_id}", json=True)
+ for map_id in prog_info.get("map_ids", []):
+ map_info = bpftool(f"map show id {map_id}", json=True)
+ if map_info.get("name", "").endswith("bss"):
+ return map_id
+ raise Exception(f"Failed to find .bss map for prog {prog_id}")
+
def _attach_bpf(self):
bpf_obj = self.test_dir / "nk_forward.bpf.o"
if not bpf_obj.exists():
@@ -487,17 +512,7 @@ class NetDrvContEnv(NetDrvEpEnv):
self._tc_attached = True
(self._bpf_prog_pref, self._bpf_prog_id) = self._get_bpf_prog_ids()
- prog_info = bpftool(f"prog show id {self._bpf_prog_id}", json=True)
- map_ids = prog_info.get("map_ids", [])
-
- bss_map_id = None
- for map_id in map_ids:
- map_info = bpftool(f"map show id {map_id}", json=True)
- if map_info.get("name").endswith("bss"):
- bss_map_id = map_id
-
- if bss_map_id is None:
- raise Exception("Failed to find .bss map")
+ bss_map_id = self._find_bss_map_id(self._bpf_prog_id)
ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix)
ipv6_bytes = ipv6_addr.packed
@@ -505,3 +520,31 @@ class NetDrvContEnv(NetDrvEpEnv):
value = ipv6_bytes + ifindex_bytes
value_hex = ' '.join(f'{b:02x}' for b in value)
bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
+
+ def _attach_primary_rx_redirect_bpf(self):
+ """Attach BPF redirect program on the primary netkit ingress."""
+ bpf_obj = self.test_dir / "nk_primary_rx_redirect.bpf.o"
+ if not bpf_obj.exists():
+ raise KsftSkipEx("Primary RX redirect BPF prog not found")
+
+ cmd(f"tc qdisc add dev {self._nk_host_ifname} clsact")
+ cmd(f"tc filter add dev {self._nk_host_ifname} ingress"
+ f" bpf obj {bpf_obj} sec tc/ingress direct-action")
+ self._primary_rx_redirect_attached = True
+
+ filters = json.loads(
+ cmd(f"tc -j filter show dev {self._nk_host_ifname} ingress").stdout)
+ redirect_prog_id = None
+ for bpf in filters:
+ if 'options' not in bpf:
+ continue
+ if bpf['options']['bpf_name'].startswith('nk_primary_rx_redirect'):
+ redirect_prog_id = bpf['options']['prog']['id']
+ break
+ if redirect_prog_id is None:
+ raise Exception("Failed to get primary RX redirect BPF prog ID")
+
+ bss_map_id = self._find_bss_map_id(redirect_prog_id)
+ phys_ifindex_bytes = self.ifindex.to_bytes(4, byteorder='little')
+ value_hex = ' '.join(f'{b:02x}' for b in phys_ifindex_bytes)
+ bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
--
2.52.0
^ permalink raw reply related
* [PATCH net-next 11/11] selftests: drv-net: add netkit devmem tests
From: Bobby Eshleman @ 2026-04-28 22:42 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Jonathan Corbet, Shuah Khan, Alex Shi,
Yanteng Si, Dongliang Mu, Michael Chan, Pavan Chebbi,
Joshua Washington, Harshitha Ramamurthy, Saeed Mahameed,
Tariq Toukan, Mark Bloch, Leon Romanovsky, Alexander Duyck,
kernel-team, Daniel Borkmann, Nikolay Aleksandrov, Shuah Khan
Cc: netdev, linux-doc, linux-kernel, linux-rdma, bpf, linux-kselftest,
Stanislav Fomichev, Mina Almasry, Bobby Eshleman
In-Reply-To: <20260428-tcp-dm-netkit-v1-0-719280eba4d2@meta.com>
From: Bobby Eshleman <bobbyeshleman@meta.com>
Add nk_devmem.py with four tests for TCP devmem through a netkit device:
These tests are just duplicates of the original devmem tests, with some
adjusted parameters such as telling ncdevmem to avoid device setup
(since it only has access to netkit, not a phys device).
Each test uses NetDrvContEnv with primary_rx_redirect=True to set up the
BPF redirect program on the primary netkit interface.
Assisted-by: Claude Code:claude-sonnet-4-6
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
.../testing/selftests/drivers/net/hw/nk_devmem.py | 40 ++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/tools/testing/selftests/drivers/net/hw/nk_devmem.py b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
new file mode 100755
index 000000000000..c069d525798b
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""Test devmem TCP with netkit."""
+
+from os import path
+from lib.py import ksft_run, ksft_exit, ksft_disruptive
+from lib.py import NetDrvContEnv
+from lib.py.devmem import setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds
+
+
+@ksft_disruptive
+def check_nk_rx(cfg) -> None:
+ run_rx(cfg)
+
+
+@ksft_disruptive
+def check_nk_tx(cfg) -> None:
+ run_tx(cfg)
+
+
+@ksft_disruptive
+def check_nk_tx_chunks(cfg) -> None:
+ run_tx_chunks(cfg)
+
+
+@ksft_disruptive
+def check_nk_rx_hds(cfg) -> None:
+ run_rx_hds(cfg)
+
+
+def main() -> None:
+ with NetDrvContEnv(__file__, rxqueues=2, primary_rx_redirect=True) as cfg:
+ setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem"))
+ ksft_run([check_nk_rx, check_nk_tx, check_nk_tx_chunks, check_nk_rx_hds],
+ args=(cfg,))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
--
2.52.0
^ permalink raw reply related
* [PATCH net-next] net/mlx5: Add MLX5_VXLAN config option
From: Marc Harvey @ 2026-04-28 22:44 UTC (permalink / raw)
To: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-rdma, linux-kernel, Kuniyuki Iwashima, Marc Harvey
Currently, there is no way to disable mlx5 vxlan offloading if vxlan
is enabled. We've (possibly) seen some minor udp rr and udp stream
regressions when enabling vxlan, and want a way to disable this
offloading. Also coupling vxlan offloading with vxlan enablement
generally limits the flexability of vxlan setups.
Add a new config option for mlx5 vxlan offloading specifically, so
that users can use vxlan without automatically opting in to the
offloading.
To keep the same behavior as before, the new config option is enabled
by default if vxlan is enabled.
Signed-off-by: Marc Harvey <marcharvey@google.com>
---
drivers/net/ethernet/mellanox/mlx5/core/Kconfig | 11 +++++++++++
drivers/net/ethernet/mellanox/mlx5/core/Makefile | 2 +-
drivers/net/ethernet/mellanox/mlx5/core/lib/vxlan.h | 2 +-
3 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/Kconfig b/drivers/net/ethernet/mellanox/mlx5/core/Kconfig
index 3c3e84100d5a..d2e091bdbafc 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/Kconfig
+++ b/drivers/net/ethernet/mellanox/mlx5/core/Kconfig
@@ -218,3 +218,14 @@ config MLX5_EN_PSP
interfaces to PSP Stack which supports PSP crypto offload.
If unsure, say Y.
+
+config MLX5_VXLAN
+ bool "Mellanox Technologies vxlan offloading"
+ depends on VXLAN
+ depends on MLX5_CORE_EN
+ default y
+ help
+ mlx5 device offload support for vxlan. Makes the mlx5 driver always
+ attempt to initialize device handling of vxlan packets.
+
+ If unsure, say Y.
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/Makefile b/drivers/net/ethernet/mellanox/mlx5/core/Makefile
index d39fe9c4a87c..6f2cc5414d07 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/Makefile
+++ b/drivers/net/ethernet/mellanox/mlx5/core/Makefile
@@ -86,7 +86,7 @@ mlx5_core-$(CONFIG_MLX5_BRIDGE) += esw/bridge.o esw/bridge_mcast.o esw/bridge
mlx5_core-$(CONFIG_HWMON) += hwmon.o
mlx5_core-$(CONFIG_MLX5_MPFS) += lib/mpfs.o
-ifneq ($(CONFIG_VXLAN),)
+ifneq ($(CONFIG_MLX5_VXLAN),)
mlx5_core-y += lib/vxlan.o
endif
mlx5_core-$(CONFIG_PTP_1588_CLOCK) += lib/clock.o
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/vxlan.h b/drivers/net/ethernet/mellanox/mlx5/core/lib/vxlan.h
index 34ef662da35e..67d0c126c2ae 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/lib/vxlan.h
+++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/vxlan.h
@@ -50,7 +50,7 @@ static inline bool mlx5_vxlan_allowed(struct mlx5_vxlan *vxlan)
return !IS_ERR_OR_NULL(vxlan);
}
-#if IS_ENABLED(CONFIG_VXLAN)
+#if IS_ENABLED(CONFIG_MLX5_VXLAN)
struct mlx5_vxlan *mlx5_vxlan_create(struct mlx5_core_dev *mdev);
void mlx5_vxlan_destroy(struct mlx5_vxlan *vxlan);
int mlx5_vxlan_add_port(struct mlx5_vxlan *vxlan, u16 port);
---
base-commit: 790ead9394860e7d70c5e0e50a35b243e909a618
change-id: 20260427-mlx5_vxlan-715699b8bbea
Best regards,
--
Marc Harvey <marcharvey@google.com>
^ permalink raw reply related
* [PATCH net] ipv6: rpl: add NULL check for idev in ipv6_rpl_srh_rcv()
From: Andrea Mayer @ 2026-04-28 22:48 UTC (permalink / raw)
To: David S . Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman
Cc: Alexander Aring, Justin Iurman, netdev, linux-kernel, stable,
stefano.salsano, Andrea Mayer
ipv6_rpl_srh_rcv() dereferences idev from __in6_dev_get() without
a NULL check when reading idev->cnf.rpl_seg_enabled.
When the device's MTU drops below IPV6_MIN_MTU, addrconf_ifdown()
clears dev->ip6_ptr through RCU_INIT_POINTER(), which is immediately
visible to concurrent readers. A packet that already passed the idev
check in ip6_rcv_core() can race with this and hit a NULL pointer
dereference.
Reproduced by flooding traffic through a route with RPL source routing
while rapidly flapping the receiving interface's MTU between 1500 and
1200:
BUG: KASAN: null-ptr-deref in ipv6_rpl_srh_rcv+0xae/0x1050
Read of size 4 at addr 00000000000006b4 by task ping6/318
CPU: 0 UID: 0 PID: 318 Comm: ping6 Not tainted 7.1.0-rc1-micro-vm-dev-g46f74a3f7d57 #82 PREEMPT(full)
Call Trace:
<IRQ>
kasan_report+0xc6/0x100
ipv6_rpl_srh_rcv+0xae/0x1050
ip6_protocol_deliver_rcu+0x717/0x960
ip6_input_finish+0xa3/0x1b0
ip6_input+0xdc/0x490
ipv6_rcv+0x338/0x460
__netif_receive_skb_one_core+0xd1/0x130
process_backlog+0x2c7/0x9f0
__napi_poll.constprop.0+0x51/0x270
net_rx_action+0x322/0x730
handle_softirqs+0x119/0x640
do_softirq+0xae/0xe0
</IRQ>
Add a NULL check for idev after __in6_dev_get() and drop the skb if
idev is NULL, consistent with the SRv6 fix in commit 064137935262
("ipv6: add NULL checks for idev in SRv6 paths").
Fixes: 8610c7c6e3bd ("net: ipv6: add support for rpl sr exthdr")
Cc: stable@vger.kernel.org
Signed-off-by: Andrea Mayer <andrea.mayer@uniroma2.it>
---
net/ipv6/exthdrs.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c
index 03cbce842c1a..e398a8851031 100644
--- a/net/ipv6/exthdrs.c
+++ b/net/ipv6/exthdrs.c
@@ -499,6 +499,10 @@ static int ipv6_rpl_srh_rcv(struct sk_buff *skb)
u32 r;
idev = __in6_dev_get(skb->dev);
+ if (!idev) {
+ kfree_skb(skb);
+ return -1;
+ }
accept_rpl_seg = min(READ_ONCE(net->ipv6.devconf_all->rpl_seg_enabled),
READ_ONCE(idev->cnf.rpl_seg_enabled));
--
2.20.1
^ permalink raw reply related
* Re: [PATCH net v4 8/8] xsk: fix u64 descriptor address truncation on 32-bit architectures
From: Stanislav Fomichev @ 2026-04-28 23:11 UTC (permalink / raw)
To: Jason Xing
Cc: davem, edumazet, kuba, pabeni, bjorn, magnus.karlsson,
maciej.fijalkowski, jonathan.lemon, sdf, ast, daniel, hawk,
john.fastabend, aleksander.lobakin, bpf, netdev, Jason Xing
In-Reply-To: <20260424053816.27965-9-kerneljasonxing@gmail.com>
On 04/24, Jason Xing wrote:
> From: Jason Xing <kernelxing@tencent.com>
>
> In copy mode TX, xsk_skb_destructor_set_addr() stores the 64-bit
> descriptor address into skb_shinfo(skb)->destructor_arg (void *) via a
> uintptr_t cast:
>
> skb_shinfo(skb)->destructor_arg = (void *)((uintptr_t)addr | 0x1UL);
>
> On 32-bit architectures uintptr_t is 32 bits, so the upper 32 bits of
> the descriptor address are silently dropped. In unaligned mode the chunk
> offset is encoded in bits 48-63 of the descriptor address
> (XSK_UNALIGNED_BUF_OFFSET_SHIFT = 48), meaning the offset is lost
> entirely. The completion queue then returns a truncated address to
> userspace, making buffer recycling impossible.
>
> Fix this by handling the 32-bit case in the destructor_arg helpers:
>
> - xsk_skb_destructor_set_addr(): on !CONFIG_64BIT, allocate an
> xsk_addrs struct via kmem_cache_zalloc() to store the full u64
> address. Leave num_descs as 0 (zalloc) so that the subsequent
> xsk_inc_num_desc() brings it to the correct count of 1.
>
> - xsk_skb_destructor_is_addr(): on !CONFIG_64BIT, return true only
> when destructor_arg is NULL (not yet set), false when it points to
> an xsk_addrs struct.
>
> - xsk_skb_init_misc(): call xsk_skb_destructor_set_addr() first
> before touching any other skb fields; on failure return early so
> the skb destructor is never changed from sock_wfree.
>
> The existing xsk_consume_skb() already handles 32-bit correctly after
> these changes: xsk_skb_destructor_is_addr() returns false for any
> allocated xsk_addrs, so the kmem_cache_free path is always taken.
>
> The overhead is one extra kmem_cache_zalloc per first descriptor on
> 32-bit only; 64-bit builds are completely unchanged.
>
> Closes: https://lore.kernel.org/all/20260419045824.D9E5EC2BCAF@smtp.kernel.org/
> Fixes: 0ebc27a4c67d ("xsk: avoid data corruption on cq descriptor number")
> Signed-off-by: Jason Xing <kernelxing@tencent.com>
> ---
> net/xdp/xsk.c | 38 +++++++++++++++++++++++++++++++-------
> 1 file changed, 31 insertions(+), 7 deletions(-)
>
> diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c
> index ed96f6ec8ff2..fe88f47741b5 100644
> --- a/net/xdp/xsk.c
> +++ b/net/xdp/xsk.c
> @@ -558,7 +558,10 @@ static int xsk_cq_reserve_locked(struct xsk_buff_pool *pool)
>
> static bool xsk_skb_destructor_is_addr(struct sk_buff *skb)
> {
> - return (uintptr_t)skb_shinfo(skb)->destructor_arg & 0x1UL;
> + if (IS_ENABLED(CONFIG_64BIT))
> + return (uintptr_t)skb_shinfo(skb)->destructor_arg & 0x1UL;
> + else
> + return !skb_shinfo(skb)->destructor_arg;
Don't understand why we need to special case CONFIG_64BIT here?
Shouldn't the same existing condition work on 32bit?
> }
>
> static u64 xsk_skb_destructor_get_addr(struct sk_buff *skb)
> @@ -566,9 +569,21 @@ static u64 xsk_skb_destructor_get_addr(struct sk_buff *skb)
> return (u64)((uintptr_t)skb_shinfo(skb)->destructor_arg & ~0x1UL);
> }
>
> -static void xsk_skb_destructor_set_addr(struct sk_buff *skb, u64 addr)
> +static int xsk_skb_destructor_set_addr(struct sk_buff *skb, u64 addr)
> {
[..]
> + if (!IS_ENABLED(CONFIG_64BIT)) {
> + struct xsk_addrs *xsk_addr;
> +
> + xsk_addr = kmem_cache_zalloc(xsk_tx_generic_cache, GFP_KERNEL);
> + if (!xsk_addr)
> + return -ENOMEM;
> + xsk_addr->addrs[0] = addr;
> + skb_shinfo(skb)->destructor_arg = (void *)xsk_addr;
> + return 0;
> + }
> +
> skb_shinfo(skb)->destructor_arg = (void *)((uintptr_t)addr | 0x1UL);
> + return 0;
I think this is gonna be a 3rd copy paste of the same logic? Let's
move to a new helper and replace existing kmem_cache_zalloc places?
xsk_skb_destructor_alloc_list(prev_addr) ?
^ permalink raw reply
* Re: [PATCH net-next] net: gianfar: use alloc_ethdev_mqs
From: Andrew Lunn @ 2026-04-28 23:13 UTC (permalink / raw)
To: Rosen Penev
Cc: netdev, Claudiu Manoil, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, open list
In-Reply-To: <20260428223058.113013-1-rosenp@gmail.com>
On Tue, Apr 28, 2026 at 03:30:58PM -0700, Rosen Penev wrote:
> >From looking at git history, mqs was introduced after mq and after this
> code was written. Having said that, mqs can be used as there is already
> an RX queue variable in place. Not only that, mqs already sets the
> num_xx_queues members. No need to open code this.
>
> Signed-off-by: Rosen Penev <rosenp@gmail.com>
> ---
> drivers/net/ethernet/freescale/gianfar.c | 6 +-----
> 1 file changed, 1 insertion(+), 5 deletions(-)
>
> diff --git a/drivers/net/ethernet/freescale/gianfar.c b/drivers/net/ethernet/freescale/gianfar.c
> index 3271de5844f8..7b47c7c49c08 100644
> --- a/drivers/net/ethernet/freescale/gianfar.c
> +++ b/drivers/net/ethernet/freescale/gianfar.c
> @@ -669,7 +669,7 @@ static int gfar_of_init(struct platform_device *ofdev, struct net_device **pdev)
> return -EINVAL;
> }
>
> - *pdev = alloc_etherdev_mq(sizeof(*priv), num_tx_qs);
> + *pdev = alloc_etherdev_mqs(sizeof(*priv), num_tx_qs, num_rx_qs);
> dev = *pdev;
> if (NULL == dev)
> return -ENOMEM;
> @@ -679,10 +679,6 @@ static int gfar_of_init(struct platform_device *ofdev, struct net_device **pdev)
>
> priv->mode = mode;
>
> - priv->num_tx_queues = num_tx_qs;
> - netif_set_real_num_rx_queues(dev, num_rx_qs);
> - priv->num_rx_queues = num_rx_qs;
Please add to the commit message an explanation of why these two
assignments can be removed, because it is not obvious.
Andrew
---
pw-bot: cr
^ permalink raw reply
* [PATCH net-next v19 04/15] net: homa: create homa_pool.h and homa_pool.c
From: John Ousterhout @ 2026-04-28 23:15 UTC (permalink / raw)
To: netdev; +Cc: pabeni, edumazet, horms, kuba, John Ousterhout
In-Reply-To: <20260428231520.1857-1-ouster@cs.stanford.edu>
These files implement Homa's mechanism for managing application-level
buffer space for incoming messages. This mechanism is needed to allow
Homa to copy data out to user space in parallel with receiving packets;
it was discussed in a talk at NetDev 0x17.
Signed-off-by: John Ousterhout <ouster@cs.stanford.edu>
---
Changes for v19:
* Add homa_pool_unlink and homa_pool_release functions to encapsulate
cleanup.
* Made accounting in homa_pool_avail_bytes more precise.
Changes for v18:
* Rename homa_pool_release_buffers to homa_pool_free_bufs
Changes for v16:
* Add homa_pool_avail_bytes() for new HOMAIOCINFO ioctl
Changes for v11:
* Rework the mechanism for waking up RPCs that stalled waiting for
buffer pool space
Changes for v10:
* Fix minor syntactic issues such as reverse xmas tree
Changes for v9:
* Eliminate use of _Static_assert
* Use new homa_clock abstraction layer.
* Allow memory to be allocated without GFP_ATOMIC
* Various name improvements (e.g. use "alloc" instead of "new" for functions
that allocate memory)
* Remove sync.txt, move its contents into comments (mostly in homa_impl.h)
Changes for v8:
* Refactor homa_pool APIs (move allocation/deallocation into homa_pool.c,
move locking responsibility out)
Changes for v7:
* Use u64 and __u64 properly
* Eliminate extraneous use of RCU
* Refactor pool->cores to use percpu variable
* Use smp_processor_id instead of raw_smp_processor_id
---
net/homa/homa_pool.c | 514 +++++++++++++++++++++++++++++++++++++++++++
net/homa/homa_pool.h | 166 ++++++++++++++
2 files changed, 680 insertions(+)
create mode 100644 net/homa/homa_pool.c
create mode 100644 net/homa/homa_pool.h
diff --git a/net/homa/homa_pool.c b/net/homa/homa_pool.c
new file mode 100644
index 000000000000..7ab7e1f11449
--- /dev/null
+++ b/net/homa/homa_pool.c
@@ -0,0 +1,514 @@
+// SPDX-License-Identifier: BSD-2-Clause or GPL-2.0+
+
+#include "homa_impl.h"
+#include "homa_pool.h"
+
+/* This file contains functions that manage user-space buffer pools. */
+
+/* Pools must always have at least this many bpages (no particular
+ * reasoning behind this value).
+ */
+#define MIN_POOL_SIZE 2
+
+/* Used when determining how many bpages to consider for allocation. */
+#define MIN_EXTRA 4
+
+/**
+ * set_bpages_needed() - Set the bpages_needed field of @pool based
+ * on the length of the first RPC that's waiting for buffer space.
+ * The caller must own the lock for @pool->hsk.
+ * @pool: Pool to update.
+ */
+static void set_bpages_needed(struct homa_pool *pool)
+{
+ struct homa_rpc *rpc = list_first_entry(&pool->hsk->waiting_for_bufs,
+ struct homa_rpc, buf_links);
+
+ pool->bpages_needed = (rpc->msgin.length + HOMA_BPAGE_SIZE - 1) >>
+ HOMA_BPAGE_SHIFT;
+}
+
+/**
+ * homa_pool_alloc() - Allocate and initialize a new homa_pool (it will have
+ * no region associated with it until homa_pool_set_region is invoked).
+ * @hsk: Socket the pool will be associated with.
+ * Return: A pointer to the new pool or a negative errno.
+ */
+struct homa_pool *homa_pool_alloc(struct homa_sock *hsk)
+{
+ struct homa_pool *pool;
+
+ pool = kzalloc_obj(*pool, GFP_KERNEL);
+ if (!pool)
+ return ERR_PTR(-ENOMEM);
+ pool->hsk = hsk;
+ return pool;
+}
+
+/**
+ * homa_pool_set_region() - Associate a region of memory with a pool.
+ * @hsk: Socket whose pool the region will be associated with.
+ * Must not be locked, and the pool must not currently
+ * have a region associated with it.
+ * @region: First byte of the memory region for the pool, allocated
+ * by the application; must be page-aligned.
+ * @region_size: Total number of bytes available at @buf_region.
+ * Return: Either zero (for success) or a negative errno for failure.
+ */
+int homa_pool_set_region(struct homa_sock *hsk, void __user *region,
+ u64 region_size)
+{
+ struct homa_pool_core __percpu *cores;
+ struct homa_bpage *descriptors;
+ int i, result, num_bpages;
+ struct homa_pool *pool;
+
+ if (((uintptr_t)region) & ~PAGE_MASK)
+ return -EINVAL;
+
+ /* Allocate memory before locking the socket, so we can allocate
+ * without GFP_ATOMIC.
+ */
+ num_bpages = region_size >> HOMA_BPAGE_SHIFT;
+ if (num_bpages < MIN_POOL_SIZE)
+ return -EINVAL;
+ descriptors = kmalloc_array(num_bpages, sizeof(struct homa_bpage),
+ GFP_KERNEL | __GFP_ZERO);
+ if (!descriptors)
+ return -ENOMEM;
+ cores = alloc_percpu_gfp(struct homa_pool_core, __GFP_ZERO);
+ if (!cores) {
+ result = -ENOMEM;
+ goto error;
+ }
+
+ homa_sock_lock(hsk);
+ pool = hsk->buffer_pool;
+ if (pool->region) {
+ result = -EINVAL;
+ homa_sock_unlock(hsk);
+ goto error;
+ }
+
+ pool->region = (char __user *)region;
+ pool->num_bpages = num_bpages;
+ pool->descriptors = descriptors;
+ atomic_set(&pool->free_bpages, pool->num_bpages);
+ pool->bpages_needed = INT_MAX;
+ pool->cores = cores;
+ pool->check_waiting_invoked = 0;
+
+ for (i = 0; i < pool->num_bpages; i++) {
+ struct homa_bpage *bp = &pool->descriptors[i];
+
+ spin_lock_init(&bp->lock);
+ bp->owner = -1;
+ }
+
+ homa_sock_unlock(hsk);
+ return 0;
+
+error:
+ kfree(descriptors);
+ free_percpu(cores);
+ return result;
+}
+
+/**
+ * homa_pool_free() - Destructor for homa_pool. After this method
+ * returns, the object should not be used (it will be freed here).
+ * @pool: Pool to destroy.
+ */
+void homa_pool_free(struct homa_pool *pool)
+{
+ if (pool->region) {
+ kfree(pool->descriptors);
+ free_percpu(pool->cores);
+ pool->region = NULL;
+ }
+ kfree(pool);
+}
+
+/**
+ * homa_pool_get_rcvbuf() - Return information needed to handle getsockopt
+ * for HOMA_SO_RCVBUF.
+ * @pool: Pool for which information is needed.
+ * @args: Store info here.
+ */
+void homa_pool_get_rcvbuf(struct homa_pool *pool,
+ struct homa_rcvbuf_args *args)
+{
+ args->start = (uintptr_t)pool->region;
+ args->length = pool->num_bpages << HOMA_BPAGE_SHIFT;
+}
+
+/**
+ * homa_bpage_available() - Check whether a bpage is available for use.
+ * @bpage: Bpage to check
+ * @now: Current time (homa_clock() units)
+ * Return: True if the bpage is free or if it can be stolen, otherwise
+ * false.
+ */
+bool homa_bpage_available(struct homa_bpage *bpage, u64 now)
+{
+ int ref_count = atomic_read(&bpage->refs);
+
+ return ref_count == 0 || (ref_count == 1 && bpage->owner >= 0 &&
+ bpage->expiration <= now);
+}
+
+/**
+ * homa_pool_get_pages() - Allocate one or more full pages from the pool.
+ * @pool: Pool from which to allocate pages
+ * @num_pages: Number of pages needed
+ * @pages: The indices of the allocated pages are stored here; caller
+ * must ensure this array is big enough. Reference counts have
+ * been set to 1 on all of these pages (or 2 if set_owner
+ * was specified).
+ * @set_owner: If nonzero, the current core is marked as owner of all
+ * of the allocated pages (and the expiration time is also
+ * set). Otherwise the pages are left unowned.
+ * Return: 0 for success, -1 if there wasn't enough free space in the pool.
+ */
+int homa_pool_get_pages(struct homa_pool *pool, int num_pages, u32 *pages,
+ int set_owner)
+{
+ int core_num = smp_processor_id();
+ struct homa_pool_core *core;
+ u64 now = homa_clock();
+ int alloced = 0;
+ int limit = 0;
+
+ core = this_cpu_ptr(pool->cores);
+ if (atomic_sub_return(num_pages, &pool->free_bpages) < 0) {
+ atomic_add(num_pages, &pool->free_bpages);
+ return -1;
+ }
+
+ /* Once we get to this point we know we will be able to find
+ * enough free pages; now we just have to find them.
+ */
+ while (alloced != num_pages) {
+ struct homa_bpage *bpage;
+ int cur;
+
+ /* If we don't need to use all of the bpages in the pool,
+ * then try to use only the ones with low indexes. This
+ * will reduce the cache footprint for the pool by reusing
+ * a few bpages over and over. Specifically this code will
+ * not consider any candidate page whose index is >= limit.
+ * Limit is chosen to make sure there are a reasonable
+ * number of free pages in the range, so we won't have to
+ * check a huge number of pages.
+ */
+ if (limit == 0) {
+ int extra;
+
+ limit = pool->num_bpages -
+ atomic_read(&pool->free_bpages);
+ extra = limit >> 2;
+ limit += (extra < MIN_EXTRA) ? MIN_EXTRA : extra;
+ if (limit > pool->num_bpages)
+ limit = pool->num_bpages;
+ }
+
+ cur = core->next_candidate;
+ core->next_candidate++;
+ if (cur >= limit) {
+ core->next_candidate = 0;
+
+ /* Must recompute the limit for each new loop through
+ * the bpage array: we may need to consider a larger
+ * range of pages because of concurrent allocations.
+ */
+ limit = 0;
+ continue;
+ }
+ bpage = &pool->descriptors[cur];
+
+ /* Figure out whether this candidate is free (or can be
+ * stolen). Do a quick check without locking the page, and
+ * if the page looks promising, then lock it and check again
+ * (must check again in case someone else snuck in and
+ * grabbed the page).
+ */
+ if (!homa_bpage_available(bpage, now))
+ continue;
+ if (!spin_trylock_bh(&bpage->lock))
+ /* Rather than wait for a locked page to become free,
+ * just go on to the next page. If the page is locked,
+ * it probably won't turn out to be available anyway.
+ */
+ continue;
+ if (!homa_bpage_available(bpage, now)) {
+ spin_unlock_bh(&bpage->lock);
+ continue;
+ }
+ if (bpage->owner >= 0)
+ atomic_inc(&pool->free_bpages);
+ if (set_owner) {
+ atomic_set(&bpage->refs, 2);
+ bpage->owner = core_num;
+ bpage->expiration = now +
+ pool->hsk->homa->bpage_lease_cycles;
+ } else {
+ atomic_set(&bpage->refs, 1);
+ bpage->owner = -1;
+ }
+ spin_unlock_bh(&bpage->lock);
+ pages[alloced] = cur;
+ alloced++;
+ }
+ return 0;
+}
+
+/**
+ * homa_pool_alloc_msg() - Allocate buffer space for an incoming message.
+ * @rpc: RPC that needs space allocated for its incoming message (space must
+ * not already have been allocated). The fields @msgin->num_buffers
+ * and @msgin->buffers are filled in. Must be locked by caller.
+ * Return: The return value is normally 0, which means either buffer space
+ * was allocated or the @rpc was queued on @hsk->waiting. If a fatal error
+ * occurred, such as no buffer pool present, then a negative errno is
+ * returned.
+ */
+int homa_pool_alloc_msg(struct homa_rpc *rpc)
+ __must_hold(rpc->bucket->lock)
+{
+ struct homa_pool *pool = rpc->hsk->buffer_pool;
+ int full_pages, partial, i, core_id;
+ struct homa_pool_core *core;
+ u32 pages[HOMA_MAX_BPAGES];
+ struct homa_bpage *bpage;
+ struct homa_rpc *other;
+
+ if (!pool->region)
+ return -ENOMEM;
+ if (rpc->state == RPC_DEAD)
+ return 0;
+
+ /* First allocate any full bpages that are needed. */
+ full_pages = rpc->msgin.length >> HOMA_BPAGE_SHIFT;
+ if (unlikely(full_pages)) {
+ if (homa_pool_get_pages(pool, full_pages, pages, 0) != 0)
+ goto out_of_space;
+ for (i = 0; i < full_pages; i++)
+ rpc->msgin.bpage_offsets[i] = pages[i] <<
+ HOMA_BPAGE_SHIFT;
+ }
+ rpc->msgin.num_bpages = full_pages;
+
+ /* The last chunk may be less than a full bpage; for this we use
+ * the bpage that we own (and reuse it for multiple messages).
+ */
+ partial = rpc->msgin.length & (HOMA_BPAGE_SIZE - 1);
+ if (unlikely(partial == 0))
+ goto success;
+ core_id = smp_processor_id();
+ core = this_cpu_ptr(pool->cores);
+ bpage = &pool->descriptors[core->page_hint];
+ spin_lock_bh(&bpage->lock);
+ if (bpage->owner != core_id) {
+ spin_unlock_bh(&bpage->lock);
+ goto new_page;
+ }
+ if ((core->allocated + partial) > HOMA_BPAGE_SIZE) {
+ if (atomic_read(&bpage->refs) == 1) {
+ /* Bpage is totally free, so we can reuse it. */
+ core->allocated = 0;
+ } else {
+ bpage->owner = -1;
+
+ /* We know the reference count can't reach zero here
+ * because of check above, so we won't have to decrement
+ * pool->free_bpages.
+ */
+ atomic_dec_return(&bpage->refs);
+ spin_unlock_bh(&bpage->lock);
+ goto new_page;
+ }
+ }
+ bpage->expiration = homa_clock() +
+ pool->hsk->homa->bpage_lease_cycles;
+ atomic_inc(&bpage->refs);
+ spin_unlock_bh(&bpage->lock);
+ goto allocate_partial;
+
+ /* Can't use the current page; get another one. */
+new_page:
+ if (homa_pool_get_pages(pool, 1, pages, 1) != 0) {
+ homa_pool_free_bufs(pool, rpc->msgin.num_bpages,
+ rpc->msgin.bpage_offsets);
+ rpc->msgin.num_bpages = 0;
+ goto out_of_space;
+ }
+ core->page_hint = pages[0];
+ core->allocated = 0;
+
+allocate_partial:
+ rpc->msgin.bpage_offsets[rpc->msgin.num_bpages] = core->allocated
+ + (core->page_hint << HOMA_BPAGE_SHIFT);
+ rpc->msgin.num_bpages++;
+ core->allocated += partial;
+
+success:
+ return 0;
+
+ /* We get here if there wasn't enough buffer space for this
+ * message; add the RPC to hsk->waiting_for_bufs. The list is sorted
+ * by RPC length in order to implement SRPT.
+ */
+out_of_space:
+ homa_sock_lock(pool->hsk);
+ list_for_each_entry(other, &pool->hsk->waiting_for_bufs, buf_links) {
+ if (other->msgin.length > rpc->msgin.length) {
+ list_add_tail(&rpc->buf_links, &other->buf_links);
+ goto queued;
+ }
+ }
+ list_add_tail(&rpc->buf_links, &pool->hsk->waiting_for_bufs);
+
+queued:
+ set_bpages_needed(pool);
+ homa_sock_unlock(pool->hsk);
+ return 0;
+}
+
+/**
+ * homa_pool_get_buffer() - Given an RPC, figure out where to store incoming
+ * message data.
+ * @rpc: RPC for which incoming message data is being processed; its
+ * msgin must be properly initialized and buffer space must have
+ * been allocated for the message.
+ * @offset: Offset within @rpc's incoming message.
+ * @available: Will be filled in with the number of bytes of space available
+ * at the returned address (could be zero if offset is
+ * (erroneously) past the end of the message).
+ * Return: The application's virtual address for buffer space corresponding
+ * to @offset in the incoming message for @rpc.
+ */
+void __user *homa_pool_get_buffer(struct homa_rpc *rpc, int offset,
+ int *available)
+{
+ int bpage_index, bpage_offset;
+
+ bpage_index = offset >> HOMA_BPAGE_SHIFT;
+ if (offset >= rpc->msgin.length) {
+ WARN_ONCE(true, "%s got offset %d >= message length %d\n",
+ __func__, offset, rpc->msgin.length);
+ *available = 0;
+ return NULL;
+ }
+ bpage_offset = offset & (HOMA_BPAGE_SIZE - 1);
+ *available = (bpage_index < (rpc->msgin.num_bpages - 1))
+ ? HOMA_BPAGE_SIZE - bpage_offset
+ : rpc->msgin.length - offset;
+ return rpc->hsk->buffer_pool->region +
+ rpc->msgin.bpage_offsets[bpage_index] + bpage_offset;
+}
+
+/**
+ * homa_pool_free_bufs() - Release buffer space so that it can be
+ * reused.
+ * @pool: Pool that the buffer space belongs to. Doesn't need to
+ * be locked.
+ * @num_buffers: How many buffers to release.
+ * @buffers: Points to @num_buffers values, each of which is an offset
+ * from the start of the pool to the buffer to be released.
+ * Return: 0 for success, otherwise a negative errno.
+ */
+int homa_pool_free_bufs(struct homa_pool *pool, int num_buffers, u32 *buffers)
+{
+ int result = 0;
+ int i;
+
+ if (!pool->region)
+ return result;
+ for (i = 0; i < num_buffers; i++) {
+ u32 bpage_index = buffers[i] >> HOMA_BPAGE_SHIFT;
+ struct homa_bpage *bpage = &pool->descriptors[bpage_index];
+
+ if (bpage_index < pool->num_bpages) {
+ if (atomic_dec_return(&bpage->refs) == 0)
+ atomic_inc(&pool->free_bpages);
+ } else {
+ result = -EINVAL;
+ }
+ }
+ return result;
+}
+
+/**
+ * homa_pool_check_waiting() - Checks to see if there are enough free
+ * bpages to wake up any RPCs that were blocked. Whenever
+ * homa_pool_free_bufs is invoked, this function must be invoked later,
+ * at a point when the caller holds no locks (homa_pool_free_bufs may
+ * be invoked with locks held, so it can't safely invoke this function).
+ * This is regrettably tricky, but I can't think of a better solution.
+ * @pool: Information about the buffer pool.
+ */
+void homa_pool_check_waiting(struct homa_pool *pool)
+{
+ if (!pool->region)
+ return;
+ while (atomic_read(&pool->free_bpages) >= pool->bpages_needed) {
+ struct homa_rpc *rpc;
+
+ homa_sock_lock(pool->hsk);
+ if (list_empty(&pool->hsk->waiting_for_bufs)) {
+ pool->bpages_needed = INT_MAX;
+ homa_sock_unlock(pool->hsk);
+ break;
+ }
+ rpc = list_first_entry(&pool->hsk->waiting_for_bufs,
+ struct homa_rpc, buf_links);
+ if (!homa_rpc_try_lock(rpc)) {
+ /* Can't just spin on the RPC lock because we're
+ * holding the socket lock and the lock order is
+ * rpc-then-socket (see "Homa Locking Strategy" in
+ * homa_impl.h). Instead, release the socket lock
+ * and try the entire operation again.
+ */
+ homa_sock_unlock(pool->hsk);
+ continue;
+ }
+ list_del_init(&rpc->buf_links);
+ if (list_empty(&pool->hsk->waiting_for_bufs))
+ pool->bpages_needed = INT_MAX;
+ else
+ set_bpages_needed(pool);
+ homa_sock_unlock(pool->hsk);
+ homa_pool_alloc_msg(rpc);
+ homa_rpc_unlock(rpc);
+ }
+}
+
+/**
+ * homa_pool_avail_bytes() - Return a count of the number of bytes currently
+ * unused and available for allocation in a pool.
+ * @pool: Pool of interest.
+ * Return: See above.
+ */
+u64 homa_pool_avail_bytes(struct homa_pool *pool)
+{
+ struct homa_pool_core *core;
+ struct homa_bpage *bpage;
+ u64 avail;
+ int cpu;
+
+ if (!pool->region)
+ return 0;
+ avail = atomic_read(&pool->free_bpages);
+ avail *= HOMA_BPAGE_SIZE;
+ for (cpu = 0; cpu < nr_cpu_ids; cpu++) {
+ core = per_cpu_ptr(pool->cores, cpu);
+ bpage = &pool->descriptors[core->page_hint];
+ if (bpage->owner == cpu) {
+ if (atomic_read(&bpage->refs) > 1)
+ avail += HOMA_BPAGE_SIZE - core->allocated;
+ else
+ avail += HOMA_BPAGE_SIZE;
+ }
+ }
+ return avail;
+}
diff --git a/net/homa/homa_pool.h b/net/homa/homa_pool.h
new file mode 100644
index 000000000000..5d61dd599850
--- /dev/null
+++ b/net/homa/homa_pool.h
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: BSD-2-Clause or GPL-2.0+ */
+
+/* This file contains definitions used to manage user-space buffer pools.
+ */
+
+#ifndef _HOMA_POOL_H
+#define _HOMA_POOL_H
+
+#include <linux/percpu.h>
+
+#include "homa_rpc.h"
+
+/**
+ * struct homa_bpage - Contains information about a single page in
+ * a buffer pool.
+ */
+struct homa_bpage {
+ /** @lock: to synchronize shared access. */
+ spinlock_t lock;
+
+ /**
+ * @refs: Counts number of distinct uses of this
+ * bpage (1 tick for each message that is using
+ * this page, plus an additional tick if the @owner
+ * field is set).
+ */
+ atomic_t refs;
+
+ /**
+ * @owner: kernel core that currently owns this page
+ * (< 0 if none).
+ */
+ int owner;
+
+ /**
+ * @expiration: homa_clock() time after which it's OK to steal this
+ * page from its current owner (if @refs is 1).
+ */
+ u64 expiration;
+} ____cacheline_aligned_in_smp;
+
+/**
+ * struct homa_pool_core - Holds core-specific data for a homa_pool (a bpage
+ * out of which that core is allocating small chunks).
+ */
+struct homa_pool_core {
+ /**
+ * @page_hint: Index of bpage in pool->descriptors,
+ * which may be owned by this core. If so, we'll use it
+ * for allocating partial pages.
+ */
+ int page_hint;
+
+ /**
+ * @allocated: if the page given by @page_hint is
+ * owned by this core, this variable gives the number of
+ * (initial) bytes that have already been allocated
+ * from the page.
+ */
+ int allocated;
+
+ /**
+ * @next_candidate: when searching for free bpages,
+ * check this index next.
+ */
+ int next_candidate;
+};
+
+/**
+ * struct homa_pool - Describes a pool of buffer space for incoming
+ * messages for a particular socket; managed by homa_pool.c. The pool is
+ * divided up into "bpages", which are a multiple of the hardware page size.
+ * A bpage may be owned by a particular core so that it can more efficiently
+ * allocate space for small messages.
+ */
+struct homa_pool {
+ /**
+ * @hsk: the socket that this pool belongs to.
+ */
+ struct homa_sock *hsk;
+
+ /**
+ * @region: beginning of the pool's region (in the app's virtual
+ * memory). Divided into bpages. 0 means the pool hasn't yet been
+ * initialized.
+ */
+ char __user *region;
+
+ /** @num_bpages: total number of bpages in the pool. */
+ int num_bpages;
+
+ /** @descriptors: kmalloced area containing one entry for each bpage. */
+ struct homa_bpage *descriptors;
+
+ /**
+ * @free_bpages: the number of pages still available for allocation
+ * by homa_pool_get_pages. This equals the number of pages with zero
+ * reference counts, minus the number of pages that have been claimed
+ * by homa_pool_get_pages but not yet allocated.
+ */
+ atomic_t free_bpages;
+
+ /**
+ * @bpages_needed: the number of free bpages required to satisfy the
+ * needs of the first RPC on @hsk->waiting_for_bufs, or INT_MAX if
+ * that queue is empty.
+ */
+ int bpages_needed;
+
+ /** @cores: core-specific info; dynamically allocated. */
+ struct homa_pool_core __percpu *cores;
+
+ /**
+ * @check_waiting_invoked: incremented during unit tests when
+ * homa_pool_check_waiting is invoked.
+ */
+ int check_waiting_invoked;
+};
+
+bool homa_bpage_available(struct homa_bpage *bpage, u64 now);
+struct homa_pool *homa_pool_alloc(struct homa_sock *hsk);
+int homa_pool_alloc_msg(struct homa_rpc *rpc);
+u64 homa_pool_avail_bytes(struct homa_pool *pool);
+void homa_pool_check_waiting(struct homa_pool *pool);
+void homa_pool_free(struct homa_pool *pool);
+void __user *homa_pool_get_buffer(struct homa_rpc *rpc, int offset,
+ int *available);
+int homa_pool_get_pages(struct homa_pool *pool, int num_pages,
+ u32 *pages, int leave_locked);
+void homa_pool_get_rcvbuf(struct homa_pool *pool,
+ struct homa_rcvbuf_args *args);
+int homa_pool_free_bufs(struct homa_pool *pool, int num_buffers,
+ u32 *buffers);
+int homa_pool_set_region(struct homa_sock *hsk, void __user *region,
+ u64 region_size);
+
+/**
+ * homa_pool_unlink() - Remove an RPC from any lists related to buffer
+ * pool memory, so the RPC will not be accessed by this module again.
+ * @rpc: RPC to unlink. Must be locked, and the rpc's socket must
+ * also be locked.
+ */
+static inline void homa_pool_unlink(struct homa_rpc *rpc)
+ __must_hold(rpc->bucket->lock)
+ __must_hold(rpc->hsk->lock)
+{
+ list_del_init(&rpc->buf_links);
+}
+
+/**
+ * homa_pool_release() - Invoked when RPCs are being reaped: releases
+ * pool space owned by the RPC.
+ * @rpc: RPC to clean up. Caller must ensure that no-one else can
+ * access the RPC concurrently (e.g., it is being reaped).
+ */
+static inline void homa_pool_release(struct homa_rpc *rpc)
+{
+ if (rpc->msgin.num_bpages > 0) {
+ homa_pool_free_bufs(rpc->hsk->buffer_pool,
+ rpc->msgin.num_bpages,
+ rpc->msgin.bpage_offsets);
+ rpc->msgin.num_bpages = 0;
+ }
+}
+
+#endif /* _HOMA_POOL_H */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v19 05/15] net: homa: create homa_peer.h and homa_peer.c
From: John Ousterhout @ 2026-04-28 23:15 UTC (permalink / raw)
To: netdev; +Cc: pabeni, edumazet, horms, kuba, John Ousterhout
In-Reply-To: <20260428231520.1857-1-ouster@cs.stanford.edu>
Homa needs to keep a small amount of information for each peer that
it has communicated with. These files define that state and provide
functions for storing and accessing it.
Signed-off-by: John Ousterhout <ouster@cs.stanford.edu>
---
Changes for v19:
* Bug fix: don't modify peer->flow in homa_peer_reset_dst unless the
function succeeds.
Changes for v18:
* Fix 2 synchronization issues related to reclamation
* Simplify reclamation (eliminate gc_stop_count).
Changes for v16:
* Clean up and simplify reference counting mechanism (use refcount_t
instead of atomic_t, eliminate dead_peers mechanism)
* Fix synchronization bugs in homa_dst_refresh (use RCU properly)
* Remove addr field of struct homa_peer
* Create separate header file for murmurhash hash function
Changes for v11:
* Clean up sparse annotations
Changes for v10:
* Use kzalloc instead of __GFP_ZERO
* Remove log messages after alloc errors
* Fix issues found by sparse, xmastree.py, etc.
* Add missing initialization for peertab->lock
Changes for v9:
* Add support for homa_net objects
* Implement limits on the number of active homa_peer objects. This includes
adding reference counts in homa_peers and adding code to release peers
where there are too many.
* Switch to using rhashtable to store homa_peers; the table is shared
across all network namespaces, though individual peers are namespace-
specific
* Invoke dst->ops->check in addition to checking the obsolete flag
* Various name improvements
* Remove the homa_peertab_gc_dsts mechanism, which is unnecessary
Changes for v7:
* Remove homa_peertab_get_peers
* Remove "lock_slow" functions, which don't add functionality in this
patch
* Remove unused fields from homa_peer structs
* Use u64 and __u64 properly
* Add lock annotations
* Refactor homa_peertab_get_peers
* Use __GFP_ZERO in kmalloc calls
---
net/homa/homa_peer.c | 558 +++++++++++++++++++++++++++++++++++++++++
net/homa/homa_peer.h | 303 ++++++++++++++++++++++
net/homa/murmurhash3.h | 44 ++++
3 files changed, 905 insertions(+)
create mode 100644 net/homa/homa_peer.c
create mode 100644 net/homa/homa_peer.h
create mode 100644 net/homa/murmurhash3.h
diff --git a/net/homa/homa_peer.c b/net/homa/homa_peer.c
new file mode 100644
index 000000000000..162f7d829681
--- /dev/null
+++ b/net/homa/homa_peer.c
@@ -0,0 +1,558 @@
+// SPDX-License-Identifier: BSD-2-Clause or GPL-2.0+
+
+/* This file provides functions related to homa_peer and homa_peertab
+ * objects.
+ */
+
+#include "homa_impl.h"
+#include "homa_peer.h"
+#include "homa_rpc.h"
+#include "murmurhash3.h"
+
+static const struct rhashtable_params ht_params = {
+ .key_len = sizeof(struct homa_peer_key),
+ .key_offset = offsetof(struct homa_peer, ht_key),
+ .head_offset = offsetof(struct homa_peer, ht_linkage),
+ .nelem_hint = 10000,
+ .hashfn = murmurhash3,
+ .obj_cmpfn = homa_peer_compare
+};
+
+/**
+ * homa_peer_alloc_peertab() - Allocate and initialize a homa_peertab.
+ *
+ * Return: A pointer to the new homa_peertab, or ERR_PTR(-errno) if there
+ * was a problem.
+ */
+struct homa_peertab *homa_peer_alloc_peertab(void)
+{
+ struct homa_peertab *peertab;
+ int err;
+
+ peertab = kzalloc_obj(*peertab, GFP_KERNEL);
+ if (!peertab)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_init(&peertab->lock);
+ err = rhashtable_init(&peertab->ht, &ht_params);
+ if (err) {
+ kfree(peertab);
+ return ERR_PTR(err);
+ }
+ peertab->ht_valid = true;
+ rhashtable_walk_enter(&peertab->ht, &peertab->ht_iter);
+ peertab->gc_threshold = 5000;
+ peertab->net_max = 10000;
+ peertab->idle_secs_min = 10;
+ peertab->idle_secs_max = 120;
+
+ homa_peer_update_sysctl_deps(peertab);
+ return peertab;
+}
+
+/**
+ * homa_peer_free_net() - Garbage collect all of the peer information
+ * associated with a particular network namespace.
+ * @hnet: Network namespace whose peers should be freed. There must not
+ * be any active sockets or RPCs for this namespace.
+ */
+void homa_peer_free_net(struct homa_net *hnet)
+{
+ struct homa_peertab *peertab = hnet->homa->peertab;
+ struct rhashtable_iter iter;
+ struct homa_peer *peer;
+
+ spin_lock_bh(&peertab->lock);
+ rhashtable_walk_enter(&peertab->ht, &iter);
+ rhashtable_walk_start(&iter);
+ while (1) {
+ peer = rhashtable_walk_next(&iter);
+ if (!peer)
+ break;
+ if (IS_ERR(peer))
+ continue;
+ if (peer->ht_key.hnet != hnet)
+ continue;
+ if (rhashtable_remove_fast(&peertab->ht, &peer->ht_linkage,
+ ht_params) == 0) {
+ homa_peer_release(peer);
+ hnet->num_peers--;
+ peertab->num_peers--;
+ }
+ }
+ rhashtable_walk_stop(&iter);
+ rhashtable_walk_exit(&iter);
+ WARN(hnet->num_peers != 0, "%s ended up with hnet->num_peers %d",
+ __func__, hnet->num_peers);
+ spin_unlock_bh(&peertab->lock);
+}
+
+/**
+ * homa_peer_release_fn() - This function is invoked for each entry in
+ * the peer hash table by the rhashtable code when the table is being
+ * deleted. It frees its argument.
+ * @object: homa_peer to free.
+ * @dummy: Not used.
+ */
+void homa_peer_release_fn(void *object, void *dummy)
+{
+ struct homa_peer *peer = object;
+
+ homa_peer_release(peer);
+}
+
+/**
+ * homa_peer_free_peertab() - Destructor for homa_peertabs.
+ * @peertab: The table to destroy. Caller must ensure that it will never
+ * be accessed again.
+ */
+void homa_peer_free_peertab(struct homa_peertab *peertab)
+{
+ if (peertab->ht_valid) {
+ rhashtable_walk_exit(&peertab->ht_iter);
+ rhashtable_free_and_destroy(&peertab->ht, homa_peer_release_fn,
+ NULL);
+ }
+ kfree(peertab);
+}
+
+/**
+ * homa_peer_prefer_evict() - Given two peers, determine which one is
+ * a better candidate for eviction.
+ * @peertab: Overall information used to manage peers.
+ * @peer1: First peer.
+ * @peer2: Second peer.
+ * Return: True if @peer1 is a better candidate for eviction than @peer2.
+ */
+int homa_peer_prefer_evict(struct homa_peertab *peertab,
+ struct homa_peer *peer1,
+ struct homa_peer *peer2)
+{
+ /* Prefer a peer whose homa-net is over its limit; if both are either
+ * over or under, then prefer the peer with the longest idle time.
+ */
+ if (peer1->ht_key.hnet->num_peers > peertab->net_max) {
+ if (peer2->ht_key.hnet->num_peers <= peertab->net_max)
+ return true;
+ else
+ return peer1->access_jiffies < peer2->access_jiffies;
+ }
+ if (peer2->ht_key.hnet->num_peers > peertab->net_max)
+ return false;
+ else
+ return peer1->access_jiffies < peer2->access_jiffies;
+}
+
+/**
+ * homa_peer_pick_victims() - Select a few peers that can be freed.
+ * @peertab: Choose peers that are stored here.
+ * @victims: Return addresses of victims here.
+ * @max_victims: Limit on how many victims to choose (and size of @victims
+ * array).
+ * Return: The number of peers stored in @victims; may be zero.
+ */
+int homa_peer_pick_victims(struct homa_peertab *peertab,
+ struct homa_peer *victims[], int max_victims)
+{
+ struct homa_peer *peer;
+ int num_victims = 0;
+ int to_scan;
+ int i, idle;
+
+ /* Scan 2 peers for every potential victim and keep the "best"
+ * peers for removal.
+ */
+ rhashtable_walk_start(&peertab->ht_iter);
+ for (to_scan = 2 * max_victims; to_scan > 0; to_scan--) {
+ peer = rhashtable_walk_next(&peertab->ht_iter);
+ if (!peer) {
+ /* Reached the end of the table; restart at
+ * the beginning.
+ */
+ rhashtable_walk_stop(&peertab->ht_iter);
+ rhashtable_walk_exit(&peertab->ht_iter);
+ rhashtable_walk_enter(&peertab->ht, &peertab->ht_iter);
+ rhashtable_walk_start(&peertab->ht_iter);
+ peer = rhashtable_walk_next(&peertab->ht_iter);
+ if (!peer)
+ break;
+ }
+ if (IS_ERR(peer)) {
+ /* rhashtable decided to restart the search at the
+ * beginning.
+ */
+ peer = rhashtable_walk_next(&peertab->ht_iter);
+ if (!peer || IS_ERR(peer))
+ break;
+ }
+
+ /* Has this peer been idle long enough to be candidate for
+ * eviction?
+ */
+ idle = jiffies - peer->access_jiffies;
+ if (idle < peertab->idle_jiffies_min)
+ continue;
+ if (idle < peertab->idle_jiffies_max &&
+ peer->ht_key.hnet->num_peers <= peertab->net_max)
+ continue;
+
+ /* Sort the candidate into the existing list of victims. */
+ for (i = 0; i < num_victims; i++) {
+ if (peer == victims[i]) {
+ /* This can happen if there aren't very many
+ * peers and we wrapped around in the hash
+ * table.
+ */
+ peer = NULL;
+ break;
+ }
+ if (homa_peer_prefer_evict(peertab, peer, victims[i]))
+ swap(peer, victims[i]);
+ }
+
+ if (num_victims < max_victims && peer) {
+ victims[num_victims] = peer;
+ num_victims++;
+ }
+ }
+ rhashtable_walk_stop(&peertab->ht_iter);
+ return num_victims;
+}
+
+/**
+ * homa_peer_gc() - This function is invoked by Homa at regular intervals;
+ * its job is to ensure that the number of peers stays within limits.
+ * If the number grows too large, it selectively deletes peers to get
+ * back under the limit.
+ * @peertab: Structure whose peers should be considered for garbage
+ * collection.
+ */
+void homa_peer_gc(struct homa_peertab *peertab)
+{
+#define EVICT_BATCH_SIZE 5
+ struct homa_peer *victims[EVICT_BATCH_SIZE];
+ int num_victims;
+ int i;
+
+ spin_lock_bh(&peertab->lock);
+ if (peertab->num_peers < peertab->gc_threshold)
+ goto done;
+ num_victims = homa_peer_pick_victims(peertab, victims,
+ EVICT_BATCH_SIZE);
+ if (num_victims == 0)
+ goto done;
+
+ for (i = 0; i < num_victims; i++) {
+ struct homa_peer *peer = victims[i];
+
+ if (rhashtable_remove_fast(&peertab->ht, &peer->ht_linkage,
+ ht_params) == 0) {
+ peertab->num_peers--;
+ peer->ht_key.hnet->num_peers--;
+ homa_peer_release(peer);
+ }
+ }
+done:
+ spin_unlock_bh(&peertab->lock);
+}
+
+/**
+ * homa_peer_alloc() - Allocate and initialize a new homa_peer object.
+ * @hsk: Socket for which the peer will be used.
+ * @addr: Address of the desired host: IPv4 addresses are represented
+ * as IPv4-mapped IPv6 addresses.
+ * Return: The peer associated with @addr, or a negative errno if an
+ * error occurred. On a successful return the reference count
+ * will be incremented for the returned peer. Sets hsk->error_msg
+ * on errors.
+ */
+struct homa_peer *homa_peer_alloc(struct homa_sock *hsk,
+ const struct in6_addr *addr)
+{
+ struct homa_peer *peer;
+ int status;
+
+ peer = kzalloc_obj(*peer, GFP_ATOMIC);
+ if (!peer) {
+ hsk->error_msg = "couldn't allocate memory for homa_peer";
+ return (struct homa_peer *)ERR_PTR(-ENOMEM);
+ }
+ peer->ht_key.addr = *addr;
+ peer->ht_key.hnet = hsk->hnet;
+ refcount_set(&peer->refs, 1);
+ peer->access_jiffies = jiffies;
+ spin_lock_init(&peer->lock);
+ peer->current_ticks = -1;
+
+ status = homa_peer_reset_dst(peer, hsk);
+ if (status != 0) {
+ hsk->error_msg = "couldn't find route for peer";
+ kfree(peer);
+ return ERR_PTR(status);
+ }
+ return peer;
+}
+
+/**
+ * homa_peer_free() - Release any resources in a peer and free the homa_peer
+ * struct. Invoked by the RCU mechanism via homa_peer_release.
+ * @head: Pointer to the rcu_head field of the peer to free.
+ */
+void homa_peer_free(struct rcu_head *head)
+{
+ struct homa_peer *peer;
+
+ peer = container_of(head, struct homa_peer, rcu_head);
+ dst_release(rcu_dereference_protected(peer->dst, 1));
+ kfree(peer);
+}
+
+/**
+ * homa_peer_get() - Returns the peer associated with a given host; creates
+ * a new homa_peer if one doesn't already exist.
+ * @hsk: Socket where the peer will be used.
+ * @addr: Address of the desired host: IPv4 addresses are represented
+ * as IPv4-mapped IPv6 addresses.
+ *
+ * Return: The peer associated with @addr, or a negative errno if an
+ * error occurred. On a successful return the reference count
+ * will be incremented for the returned peer. The caller must
+ * eventually call homa_peer_release to release the reference.
+ */
+struct homa_peer *homa_peer_get(struct homa_sock *hsk,
+ const struct in6_addr *addr)
+{
+ struct homa_peertab *peertab = hsk->homa->peertab;
+ struct homa_peer *peer, *other;
+ struct homa_peer_key key;
+
+ key.addr = *addr;
+ key.hnet = hsk->hnet;
+ rcu_read_lock();
+ peer = rhashtable_lookup(&peertab->ht, &key, ht_params);
+ if (peer && refcount_inc_not_zero(&peer->refs)) {
+ peer->access_jiffies = jiffies;
+ rcu_read_unlock();
+ return peer;
+ }
+
+ /* No existing entry, so we have to create a new one. */
+ peer = homa_peer_alloc(hsk, addr);
+ if (IS_ERR(peer)) {
+ rcu_read_unlock();
+ return peer;
+ }
+ spin_lock_bh(&peertab->lock);
+ other = rhashtable_lookup_get_insert_fast(&peertab->ht,
+ &peer->ht_linkage, ht_params);
+ if (IS_ERR(other)) {
+ /* Couldn't insert; return the error info. */
+ homa_peer_release(peer);
+ peer = other;
+ } else if (other) {
+ /* Someone else already created the desired peer; use that
+ * one instead of ours.
+ */
+ homa_peer_release(peer);
+ refcount_inc(&other->refs);
+ peer = other;
+ peer->access_jiffies = jiffies;
+ } else {
+ refcount_inc(&peer->refs);
+ peertab->num_peers++;
+ key.hnet->num_peers++;
+ }
+ spin_unlock_bh(&peertab->lock);
+ rcu_read_unlock();
+ return peer;
+}
+
+/**
+ * homa_get_dst() - Returns destination information associated with a peer,
+ * updating it if the cached information is stale.
+ * @peer: Peer whose destination information is desired.
+ * @hsk: Homa socket with which the dst will be used; needed by lower-level
+ * code to recreate the dst.
+ * Return: Up-to-date destination for peer; a reference has been taken
+ * on this dst_entry, which the caller must eventually release.
+ */
+struct dst_entry *homa_get_dst(struct homa_peer *peer, struct homa_sock *hsk)
+{
+ struct dst_entry *dst;
+ int pass;
+
+ rcu_read_lock();
+ for (pass = 0; ; pass++) {
+ do {
+ /* This loop repeats only if we happen to fetch
+ * the dst right when it is being reset.
+ */
+ dst = rcu_dereference(peer->dst);
+ } while (!dst_hold_safe(dst));
+
+ /* After the first pass it's OK to return an obsolete dst
+ * (we're basically giving up; continuing could result in
+ * an infinite loop if homa_dst_refresh can't create a new dst).
+ */
+ if (dst_check(dst, peer->dst_cookie) || pass > 0)
+ break;
+ dst_release(dst);
+ homa_peer_reset_dst(peer, hsk);
+ }
+ rcu_read_unlock();
+
+ /* This code is needed to handle situations where the same peer
+ * is used by multiple sockets, some of which use TCP hijacking
+ * and some of which don't (e.g. the peer is created for a socket
+ * without hijacking, then hijacking is enabled and a new socket
+ * uses the same peer). flowi_proto determines the IP protocol
+ * that will be stored in IP headers for IPv6; sk_protocol is
+ * IPPROTO_TCP if hijacking is being used, IPPROTO_HOMA if not.
+ */
+ peer->flow.flowi_proto = hsk->sock.sk_protocol;
+ return dst;
+}
+
+/**
+ * homa_peer_reset_dst() - Find an appropriate dst_entry for a peer and
+ * store it in the peer's dst field. If the field is already set, the
+ * current value is assumed to be stale and will be discarded if a new
+ * dst_entry can be created.
+ * @peer: The peer whose dst field should be reset.
+ * @hsk: Socket that will be used for sending packets.
+ * Return: Zero for success, or a negative errno if there was an error
+ * (in which case peer is unmodified).
+ */
+int homa_peer_reset_dst(struct homa_peer *peer, struct homa_sock *hsk)
+{
+ struct dst_entry *dst;
+ struct flowi flow;
+ int result = 0;
+
+ homa_peer_lock(peer);
+ memset(&flow, 0, sizeof(flow));
+ if (hsk->sock.sk_family == AF_INET) {
+ struct rtable *rt;
+
+ flowi4_init_output(&flow.u.ip4, hsk->sock.sk_bound_dev_if,
+ hsk->sock.sk_mark, hsk->inet.tos,
+ RT_SCOPE_UNIVERSE, hsk->sock.sk_protocol, 0,
+ ipv6_to_ipv4(peer->addr),
+ hsk->inet.inet_saddr, 0, 0,
+ hsk->sock.sk_uid);
+ security_sk_classify_flow(&hsk->sock,
+ &flow.u.__fl_common);
+ rt = ip_route_output_flow(sock_net(&hsk->sock),
+ &flow.u.ip4, &hsk->sock);
+ if (IS_ERR(rt)) {
+ result = PTR_ERR(rt);
+ goto done;
+ }
+ dst = &rt->dst;
+ peer->dst_cookie = 0;
+ } else {
+ /* This code is derived from code in tcp_v6_connect. */
+ flow.u.ip6.flowi6_proto = hsk->sock.sk_protocol;
+ flow.u.ip6.daddr = peer->addr;
+ flow.u.ip6.saddr = hsk->inet.pinet6->saddr;
+ flow.u.ip6.flowlabel = ip6_make_flowinfo(hsk->inet.tos, 0);
+ flow.u.ip6.flowi6_oif = hsk->sock.sk_bound_dev_if;
+ flow.u.ip6.flowi6_mark = hsk->sock.sk_mark;
+ flow.u.ip6.fl6_dport = 0;
+ flow.u.ip6.fl6_sport = 0;
+ flow.u.ip6.flowi6_uid = hsk->sock.sk_uid;
+ security_sk_classify_flow(&hsk->sock,
+ &flow.u.__fl_common);
+ dst = ip6_dst_lookup_flow(sock_net(&hsk->sock), &hsk->sock,
+ &flow.u.ip6, NULL);
+ if (IS_ERR(dst)) {
+ result = PTR_ERR(dst);
+ goto done;
+ }
+ peer->dst_cookie = rt6_get_cookie(dst_rt6_info(dst));
+ }
+ memcpy(&peer->flow, &flow, sizeof(flow));
+
+ /* From the standpoint of homa_get_dst, peer->dst is not updated
+ * atomically with peer->dst_cookie, which means homa_get_dst could
+ * use a new cookie with an old dest. Fortunately, this is benign; at
+ * worst, it might cause an obsolete dst to be reused (resulting in
+ * a lost packet) or a valid dst to be replaced (resulting in
+ * unnecessary work).
+ */
+ dst_release(rcu_replace_pointer(peer->dst, dst, true));
+
+done:
+ homa_peer_unlock(peer);
+ return result;
+}
+
+/**
+ * homa_peer_add_ack() - Add a given RPC to the list of unacked
+ * RPCs for its server. Once this method has been invoked, it's safe
+ * to delete the RPC, since it will eventually be acked to the server.
+ * @rpc: Client RPC that has now completed.
+ */
+void homa_peer_add_ack(struct homa_rpc *rpc)
+{
+ struct homa_peer *peer = rpc->peer;
+ struct homa_ack_hdr ack;
+
+ homa_peer_lock(peer);
+ if (peer->num_acks < HOMA_MAX_ACKS_PER_PKT) {
+ peer->acks[peer->num_acks].client_id = cpu_to_be64(rpc->id);
+ peer->acks[peer->num_acks].server_port = htons(rpc->dport);
+ peer->num_acks++;
+ homa_peer_unlock(peer);
+ return;
+ }
+
+ /* The peer has filled up; send an ACK message to empty it. The
+ * RPC in the message header will also be considered ACKed.
+ */
+ memcpy(ack.acks, peer->acks, sizeof(peer->acks));
+ ack.num_acks = htons(peer->num_acks);
+ peer->num_acks = 0;
+ homa_peer_unlock(peer);
+ homa_xmit_control(ACK, &ack, sizeof(ack), rpc);
+}
+
+/**
+ * homa_peer_get_acks() - Copy acks out of a peer, and remove them from the
+ * peer.
+ * @peer: Peer to check for possible unacked RPCs.
+ * @count: Maximum number of acks to return.
+ * @dst: The acks are copied to this location.
+ *
+ * Return: The number of acks extracted from the peer (<= count).
+ */
+int homa_peer_get_acks(struct homa_peer *peer, int count, struct homa_ack *dst)
+{
+ /* Don't waste time acquiring the lock if there are no ids available. */
+ if (peer->num_acks == 0)
+ return 0;
+
+ homa_peer_lock(peer);
+
+ if (count > peer->num_acks)
+ count = peer->num_acks;
+ memcpy(dst, &peer->acks[peer->num_acks - count],
+ count * sizeof(peer->acks[0]));
+ peer->num_acks -= count;
+
+ homa_peer_unlock(peer);
+ return count;
+}
+
+/**
+ * homa_peer_update_sysctl_deps() - Update any peertab fields that depend
+ * on values set by sysctl. This function is invoked anytime a peer sysctl
+ * value is updated.
+ * @peertab: Struct to update.
+ */
+void homa_peer_update_sysctl_deps(struct homa_peertab *peertab)
+{
+ peertab->idle_jiffies_min = peertab->idle_secs_min * HZ;
+ peertab->idle_jiffies_max = peertab->idle_secs_max * HZ;
+}
+
diff --git a/net/homa/homa_peer.h b/net/homa/homa_peer.h
new file mode 100644
index 000000000000..c6af84abf2b9
--- /dev/null
+++ b/net/homa/homa_peer.h
@@ -0,0 +1,303 @@
+/* SPDX-License-Identifier: BSD-2-Clause or GPL-2.0+ */
+
+/* This file contains definitions related to managing peers (homa_peer
+ * and homa_peertab).
+ */
+
+#ifndef _HOMA_PEER_H
+#define _HOMA_PEER_H
+
+#include "homa_wire.h"
+#include "homa_sock.h"
+
+#include <linux/rhashtable.h>
+
+struct homa_rpc;
+
+/**
+ * struct homa_peertab - Stores homa_peer objects, indexed by IPv6
+ * address. There is one of these per struct homa.
+ */
+struct homa_peertab {
+ /**
+ * @lock: Used to synchronize updates to @ht as well as other
+ * operations on this object.
+ */
+ spinlock_t lock;
+
+ /** @ht: Hash table that stores all struct peers. */
+ struct rhashtable ht;
+
+ /** @ht_iter: Used to scan ht to find peers to garbage collect. */
+ struct rhashtable_iter ht_iter;
+
+ /** @num_peers: Total number of peers currently in @ht. */
+ int num_peers;
+
+ /**
+ * @ht_valid: True means ht and ht_iter have been initialized and must
+ * eventually be destroyed.
+ */
+ bool ht_valid;
+
+ /** @rcu_head: Holds state of a pending call_rcu invocation. */
+ struct rcu_head rcu_head;
+
+ /**
+ * @gc_stop_count: Nonzero means that peer garbage collection
+ * should not be performed (conflicting state changes are underway).
+ */
+ int gc_stop_count;
+
+ /**
+ * @gc_threshold: If @num_peers is less than this, don't bother
+ * doing any peer garbage collection. Set externally via sysctl.
+ */
+ int gc_threshold;
+
+ /**
+ * @net_max: If the number of peers for a homa_net exceeds this number,
+ * work aggressively to reclaim peers for that homa_net. Set
+ * externally via sysctl.
+ */
+ int net_max;
+
+ /**
+ * @idle_secs_min: A peer will not be considered for garbage collection
+ * under any circumstances if it has been idle less than this many
+ * seconds. Set externally via sysctl.
+ */
+ int idle_secs_min;
+
+ /**
+ * @idle_jiffies_min: Same as idle_secs_min except in units
+ * of jiffies.
+ */
+ unsigned long idle_jiffies_min;
+
+ /**
+ * @idle_secs_max: A peer that has been idle for less than
+ * this many seconds will not be considered for garbage collection
+ * unless its homa_net has more than @net_threshold peers. Set
+ * externally via sysctl.
+ */
+ int idle_secs_max;
+
+ /**
+ * @idle_jiffies_max: Same as idle_secs_max except in units
+ * of jiffies.
+ */
+ unsigned long idle_jiffies_max;
+
+};
+
+/**
+ * struct homa_peer_key - Used to look up homa_peer structs in an rhashtable.
+ */
+struct homa_peer_key {
+ /**
+ * @addr: Address of the desired host. IPv4 addresses are represented
+ * with IPv4-mapped IPv6 addresses. Must be the first variable in
+ * the struct, because of union in homa_peer.
+ */
+ struct in6_addr addr;
+
+ /** @hnet: The network namespace in which this peer is valid. */
+ struct homa_net *hnet;
+};
+
+/**
+ * struct homa_peer - One of these objects exists for each machine that we
+ * have communicated with (either as client or server).
+ */
+struct homa_peer {
+ union {
+ /**
+ * @addr: IPv6 address for the machine (IPv4 addresses are
+ * stored as IPv4-mapped IPv6 addresses).
+ */
+ struct in6_addr addr;
+
+ /** @ht_key: The hash table key for this peer in peertab->ht. */
+ struct homa_peer_key ht_key;
+ };
+
+ /**
+ * @refs: Number of outstanding references to this peer. Includes
+ * one reference for the entry in peertab->ht, plus one for each
+ * call to homa_peer_get that has not been canceled by a call to
+ * homa_peer_release; the peer gets freed when this value becomes
+ * zero.
+ */
+ refcount_t refs;
+
+ /**
+ * @access_jiffies: Time in jiffies of most recent access to this
+ * peer.
+ */
+ unsigned long access_jiffies;
+
+ /**
+ * @ht_linkage: Used by rashtable implement to link this peer into
+ * peertab->ht.
+ */
+ struct rhash_head ht_linkage;
+
+ /**
+ * @lock: used to synchronize access to fields in this struct, such
+ * as @num_acks, @acks, @dst, and @dst_cookie.
+ */
+ spinlock_t lock ____cacheline_aligned_in_smp;
+
+ /**
+ * @num_acks: the number of (initial) entries in @acks that
+ * currently hold valid information.
+ */
+ int num_acks;
+
+ /**
+ * @acks: info about client RPCs whose results have been completely
+ * received.
+ */
+ struct homa_ack acks[HOMA_MAX_ACKS_PER_PKT];
+
+ /**
+ * @dst: Used to route packets to this peer; this object owns a
+ * reference that must eventually be released.
+ */
+ struct dst_entry __rcu *dst;
+
+ /**
+ * @dst_cookie: Used to check whether dst is still valid. This is
+ * accessed without synchronization, which is racy, but the worst
+ * that can happen is using an obsolete dst.
+ */
+ u32 dst_cookie;
+
+ /**
+ * @flow: Addressing info used to create @dst and also required
+ * when transmitting packets.
+ */
+ struct flowi flow;
+
+ /**
+ * @outstanding_resends: the number of resend requests we have
+ * sent to this server (spaced @homa.resend_interval apart) since
+ * we received a packet from this peer.
+ */
+ int outstanding_resends;
+
+ /**
+ * @most_recent_resend: @homa->timer_ticks when the most recent
+ * resend was sent to this peer.
+ */
+ int most_recent_resend;
+
+ /**
+ * @least_recent_rpc: of all the RPCs for this peer scanned at
+ * @current_ticks, this is the RPC whose @resend_timer_ticks
+ * is farthest in the past.
+ */
+ struct homa_rpc *least_recent_rpc;
+
+ /**
+ * @least_recent_ticks: the @resend_timer_ticks value for
+ * @least_recent_rpc.
+ */
+ u32 least_recent_ticks;
+
+ /**
+ * @current_ticks: the value of @homa->timer_ticks the last time
+ * that @least_recent_rpc and @least_recent_ticks were computed.
+ * Used to detect the start of a new homa_timer pass.
+ */
+ u32 current_ticks;
+
+ /**
+ * @resend_rpc: the value of @least_recent_rpc computed in the
+ * previous homa_timer pass. This RPC will be issued a RESEND
+ * in the current pass, if it still needs one.
+ */
+ struct homa_rpc *resend_rpc;
+
+ /** @rcu_head: Holds state of a pending call_rcu invocation. */
+ struct rcu_head rcu_head;
+};
+
+void homa_dst_refresh(struct homa_peertab *peertab,
+ struct homa_peer *peer, struct homa_sock *hsk);
+struct dst_entry
+ *homa_get_dst(struct homa_peer *peer, struct homa_sock *hsk);
+void homa_peer_add_ack(struct homa_rpc *rpc);
+struct homa_peer
+ *homa_peer_alloc(struct homa_sock *hsk, const struct in6_addr *addr);
+struct homa_peertab
+ *homa_peer_alloc_peertab(void);
+int homa_peer_dointvec(const struct ctl_table *table, int write,
+ void *buffer, size_t *lenp, loff_t *ppos);
+void homa_peer_free(struct rcu_head *head);
+void homa_peer_free_net(struct homa_net *hnet);
+void homa_peer_free_peertab(struct homa_peertab *peertab);
+void homa_peer_gc(struct homa_peertab *peertab);
+struct homa_peer
+ *homa_peer_get(struct homa_sock *hsk, const struct in6_addr *addr);
+int homa_peer_get_acks(struct homa_peer *peer, int count,
+ struct homa_ack *dst);
+int homa_peer_pick_victims(struct homa_peertab *peertab,
+ struct homa_peer *victims[], int max_victims);
+int homa_peer_prefer_evict(struct homa_peertab *peertab,
+ struct homa_peer *peer1,
+ struct homa_peer *peer2);
+void homa_peer_release_fn(void *object, void *dummy);
+int homa_peer_reset_dst(struct homa_peer *peer, struct homa_sock *hsk);
+void homa_peer_update_sysctl_deps(struct homa_peertab *peertab);
+
+/**
+ * homa_peer_lock() - Acquire the lock for a peer.
+ * @peer: Peer to lock.
+ */
+static inline void homa_peer_lock(struct homa_peer *peer)
+ __acquires(peer->lock)
+{
+ spin_lock_bh(&peer->lock);
+}
+
+/**
+ * homa_peer_unlock() - Release the lock for a peer.
+ * @peer: Peer to lock.
+ */
+static inline void homa_peer_unlock(struct homa_peer *peer)
+ __releases(peer->lock)
+{
+ spin_unlock_bh(&peer->lock);
+}
+
+/**
+ * homa_peer_release() - Release a reference on a peer (cancels the effect of
+ * a previous call to homa_peer_hold). If the reference count becomes zero
+ * then the peer may be deleted at any time.
+ * @peer: Object to release.
+ */
+static inline void homa_peer_release(struct homa_peer *peer)
+{
+ if (refcount_dec_and_test(&peer->refs))
+ call_rcu(&peer->rcu_head, homa_peer_free);
+}
+
+/**
+ * homa_peer_compare() - Comparison function for entries in @peertab->ht.
+ * @arg: Contains one of the keys to compare.
+ * @obj: homa_peer object containing the other key to compare.
+ * Return: 0 means the keys match, 1 means mismatch.
+ */
+static inline int homa_peer_compare(struct rhashtable_compare_arg *arg,
+ const void *obj)
+{
+ const struct homa_peer_key *key = arg->key;
+ const struct homa_peer *peer = obj;
+
+ return !(ipv6_addr_equal(&key->addr, &peer->ht_key.addr) &&
+ peer->ht_key.hnet == key->hnet);
+}
+
+#endif /* _HOMA_PEER_H */
diff --git a/net/homa/murmurhash3.h b/net/homa/murmurhash3.h
new file mode 100644
index 000000000000..1ed1f0b67a93
--- /dev/null
+++ b/net/homa/murmurhash3.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-2-Clause or GPL-2.0+ */
+
+/* This file contains a limited implementation of MurmurHash3; it is
+ * used for rhashtables instead of the default jhash because it is
+ * faster (25 ns. vs. 40 ns as of May 2025)
+ */
+
+/**
+ * murmurhash3() - Hash function.
+ * @data: Pointer to key for which a hash is desired.
+ * @len: Length of the key; must be a multiple of 4.
+ * @seed: Seed for the hash.
+ * Return: A 32-bit hash value for the given key.
+ */
+static inline u32 murmurhash3(const void *data, u32 len, u32 seed)
+{
+ const u32 c1 = 0xcc9e2d51;
+ const u32 c2 = 0x1b873593;
+ const u32 *key = data;
+ u32 h = seed;
+
+ len = len >> 2;
+ for (size_t i = 0; i < len; i++) {
+ u32 k = key[i];
+
+ k *= c1;
+ k = (k << 15) | (k >> (32 - 15));
+ k *= c2;
+
+ h ^= k;
+ h = (h << 13) | (h >> (32 - 13));
+ h = h * 5 + 0xe6546b64;
+ }
+
+ /* Total number of input bytes */
+ h ^= len * 4;
+
+ h ^= h >> 16;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+ return h;
+}
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v19 06/15] net: homa: create homa_sock.h and homa_sock.c
From: John Ousterhout @ 2026-04-28 23:15 UTC (permalink / raw)
To: netdev; +Cc: pabeni, edumazet, horms, kuba, John Ousterhout
In-Reply-To: <20260428231520.1857-1-ouster@cs.stanford.edu>
These files provide functions for managing the state that Homa keeps
for each open Homa socket.
Signed-off-by: John Ousterhout <ouster@cs.stanford.edu>
---
Changes for v19:
* Use a flag bit in homa_sock instead of SOCK_NOSPACE in struct socket
(this is needed because hsk->sock.sk_socket can become NULL at
inconvenient times).
* Fix bug with initialization order in homa_sock_init.
Changes for v16:
* Add error_msg field to struct homa_sock (for HOMAIOCINFO)
* Acquire RCU read lock in homa_sock_wakeup_wmem for safety
* Refactor homa_sock_init to reduce time in atomic context
Changes for v11:
* Clean up sparse annotations
Changes for v10:
* Revise sparse annotations to eliminate __context__ definition
* Replace __u16 with u16, __u8 with u8, etc.
* Use the destroy function from struct proto properly (fixes races in
socket cleanup)
Changes for v9:
* Add support for homa_net objects; there is now a single socket table shared
across all network namespaces
* Set SOCK_RCU_FREE in homa_sock_init, not homa_sock_shutdown
* Various name improvements (e.g. use "alloc" instead of "new" for functions
that allocate memory)
Changes for v8:
* Update for new homa_pool APIs
Changes for v7:
* Refactor homa_sock_start_scan etc. (take a reference on the socket, so
homa_socktab::active_scans and struct homa_socktab_links are no longer
needed; encapsulate RCU usage entirely in homa_sock.c).
* Add functions for tx memory accounting
* Refactor waiting mechanism for incoming messages
* Add hsk->is_server, setsockopt SO_HOMA_SERVER
* Remove "lock_slow" functions, which don't add functionality in this
patch series
* Remove locker argument from locking functions
* Use u64 and __u64 properly
* Take a reference to the socket in homa_sock_find
---
net/homa/homa_sock.c | 448 +++++++++++++++++++++++++++++++++++++++++++
net/homa/homa_sock.h | 446 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 894 insertions(+)
create mode 100644 net/homa/homa_sock.c
create mode 100644 net/homa/homa_sock.h
diff --git a/net/homa/homa_sock.c b/net/homa/homa_sock.c
new file mode 100644
index 000000000000..153511b72f68
--- /dev/null
+++ b/net/homa/homa_sock.c
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier: BSD-2-Clause or GPL-2.0+
+
+/* This file manages homa_sock and homa_socktab objects. */
+
+#include "homa_impl.h"
+#include "homa_interest.h"
+#include "homa_peer.h"
+#include "homa_pool.h"
+
+/**
+ * homa_socktab_init() - Constructor for homa_socktabs.
+ * @socktab: The object to initialize; previous contents are discarded.
+ */
+void homa_socktab_init(struct homa_socktab *socktab)
+{
+ int i;
+
+ spin_lock_init(&socktab->write_lock);
+ for (i = 0; i < HOMA_SOCKTAB_BUCKETS; i++)
+ INIT_HLIST_HEAD(&socktab->buckets[i]);
+}
+
+/**
+ * homa_socktab_destroy() - Destructor for homa_socktabs: deletes all
+ * existing sockets.
+ * @socktab: The object to destroy.
+ * @hnet: If non-NULL, only sockets for this namespace are deleted.
+ */
+void homa_socktab_destroy(struct homa_socktab *socktab, struct homa_net *hnet)
+{
+ struct homa_socktab_scan scan;
+ struct homa_sock *hsk;
+
+ for (hsk = homa_socktab_start_scan(socktab, &scan); hsk;
+ hsk = homa_socktab_next(&scan)) {
+ if (hnet && hnet != hsk->hnet)
+ continue;
+
+ /* In actual use there should be no sockets left when this
+ * function is invoked, so the code below will never be
+ * invoked. However, it is useful during unit tests.
+ */
+ homa_sock_shutdown(hsk);
+ homa_sock_destroy(&hsk->sock);
+ }
+ homa_socktab_end_scan(&scan);
+}
+
+/**
+ * homa_socktab_start_scan() - Begin an iteration over all of the sockets
+ * in a socktab.
+ * @socktab: Socktab to scan.
+ * @scan: Will hold the current state of the scan; any existing
+ * contents are discarded. The caller must eventually pass this
+ * to homa_socktab_end_scan.
+ *
+ * Return: The first socket in the table, or NULL if the table is
+ * empty. If non-NULL, a reference is held on the socket to
+ * prevent its deletion.
+ *
+ * Each call to homa_socktab_next will return the next socket in the table.
+ * All sockets that are present in the table at the time this function is
+ * invoked will eventually be returned, as long as they are not removed
+ * from the table. It is safe to remove sockets from the table while the
+ * scan is in progress. If a socket is removed from the table during the scan,
+ * it may or may not be returned by homa_socktab_next. New entries added
+ * during the scan may or may not be returned.
+ */
+struct homa_sock *homa_socktab_start_scan(struct homa_socktab *socktab,
+ struct homa_socktab_scan *scan)
+{
+ scan->socktab = socktab;
+ scan->hsk = NULL;
+ scan->current_bucket = -1;
+
+ return homa_socktab_next(scan);
+}
+
+/**
+ * homa_socktab_next() - Return the next socket in an iteration over a socktab.
+ * @scan: State of the scan.
+ *
+ * Return: The next socket in the table, or NULL if the iteration has
+ * returned all of the sockets in the table. If non-NULL, a
+ * reference is held on the socket to prevent its deletion.
+ * Sockets are not returned in any particular order. It's
+ * possible that the returned socket has been destroyed.
+ */
+struct homa_sock *homa_socktab_next(struct homa_socktab_scan *scan)
+{
+ struct hlist_head *bucket;
+ struct hlist_node *next;
+
+ rcu_read_lock();
+ if (scan->hsk) {
+ sock_put(&scan->hsk->sock);
+ next = rcu_dereference(hlist_next_rcu(&scan->hsk->socktab_links));
+ if (next)
+ goto success;
+ }
+ for (scan->current_bucket++;
+ scan->current_bucket < HOMA_SOCKTAB_BUCKETS;
+ scan->current_bucket++) {
+ bucket = &scan->socktab->buckets[scan->current_bucket];
+ next = rcu_dereference(hlist_first_rcu(bucket));
+ if (next)
+ goto success;
+ }
+ scan->hsk = NULL;
+ rcu_read_unlock();
+ return NULL;
+
+success:
+ scan->hsk = hlist_entry(next, struct homa_sock, socktab_links);
+ sock_hold(&scan->hsk->sock);
+ rcu_read_unlock();
+ return scan->hsk;
+}
+
+/**
+ * homa_socktab_end_scan() - Must be invoked on completion of each scan
+ * to clean up state associated with the scan.
+ * @scan: State of the scan.
+ */
+void homa_socktab_end_scan(struct homa_socktab_scan *scan)
+{
+ if (scan->hsk) {
+ sock_put(&scan->hsk->sock);
+ scan->hsk = NULL;
+ }
+}
+
+/**
+ * homa_sock_init() - Constructor for homa_sock objects. This function
+ * handles Homa-specific initialization.
+ * @hsk: Object to initialize. The Homa-specific parts must have been
+ * initialized to zeroes by the caller.
+ *
+ * Return: 0 for success, otherwise a negative errno.
+ */
+int homa_sock_init(struct homa_sock *hsk)
+{
+ struct homa_pool *buffer_pool;
+ struct homa_socktab *socktab;
+ struct homa_sock *other;
+ struct homa_net *hnet;
+ struct homa *homa;
+ int starting_port;
+ int result = 0;
+ int i;
+
+ hnet = (struct homa_net *)net_generic(sock_net(&hsk->sock),
+ homa_net_id);
+ homa = hnet->homa;
+ socktab = homa->socktab;
+
+ /* Do things requiring memory allocation before locking the socket,
+ * so that GFP_ATOMIC is not needed.
+ */
+ buffer_pool = homa_pool_alloc(hsk);
+ if (IS_ERR(buffer_pool))
+ return PTR_ERR(buffer_pool);
+
+ /* Initialize the fields private to Homa. We can initialize
+ * everything except the port and hash table links without acquiring
+ * the socket table lock.
+ */
+ hsk->homa = homa;
+ hsk->hnet = hnet;
+ hsk->buffer_pool = buffer_pool;
+
+ hsk->is_server = false;
+ hsk->shutdown = false;
+ hsk->ip_header_length = (hsk->inet.sk.sk_family == AF_INET) ?
+ sizeof(struct iphdr) : sizeof(struct ipv6hdr);
+ spin_lock_init(&hsk->lock);
+ atomic_set(&hsk->protect_count, 0);
+ INIT_LIST_HEAD(&hsk->active_rpcs);
+ INIT_LIST_HEAD(&hsk->dead_rpcs);
+ hsk->dead_skbs = 0;
+ INIT_LIST_HEAD(&hsk->waiting_for_bufs);
+ INIT_LIST_HEAD(&hsk->ready_rpcs);
+ INIT_LIST_HEAD(&hsk->interests);
+ for (i = 0; i < HOMA_CLIENT_RPC_BUCKETS; i++) {
+ struct homa_rpc_bucket *bucket = &hsk->client_rpc_buckets[i];
+
+ spin_lock_init(&bucket->lock);
+ bucket->id = i;
+ INIT_HLIST_HEAD(&bucket->rpcs);
+ }
+ for (i = 0; i < HOMA_SERVER_RPC_BUCKETS; i++) {
+ struct homa_rpc_bucket *bucket = &hsk->server_rpc_buckets[i];
+
+ spin_lock_init(&bucket->lock);
+ bucket->id = i + 1000000;
+ INIT_HLIST_HEAD(&bucket->rpcs);
+ }
+
+ /* Initialize fields outside the Homa part. */
+ hsk->sock.sk_sndbuf = homa->wmem_max;
+ sock_set_flag(&hsk->inet.sk, SOCK_RCU_FREE);
+
+ /* Pick a default port. Must keep the socktab locked from now
+ * until the new socket is added to the socktab, to ensure that
+ * no other socket chooses the same port.
+ */
+ spin_lock_bh(&socktab->write_lock);
+ starting_port = hnet->prev_default_port;
+ while (1) {
+ hnet->prev_default_port++;
+ if (hnet->prev_default_port < HOMA_MIN_DEFAULT_PORT)
+ hnet->prev_default_port = HOMA_MIN_DEFAULT_PORT;
+ other = homa_sock_find(hnet, hnet->prev_default_port);
+ if (!other)
+ break;
+ sock_put(&other->sock);
+ if (hnet->prev_default_port == starting_port) {
+ spin_unlock_bh(&socktab->write_lock);
+ hsk->shutdown = true;
+ hsk->homa = NULL;
+ result = -EADDRNOTAVAIL;
+ goto error;
+ }
+ spin_unlock_bh(&socktab->write_lock);
+ cond_resched();
+ spin_lock_bh(&socktab->write_lock);
+ }
+ hsk->port = hnet->prev_default_port;
+ hsk->inet.inet_num = hsk->port;
+ hsk->inet.inet_sport = htons(hsk->port);
+ hlist_add_head_rcu(&hsk->socktab_links,
+ &socktab->buckets[homa_socktab_bucket(hnet,
+ hsk->port)]);
+ spin_unlock_bh(&socktab->write_lock);
+ return result;
+
+error:
+ homa_pool_free(buffer_pool);
+ return result;
+}
+
+/*
+ * homa_sock_unlink() - Unlinks a socket from its socktab and does
+ * related cleanups. Once this method returns, the socket will not be
+ * discoverable through the socktab.
+ * @hsk: Socket to unlink.
+ */
+void homa_sock_unlink(struct homa_sock *hsk)
+{
+ struct homa_socktab *socktab = hsk->homa->socktab;
+
+ spin_lock_bh(&socktab->write_lock);
+ hlist_del_rcu(&hsk->socktab_links);
+ spin_unlock_bh(&socktab->write_lock);
+}
+
+/**
+ * homa_sock_shutdown() - Disable a socket so that it can no longer
+ * be used for either sending or receiving messages. Any system calls
+ * currently waiting to send or receive messages will be aborted. This
+ * function will terminate any existing use of the socket, but it does
+ * not free up socket resources: that happens in homa_sock_destroy.
+ * @hsk: Socket to shut down.
+ */
+void homa_sock_shutdown(struct homa_sock *hsk)
+{
+ struct homa_interest *interest;
+ struct homa_rpc *rpc;
+
+ homa_sock_lock(hsk);
+ if (hsk->shutdown || !hsk->homa) {
+ homa_sock_unlock(hsk);
+ return;
+ }
+
+ /* The order of cleanup is very important, because there could be
+ * active operations that hold RPC locks but not the socket lock.
+ * 1. Set @shutdown; this ensures that no new RPCs will be created for
+ * this socket (though some creations might already be in progress).
+ * 2. Remove the socket from its socktab: this ensures that
+ * incoming packets for the socket will be dropped.
+ * 3. Go through all of the RPCs and delete them; this will
+ * synchronize with any operations in progress.
+ * 4. Perform other socket cleanup: at this point we know that
+ * there will be no concurrent activities on individual RPCs.
+ * 5. Don't delete the buffer pool until after all of the RPCs
+ * have been reaped.
+ * See "Homa Locking Strategy" in homa_impl.h for additional information
+ * about locking.
+ */
+ hsk->shutdown = true;
+ homa_sock_unlink(hsk);
+ homa_sock_unlock(hsk);
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(rpc, &hsk->active_rpcs, active_links) {
+ homa_rpc_lock(rpc);
+ homa_rpc_end(rpc);
+ homa_rpc_unlock(rpc);
+ }
+ rcu_read_unlock();
+
+ homa_sock_lock(hsk);
+ while (!list_empty(&hsk->interests)) {
+ interest = list_first_entry(&hsk->interests,
+ struct homa_interest, links);
+ list_del_init(&interest->links);
+ atomic_set_release(&interest->ready, 1);
+ wake_up(&interest->wait_queue);
+ }
+ homa_sock_unlock(hsk);
+}
+
+/**
+ * homa_sock_destroy() - Release all of the internal resources associated
+ * with a socket; is invoked at time when that is safe (i.e., all references
+ * on the socket have been dropped).
+ * @sk: Socket to destroy.
+ */
+void homa_sock_destroy(struct sock *sk)
+{
+ struct homa_sock *hsk = homa_sk(sk);
+
+ if (!hsk->homa)
+ return;
+
+ while (!list_empty(&hsk->dead_rpcs))
+ homa_rpc_reap(hsk, true);
+
+ WARN_ON_ONCE(refcount_read(&hsk->sock.sk_wmem_alloc) != 1);
+
+ if (hsk->buffer_pool) {
+ homa_pool_free(hsk->buffer_pool);
+ hsk->buffer_pool = NULL;
+ }
+}
+
+/**
+ * homa_sock_bind() - Associates a server port with a socket; if there
+ * was a previous server port assignment for @hsk, it is abandoned.
+ * @hnet: Network namespace with which port is associated.
+ * @hsk: Homa socket.
+ * @port: Desired server port for @hsk. If 0, then this call
+ * becomes a no-op: the socket will continue to use
+ * its randomly assigned client port.
+ *
+ * Return: 0 for success, otherwise a negative errno. If an error is
+ * returned, hsk->error_msg is set.
+ */
+int homa_sock_bind(struct homa_net *hnet, struct homa_sock *hsk,
+ u16 port)
+{
+ struct homa_socktab *socktab = hnet->homa->socktab;
+ struct homa_sock *owner;
+ int result = 0;
+
+ if (port == 0)
+ return result;
+ if (port >= HOMA_MIN_DEFAULT_PORT) {
+ hsk->error_msg = "port number invalid: in the automatically assigned range";
+ return -EINVAL;
+ }
+ homa_sock_lock(hsk);
+ spin_lock_bh(&socktab->write_lock);
+ if (hsk->shutdown) {
+ hsk->error_msg = "socket has been shut down";
+ result = -ESHUTDOWN;
+ goto done;
+ }
+
+ owner = homa_sock_find(hnet, port);
+ if (owner) {
+ sock_put(&owner->sock);
+ if (owner != hsk) {
+ hsk->error_msg = "requested port number is already in use";
+ result = -EADDRINUSE;
+ }
+ goto done;
+ }
+ hlist_del_rcu(&hsk->socktab_links);
+ hsk->port = port;
+ hsk->inet.inet_num = port;
+ hsk->inet.inet_sport = htons(hsk->port);
+ hlist_add_head_rcu(&hsk->socktab_links,
+ &socktab->buckets[homa_socktab_bucket(hnet, port)]);
+ hsk->is_server = true;
+done:
+ spin_unlock_bh(&socktab->write_lock);
+ homa_sock_unlock(hsk);
+ return result;
+}
+
+/**
+ * homa_sock_find() - Returns the socket associated with a given port.
+ * @hnet: Network namespace where the socket will be used.
+ * @port: The port of interest.
+ * Return: The socket that owns @port, or NULL if none. If non-NULL
+ * then this method has taken a reference on the socket and
+ * the caller must call sock_put to release it.
+ */
+struct homa_sock *homa_sock_find(struct homa_net *hnet, u16 port)
+{
+ int bucket = homa_socktab_bucket(hnet, port);
+ struct homa_sock *result = NULL;
+ struct homa_sock *hsk;
+
+ rcu_read_lock();
+ hlist_for_each_entry_rcu(hsk, &hnet->homa->socktab->buckets[bucket],
+ socktab_links) {
+ if (hsk->port == port && hsk->hnet == hnet) {
+ result = hsk;
+ sock_hold(&hsk->sock);
+ break;
+ }
+ }
+ rcu_read_unlock();
+ return result;
+}
+
+/**
+ * homa_sock_wait_wmem() - Block the thread until @hsk's usage of tx
+ * packet memory drops below the socket's limit.
+ * @hsk: Socket of interest.
+ * @nonblocking: If there's not enough memory, return -EWOLDBLOCK instead
+ * of blocking.
+ * Return: 0 for success, otherwise a negative errno.
+ */
+int homa_sock_wait_wmem(struct homa_sock *hsk, int nonblocking)
+{
+ long timeo = hsk->sock.sk_sndtimeo;
+ int result;
+
+ /* Note: we can't use sock_wait_for_wmem because that function
+ * is not available to modules (as of August 2025 it's static).
+ */
+
+ if (nonblocking)
+ timeo = 0;
+ set_bit(HOMA_SOCK_NOSPACE, &hsk->flags);
+ result = wait_event_interruptible_timeout(*sk_sleep(&hsk->sock),
+ homa_sock_wmem_avl(hsk) ||
+ hsk->shutdown, timeo);
+ if (signal_pending(current))
+ return -EINTR;
+ if (result == 0)
+ return -EWOULDBLOCK;
+ return 0;
+}
diff --git a/net/homa/homa_sock.h b/net/homa/homa_sock.h
new file mode 100644
index 000000000000..9e477d2d9e3f
--- /dev/null
+++ b/net/homa/homa_sock.h
@@ -0,0 +1,446 @@
+/* SPDX-License-Identifier: BSD-2-Clause or GPL-2.0+ */
+
+/* This file defines structs and other things related to Homa sockets. */
+
+#ifndef _HOMA_SOCK_H
+#define _HOMA_SOCK_H
+
+/* Forward declarations. */
+struct homa;
+struct homa_pool;
+
+/* Number of hash buckets in a homa_socktab. Must be a power of 2. */
+#define HOMA_SOCKTAB_BUCKET_BITS 10
+#define HOMA_SOCKTAB_BUCKETS BIT(HOMA_SOCKTAB_BUCKET_BITS)
+
+/**
+ * struct homa_socktab - A hash table that maps from port numbers (either
+ * client or server) to homa_sock objects.
+ *
+ * This table is managed exclusively by homa_socktab.c, using RCU to
+ * minimize synchronization during lookups.
+ */
+struct homa_socktab {
+ /**
+ * @write_lock: Controls all modifications to this object; not needed
+ * for socket lookups (RCU is used instead). Also used to
+ * synchronize port allocation.
+ */
+ spinlock_t write_lock;
+
+ /**
+ * @buckets: Heads of chains for hash table buckets. Chains
+ * consist of homa_sock objects.
+ */
+ struct hlist_head buckets[HOMA_SOCKTAB_BUCKETS];
+};
+
+/**
+ * struct homa_socktab_scan - Records the state of an iteration over all
+ * the entries in a homa_socktab, in a way that is safe against concurrent
+ * reclamation of sockets.
+ */
+struct homa_socktab_scan {
+ /** @socktab: The table that is being scanned. */
+ struct homa_socktab *socktab;
+
+ /**
+ * @hsk: Points to the current socket in the iteration, or NULL if
+ * we're at the beginning or end of the iteration. If non-NULL then
+ * we are holding a reference to this socket.
+ */
+ struct homa_sock *hsk;
+
+ /**
+ * @current_bucket: The index of the bucket in socktab->buckets
+ * currently being scanned (-1 if @hsk == NULL).
+ */
+ int current_bucket;
+};
+
+/**
+ * struct homa_rpc_bucket - One bucket in a hash table of RPCs.
+ */
+
+struct homa_rpc_bucket {
+ /**
+ * @lock: serves as a lock both for this bucket (e.g., when
+ * adding and removing RPCs) and also for all of the RPCs in
+ * the bucket. Must be held whenever looking up an RPC in
+ * this bucket or manipulating an RPC in the bucket. This approach
+ * has the following properties:
+ * 1. An RPC can be looked up and locked (a common operation) with
+ * a single lock acquisition.
+ * 2. Looking up and locking are atomic: there is no window of
+ * vulnerability where someone else could delete an RPC after
+ * it has been looked up and before it has been locked.
+ * 3. The lookup mechanism does not use RCU. This is important because
+ * RPCs are created rapidly and typically live only a few tens of
+ * microseconds. As of May 2025 RCU introduces a lag of about
+ * 25 ms before objects can be deleted; for RPCs this would result
+ * in hundreds or thousands of RPCs accumulating before RCU allows
+ * them to be deleted.
+ * This approach has the disadvantage that RPCs within a bucket share
+ * locks and thus may not be able to work concurrently, but there are
+ * enough buckets in the table to make such colllisions rare.
+ *
+ * See "Homa Locking Strategy" in homa_impl.h for more info about
+ * locking.
+ */
+ spinlock_t lock;
+
+ /**
+ * @id: identifier for this bucket, used in error messages etc.
+ * It's the index of the bucket within its hash table bucket
+ * array, with an additional offset to separate server and
+ * client RPCs.
+ */
+ int id;
+
+ /** @rpcs: list of RPCs that hash to this bucket. */
+ struct hlist_head rpcs;
+};
+
+/**
+ * define HOMA_CLIENT_RPC_BUCKETS - Number of buckets in hash tables for
+ * client RPCs. Must be a power of 2.
+ */
+#define HOMA_CLIENT_RPC_BUCKETS 1024
+
+/**
+ * define HOMA_SERVER_RPC_BUCKETS - Number of buckets in hash tables for
+ * server RPCs. Must be a power of 2.
+ */
+#define HOMA_SERVER_RPC_BUCKETS 1024
+
+/**
+ * struct homa_sock - Information about an open socket.
+ */
+struct homa_sock {
+ /* Info for other network layers. Note: IPv6 info (struct ipv6_pinfo
+ * comes at the very end of the struct, *after* Homa's data, if this
+ * socket uses IPv6).
+ */
+ union {
+ /** @sock: generic socket data; must be the first field. */
+ struct sock sock;
+
+ /**
+ * @inet: generic Internet socket data; must also be the
+ first field (contains sock as its first member).
+ */
+ struct inet_sock inet;
+ };
+
+ /**
+ * @homa: Overall state about the Homa implementation. NULL
+ * means this socket was never initialized or has been deleted.
+ */
+ struct homa *homa;
+
+ /**
+ * @hnet: Overall state specific to the network namespace for
+ * this socket.
+ */
+ struct homa_net *hnet;
+
+ /**
+ * @buffer_pool: used to allocate buffer space for incoming messages.
+ * Storage is dynamically allocated.
+ */
+ struct homa_pool *buffer_pool;
+
+ /**
+ * @port: Port number: identifies this socket uniquely among all
+ * those on this node.
+ */
+ u16 port;
+
+ /**
+ * @is_server: True means that this socket can act as both client
+ * and server; false means the socket is client-only.
+ */
+ bool is_server;
+
+ /**
+ * @shutdown: True means the socket is no longer usable (either
+ * shutdown has already been invoked, or the socket was never
+ * properly initialized). Note: can't use the SOCK_DEAD flag for
+ * this because that flag doesn't get set until much later in the
+ * process of closing a socket.
+ */
+ bool shutdown;
+
+ /**
+ * @ip_header_length: Length of IP headers for this socket (depends
+ * on IPv4 vs. IPv6).
+ */
+ int ip_header_length;
+
+ /** @socktab_links: Links this socket into a homa_socktab bucket. */
+ struct hlist_node socktab_links;
+
+ /**
+ * @error_msg: Static string giving human-readable information about
+ * the reason for the last error returned by a Homa kernel call.
+ * Applications can fetch this with the HOMAIOCINFO ioctl to figure
+ * out why a call failed.
+ */
+ char *error_msg;
+
+ /* Information above is (almost) never modified; start a new
+ * cache line below for info that is modified frequently.
+ */
+
+ /**
+ * @lock: Must be held when modifying fields such as interests
+ * and lists of RPCs. This lock is used in place of sk->sk_lock
+ * because it's used differently (it's always used as a simple
+ * spin lock). See "Homa Locking Strategy" in homa_impl.h
+ * for more on Homa's synchronization strategy.
+ */
+ spinlock_t lock ____cacheline_aligned_in_smp;
+
+ /**
+ * @protect_count: counts the number of calls to homa_protect_rpcs
+ * for which there have not yet been calls to homa_unprotect_rpcs.
+ */
+ atomic_t protect_count;
+
+ /**
+ * @flags: Additional state information: an OR'ed combination of
+ * various single-bit flags. See below for definitions. Must be
+ * manipulated with test_bit etc. because some of the manipulations
+ * occur without holding @lock.
+ */
+ unsigned long flags;
+
+ /* Valid bit numbers for @flags:
+ * HOMA_SOCK_NOSPACE - Nonzero means that the socket has hit its
+ * limit on tx buffer space and threads are
+ * blocked waiting for skbs to be released. Used
+ * instead of the SOCK_NOSPACE flag in
+ * @sock.sk_socket->flags. This is because
+ * @sock.sk_socket can become NULL unexpectedly
+ * (especially since Homa never acquires the
+ * Linux socket lock). Thus code using sk_socket
+ * requires tricky synchronization and is
+ * error-prone.
+ */
+#define HOMA_SOCK_NOSPACE 0
+
+ /**
+ * @active_rpcs: List of all existing RPCs related to this socket,
+ * including both client and server RPCs. This list isn't strictly
+ * needed, since RPCs are already in one of the hash tables below,
+ * but it's more efficient for homa_timer to have this list
+ * (so it doesn't have to scan large numbers of hash buckets).
+ * The list is sorted, with the oldest RPC first. Manipulate with
+ * RCU so timer can access without locking.
+ */
+ struct list_head active_rpcs;
+
+ /**
+ * @dead_rpcs: Contains RPCs for which homa_rpc_end has been
+ * called, but which have not yet been reaped by homa_rpc_reap.
+ */
+ struct list_head dead_rpcs;
+
+ /** @dead_skbs: Total number of socket buffers in RPCs on dead_rpcs. */
+ int dead_skbs;
+
+ /**
+ * @waiting_for_bufs: Contains RPCs that are blocked because there
+ * wasn't enough space in the buffer pool region for their incoming
+ * messages. Sorted in increasing order of message length.
+ */
+ struct list_head waiting_for_bufs;
+
+ /**
+ * @ready_rpcs: List of all RPCs that are ready for attention from
+ * an application thread.
+ */
+ struct list_head ready_rpcs;
+
+ /**
+ * @interests: List of threads that are currently waiting for
+ * incoming messages via homa_wait_shared.
+ */
+ struct list_head interests;
+
+ /**
+ * @client_rpc_buckets: Hash table for fast lookup of client RPCs.
+ * Modifications are synchronized with bucket locks, not
+ * the socket lock.
+ */
+ struct homa_rpc_bucket client_rpc_buckets[HOMA_CLIENT_RPC_BUCKETS];
+
+ /**
+ * @server_rpc_buckets: Hash table for fast lookup of server RPCs.
+ * Modifications are synchronized with bucket locks, not
+ * the socket lock.
+ */
+ struct homa_rpc_bucket server_rpc_buckets[HOMA_SERVER_RPC_BUCKETS];
+};
+
+/**
+ * struct homa_v6_sock - For IPv6, additional IPv6-specific information
+ * is present in the socket struct after Homa-specific information.
+ */
+struct homa_v6_sock {
+ /** @homa: All socket info except for IPv6-specific stuff. */
+ struct homa_sock homa;
+
+ /** @inet6: Socket info specific to IPv6. */
+ struct ipv6_pinfo inet6;
+};
+
+int homa_sock_bind(struct homa_net *hnet, struct homa_sock *hsk,
+ u16 port);
+void homa_sock_destroy(struct sock *sk);
+struct homa_sock *homa_sock_find(struct homa_net *hnet, u16 port);
+int homa_sock_init(struct homa_sock *hsk);
+void homa_sock_shutdown(struct homa_sock *hsk);
+void homa_sock_unlink(struct homa_sock *hsk);
+int homa_sock_wait_wmem(struct homa_sock *hsk, int nonblocking);
+void homa_socktab_destroy(struct homa_socktab *socktab,
+ struct homa_net *hnet);
+void homa_socktab_end_scan(struct homa_socktab_scan *scan);
+void homa_socktab_init(struct homa_socktab *socktab);
+struct homa_sock *homa_socktab_next(struct homa_socktab_scan *scan);
+struct homa_sock *homa_socktab_start_scan(struct homa_socktab *socktab,
+ struct homa_socktab_scan *scan);
+
+/**
+ * homa_sock_lock() - Acquire the lock for a socket.
+ * @hsk: Socket to lock.
+ */
+static inline void homa_sock_lock(struct homa_sock *hsk)
+ __acquires(hsk->lock)
+{
+ spin_lock_bh(&hsk->lock);
+}
+
+/**
+ * homa_sock_unlock() - Release the lock for a socket.
+ * @hsk: Socket to lock.
+ */
+static inline void homa_sock_unlock(struct homa_sock *hsk)
+ __releases(hsk->lock)
+{
+ spin_unlock_bh(&hsk->lock);
+}
+
+/**
+ * homa_socktab_bucket() - Compute the bucket number in a homa_socktab
+ * that will contain a particular socket.
+ * @hnet: Network namespace of the desired socket.
+ * @port: Port number of the socket.
+ *
+ * Return: The index of the bucket in which a socket matching @hnet and
+ * @port will be found (if it exists).
+ */
+static inline int homa_socktab_bucket(struct homa_net *hnet, u16 port)
+{
+ return hash_32((uintptr_t)hnet ^ port, HOMA_SOCKTAB_BUCKET_BITS);
+}
+
+/**
+ * homa_client_rpc_bucket() - Find the bucket containing a given
+ * client RPC.
+ * @hsk: Socket associated with the RPC.
+ * @id: Id of the desired RPC.
+ *
+ * Return: The bucket in which this RPC will appear, if the RPC exists.
+ */
+static inline struct homa_rpc_bucket
+ *homa_client_rpc_bucket(struct homa_sock *hsk, u64 id)
+{
+ /* We can use a really simple hash function here because RPC ids
+ * are allocated sequentially.
+ */
+ return &hsk->client_rpc_buckets[(id >> 1) &
+ (HOMA_CLIENT_RPC_BUCKETS - 1)];
+}
+
+/**
+ * homa_server_rpc_bucket() - Find the bucket containing a given
+ * server RPC.
+ * @hsk: Socket associated with the RPC.
+ * @id: Id of the desired RPC.
+ *
+ * Return: The bucket in which this RPC will appear, if the RPC exists.
+ */
+static inline struct homa_rpc_bucket
+ *homa_server_rpc_bucket(struct homa_sock *hsk, u64 id)
+{
+ /* Each client allocates RPC ids sequentially, so they will
+ * naturally distribute themselves across the hash space.
+ * Thus we can use the id directly as hash.
+ */
+ return &hsk->server_rpc_buckets[(id >> 1)
+ & (HOMA_SERVER_RPC_BUCKETS - 1)];
+}
+
+/**
+ * homa_bucket_lock() - Acquire the lock for an RPC hash table bucket.
+ * @bucket: Bucket to lock.
+ * @id: Id of the RPC on whose behalf the bucket is being locked.
+ * Used only for metrics.
+ */
+static inline void homa_bucket_lock(struct homa_rpc_bucket *bucket, u64 id)
+ __acquires(bucket->lock)
+{
+ spin_lock_bh(&bucket->lock);
+}
+
+/**
+ * homa_bucket_unlock() - Release the lock for an RPC hash table bucket.
+ * @bucket: Bucket to unlock.
+ * @id: ID of the RPC that was using the lock.
+ */
+static inline void homa_bucket_unlock(struct homa_rpc_bucket *bucket, u64 id)
+ __releases(bucket->lock)
+{
+ spin_unlock_bh(&bucket->lock);
+}
+
+static inline struct homa_sock *homa_sk(const struct sock *sk)
+{
+ return (struct homa_sock *)sk;
+}
+
+/**
+ * homa_sock_wmem_avl() - Returns true if the socket is within its limit
+ * for output memory usage. False means that no new messages should be sent
+ * until memory is freed.
+ * @hsk: Socket of interest.
+ * Return: See above.
+ */
+static inline bool homa_sock_wmem_avl(struct homa_sock *hsk)
+{
+ return refcount_read(&hsk->sock.sk_wmem_alloc) < hsk->sock.sk_sndbuf;
+}
+
+/**
+ * homa_sock_wakeup_wmem() - Invoked when tx packet memory has been freed;
+ * if memory usage is below the limit and there are tasks waiting for memory,
+ * wake them up.
+ * @hsk: Socket of interest.
+ */
+static inline void homa_sock_wakeup_wmem(struct homa_sock *hsk)
+{
+ /* Note: can't use sk_stream_write_space for this functionality
+ * because it uses a different test to determine whether enough
+ * memory is available.
+ */
+ if (test_bit(HOMA_SOCK_NOSPACE, &hsk->flags) &&
+ homa_sock_wmem_avl(hsk)) {
+ clear_bit(HOMA_SOCK_NOSPACE, &hsk->flags);
+ rcu_read_lock();
+ wake_up_interruptible_poll(sk_sleep(&hsk->sock), EPOLLOUT);
+ rcu_read_unlock();
+ }
+}
+
+#endif /* _HOMA_SOCK_H */
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox