[PATCH net-next v6 6/6] net: devmem: add tests for SO_DEVMEM_AUTORELEASE socket option

Bobby Eshleman
2025-11-05T01:23:30Z

From: Bobby Eshleman <bobbyeshleman@meta.com>

Add -A flag to ncdevmem to set autorelease mode.

Add tests for the SO_DEVMEM_AUTORELEASE socket option:

New tests include:
- check_sockopt_autorelease_default: Verifies default value is 0
- check_sockopt_autorelease_set_0: Tests setting to 0 and reading
back
- check_sockopt_autorelease_set_1: Tests toggling from 0 to 1
- check_sockopt_autorelease_invalid: Tests invalid value (2) returns
EINVAL
- check_autorelease_disabled: Tests ncdevmem in manual token release
mode
- check_autorelease_enabled: Tests ncdevmem in autorelease mode

All check_sockopt tests gracefully skip with KsftSkipEx if
SO_DEVMEM_AUTORELEASE is not supported by the kernel.

Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
tools/testing/selftests/drivers/net/hw/devmem.py | 115 +++++++++++++++++++++-
tools/testing/selftests/drivers/net/hw/ncdevmem.c | 20 +++-
2 files changed, 133 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/drivers/net/hw/devmem.py b/tools/testing/selftests/drivers/net/hw/devmem.py
index 45c2d49d55b6..29ec179d651f 100755
--- a/tools/testing/selftests/drivers/net/hw/devmem.py
+++ b/tools/testing/selftests/drivers/net/hw/devmem.py
@@ -1,6 +1,9 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0

+import socket
+import errno
+
from os import path
from lib.py import ksft_run, ksft_exit
from lib.py import ksft_eq, KsftSkipEx
@@ -63,12 +66,122 @@ def check_tx_chunks(cfg) -> None:
ksft_eq(socat.stdout.strip(), "hello\nworld")

+@ksft_disruptive
+def check_autorelease_disabled(cfg) -> None:
+ """Test RX with autorelease disabled (requires manual token release in ncdevmem)"""
+ 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 -A 0"
+
+ 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)
+
+
+@ksft_disruptive
+def check_autorelease_enabled(cfg) -> None:
+ """Test RX with autorelease enabled (requires token autorelease in ncdevmem)"""
+ 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 -A 1"
+
+ 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)
+
+
+def check_sockopt_autorelease_default(cfg) -> None:
+ """Test that SO_DEVMEM_AUTORELEASE default is 0"""
+ SO_DEVMEM_AUTORELEASE = 85
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ val = sock.getsockopt(socket.SOL_SOCKET, SO_DEVMEM_AUTORELEASE)
+ ksft_eq(val, 0, "Default autorelease should be 0")
+ except OSError as e:
+ if e.errno == errno.ENOPROTOOPT:
+ raise KsftSkipEx("SO_DEVMEM_AUTORELEASE not supported")
+ raise
+ finally:
+ sock.close()
+
+
+def check_sockopt_autorelease_set_0(cfg) -> None:
+ """Test setting SO_DEVMEM_AUTORELEASE to 0"""
+ SO_DEVMEM_AUTORELEASE = 85
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ sock.setsockopt(socket.SOL_SOCKET, SO_DEVMEM_AUTORELEASE, 0)
+ val = sock.getsockopt(socket.SOL_SOCKET, SO_DEVMEM_AUTORELEASE)
+ ksft_eq(val, 0, "Autorelease should be 0 after setting")
+ except OSError as e:
+ if e.errno == errno.ENOPROTOOPT:
+ raise KsftSkipEx("SO_DEVMEM_AUTORELEASE not supported")
+ raise
+ finally:
+ sock.close()
+
+
+def check_sockopt_autorelease_set_1(cfg) -> None:
+ """Test setting SO_DEVMEM_AUTORELEASE to 1"""
+ SO_DEVMEM_AUTORELEASE = 85
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ # First set to 0
+ sock.setsockopt(socket.SOL_SOCKET, SO_DEVMEM_AUTORELEASE, 0)
+ # Then set back to 1
+ sock.setsockopt(socket.SOL_SOCKET, SO_DEVMEM_AUTORELEASE, 1)
+ val = sock.getsockopt(socket.SOL_SOCKET, SO_DEVMEM_AUTORELEASE)
+ ksft_eq(val, 1, "Autorelease should be 1 after setting")
+ except OSError as e:
+ if e.errno == errno.ENOPROTOOPT:
+ raise KsftSkipEx("SO_DEVMEM_AUTORELEASE not supported")
+ raise
+ finally:
+ sock.close()
+
+
+def check_sockopt_autorelease_invalid(cfg) -> None:
+ """Test that SO_DEVMEM_AUTORELEASE rejects invalid values"""
+ SO_DEVMEM_AUTORELEASE = 85
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ try:
+ sock.setsockopt(socket.SOL_SOCKET, SO_DEVMEM_AUTORELEASE, 2)
+ raise Exception("setsockopt should have failed with EINVAL")
+ except OSError as e:
+ if e.errno == errno.ENOPROTOOPT:
+ raise KsftSkipEx("SO_DEVMEM_AUTORELEASE not supported")
+ ksft_eq(e.errno, errno.EINVAL, "Should fail with EINVAL for invalid value")
+ finally:
+ sock.close()
+
+
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)

