mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/2] sched_ext: Honor SCX_PICK_IDLE_CORE in CPU selection shortcuts
@ 2026-10-12  6:51 Hui Su
  2026-10-12  6:51 ` [PATCH 1/2] sched_ext: idle: " Hui Su
  2026-10-12  6:51 ` [PATCH 2/2] selftests/sched_ext: Cover strict-core " Hui Su
  0 siblings, 2 replies; 3+ messages in thread
From: Hui Su @ 2026-10-12  6:51 UTC (permalink / raw)
  To: tj
  Cc: void, arighi, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, sched-ext, linux-kernel, bpf, sh_def,
	stable

The restricted select_cpu kfunc path and the normal WAKE_SYNC shortcuts can
bypass SCX_PICK_IDLE_CORE on SMT systems. The first patch skips the WAKE_SYNC
shortcuts when strict-core selection is requested and validates restricted
candidates against their idle SMT mask. The second patch covers the restricted
path and both WAKE_SYNC shortcuts while retaining the generic smoke test when
no SMT pair is available.

This series is based on:

  [PATCH v3] sched_ext: idle: Respect task affinity in restricted CPU selection
  https://lore.kernel.org/lkml/20261011173100.2090211-1-sh_def@163.com/

Testing:
- x86_64 KVM/QEMU: all three restricted/WAKE_SYNC differential cases
  reproduced the strict-core violation before the fix and returned -EBUSY
  after it.
- The sched_ext selftests passed apart from an unrelated dsp_local_on
  environment failure while opening system Kconfig.
- The strict-core test passed 50 consecutive runs; non-SMT coverage retained
  the generic smoke test and skipped the SMT-specific cases.

Hui Su (2):
  sched_ext: idle: Honor SCX_PICK_IDLE_CORE in CPU selection shortcuts
  selftests/sched_ext: Cover strict-core CPU selection shortcuts

 kernel/sched/ext/idle.c                       |   6 +-
 .../selftests/sched_ext/allowed_cpus.bpf.c    |  36 +-
 .../selftests/sched_ext/allowed_cpus.c        | 316 +++++++++++++++++-
 3 files changed, 347 insertions(+), 11 deletions(-)


base-commit: 9a2d086eba0f331eb5940d6a91efa7caa39080ad
-- 
2.55.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] sched_ext: idle: Honor SCX_PICK_IDLE_CORE in CPU selection shortcuts
  2026-10-12  6:51 [PATCH 0/2] sched_ext: Honor SCX_PICK_IDLE_CORE in CPU selection shortcuts Hui Su
@ 2026-10-12  6:51 ` Hui Su
  2026-10-12  6:51 ` [PATCH 2/2] selftests/sched_ext: Cover strict-core " Hui Su
  1 sibling, 0 replies; 3+ messages in thread
From: Hui Su @ 2026-10-12  6:51 UTC (permalink / raw)
  To: tj
  Cc: void, arighi, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, sched-ext, linux-kernel, bpf, sh_def,
	stable

SCX_PICK_IDLE_CORE requires selecting a CPU whose SMT siblings are all
idle. The restricted select_cpu kfunc path now derives its candidate from
task state, but it does not check that condition before claiming the CPU.

The WAKE_SYNC shortcuts in scx_select_cpu_dfl() can bypass strict-core
selection on SMT systems as well. Skip those shortcuts when
SCX_PICK_IDLE_CORE is requested, and validate restricted candidates against
their idle SMT mask before consuming the idle bit.

Preserve individual-idle selection when the flag is not set and on
non-SMT systems.

Fixes: 683d2d0faba1 ("sched_ext: idle: Introduce scx_bpf_select_cpu_and()")
Cc: stable@vger.kernel.org
Signed-off-by: Hui Su <sh_def@163.com>
---
 kernel/sched/ext/idle.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c
