diff --git a/tools/testing/selftests/net/so_txtime.c b/tools/testing/selftests/net/so_txtime.c
index fa748e4209c0..da4ef411693c 100644
--- a/tools/testing/selftests/net/so_txtime.c
+++ b/tools/testing/selftests/net/so_txtime.c
@@ -17,6 +17,7 @@
 #include <linux/errqueue.h>
 #include <linux/ipv6.h>
 #include <linux/tcp.h>
+#include <netinet/udp.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -32,7 +33,11 @@ static const char *cfg_addr;
 static int	cfg_clockid	= CLOCK_TAI;
 static bool	cfg_do_ipv4;
 static bool	cfg_do_ipv6;
+static uint8_t	cfg_mr_num;
+static uint8_t	cfg_mr_ival;
+static int	cfg_mss		= 1400;
 static bool	cfg_rxonly;
+static uint16_t	cfg_size	= 1;
 static int	cfg_timeout_sec;
 static bool	cfg_txonly;
 static uint16_t	cfg_port	= 8000;
@@ -66,6 +71,7 @@ static uint64_t gettime_ns(void)
 
 static void do_send_one(int fdt, struct timed_send *ts)
 {
+	static char buf[1 << 16];
 	char control[CMSG_SPACE(sizeof(uint64_t))];
 	struct msghdr msg = {0};
 	struct iovec iov = {0};
@@ -73,8 +79,10 @@ static void do_send_one(int fdt, struct timed_send *ts)
 	uint64_t tdeliver;
 	int ret;
 
-	iov.iov_base = &ts->data;
-	iov.iov_len = 1;
+	memset(buf, ts->data, cfg_size);
+
+	iov.iov_base = buf;
+	iov.iov_len = cfg_size;
 
 	msg.msg_iov = &iov;
 	msg.msg_iovlen = 1;
@@ -85,6 +93,11 @@ static void do_send_one(int fdt, struct timed_send *ts)
 		msg.msg_controllen = sizeof(control);
 
 		tdeliver = glob_tstart + ts->delay_us * 1000;
+		if (cfg_mr_ival) {
+			tdeliver &= ~0xFF;
+			tdeliver |= cfg_mr_ival << 4;
+			tdeliver |= cfg_mr_num;
+		}
 
 		cm = CMSG_FIRSTHDR(&msg);
 		cm->cmsg_level = SOL_SOCKET;
@@ -104,30 +117,41 @@ static void do_send_one(int fdt, struct timed_send *ts)
 static bool do_recv_one(int fdr, struct timed_send *ts)
 {
 	int64_t tstop, texpect;
+	int total = 0;
 	char rbuf[2];
 	int ret;
 
-	ret = recv(fdr, rbuf, sizeof(rbuf), 0);
+read_again:
+	ret = recv(fdr, rbuf, sizeof(rbuf), MSG_TRUNC);
 	if (ret == -1 && errno == EAGAIN)
-		return true;
+		goto timedout;
 	if (ret == -1)
 		error(1, errno, "read");
-	if (ret != 1)
-		error(1, 0, "read: %dB", ret);
 
 	tstop = (gettime_ns() - glob_tstart) / 1000;
 	texpect = ts->delay_us >= 0 ? ts->delay_us : 0;
 
-	fprintf(stderr, "payload:%c delay:%lld expected:%lld (us)\n",
-			rbuf[0], (long long)tstop, (long long)texpect);
+	fprintf(stderr, "payload:%c delay:%lld expected:%lld (us) -- read=%d,len=%d,total=%d\n",
+			rbuf[0], (long long)tstop, (long long)texpect,
+			total, ret, cfg_size);
 
 	if (rbuf[0] != ts->data)
 		error(1, 0, "payload mismatch. expected %c", ts->data);
 
-	if (labs(tstop - texpect) > cfg_variance_us)
+	total += ret;
+	if (total < cfg_size)
+		goto read_again;
+
+	/* measure latency if all data arrives in a single datagram (not GSO) */
+	if (ret == cfg_size && labs(tstop - texpect) > cfg_variance_us)
 		error(1, 0, "exceeds variance (%d us)", cfg_variance_us);
 
 	return false;
+
+timedout:
+	if (total != 0 && total != cfg_size)
+		error(1, 0, "timeout mid-read");
+	return true;
 }
 
 static void do_recv_verify_empty(int fdr)
@@ -168,7 +192,9 @@ static void do_recv_errqueue_timeout(int fdt)
 			break;
 		if (ret == -1)
 			error(1, errno, "errqueue");
-		if (msg.msg_flags != MSG_ERRQUEUE)
+		if (ret != sizeof(data))
+			error(1, errno, "insufficient data");
+		if (msg.msg_flags & ~(MSG_ERRQUEUE | MSG_TRUNC))
 			error(1, 0, "errqueue: flags 0x%x\n", msg.msg_flags);
 
 		cm = CMSG_FIRSTHDR(&msg);
@@ -180,7 +206,9 @@ static void do_recv_errqueue_timeout(int fdt)
 		err = (struct sock_extended_err *)CMSG_DATA(cm);
 		if (err->ee_origin != SO_EE_ORIGIN_TXTIME)
 			error(1, 0, "errqueue: origin 0x%x\n", err->ee_origin);
-		if (err->ee_code != ECANCELED)
+		if (err->ee_errno != ECANCELED)
+			error(1, 0, "errqueue: errno 0x%x\n", err->ee_errno);
+		if (err->ee_code != SO_EE_CODE_TXTIME_MISSED)
 			error(1, 0, "errqueue: code 0x%x\n", err->ee_code);
 
 		tstamp = ((int64_t) err->ee_data) << 32 | err->ee_info;
@@ -202,7 +230,7 @@ static void setsockopt_txtime(int fd)
 	struct sock_txtime so_txtime_val_read = { 0 };
 	socklen_t vallen = sizeof(so_txtime_val);
 
-	so_txtime_val.flags = SOF_TXTIME_REPORT_ERRORS;
+	so_txtime_val.flags = SOF_TXTIME_REPORT_ERRORS | SOF_TXTIME_MULTI_RELEASE;
 
 	if (setsockopt(fd, SOL_SOCKET, SO_TXTIME,
 		       &so_txtime_val, sizeof(so_txtime_val)))
@@ -230,6 +258,12 @@ static int setup_tx(struct sockaddr *addr, socklen_t alen)
 
 	setsockopt_txtime(fd);
 
+	if (cfg_size > cfg_mss) {
+		if (setsockopt(fd, SOL_UDP, UDP_SEGMENT,
+			       &cfg_mss, sizeof(cfg_mss)))
+			error(1, errno, "setsockopt udp segment");
+	}
+
 	return fd;
 }
 