- ksft_run([check_rx, check_tx, check_tx_chunks],
+ ksft_run([check_rx, check_tx, check_tx_chunks,
+ check_autorelease_enabled,
+ check_autorelease_disabled,
+ check_sockopt_autorelease_default,
+ check_sockopt_autorelease_set_0,
+ check_sockopt_autorelease_set_1,
+ check_sockopt_autorelease_invalid],
args=(cfg, ))
ksft_exit()

diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
index 3288ed04ce08..34d608d07bec 100644
--- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c
+++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
@@ -83,6 +83,10 @@
#define MSG_SOCK_DEVMEM 0x2000000
#endif

+#ifndef SO_DEVMEM_AUTORELEASE
+#define SO_DEVMEM_AUTORELEASE 85
+#endif
+
#define MAX_IOV 1024

static size_t max_chunk;
@@ -97,6 +101,7 @@ static unsigned int ifindex;
static unsigned int dmabuf_id;
static uint32_t tx_dmabuf_id;
static int waittime_ms = 500;
+static int autorelease = -1;

/* System state loaded by current_config_load() */
#define MAX_FLOWS 8
@@ -890,6 +895,16 @@ static int do_server(struct memory_buffer *mem)
if (enable_reuseaddr(socket_fd))
goto err_close_socket;

+ if (autorelease >= 0) {
+ ret = setsockopt(socket_fd, SOL_SOCKET, SO_DEVMEM_AUTORELEASE,
+ &autorelease, sizeof(autorelease));
+ if (ret) {
+ pr_err("SO_DEVMEM_AUTORELEASE failed");
+ goto err_close_socket;
+ }
+ fprintf(stderr, "Set SO_DEVMEM_AUTORELEASE to %d\n", autorelease);
+ }
+
fprintf(stderr, "binding to address %s:%d\n", server_ip,
ntohs(server_sin.sin6_port));

@@ -1397,7 +1412,7 @@ int main(int argc, char *argv[])
int is_server = 0, opt;
int ret, err = 1;

- while ((opt = getopt(argc, argv, "ls:c:p:v:q:t:f:z:")) != -1) {
+ while ((opt = getopt(argc, argv, "ls:c:p:v:q:t:f:z:A:")) != -1) {
switch (opt) {
case 'l':
is_server = 1;
@@ -1426,6 +1441,9 @@ int main(int argc, char *argv[])
case 'z':
max_chunk = atoi(optarg);
break;
+ case 'A':
+ autorelease = atoi(optarg);
+ break;
case '?':
fprintf(stderr, "unknown option: %c\n", optopt);
break;

--
2.47.3