index 28ed9e327085..09e40928aef4 100644
--- a/kernel/sched/ext/idle.c
+++ b/kernel/sched/ext/idle.c
@@ -523,7 +523,7 @@ s32 scx_select_cpu_dfl(struct task_struct *p, s32 prev_cpu, u64 wake_flags,
 	/*
 	 * If WAKE_SYNC, try to migrate the wakee to the waker's CPU.
 	 */
-	if (wake_flags & SCX_WAKE_SYNC) {
+	if ((wake_flags & SCX_WAKE_SYNC) && !(sched_smt_active() && (flags & SCX_PICK_IDLE_CORE))) {
 		int waker_node;
 
 		/*
@@ -976,6 +976,10 @@ static s32 select_cpu_restricted(struct task_struct *p, s32 prev_cpu,
 			return -EBUSY;
 	}
 
+	if ((flags & SCX_PICK_IDLE_CORE) && sched_smt_active() &&
+	    !cpumask_test_cpu(cpu, idle_cpumask(scx_cpu_node_if_enabled(cpu))->smt))
+		return -EBUSY;
+
 	if (!scx_idle_test_and_clear_cpu(cpu))
 		return -EBUSY;
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 2/2] selftests/sched_ext: Cover strict-core CPU selection shortcuts
  2026-10-12  6:51 [PATCH 0/2] sched_ext: Honor SCX_PICK_IDLE_CORE in CPU selection shortcuts Hui Su
  2026-10-12  6:51 ` [PATCH 1/2] sched_ext: idle: " Hui Su
@ 2026-10-12  6:51 ` Hui Su
  1 sibling, 0 replies; 3+ messages in thread
From: Hui Su @ 2026-10-12  6:51 UTC (permalink / raw)
  To: tj
  Cc: void, arighi, changwoo, mingo, peterz, juri.lelli,
	vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
	vschneid, kprateek.nayak, sched-ext, linux-kernel, bpf, sh_def,
	stable

Exercise scx_bpf_select_cpu_and() with a task whose candidate CPU is
individually idle while its SMT sibling is occupied by the test runner.
Verify that SCX_PICK_IDLE_CORE rejects the candidate, while the same
restricted path without the flag still selects it.

Also cover both WAKE_SYNC shortcuts with SCX_PICK_IDLE_CORE set, including
the waker-CPU path. The test uses a separate sleeping target task and
private allowed masks so strict-core checks do not depend on task-affinity
intersection or mutate the scheduler's configured mask.

Retain the generic user-space smoke test when no SMT pair is available, and
skip only the strict-core cases in that configuration.

Signed-off-by: Hui Su <sh_def@163.com>
---
 .../selftests/sched_ext/allowed_cpus.bpf.c    |  36 +-
 .../selftests/sched_ext/allowed_cpus.c        | 316 +++++++++++++++++-
 2 files changed, 342 insertions(+), 10 deletions(-)

diff --git a/tools/testing/selftests/sched_ext/allowed_cpus.bpf.c b/tools/testing/selftests/sched_ext/allowed_cpus.bpf.c
index 9dd72d0da29b..4e0a798b696e 100644
--- a/tools/testing/selftests/sched_ext/allowed_cpus.bpf.c
+++ b/tools/testing/selftests/sched_ext/allowed_cpus.bpf.c
@@ -148,22 +148,54 @@ void BPF_STRUCT_OPS(allowed_cpus_exit, struct scx_exit_info *ei)
 
 struct task_cpu_arg {
 	pid_t pid;
+	int prev_cpu;
+	int allowed_cpu;
+	__u64 wake_flags;
+	__u64 flags;
+	__u32 check_idle;
 };
 
 SEC("syscall")
 int select_cpu_from_user(struct task_cpu_arg *input)
 {
+	const struct cpumask *idle;
 	struct task_struct *p;
+	struct bpf_cpumask *mask;
 	int cpu;
 
 	p = bpf_task_from_pid(input->pid);
 	if (!p)
 		return -EINVAL;
 
+	mask = bpf_cpumask_create();
+	if (!mask) {
+		bpf_task_release(p);
+		return -ENOMEM;
+	}
+
+	if (input->allowed_cpu < 0)
+		bpf_cpumask_setall(mask);
+	else
+		bpf_cpumask_set_cpu(input->allowed_cpu, mask);
+
 	bpf_rcu_read_lock();
-	cpu = scx_bpf_select_cpu_and(p, bpf_get_smp_processor_id(), 0, p->cpus_ptr, 0);
-	bpf_rcu_read_unlock();
+	if (input->check_idle) {
+		idle = scx_bpf_get_idle_cpumask();
+		if (!bpf_cpumask_test_cpu(input->prev_cpu, idle)) {
+			scx_bpf_put_idle_cpumask(idle);
+			bpf_rcu_read_unlock();
+			bpf_cpumask_release(mask);
+			bpf_task_release(p);
+			return -EAGAIN;
+		}
+		scx_bpf_put_idle_cpumask(idle);
+	}
 
+	cpu = scx_bpf_select_cpu_and(p, input->prev_cpu, input->wake_flags,
+				     cast_mask(mask), input->flags);
+
+	bpf_rcu_read_unlock();
+	bpf_cpumask_release(mask);
 	bpf_task_release(p);
 
 	return cpu;
diff --git a/tools/testing/selftests/sched_ext/allowed_cpus.c b/tools/testing/selftests/sched_ext/allowed_cpus.c
index 093f285ab4ba..8e67c7598c4d 100644
--- a/tools/testing/selftests/sched_ext/allowed_cpus.c
+++ b/tools/testing/selftests/sched_ext/allowed_cpus.c
@@ -3,7 +3,11 @@
  * Copyright (c) 2025 Andrea Righi <arighi@nvidia.com>
  */
 #include <bpf/bpf.h>
+#include <linux/kernel.h>
 #include <scx/common.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdlib.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #include "allowed_cpus.bpf.skel.h"
@@ -23,17 +27,95 @@ static enum scx_test_status setup(void **ctx)
 	return SCX_TEST_PASS;
 }
 
-static int test_select_cpu_from_user(const struct allowed_cpus *skel)
+struct task_cpu_arg {
+	pid_t pid;
+	int prev_cpu;
+	int allowed_cpu;
+	uint64_t wake_flags;
+	uint64_t flags;
+	uint32_t check_idle;
+};
+
+static int find_smt_sibling(const char *list, int cpu,
+			    const cpu_set_t *affinity, int *sibling)
+{
+	const char *pos = list;
+
+	while (*pos) {
+		char *end;
+		long first = strtol(pos, &end, 10);
+		long last = first;
+		long candidate;
+
+		if (end == pos)
+			break;
+		pos = end;
+		if (*pos == '-') {
+			last = strtol(pos + 1, &end, 10);
+			if (end == pos + 1)
+				break;
+			pos = end;
+		}
+
+		for (candidate = first; candidate <= last; candidate++) {
+			if (candidate != cpu && candidate >= 0 &&
+			    candidate < CPU_SETSIZE &&
+			    CPU_ISSET(candidate, affinity)) {
+				*sibling = candidate;
+				return 0;
+			}
+		}
+
+		while (*pos && *pos != ',')
+			pos++;
+		if (*pos == ',')
+			pos++;
+	}
+
+	return -ENOENT;
+}
+
+static int find_smt_pair(const cpu_set_t *affinity, int *first, int *second)
+{
+	char path[128], list[128];
+	FILE *file;
+	int cpu;
+
+	for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
+		if (!CPU_ISSET(cpu, affinity))
+			continue;
+
+		snprintf(path, sizeof(path),
+			 "/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list",
+			 cpu);
+		file = fopen(path, "r");
+		if (!file)
+			continue;
+		if (!fgets(list, sizeof(list), file)) {
+			fclose(file);
+			continue;
+		}
+		fclose(file);
+
+		if (!find_smt_sibling(list, cpu, affinity, second)) {
+			*first = cpu;
+			return 0;
+		}
+	}
+
+	return -ENOENT;
+}
+
+static int test_select_cpu_from_user(const struct allowed_cpus *skel,
+				     struct task_cpu_arg *input, int *result)
 {
 	int fd, ret;
-	__u64 args[1];
 
 	LIBBPF_OPTS(bpf_test_run_opts, attr,
-		.ctx_in = args,
-		.ctx_size_in = sizeof(args),
+		.ctx_in = input,
+		.ctx_size_in = sizeof(*input),
 	);
 
-	args[0] = getpid();
 	fd = bpf_program__fd(skel->progs.select_cpu_from_user);
 	if (fd < 0)
 		return fd;
@@ -42,22 +124,240 @@ static int test_select_cpu_from_user(const struct allowed_cpus *skel)
 	if (ret < 0)
 		return ret;
 
-	fprintf(stderr, "%s: CPU %d\n", __func__, attr.retval);
+	*result = (int)attr.retval;
+	fprintf(stderr, "%s: prev=%d allowed=%d wake=0x%llx flags=0x%llx result=%d\n",
+		__func__, input->prev_cpu, input->allowed_cpu,
+		(unsigned long long)input->wake_flags,
+		(unsigned long long)input->flags, *result);
 
 	return 0;
 }
 
+static pid_t start_target(const cpu_set_t *affinity)
+{
+	char ready;
+	int pipefd[2];
+	ssize_t n;
+	int err;
+	pid_t pid;
+
+	if (pipe(pipefd))
+		return -1;
+
+	pid = fork();
+	if (pid < 0) {
+		close(pipefd[0]);
+		close(pipefd[1]);
+		return -1;
+	}
+	if (!pid) {
+		close(pipefd[0]);
+		/* Let stop_target() terminate the sleeping child. */
+		(void)signal(SIGTERM, SIG_DFL);
+		if (sched_setaffinity(0, sizeof(*affinity), affinity) ||
+		    write(pipefd[1], "R", 1) != 1)
+			_exit(1);
+		close(pipefd[1]);
+		for (;;)
+			pause();
+	}
+
+	close(pipefd[1]);
+	n = read(pipefd[0], &ready, 1);
+	if (n != 1) {
+		err = n < 0 ? errno : EIO;
+		close(pipefd[0]);
+		kill(pid, SIGTERM);
+		waitpid(pid, NULL, 0);
+		errno = err;
+		return -1;
+	}
+	close(pipefd[0]);
+
+	return pid;
+}
+
+static void stop_target(pid_t pid)
+{
+	if (pid > 0) {
+		kill(pid, SIGTERM);
+		waitpid(pid, NULL, 0);
+	}
+}
+
+static int pin_current_cpu(int cpu)
+{
+	cpu_set_t affinity;
+
+	CPU_ZERO(&affinity);
+	CPU_SET(cpu, &affinity);
+	return sched_setaffinity(0, sizeof(affinity), &affinity);
+}
+
+static int expect_selection(const struct allowed_cpus *skel,
+			    const struct task_cpu_arg *input, int expected)
+{
+	struct task_cpu_arg attempt = *input;
+	int result, ret, i;
+
+	for (i = 0; i < 64; i++) {
+		ret = test_select_cpu_from_user(skel, &attempt, &result);
+		if (ret)
+			return ret;
+		ret = result;
+		if (ret == -EAGAIN || (expected >= 0 && ret == -EBUSY)) {
+			usleep(1000);
+			continue;
+		}
+		if (ret == expected)
+			return 0;
+		return ret ? ret : -EINVAL;
+	}
+
+	return -ETIMEDOUT;
+}
+
+struct selection_case {
+	struct task_cpu_arg input;
+	int expected;
+};
+
+static int run_selection_cases(const struct allowed_cpus *skel,
+			       const cpu_set_t *original, int second,
+			       const cpu_set_t *target_affinity,
+			       struct selection_case *cases, int nr_cases)
+{
+	pid_t pid = -1;
+	int ret = 0, i;
+
+	if (pin_current_cpu(second))
+		return -errno;
+
+	pid = start_target(target_affinity);
+	if (pid < 0) {
+		ret = -errno;
+		goto out;
+	}
+
+	for (i = 0; i < nr_cases; i++) {
+		cases[i].input.pid = pid;
+		ret = expect_selection(skel, &cases[i].input, cases[i].expected);
+		if (ret)
+			goto out;
+	}
+
+out:
+	stop_target(pid);
+	if (sched_setaffinity(0, sizeof(*original), original) && !ret)
+		ret = -errno;
+	return ret;
+}
+
+static int test_restricted_path(const struct allowed_cpus *skel,
+				const cpu_set_t *original,
+				int first, int second)
+{
+	cpu_set_t target_affinity;
+	struct selection_case cases[] = {
+		{
+			.input = {
+				.prev_cpu = first,
+				.allowed_cpu = first,
+				.flags = SCX_PICK_IDLE_CORE,
+				.check_idle = 1,
+			},
+			.expected = -EBUSY,
+		},
+		/* flags=0 must retain individual-idle behavior on an SMT system. */
+		{
+			.input = {
+				.prev_cpu = first,
+				.allowed_cpu = first,
+				.flags = 0,
+				.check_idle = 1,
+			},
+			.expected = first,
+		},
+	};
+
+	CPU_ZERO(&target_affinity);
+	CPU_SET(first, &target_affinity);
+
+	return run_selection_cases(skel, original, second, &target_affinity,
+				   cases, ARRAY_SIZE(cases));
+}
+
+static int test_wake_sync_path(const struct allowed_cpus *skel,
+			       const cpu_set_t *original,
+				int first, int second)
+{
+	cpu_set_t target_affinity;
+	struct selection_case cases[] = {
+		{
+			.input = {
+				.prev_cpu = first,
+				.allowed_cpu = first,
+				.wake_flags = 1ULL << 4, /* SCX_WAKE_SYNC */
+				.flags = SCX_PICK_IDLE_CORE,
+				.check_idle = 1,
+			},
+			.expected = -EBUSY,
+		},
+		/* A busy waker CPU exercises the second WAKE_SYNC shortcut. */
+		{
+			.input = {
+				.prev_cpu = first,
+				.allowed_cpu = second,
+				.wake_flags = 1ULL << 4, /* SCX_WAKE_SYNC */
+				.flags = SCX_PICK_IDLE_CORE,
+				.check_idle = 1,
+			},
+			.expected = -EBUSY,
+		},
+	};
+
+	CPU_ZERO(&target_affinity);
+	CPU_SET(first, &target_affinity);
+	CPU_SET(second, &target_affinity);
+
+	return run_selection_cases(skel, original, second, &target_affinity,
+				   cases, ARRAY_SIZE(cases));
+}
+
 static enum scx_test_status run(void *ctx)
 {
 	struct allowed_cpus *skel = ctx;
 	struct bpf_link *link;
+	struct task_cpu_arg generic = {
+		.pid = getpid(),
+		.prev_cpu = sched_getcpu(),
+		.allowed_cpu = -1,
+	};
+	cpu_set_t original;
+	int first, second, ret, result;
 
 	link = bpf_map__attach_struct_ops(skel->maps.allowed_cpus_ops);
 	SCX_FAIL_IF(!link, "Failed to attach scheduler");
 
-	/* Pick an idle CPU from user-space */
-	SCX_FAIL_IF(test_select_cpu_from_user(skel), "Failed to pick idle CPU");
+	SCX_FAIL_IF(sched_getaffinity(0, sizeof(original), &original),
+		    "Failed to read affinity");
+	ret = test_select_cpu_from_user(skel, &generic, &result);
+	SCX_FAIL_IF(ret, "Failed to run select_cpu test (%d)", ret);
+
+	if (find_smt_pair(&original, &first, &second)) {
+		fprintf(stderr, "SKIP: no SMT pair for strict-core cases\n");
+		goto out;
+	}
+
+	ret = test_restricted_path(skel, &original, first, second);
+	SCX_FAIL_IF(ret, "Failed to validate restricted strict-core selection (%d)",
+		    ret);
+
+	ret = test_wake_sync_path(skel, &original, first, second);
+	SCX_FAIL_IF(ret, "Failed to validate WAKE_SYNC strict-core selection (%d)",
+		    ret);
 
+out:
 	/* Just sleeping is fine, plenty of scheduling events happening */
 	sleep(1);
 
-- 
2.55.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-12  6:53 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-12  6:51 [PATCH 0/2] sched_ext: Honor SCX_PICK_IDLE_CORE in CPU selection shortcuts Hui Su
2026-10-12  6:51 ` [PATCH 1/2] sched_ext: idle: " Hui Su
2026-10-12  6:51 ` [PATCH 2/2] selftests/sched_ext: Cover strict-core " Hui Su

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®