summaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2026-04-09 08:31:21 +0200
committerTakashi Iwai <tiwai@suse.de>2026-04-09 08:32:17 +0200
commit3f44bccdd6605d5d156c02ea2f861dcd30ef5dcd (patch)
tree799286d69c8ea6060573bce2efdc10ea089ed356 /tools/testing
parent292286b2d229fb732421429b027d38ac3f969383 (diff)
parent00afb1811fa638dacf125dd1c343b7a181624dfd (diff)
Merge branch 'for-linus' into for-next
Pull 7.0-devel branch for further development of HD-audio codec quirks. Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'tools/testing')
-rw-r--r--tools/testing/selftests/bpf/progs/verifier_precision.c341
-rw-r--r--tools/testing/selftests/cgroup/lib/cgroup_util.c15
-rw-r--r--tools/testing/selftests/cgroup/lib/include/cgroup_util.h2
-rw-r--r--tools/testing/selftests/cgroup/test_core.c3
-rw-r--r--tools/testing/selftests/cgroup/test_kill.c7
-rw-r--r--tools/testing/selftests/drivers/net/team/Makefile1
-rw-r--r--tools/testing/selftests/drivers/net/team/config2
-rwxr-xr-xtools/testing/selftests/drivers/net/team/non_ether_header_ops.sh41
-rw-r--r--tools/testing/selftests/kvm/Makefile.kvm1
-rw-r--r--tools/testing/selftests/kvm/s390/irq_routing.c75
-rw-r--r--tools/testing/selftests/landlock/tsync_test.c91
-rw-r--r--tools/testing/selftests/mount_setattr/mount_setattr_test.c2
-rwxr-xr-xtools/testing/selftests/net/fib_tests.sh61
-rwxr-xr-xtools/testing/selftests/net/netfilter/nft_concat_range.sh70
-rw-r--r--tools/testing/selftests/riscv/vector/validate_v_ptrace.c19
-rw-r--r--tools/testing/selftests/sched_ext/Makefile1
-rw-r--r--tools/testing/selftests/sched_ext/cyclic_kick_wait.bpf.c68
-rw-r--r--tools/testing/selftests/sched_ext/cyclic_kick_wait.c194
-rw-r--r--tools/testing/selftests/tc-testing/tc-tests/infra/filter.json44
-rw-r--r--tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json25
20 files changed, 1045 insertions, 18 deletions
diff --git a/tools/testing/selftests/bpf/progs/verifier_precision.c b/tools/testing/selftests/bpf/progs/verifier_precision.c
index 1fe090cd6744..4794903aec8e 100644
--- a/tools/testing/selftests/bpf/progs/verifier_precision.c
+++ b/tools/testing/selftests/bpf/progs/verifier_precision.c
@@ -5,6 +5,13 @@
#include "../../../include/linux/filter.h"
#include "bpf_misc.h"
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, __u64);
+} precision_map SEC(".maps");
+
SEC("?raw_tp")
__success __log_level(2)
__msg("mark_precise: frame0: regs=r2 stack= before 3: (bf) r1 = r10")
@@ -301,4 +308,338 @@ __naked int bpf_neg_5(void)
::: __clobber_all);
}
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_add((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_add_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_add_insn];" /* r2 = atomic_fetch_add(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_add_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_xchg((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_xchg_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[xchg_insn];" /* r2 = atomic_xchg(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(xchg_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_XCHG, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_or((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_or_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_or_insn];" /* r2 = atomic_fetch_or(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_or_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_OR | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_and((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_and_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_and_insn];" /* r2 = atomic_fetch_and(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_and_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_AND | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2 stack= before 4: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_xor((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_xor_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_xor_insn];" /* r2 = atomic_fetch_xor(*(u64 *)(r10 - 8), r2) */
+ "r3 = r10;"
+ "r3 += r2;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_xor_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_XOR | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r0 stack= before 5: (bf) r3 = r10")
+__msg("mark_precise: frame0: regs=r0 stack= before 4: (db) r0 = atomic64_cmpxchg((u64 *)(r10 -8), r0, r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 3: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r0 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_cmpxchg_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r0 = 0;"
+ "r2 = 0;"
+ ".8byte %[cmpxchg_insn];" /* r0 = atomic_cmpxchg(*(u64 *)(r10 - 8), r0, r2) */
+ "r3 = r10;"
+ "r3 += r0;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(cmpxchg_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+/* Regression test for dual precision: Both the fetched value (r2) and
+ * a reread of the same stack slot (r3) are tracked for precision. After
+ * the atomic operation, the stack slot is STACK_MISC. Thus, the ldx at
+ * insn 4 does NOT set INSN_F_STACK_ACCESS. Precision for the stack slot
+ * propagates solely through the atomic fetch's load side (insn 3).
+ */
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r2,r3 stack= before 4: (79) r3 = *(u64 *)(r10 -8)")
+__msg("mark_precise: frame0: regs=r2 stack= before 3: (db) r2 = atomic64_fetch_add((u64 *)(r10 -8), r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_fetch_add_dual_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = 0;"
+ ".8byte %[fetch_add_insn];" /* r2 = atomic_fetch_add(*(u64 *)(r10 - 8), r2) */
+ "r3 = *(u64 *)(r10 - 8);"
+ "r4 = r2;"
+ "r4 += r3;"
+ "r4 &= 7;"
+ "r5 = r10;"
+ "r5 += r4;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(fetch_add_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r0,r3 stack= before 5: (79) r3 = *(u64 *)(r10 -8)")
+__msg("mark_precise: frame0: regs=r0 stack= before 4: (db) r0 = atomic64_cmpxchg((u64 *)(r10 -8), r0, r2)")
+__msg("mark_precise: frame0: regs= stack=-8 before 3: (b7) r2 = 0")
+__msg("mark_precise: frame0: regs= stack=-8 before 2: (b7) r0 = 8")
+__msg("mark_precise: frame0: regs= stack=-8 before 1: (7b) *(u64 *)(r10 -8) = r1")
+__msg("mark_precise: frame0: regs=r1 stack= before 0: (b7) r1 = 8")
+__naked int bpf_atomic_cmpxchg_dual_precision(void)
+{
+ asm volatile (
+ "r1 = 8;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r0 = 8;"
+ "r2 = 0;"
+ ".8byte %[cmpxchg_insn];" /* r0 = atomic_cmpxchg(*(u64 *)(r10 - 8), r0, r2) */
+ "r3 = *(u64 *)(r10 - 8);"
+ "r4 = r0;"
+ "r4 += r3;"
+ "r4 &= 7;"
+ "r5 = r10;"
+ "r5 += r4;" /* mark_precise */
+ "r0 = 0;"
+ "exit;"
+ :
+ : __imm_insn(cmpxchg_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_10, BPF_REG_2, -8))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r1 stack= before 10: (57) r1 &= 7")
+__msg("mark_precise: frame0: regs=r1 stack= before 9: (db) r1 = atomic64_fetch_add((u64 *)(r0 +0), r1)")
+__not_msg("falling back to forcing all scalars precise")
+__naked int bpf_atomic_fetch_add_map_precision(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "r1 = %[precision_map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ "r1 = 0;"
+ ".8byte %[fetch_add_insn];" /* r1 = atomic_fetch_add(*(u64 *)(r0 + 0), r1) */
+ "r1 &= 7;"
+ "r2 = r10;"
+ "r2 += r1;" /* mark_precise */
+ "1: r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(precision_map),
+ __imm(bpf_map_lookup_elem),
+ __imm_insn(fetch_add_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_ADD | BPF_FETCH, BPF_REG_0, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r0 stack= before 12: (57) r0 &= 7")
+__msg("mark_precise: frame0: regs=r0 stack= before 11: (db) r0 = atomic64_cmpxchg((u64 *)(r6 +0), r0, r1)")
+__not_msg("falling back to forcing all scalars precise")
+__naked int bpf_atomic_cmpxchg_map_precision(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "r1 = %[precision_map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ "r6 = r0;"
+ "r0 = 0;"
+ "r1 = 0;"
+ ".8byte %[cmpxchg_insn];" /* r0 = atomic_cmpxchg(*(u64 *)(r6 + 0), r0, r1) */
+ "r0 &= 7;"
+ "r2 = r10;"
+ "r2 += r0;" /* mark_precise */
+ "1: r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(precision_map),
+ __imm(bpf_map_lookup_elem),
+ __imm_insn(cmpxchg_insn,
+ BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_6, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r1 stack= before 10: (57) r1 &= 7")
+__msg("mark_precise: frame0: regs=r1 stack= before 9: (c3) r1 = atomic_fetch_add((u32 *)(r0 +0), r1)")
+__not_msg("falling back to forcing all scalars precise")
+__naked int bpf_atomic_fetch_add_32bit_precision(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "r1 = %[precision_map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ "r1 = 0;"
+ ".8byte %[fetch_add_insn];" /* r1 = atomic_fetch_add(*(u32 *)(r0 + 0), r1) */
+ "r1 &= 7;"
+ "r2 = r10;"
+ "r2 += r1;" /* mark_precise */
+ "1: r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(precision_map),
+ __imm(bpf_map_lookup_elem),
+ __imm_insn(fetch_add_insn,
+ BPF_ATOMIC_OP(BPF_W, BPF_ADD | BPF_FETCH, BPF_REG_0, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
+SEC("?raw_tp")
+__success __log_level(2)
+__msg("mark_precise: frame0: regs=r0 stack= before 12: (57) r0 &= 7")
+__msg("mark_precise: frame0: regs=r0 stack= before 11: (c3) r0 = atomic_cmpxchg((u32 *)(r6 +0), r0, r1)")
+__not_msg("falling back to forcing all scalars precise")
+__naked int bpf_atomic_cmpxchg_32bit_precision(void)
+{
+ asm volatile (
+ "r1 = 0;"
+ "*(u64 *)(r10 - 8) = r1;"
+ "r2 = r10;"
+ "r2 += -8;"
+ "r1 = %[precision_map] ll;"
+ "call %[bpf_map_lookup_elem];"
+ "if r0 == 0 goto 1f;"
+ "r6 = r0;"
+ "r0 = 0;"
+ "r1 = 0;"
+ ".8byte %[cmpxchg_insn];" /* r0 = atomic_cmpxchg(*(u32 *)(r6 + 0), r0, r1) */
+ "r0 &= 7;"
+ "r2 = r10;"
+ "r2 += r0;" /* mark_precise */
+ "1: r0 = 0;"
+ "exit;"
+ :
+ : __imm_addr(precision_map),
+ __imm(bpf_map_lookup_elem),
+ __imm_insn(cmpxchg_insn,
+ BPF_ATOMIC_OP(BPF_W, BPF_CMPXCHG, BPF_REG_6, BPF_REG_1, 0))
+ : __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/cgroup/lib/cgroup_util.c b/tools/testing/selftests/cgroup/lib/cgroup_util.c
index ce6c2642fd9b..6a7295347e90 100644
--- a/tools/testing/selftests/cgroup/lib/cgroup_util.c
+++ b/tools/testing/selftests/cgroup/lib/cgroup_util.c
@@ -123,6 +123,21 @@ int cg_read_strcmp(const char *cgroup, const char *control,
return ret;
}
+int cg_read_strcmp_wait(const char *cgroup, const char *control,
+ const char *expected)
+{
+ int i, ret;
+
+ for (i = 0; i < 100; i++) {
+ ret = cg_read_strcmp(cgroup, control, expected);
+ if (!ret)
+ return ret;
+ usleep(10000);
+ }
+
+ return ret;
+}
+
int cg_read_strstr(const char *cgroup, const char *control, const char *needle)
{
char buf[PAGE_SIZE];
diff --git a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h
index 77f386dab5e8..567b1082974c 100644
--- a/tools/testing/selftests/cgroup/lib/include/cgroup_util.h
+++ b/tools/testing/selftests/cgroup/lib/include/cgroup_util.h
@@ -61,6 +61,8 @@ extern int cg_read(const char *cgroup, const char *control,
char *buf, size_t len);
extern int cg_read_strcmp(const char *cgroup, const char *control,
const char *expected);
+extern int cg_read_strcmp_wait(const char *cgroup, const char *control,
+ const char *expected);
extern int cg_read_strstr(const char *cgroup, const char *control,
const char *needle);
extern long cg_read_long(const char *cgroup, const char *control);
diff --git a/tools/testing/selftests/cgroup/test_core.c b/tools/testing/selftests/cgroup/test_core.c
index 102262555a59..7b83c7e7c9d4 100644
--- a/tools/testing/selftests/cgroup/test_core.c
+++ b/tools/testing/selftests/cgroup/test_core.c
@@ -233,7 +233,8 @@ static int test_cgcore_populated(const char *root)
if (err)
goto cleanup;
- if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
+ if (cg_read_strcmp_wait(cg_test_d, "cgroup.events",
+ "populated 0\n"))
goto cleanup;
/* Remove cgroup. */
diff --git a/tools/testing/selftests/cgroup/test_kill.c b/tools/testing/selftests/cgroup/test_kill.c
index c8c9d306925b..f6cd23a8ecc7 100644
--- a/tools/testing/selftests/cgroup/test_kill.c
+++ b/tools/testing/selftests/cgroup/test_kill.c
@@ -86,7 +86,7 @@ cleanup:
wait_for_pid(pids[i]);
if (ret == KSFT_PASS &&
- cg_read_strcmp(cgroup, "cgroup.events", "populated 0\n"))
+ cg_read_strcmp_wait(cgroup, "cgroup.events", "populated 0\n"))
ret = KSFT_FAIL;
if (cgroup)
@@ -190,7 +190,8 @@ cleanup:
wait_for_pid(pids[i]);
if (ret == KSFT_PASS &&
- cg_read_strcmp(cgroup[0], "cgroup.events", "populated 0\n"))
+ cg_read_strcmp_wait(cgroup[0], "cgroup.events",
+ "populated 0\n"))
ret = KSFT_FAIL;
for (i = 9; i >= 0 && cgroup[i]; i--) {
@@ -251,7 +252,7 @@ cleanup:
wait_for_pid(pid);
if (ret == KSFT_PASS &&
- cg_read_strcmp(cgroup, "cgroup.events", "populated 0\n"))
+ cg_read_strcmp_wait(cgroup, "cgroup.events", "populated 0\n"))
ret = KSFT_FAIL;
if (cgroup)
diff --git a/tools/testing/selftests/drivers/net/team/Makefile b/tools/testing/selftests/drivers/net/team/Makefile
index 45a3e7ad3dcb..02d6f51d5a06 100644
--- a/tools/testing/selftests/drivers/net/team/Makefile
+++ b/tools/testing/selftests/drivers/net/team/Makefile
@@ -3,6 +3,7 @@
TEST_PROGS := \
dev_addr_lists.sh \
+ non_ether_header_ops.sh \
options.sh \
propagation.sh \
refleak.sh \
diff --git a/tools/testing/selftests/drivers/net/team/config b/tools/testing/selftests/drivers/net/team/config
index 558e1d0cf565..5d36a22ef080 100644
--- a/tools/testing/selftests/drivers/net/team/config
+++ b/tools/testing/selftests/drivers/net/team/config
@@ -1,7 +1,9 @@
+CONFIG_BONDING=y
CONFIG_DUMMY=y
CONFIG_IPV6=y
CONFIG_MACVLAN=y
CONFIG_NETDEVSIM=m
+CONFIG_NET_IPGRE=y
CONFIG_NET_TEAM=y
CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=y
CONFIG_NET_TEAM_MODE_LOADBALANCE=y
diff --git a/tools/testing/selftests/drivers/net/team/non_ether_header_ops.sh b/tools/testing/selftests/drivers/net/team/non_ether_header_ops.sh
new file mode 100755
index 000000000000..948a43576bdc
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/team/non_ether_header_ops.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# shellcheck disable=SC2154
+#
+# Reproduce the non-Ethernet header_ops confusion scenario with:
+# g0 (gre) -> b0 (bond) -> t0 (team)
+#
+# Before the fix, direct header_ops inheritance in this stack could call
+# callbacks with the wrong net_device context and crash.
+
+lib_dir=$(dirname "$0")
+source "$lib_dir"/../../../net/lib.sh
+
+trap cleanup_all_ns EXIT
+
+setup_ns ns1
+
+ip -n "$ns1" link add d0 type dummy
+ip -n "$ns1" addr add 10.10.10.1/24 dev d0
+ip -n "$ns1" link set d0 up
+
+ip -n "$ns1" link add g0 type gre local 10.10.10.1
+ip -n "$ns1" link add b0 type bond mode active-backup
+ip -n "$ns1" link add t0 type team
+
+ip -n "$ns1" link set g0 master b0
+ip -n "$ns1" link set b0 master t0
+
+ip -n "$ns1" link set g0 up
+ip -n "$ns1" link set b0 up
+ip -n "$ns1" link set t0 up
+
+# IPv6 address assignment triggers MLD join reports that call
+# dev_hard_header() on t0, exercising the inherited header_ops path.
+ip -n "$ns1" -6 addr add 2001:db8:1::1/64 dev t0 nodad
+for i in $(seq 1 20); do
+ ip netns exec "$ns1" ping -6 -I t0 ff02::1 -c1 -W1 &>/dev/null || true
+done
+
+echo "PASS: non-Ethernet header_ops stacking did not crash"
+exit "$EXIT_STATUS"
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index dc68371f76a3..6471fa214a9f 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -206,6 +206,7 @@ TEST_GEN_PROGS_s390 += s390/ucontrol_test
TEST_GEN_PROGS_s390 += s390/user_operexec
TEST_GEN_PROGS_s390 += s390/keyop
TEST_GEN_PROGS_s390 += rseq_test
+TEST_GEN_PROGS_s390 += s390/irq_routing
TEST_GEN_PROGS_riscv = $(TEST_GEN_PROGS_COMMON)
TEST_GEN_PROGS_riscv += riscv/sbi_pmu_test
diff --git a/tools/testing/selftests/kvm/s390/irq_routing.c b/tools/testing/selftests/kvm/s390/irq_routing.c
new file mode 100644
index 000000000000..7819a0af19a8
--- /dev/null
+++ b/tools/testing/selftests/kvm/s390/irq_routing.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IRQ routing offset tests.
+ *
+ * Copyright IBM Corp. 2026
+ *
+ * Authors:
+ * Janosch Frank <frankja@linux.ibm.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "kselftest.h"
+#include "ucall_common.h"
+
+extern char guest_code[];
+asm("guest_code:\n"
+ "diag %r0,%r0,0\n"
+ "j .\n");
+
+static void test(void)
+{
+ struct kvm_irq_routing *routing;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ vm_paddr_t mem;
+ int ret;
+
+ struct kvm_irq_routing_entry ue = {
+ .type = KVM_IRQ_ROUTING_S390_ADAPTER,
+ .gsi = 1,
+ };
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+ mem = vm_phy_pages_alloc(vm, 2, 4096 * 42, 0);
+
+ routing = kvm_gsi_routing_create();
+ routing->nr = 1;
+ routing->entries[0] = ue;
+ routing->entries[0].u.adapter.summary_addr = (uintptr_t)mem;
+ routing->entries[0].u.adapter.ind_addr = (uintptr_t)mem;
+
+ routing->entries[0].u.adapter.summary_offset = 4096 * 8;
+ ret = __vm_ioctl(vm, KVM_SET_GSI_ROUTING, routing);
+ ksft_test_result(ret == -1 && errno == EINVAL, "summary offset outside of page\n");
+
+ routing->entries[0].u.adapter.summary_offset -= 4;
+ ret = __vm_ioctl(vm, KVM_SET_GSI_ROUTING, routing);
+ ksft_test_result(ret == 0, "summary offset inside of page\n");
+
+ routing->entries[0].u.adapter.ind_offset = 4096 * 8;
+ ret = __vm_ioctl(vm, KVM_SET_GSI_ROUTING, routing);
+ ksft_test_result(ret == -1 && errno == EINVAL, "ind offset outside of page\n");
+
+ routing->entries[0].u.adapter.ind_offset -= 4;
+ ret = __vm_ioctl(vm, KVM_SET_GSI_ROUTING, routing);
+ ksft_test_result(ret == 0, "ind offset inside of page\n");
+
+ kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+ TEST_REQUIRE(kvm_has_cap(KVM_CAP_IRQ_ROUTING));
+
+ ksft_print_header();
+ ksft_set_plan(4);
+ test();
+
+ ksft_finished(); /* Print results and exit() accordingly */
+}
diff --git a/tools/testing/selftests/landlock/tsync_test.c b/tools/testing/selftests/landlock/tsync_test.c
index 37ef0d2270db..2b9ad4f154f4 100644
--- a/tools/testing/selftests/landlock/tsync_test.c
+++ b/tools/testing/selftests/landlock/tsync_test.c
@@ -6,9 +6,10 @@
*/
#define _GNU_SOURCE
+#include <linux/landlock.h>
#include <pthread.h>
+#include <signal.h>
#include <sys/prctl.h>
-#include <linux/landlock.h>
#include "common.h"
@@ -158,4 +159,92 @@ TEST(competing_enablement)
EXPECT_EQ(0, close(ruleset_fd));
}
+static void signal_nop_handler(int sig)
+{
+}
+
+struct signaler_data {
+ pthread_t target;
+ volatile bool stop;
+};
+
+static void *signaler_thread(void *data)
+{
+ struct signaler_data *sd = data;
+
+ while (!sd->stop)
+ pthread_kill(sd->target, SIGUSR1);
+
+ return NULL;
+}
+
+/*
+ * Number of idle sibling threads. This must be large enough that even on
+ * machines with many cores, the sibling threads cannot all complete their
+ * credential preparation in a single parallel wave, otherwise the signaler
+ * thread has no window to interrupt wait_for_completion_interruptible().
+ * 200 threads on a 64-core machine yields ~3 serialized waves, giving the
+ * tight signal loop enough time to land an interruption.
+ */
+#define NUM_IDLE_THREADS 200
+
+/*
+ * Exercises the tsync interruption and cancellation paths in tsync.c.
+ *
+ * When a signal interrupts the calling thread while it waits for sibling
+ * threads to finish their credential preparation
+ * (wait_for_completion_interruptible in landlock_restrict_sibling_threads),
+ * the kernel sets ERESTARTNOINTR, cancels queued task works that have not
+ * started yet (cancel_tsync_works), then waits for the remaining works to
+ * finish. On the error return, syscalls.c aborts the prepared credentials.
+ * The kernel automatically restarts the syscall, so userspace sees success.
+ */
+TEST(tsync_interrupt)
+{
+ size_t i;
+ pthread_t threads[NUM_IDLE_THREADS];
+ pthread_t signaler;
+ struct signaler_data sd;
+ struct sigaction sa = {};
+ const int ruleset_fd = create_ruleset(_metadata);
+
+ disable_caps(_metadata);
+
+ /* Install a no-op SIGUSR1 handler so the signal does not kill us. */
+ sa.sa_handler = signal_nop_handler;
+ sigemptyset(&sa.sa_mask);
+ ASSERT_EQ(0, sigaction(SIGUSR1, &sa, NULL));
+
+ ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+
+ for (i = 0; i < NUM_IDLE_THREADS; i++)
+ ASSERT_EQ(0, pthread_create(&threads[i], NULL, idle, NULL));
+
+ /*
+ * Start a signaler thread that continuously sends SIGUSR1 to the
+ * calling thread. This maximizes the chance of interrupting
+ * wait_for_completion_interruptible() in the kernel's tsync path.
+ */
+ sd.target = pthread_self();
+ sd.stop = false;
+ ASSERT_EQ(0, pthread_create(&signaler, NULL, signaler_thread, &sd));
+
+ /*
+ * The syscall may be interrupted and transparently restarted by the
+ * kernel (ERESTARTNOINTR). From userspace, it should always succeed.
+ */
+ EXPECT_EQ(0, landlock_restrict_self(ruleset_fd,
+ LANDLOCK_RESTRICT_SELF_TSYNC));
+
+ sd.stop = true;
+ ASSERT_EQ(0, pthread_join(signaler, NULL));
+
+ for (i = 0; i < NUM_IDLE_THREADS; i++) {
+ ASSERT_EQ(0, pthread_cancel(threads[i]));
+ ASSERT_EQ(0, pthread_join(threads[i], NULL));
+ }
+
+ EXPECT_EQ(0, close(ruleset_fd));
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/mount_setattr/mount_setattr_test.c b/tools/testing/selftests/mount_setattr/mount_setattr_test.c
index 7aec3ae82a44..c6dafb3cc116 100644
--- a/tools/testing/selftests/mount_setattr/mount_setattr_test.c
+++ b/tools/testing/selftests/mount_setattr/mount_setattr_test.c
@@ -1020,7 +1020,7 @@ FIXTURE_SETUP(mount_setattr_idmapped)
"size=100000,mode=700"), 0);
ASSERT_EQ(mount("testing", "/mnt", "tmpfs", MS_NOATIME | MS_NODEV,
- "size=2m,mode=700"), 0);
+ "size=256m,mode=700"), 0);
ASSERT_EQ(mkdir("/mnt/A", 0777), 0);
diff --git a/tools/testing/selftests/net/fib_tests.sh b/tools/testing/selftests/net/fib_tests.sh
index c5694cc4ddd2..829f72c8ee07 100755
--- a/tools/testing/selftests/net/fib_tests.sh
+++ b/tools/testing/selftests/net/fib_tests.sh
@@ -868,6 +868,64 @@ fib6_gc_test()
check_rt_num 5 $($IP -6 route list |grep -v expires|grep 2001:20::|wc -l)
log_test $ret 0 "ipv6 route garbage collection (replace with permanent)"
+ # Delete dummy_10 and remove all routes
+ $IP link del dev dummy_10
+
+ # rd6 is required for the next test. (ipv6toolkit)
+ if [ ! -x "$(command -v rd6)" ]; then
+ echo "SKIP: rd6 not found."
+ set +e
+ cleanup &> /dev/null
+ return
+ fi
+
+ setup_ns ns2
+ $IP link add veth1 type veth peer veth2 netns $ns2
+ $IP link set veth1 up
+ ip -netns $ns2 link set veth2 up
+ $IP addr add fe80:dead::1/64 dev veth1
+ ip -netns $ns2 addr add fe80:dead::2/64 dev veth2
+
+ # Add NTF_ROUTER neighbour to prevent rt6_age_examine_exception()
+ # from removing not-yet-expired exceptions.
+ ip -netns $ns2 link set veth2 address 00:11:22:33:44:55
+ $IP neigh add fe80:dead::3 lladdr 00:11:22:33:44:55 dev veth1 router
+
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.accept_redirects=1
+ $NS_EXEC sysctl -wq net.ipv6.conf.veth1.forwarding=0
+
+ # Temporary routes
+ for i in $(seq 1 5); do
+ # Expire route after $EXPIRE seconds
+ $IP -6 route add 2001:10::$i \
+ via fe80:dead::2 dev veth1 expires $EXPIRE
+
+ ip netns exec $ns2 rd6 -i veth2 \
+ -s fe80:dead::2 -d fe80:dead::1 \
+ -r 2001:10::$i -t fe80:dead::3 -p ICMP6
+ done
+
+ check_rt_num 5 $($IP -6 route list | grep expires | grep 2001:10:: | wc -l)
+
+ # Promote to permanent routes by "prepend" (w/o NLM_F_EXCL and NLM_F_REPLACE)
+ for i in $(seq 1 5); do
+ # -EEXIST, but the temporary route becomes the permanent route.
+ $IP -6 route append 2001:10::$i \
+ via fe80:dead::2 dev veth1 2>/dev/null || true
+ done
+
+ check_rt_num 5 $($IP -6 route list | grep -v expires | grep 2001:10:: | wc -l)
+ check_rt_num 5 $($IP -6 route list cache | grep 2001:10:: | wc -l)
+
+ # Trigger GC instead of waiting $GC_WAIT_TIME.
+ # rt6_nh_dump_exceptions() just skips expired exceptions.
+ $NS_EXEC sysctl -wq net.ipv6.route.flush=1
+ check_rt_num 0 $($IP -6 route list cache | grep 2001:10:: | wc -l)
+ log_test $ret 0 "ipv6 route garbage collection (promote to permanent routes)"
+
+ $IP neigh del fe80:dead::3 lladdr 00:11:22:33:44:55 dev veth1 router
+ $IP link del veth1
+
# ra6 is required for the next test. (ipv6toolkit)
if [ ! -x "$(command -v ra6)" ]; then
echo "SKIP: ra6 not found."
@@ -876,9 +934,6 @@ fib6_gc_test()
return
fi
- # Delete dummy_10 and remove all routes
- $IP link del dev dummy_10
-
# Create a pair of veth devices to send a RA message from one
# device to another.
$IP link add veth1 type veth peer name veth2
diff --git a/tools/testing/selftests/net/netfilter/nft_concat_range.sh b/tools/testing/selftests/net/netfilter/nft_concat_range.sh
index 394166f224a4..ffdc6ccc6511 100755
--- a/tools/testing/selftests/net/netfilter/nft_concat_range.sh
+++ b/tools/testing/selftests/net/netfilter/nft_concat_range.sh
@@ -29,7 +29,8 @@ TYPES="net_port port_net net6_port port_proto net6_port_mac net6_port_mac_proto
net6_port_net6_port net_port_mac_proto_net"
# Reported bugs, also described by TYPE_ variables below
-BUGS="flush_remove_add reload net_port_proto_match avx2_mismatch doublecreate insert_overlap"
+BUGS="flush_remove_add reload net_port_proto_match avx2_mismatch doublecreate
+ insert_overlap load_flush_load4 load_flush_load8"
# List of possible paths to pktgen script from kernel tree for performance tests
PKTGEN_SCRIPT_PATHS="
@@ -432,6 +433,30 @@ race_repeat 0
perf_duration 0
"
+TYPE_load_flush_load4="
+display reload with flush, 4bit groups
+type_spec ipv4_addr . ipv4_addr
+chain_spec ip saddr . ip daddr
+dst addr4
+proto icmp
+
+race_repeat 0
+
+perf_duration 0
+"
+
+TYPE_load_flush_load8="
+display reload with flush, 8bit groups
+type_spec ipv4_addr . ipv4_addr
+chain_spec ip saddr . ip daddr
+dst addr4
+proto icmp
+
+race_repeat 0
+
+perf_duration 0
+"
+
# Set template for all tests, types and rules are filled in depending on test
set_template='
flush ruleset
@@ -1997,6 +2022,49 @@ test_bug_insert_overlap()
return 0
}
+test_bug_load_flush_load4()
+{
+ local i
+
+ setup veth send_"${proto}" set || return ${ksft_skip}
+
+ for i in $(seq 0 255); do
+ local addelem="add element inet filter test"
+ local j
+
+ for j in $(seq 0 20); do
+ echo "$addelem { 10.$j.0.$i . 10.$j.1.$i }"
+ echo "$addelem { 10.$j.0.$i . 10.$j.2.$i }"
+ done
+ done > "$tmp"
+
+ nft -f "$tmp" || return 1
+
+ ( echo "flush set inet filter test";cat "$tmp") | nft -f -
+ [ $? -eq 0 ] || return 1
+
+ return 0
+}
+
+test_bug_load_flush_load8()
+{
+ local i
+
+ setup veth send_"${proto}" set || return ${ksft_skip}
+
+ for i in $(seq 1 100); do
+ echo "add element inet filter test { 10.0.0.$i . 10.0.1.$i }"
+ echo "add element inet filter test { 10.0.0.$i . 10.0.2.$i }"
+ done > "$tmp"
+
+ nft -f "$tmp" || return 1
+
+ ( echo "flush set inet filter test";cat "$tmp") | nft -f -
+ [ $? -eq 0 ] || return 1
+
+ return 0
+}
+
test_reported_issues() {
eval test_bug_"${subtest}"
}
diff --git a/tools/testing/selftests/riscv/vector/validate_v_ptrace.c b/tools/testing/selftests/riscv/vector/validate_v_ptrace.c
index 3589549f7228..74b6f6bcf067 100644
--- a/tools/testing/selftests/riscv/vector/validate_v_ptrace.c
+++ b/tools/testing/selftests/riscv/vector/validate_v_ptrace.c
@@ -290,10 +290,11 @@ TEST(ptrace_v_syscall_clobbering)
/* verify initial vsetvli settings */
- if (is_xtheadvector_supported())
+ if (is_xtheadvector_supported()) {
EXPECT_EQ(5UL, regset_data->vtype);
- else
+ } else {
EXPECT_EQ(9UL, regset_data->vtype);
+ }
EXPECT_EQ(regset_data->vlenb, regset_data->vl);
EXPECT_EQ(vlenb, regset_data->vlenb);
@@ -346,8 +347,8 @@ FIXTURE_TEARDOWN(v_csr_invalid)
{
}
-#define VECTOR_1_0 BIT(0)
-#define XTHEAD_VECTOR_0_7 BIT(1)
+#define VECTOR_1_0 _BITUL(0)
+#define XTHEAD_VECTOR_0_7 _BITUL(1)
#define vector_test(x) ((x) & VECTOR_1_0)
#define xthead_test(x) ((x) & XTHEAD_VECTOR_0_7)
@@ -619,10 +620,11 @@ TEST_F(v_csr_invalid, ptrace_v_invalid_values)
/* verify initial vsetvli settings */
- if (is_xtheadvector_supported())
+ if (is_xtheadvector_supported()) {
EXPECT_EQ(5UL, regset_data->vtype);
- else
+ } else {
EXPECT_EQ(9UL, regset_data->vtype);
+ }
EXPECT_EQ(regset_data->vlenb, regset_data->vl);
EXPECT_EQ(vlenb, regset_data->vlenb);
@@ -827,10 +829,11 @@ TEST_F(v_csr_valid, ptrace_v_valid_values)
/* verify initial vsetvli settings */
- if (is_xtheadvector_supported())
+ if (is_xtheadvector_supported()) {
EXPECT_EQ(5UL, regset_data->vtype);
- else
+ } else {
EXPECT_EQ(9UL, regset_data->vtype);
+ }
EXPECT_EQ(regset_data->vlenb, regset_data->vl);
EXPECT_EQ(vlenb, regset_data->vlenb);
diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile
index 006300ac6dff..1c9ca328cca1 100644
--- a/tools/testing/selftests/sched_ext/Makefile
+++ b/tools/testing/selftests/sched_ext/Makefile
@@ -188,6 +188,7 @@ auto-test-targets := \
rt_stall \
test_example \
total_bw \
+ cyclic_kick_wait \
testcase-targets := $(addsuffix .o,$(addprefix $(SCXOBJ_DIR)/,$(auto-test-targets)))
diff --git a/tools/testing/selftests/sched_ext/cyclic_kick_wait.bpf.c b/tools/testing/selftests/sched_ext/cyclic_kick_wait.bpf.c
new file mode 100644
index 000000000000..cb34d3335917
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/cyclic_kick_wait.bpf.c
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Stress concurrent SCX_KICK_WAIT calls to reproduce wait-cycle deadlock.
+ *
+ * Three CPUs are designated from userspace. Every enqueue from one of the
+ * three CPUs kicks the next CPU in the ring with SCX_KICK_WAIT, creating a
+ * persistent A -> B -> C -> A wait cycle pressure.
+ */
+#include <scx/common.bpf.h>
+
+char _license[] SEC("license") = "GPL";
+
+const volatile s32 test_cpu_a;
+const volatile s32 test_cpu_b;
+const volatile s32 test_cpu_c;
+
+u64 nr_enqueues;
+u64 nr_wait_kicks;
+
+UEI_DEFINE(uei);
+
+static s32 target_cpu(s32 cpu)
+{
+ if (cpu == test_cpu_a)
+ return test_cpu_b;
+ if (cpu == test_cpu_b)
+ return test_cpu_c;
+ if (cpu == test_cpu_c)
+ return test_cpu_a;
+ return -1;
+}
+
+void BPF_STRUCT_OPS(cyclic_kick_wait_enqueue, struct task_struct *p,
+ u64 enq_flags)
+{
+ s32 this_cpu = bpf_get_smp_processor_id();
+ s32 tgt;
+
+ __sync_fetch_and_add(&nr_enqueues, 1);
+
+ if (p->flags & PF_KTHREAD) {
+ scx_bpf_dsq_insert(p, SCX_DSQ_LOCAL, SCX_SLICE_INF,
+ enq_flags | SCX_ENQ_PREEMPT);
+ return;
+ }
+
+ scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags);
+
+ tgt = target_cpu(this_cpu);
+ if (tgt < 0 || tgt == this_cpu)
+ return;
+
+ __sync_fetch_and_add(&nr_wait_kicks, 1);
+ scx_bpf_kick_cpu(tgt, SCX_KICK_WAIT);
+}
+
+void BPF_STRUCT_OPS(cyclic_kick_wait_exit, struct scx_exit_info *ei)
+{
+ UEI_RECORD(uei, ei);
+}
+
+SEC(".struct_ops.link")
+struct sched_ext_ops cyclic_kick_wait_ops = {
+ .enqueue = cyclic_kick_wait_enqueue,
+ .exit = cyclic_kick_wait_exit,
+ .name = "cyclic_kick_wait",
+ .timeout_ms = 1000U,
+};
diff --git a/tools/testing/selftests/sched_ext/cyclic_kick_wait.c b/tools/testing/selftests/sched_ext/cyclic_kick_wait.c
new file mode 100644
index 000000000000..c2e5aa9de715
--- /dev/null
+++ b/tools/testing/selftests/sched_ext/cyclic_kick_wait.c
@@ -0,0 +1,194 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Test SCX_KICK_WAIT forward progress under cyclic wait pressure.
+ *
+ * SCX_KICK_WAIT busy-waits until the target CPU enters the scheduling path.
+ * If multiple CPUs form a wait cycle (A waits for B, B waits for C, C waits
+ * for A), all CPUs deadlock unless the implementation breaks the cycle.
+ *
+ * This test creates that scenario: three CPUs are arranged in a ring. The BPF
+ * scheduler's ops.enqueue() kicks the next CPU in the ring with SCX_KICK_WAIT
+ * on every enqueue. Userspace pins 4 worker threads per CPU that loop calling
+ * sched_yield(), generating a steady stream of enqueues and thus sustained
+ * A->B->C->A kick_wait cycle pressure. The test passes if the system remains
+ * responsive for 5 seconds without the scheduler being killed by the watchdog.
+ */
+#define _GNU_SOURCE
+
+#include <bpf/bpf.h>
+#include <errno.h>
+#include <pthread.h>
+#include <sched.h>
+#include <scx/common.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "scx_test.h"
+#include "cyclic_kick_wait.bpf.skel.h"
+
+#define WORKERS_PER_CPU 4
+#define NR_TEST_CPUS 3
+#define NR_WORKERS (NR_TEST_CPUS * WORKERS_PER_CPU)
+
+struct worker_ctx {
+ pthread_t tid;
+ int cpu;
+ volatile bool stop;
+ volatile __u64 iters;
+ bool started;
+};
+
+static void *worker_fn(void *arg)
+{
+ struct worker_ctx *worker = arg;
+ cpu_set_t mask;
+
+ CPU_ZERO(&mask);
+ CPU_SET(worker->cpu, &mask);
+
+ if (sched_setaffinity(0, sizeof(mask), &mask))
+ return (void *)(uintptr_t)errno;
+
+ while (!worker->stop) {
+ sched_yield();
+ worker->iters++;
+ }
+
+ return NULL;
+}
+
+static int join_worker(struct worker_ctx *worker)
+{
+ void *ret;
+ struct timespec ts;
+ int err;
+
+ if (!worker->started)
+ return 0;
+
+ if (clock_gettime(CLOCK_REALTIME, &ts))
+ return -errno;
+
+ ts.tv_sec += 2;
+ err = pthread_timedjoin_np(worker->tid, &ret, &ts);
+ if (err == ETIMEDOUT)
+ pthread_detach(worker->tid);
+ if (err)
+ return -err;
+
+ if ((uintptr_t)ret)
+ return -(int)(uintptr_t)ret;
+
+ return 0;
+}
+
+static enum scx_test_status setup(void **ctx)
+{
+ struct cyclic_kick_wait *skel;
+
+ skel = cyclic_kick_wait__open();
+ SCX_FAIL_IF(!skel, "Failed to open skel");
+ SCX_ENUM_INIT(skel);
+
+ *ctx = skel;
+ return SCX_TEST_PASS;
+}
+
+static enum scx_test_status run(void *ctx)
+{
+ struct cyclic_kick_wait *skel = ctx;
+ struct worker_ctx workers[NR_WORKERS] = {};
+ struct bpf_link *link = NULL;
+ enum scx_test_status status = SCX_TEST_PASS;
+ int test_cpus[NR_TEST_CPUS];
+ int nr_cpus = 0;
+ cpu_set_t mask;
+ int ret, i;
+
+ if (sched_getaffinity(0, sizeof(mask), &mask)) {
+ SCX_ERR("Failed to get affinity (%d)", errno);
+ return SCX_TEST_FAIL;
+ }
+
+ for (i = 0; i < CPU_SETSIZE; i++) {
+ if (CPU_ISSET(i, &mask))
+ test_cpus[nr_cpus++] = i;
+ if (nr_cpus == NR_TEST_CPUS)
+ break;
+ }
+
+ if (nr_cpus < NR_TEST_CPUS)
+ return SCX_TEST_SKIP;
+
+ skel->rodata->test_cpu_a = test_cpus[0];
+ skel->rodata->test_cpu_b = test_cpus[1];
+ skel->rodata->test_cpu_c = test_cpus[2];
+
+ if (cyclic_kick_wait__load(skel)) {
+ SCX_ERR("Failed to load skel");
+ return SCX_TEST_FAIL;
+ }
+
+ link = bpf_map__attach_struct_ops(skel->maps.cyclic_kick_wait_ops);
+ if (!link) {
+ SCX_ERR("Failed to attach scheduler");
+ return SCX_TEST_FAIL;
+ }
+
+ for (i = 0; i < NR_WORKERS; i++)
+ workers[i].cpu = test_cpus[i / WORKERS_PER_CPU];
+
+ for (i = 0; i < NR_WORKERS; i++) {
+ ret = pthread_create(&workers[i].tid, NULL, worker_fn, &workers[i]);
+ if (ret) {
+ SCX_ERR("Failed to create worker thread %d (%d)", i, ret);
+ status = SCX_TEST_FAIL;
+ goto out;
+ }
+ workers[i].started = true;
+ }
+
+ sleep(5);
+
+ if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) {
+ SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)",
+ (unsigned long long)skel->data->uei.kind,
+ (long long)skel->data->uei.exit_code);
+ status = SCX_TEST_FAIL;
+ }
+
+out:
+ for (i = 0; i < NR_WORKERS; i++)
+ workers[i].stop = true;
+
+ for (i = 0; i < NR_WORKERS; i++) {
+ ret = join_worker(&workers[i]);
+ if (ret && status == SCX_TEST_PASS) {
+ SCX_ERR("Failed to join worker thread %d (%d)", i, ret);
+ status = SCX_TEST_FAIL;
+ }
+ }
+
+ if (link)
+ bpf_link__destroy(link);
+
+ return status;
+}
+
+static void cleanup(void *ctx)
+{
+ struct cyclic_kick_wait *skel = ctx;
+
+ cyclic_kick_wait__destroy(skel);
+}
+
+struct scx_test cyclic_kick_wait = {
+ .name = "cyclic_kick_wait",
+ .description = "Verify SCX_KICK_WAIT forward progress under a 3-CPU wait cycle",
+ .setup = setup,
+ .run = run,
+ .cleanup = cleanup,
+};
+REGISTER_SCX_TEST(&cyclic_kick_wait)
diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/filter.json b/tools/testing/selftests/tc-testing/tc-tests/infra/filter.json
index 8d10042b489b..dbce6436ed26 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/infra/filter.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/infra/filter.json
@@ -22,5 +22,49 @@
"teardown": [
"$TC qdisc del dev $DUMMY root handle 1: htb default 1"
]
+ },
+ {
+ "id": "b7e3",
+ "name": "Empty fw filter on shared block - rejected at config time",
+ "category": [
+ "filter",
+ "fw"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "$TC qdisc add dev $DEV1 egress_block 1 clsact"
+ ],
+ "cmdUnderTest": "$TC filter add block 1 protocol ip prio 1 fw",
+ "expExitCode": "2",
+ "verifyCmd": "$TC filter show block 1",
+ "matchPattern": "fw",
+ "matchCount": "0",
+ "teardown": [
+ "$TC qdisc del dev $DEV1 clsact"
+ ]
+ },
+ {
+ "id": "c8f4",
+ "name": "Flow filter on shared block without baseclass - rejected at config time",
+ "category": [
+ "filter",
+ "flow"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "$TC qdisc add dev $DEV1 ingress_block 1 clsact"
+ ],
+ "cmdUnderTest": "$TC filter add block 1 protocol ip prio 1 handle 1 flow map key dst",
+ "expExitCode": "2",
+ "verifyCmd": "$TC filter show block 1",
+ "matchPattern": "flow",
+ "matchCount": "0",
+ "teardown": [
+ "$TC qdisc del dev $DEV1 clsact"
+ ]
}
]
diff --git a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
index 6a39640aa2a8..1e5efb2a31eb 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/infra/qdiscs.json
@@ -1111,5 +1111,30 @@
"teardown": [
"$TC qdisc del dev $DUMMY root handle 1:"
]
+ },
+ {
+ "id": "a3d7",
+ "name": "HFSC with large m1 - no divide-by-zero on class reactivation",
+ "category": [
+ "qdisc",
+ "hfsc"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "$TC qdisc replace dev $DUMMY root handle 1: hfsc default 1",
+ "$TC class replace dev $DUMMY parent 1: classid 1:1 hfsc rt m1 32gbit d 1ms m2 0bit ls m1 32gbit d 1ms m2 0bit",
+ "ping -I$DUMMY -f -c1 -s64 -W1 10.10.10.1 || true",
+ "sleep 1"
+ ],
+ "cmdUnderTest": "ping -I$DUMMY -f -c1 -s64 -W1 10.10.10.1 || true",
+ "expExitCode": "0",
+ "verifyCmd": "$TC qdisc show dev $DUMMY",
+ "matchPattern": "qdisc hfsc 1: root",
+ "matchCount": "1",
+ "teardown": [
+ "$TC qdisc del dev $DUMMY handle 1: root"
+ ]
}
]