@@ -321,7 +355,7 @@ static void parse_opts(int argc, char **argv)
 {
 	int c, ilen, olen;
 
-	while ((c = getopt(argc, argv, "46A:c:rtT:")) != -1) {
+	while ((c = getopt(argc, argv, "46A:c:m:M:N:rs:tT:")) != -1) {
 		switch (c) {
 		case '4':
 			cfg_do_ipv4 = true;
@@ -341,9 +375,25 @@ static void parse_opts(int argc, char **argv)
 			else
 				error(1, 0, "unknown clock id %s", optarg);
 			break;
+		case 'm':
+			cfg_mss = strtol(optarg, NULL, 0);
+			break;
+		case 'M':
+			cfg_mr_ival = atoi(optarg);
+			if (cfg_mr_ival > 0xF)
+				error(1, 0, "multi release ival exceeds max");
+			break;
+		case 'N':
+			cfg_mr_num = atoi(optarg);
+			if (cfg_mr_num > 0xF)
+				error(1, 0, "multi release count exceeds max");
+			break;
 		case 'r':
 			cfg_rxonly = true;
 			break;
+		case 's':
+			cfg_size = atoi(optarg);
+			break;
 		case 't':
 			cfg_txonly = true;
 			break;
@@ -356,12 +406,14 @@ static void parse_opts(int argc, char **argv)
 	}
 
 	if (argc - optind != 2)
-		error(1, 0, "Usage: %s [-46rt] [-A addr] [-c clock] [-T timeout] <in> <out>", argv[0]);
+		error(1, 0, "Usage: %s [-46rt] [-A addr] [-c clock] [-m mtu] [-M ival] [-N num] [-s size] [-T timeout] <in> <out>", argv[0]);
 
 	if (cfg_rxonly && cfg_txonly)
 		error(1, 0, "Select rx-only or tx-only, not both");
 	if (cfg_addr && cfg_do_ipv4 && cfg_do_ipv6)
 		error(1, 0, "Cannot run both IPv4 and IPv6 when passing address");
+	if (!!cfg_mr_ival ^ !!cfg_mr_num)
+		error(1, 0, "Multi release pacing requires both -M and -N");
 
 	ilen = parse_io(argv[optind], cfg_in);
 	olen = parse_io(argv[optind + 1], cfg_out);
diff --git a/tools/testing/selftests/net/so_txtime.sh b/tools/testing/selftests/net/so_txtime.sh
index 3f7800eaecb1..7c60c11717e4 100755
--- a/tools/testing/selftests/net/so_txtime.sh
+++ b/tools/testing/selftests/net/so_txtime.sh
@@ -16,13 +16,20 @@ fi
 
 set -e
 
+ip link set dev lo mtu 1500
 tc qdisc add dev lo root fq
+
 ./so_txtime -4 -6 -c mono a,-1 a,-1
 ./so_txtime -4 -6 -c mono a,0 a,0
 ./so_txtime -4 -6 -c mono a,10 a,10
 ./so_txtime -4 -6 -c mono a,10,b,20 a,10,b,20
 ./so_txtime -4 -6 -c mono a,20,b,10 b,20,a,20
 
+# test gso
+./so_txtime -4 -6 -m 1000 -s 3500 -c mono a,50,b,100 a,50,b,100
+./so_txtime -4 -6 -m 1000 -s 3500 -M 5 -N 1 -c mono a,50,b,100 a,50,b,100
+./so_txtime -4 -6 -m 1000 -s 3500 -M 5 -N 2 -c mono a,50,b,100 a,50,b,100
+
 if tc qdisc replace dev lo root etf clockid CLOCK_TAI delta 400000; then
 	! ./so_txtime -4 -6 -c tai a,-1 a,-1
 	! ./so_txtime -4 -6 -c tai a,0 a,0
diff --git a/tools/testing/selftests/net/so_txtime_multi.sh b/tools/testing/selftests/net/so_txtime_multi.sh
new file mode 100755
index 000000000000..4e5ab06fd178
--- /dev/null
+++ b/tools/testing/selftests/net/so_txtime_multi.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Regression tests for the SO_TXTIME interface
+
+readonly ns_prefix="ns-sotxtime-"
+readonly ns1="${ns_prefix}1"
+readonly ns2="${ns_prefix}2"
+
+readonly ns1_v4=192.168.1.1
+readonly ns2_v4=192.168.1.2
+readonly ns1_v6=fd::1
+readonly ns2_v6=fd::2
+
+set -eu
+
+cleanup() {
+	ip netns del "${ns2}"
+	ip netns del "${ns1}"
+}
+
+setup() {
+	ip netns add "${ns1}"
+	ip netns add "${ns2}"
+
+	ip link add dev veth1 mtu 1500 netns "${ns1}" type veth \
+	      peer name veth2 mtu 1500 netns "${ns2}"
+
+	ip -netns "${ns1}" link set veth1 up
+	ip -netns "${ns2}" link set veth2 up
+
+	ip -netns "${ns1}" -4 addr add "${ns1_v4}/24" dev veth1
+	ip -netns "${ns2}" -4 addr add "${ns2_v4}/24" dev veth2
+	ip -netns "${ns1}" -6 addr add "${ns1_v6}/64" dev veth1 nodad
+	ip -netns "${ns2}" -6 addr add "${ns2_v6}/64" dev veth2 nodad
+
+	ip netns exec "${ns1}" tc qdisc add dev veth1 root fq
+}
+
+run_test() {
+	ip netns exec "${ns2}" ./so_txtime -r -T 1 $@ &
+	sleep 0.1
+	ip netns exec "${ns1}" ./so_txtime -t $@
+	wait
+}
+
+run_test_46() {
+	run_test -4 -A "${ns2_v4}" $@
+	run_test -6 -A "${ns2_v6}" $@
+}
+
+trap cleanup EXIT
+setup
+
+echo "pacing"
+TEST_ARGS="-c mono a,10 a,10"
+run_test_46 ${TEST_ARGS}
+
+echo "gso + pacing"
+TEST_ARGS_GSO="-m 1000 -s 4500 ${TEST_ARGS}"
+run_test_46 ${TEST_ARGS_GSO}
+
+echo "gso + multi release pacing"
+run_test_46 -M 5 -N 1 ${TEST_ARGS_GSO}
+run_test_46 -M 5 -N 2 ${TEST_ARGS_GSO}
+
+# Does not validate pacing delay yet. Check manually.
+echo "Ok. Executed tests."
