Linux Kernel Selftest development
 help / color / mirror / Atom feed
* [PATCH RFC 0/7] selftests/sched: Add comprehensive SCHED_DEADLINE test suite
@ 2026-03-06 16:10 Juri Lelli
  2026-03-06 16:10 ` [PATCH RFC 1/7] selftests/sched: Add SCHED_DEADLINE test framework infrastructure Juri Lelli
                   ` (6 more replies)
  0 siblings, 7 replies; 21+ messages in thread
From: Juri Lelli @ 2026-03-06 16:10 UTC (permalink / raw)
  To: Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest,
	Juri Lelli

This series introduces a comprehensive test suite for the SCHED_DEADLINE
scheduler, providing coverage for core functionality, bandwidth admission
control, and the fair_server (dl-server) mechanism.

I've been playing with/using this set of tests for a while and they
helped me a little checking for regressions and corner cases. So, I
guess I've also now found the courage to share this with everyone and
see what happens. :P

Motivation
----------
SCHED_DEADLINE currently lacks dedicated test coverage in kselftest.
While the scheduler itself is well-tested in production, having automated
tests helps catch regressions during development and provides examples of
correct SCHED_DEADLINE usage for developers.

Development Process
-------------------
This series was developed using Claude Code (claude.ai/code), making it
both a testing improvement and an experiment in itself. The goal is to
explore what AI-assisted development can contribute to kernel testing
efforts - from framework design to test implementation to documentation.

All code has been reviewed and validated by the human maintainer, but the
bulk of the implementation, test design, and even this cover letter were
produced through iterative collaboration with Claude. This represents an
experiment in whether AI tools can help scale testing efforts while
maintaining code quality and kernel development standards.

Feedback on both the test suite itself and this development approach is
welcome.

Design
------
The test framework follows the pattern established by sched_ext selftests,
using ELF constructors for automatic test registration. This allows tests
to be self-contained and easily extensible - new tests are added simply by
defining a struct dl_test and calling REGISTER_DL_TEST().

The suite includes a utility library (dl_util) that provides reusable
helpers for common operations: setting SCHED_DEADLINE parameters, managing
bandwidth, controlling CPU topology, and process management.

Series Overview
---------------
Patch 1: Framework infrastructure with test discovery and structured output
Patch 2: Utility library and cpuhog helper program
Patch 3: Integration with kselftest build system
Patch 4: Basic functionality tests (scheduling, parameter validation)
Patch 5: Bandwidth admission control tests
Patch 6: fair_server (dl-server) tests
Patch 7: Regression test for ENQUEUE_REPLENISH bug

Testing
-------
All tests pass on x86_64 with both VM and bare-metal systems. The tests
adapt automatically to system configuration, detecting available bandwidth
and dl-server bandwidth allocations dynamically.

Tests can be run via:
  make -C tools/testing/selftests TARGETS=sched run_tests
  make -C tools/testing/selftests/sched/deadline run_tests

Or selectively:
  cd tools/testing/selftests/sched/deadline
  make
  ./runner -h

Current test results:
  PASSED:  7
  SKIPPED: 0
  FAILED:  0

Series also available at

git@github.com:jlelli/linux.git deadline-kselftests-rfc

Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
---
Juri Lelli (7):
      selftests/sched: Add SCHED_DEADLINE test framework infrastructure
      selftests/sched: Add SCHED_DEADLINE utility library
      selftests/sched: Integrate SCHED_DEADLINE tests into kselftest framework
      selftests/sched: Add basic SCHED_DEADLINE functionality tests
      selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest
      selftests/sched: Add SCHED_DEADLINE fair_server tests to kselftest
      selftests/sched: Add SCHED_DEADLINE ENQUEUE_REPLENISH bug test

 tools/testing/selftests/sched/Makefile             |   8 +
 tools/testing/selftests/sched/deadline/.gitignore  |   3 +
 tools/testing/selftests/sched/deadline/Makefile    |  54 +++
 tools/testing/selftests/sched/deadline/bandwidth.c | 270 +++++++++++
 tools/testing/selftests/sched/deadline/basic.c     | 127 +++++
 tools/testing/selftests/sched/deadline/cpuhog.c    | 107 +++++
 tools/testing/selftests/sched/deadline/dl_test.h   | 238 +++++++++
 tools/testing/selftests/sched/deadline/dl_util.c   | 530 +++++++++++++++++++++
 tools/testing/selftests/sched/deadline/dl_util.h   | 294 ++++++++++++
 .../testing/selftests/sched/deadline/fair_server.c | 260 ++++++++++
 .../selftests/sched/deadline/replenish_bug.c       | 337 +++++++++++++
 tools/testing/selftests/sched/deadline/runner.c    | 219 +++++++++
 12 files changed, 2447 insertions(+)
---
base-commit: d658686a1331db3bb108ca079d76deb3208ed949
change-id: 20260306-upstream-deadline-kselftests-f7357ae20166

Best regards,
--  
Juri Lelli <juri.lelli@redhat.com>


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

* [PATCH RFC 1/7] selftests/sched: Add SCHED_DEADLINE test framework infrastructure
  2026-03-06 16:10 [PATCH RFC 0/7] selftests/sched: Add comprehensive SCHED_DEADLINE test suite Juri Lelli
@ 2026-03-06 16:10 ` Juri Lelli
  2026-03-09  8:20   ` Gabriele Monaco
  2026-03-06 16:10 ` [PATCH RFC 2/7] selftests/sched: Add SCHED_DEADLINE utility library Juri Lelli
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 21+ messages in thread
From: Juri Lelli @ 2026-03-06 16:10 UTC (permalink / raw)
  To: Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest,
	Juri Lelli

Add the foundational infrastructure for SCHED_DEADLINE scheduler tests
following the pattern established by sched_ext selftests. This provides
a clean, extensible framework for testing SCHED_DEADLINE functionality.

The framework uses ELF constructors for automatic test registration,
allowing new tests to be added simply by defining a struct dl_test and
calling REGISTER_DL_TEST(). The runner discovers all registered tests
at runtime and executes them serially, providing a simple and consistent
way to expand test coverage without modifying the test runner itself.

The framework provides a complete test lifecycle with setup, run, and
cleanup phases for each test. Tests can be filtered by name, listed for
inspection, or run in quiet mode for automated testing environments. The
framework includes assertion macros such as DL_EQ and DL_FAIL_IF to
simplify test authoring and provide consistent error reporting. Signal
handling ensures graceful interruption and cleanup when tests are
terminated early.

This commit establishes the framework without any actual tests. Tests
will be added in subsequent patches. The framework design is inspired by
tools/testing/selftests/sched_ext/ but tailored for SCHED_DEADLINE
testing needs.

Assisted-by: Claude Code: claude-sonnet-4-5@20250929
Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
---
 tools/testing/selftests/sched/deadline/.gitignore |   3 +
 tools/testing/selftests/sched/deadline/Makefile   |  34 ++++
 tools/testing/selftests/sched/deadline/dl_test.h  | 238 ++++++++++++++++++++++
 tools/testing/selftests/sched/deadline/runner.c   | 219 ++++++++++++++++++++
 4 files changed, 494 insertions(+)

diff --git a/tools/testing/selftests/sched/deadline/.gitignore b/tools/testing/selftests/sched/deadline/.gitignore
new file mode 100644
index 0000000000000..503a1a968f952
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/.gitignore
@@ -0,0 +1,3 @@
+runner
+cpuhog
+*.o
diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
new file mode 100644
index 0000000000000..fd57794f1a543
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/Makefile
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_GEN_PROGS := runner
+
+# override lib.mk's default rules
+OVERRIDE_TARGETS := 1
+include ../../lib.mk
+
+CFLAGS += -Wall -O2 -g -pthread
+
+OUTPUT_DIR := $(OUTPUT)
+
+# Utility object files
+UTIL_OBJS :=
+
+# Test object files (all .c files except runner.c, dl_util.c, cpuhog.c)
+# Will be populated as we add tests
+TEST_OBJS :=
+
+# Runner binary links utility and test objects
+$(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h | $(OUTPUT_DIR)
+	$(CC) $(CFLAGS) -o $@ runner.c $(UTIL_OBJS) $(TEST_OBJS) $(LDFLAGS)
+
+$(OUTPUT_DIR):
+	mkdir -p $@
+
+.PHONY: all clean
+
+all: $(TEST_GEN_PROGS)
+
+clean:
+	rm -f $(OUTPUT)/runner
+	rm -f $(OUTPUT)/*.o
+	rm -f *.o
diff --git a/tools/testing/selftests/sched/deadline/dl_test.h b/tools/testing/selftests/sched/deadline/dl_test.h
new file mode 100644
index 0000000000000..545fae1af6631
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/dl_test.h
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SCHED_DEADLINE Test Framework
+ *
+ * Provides infrastructure for testing SCHED_DEADLINE scheduler functionality.
+ * Tests register themselves using REGISTER_DL_TEST() macro and are
+ * automatically discovered by the runner at runtime.
+ */
+
+#ifndef __DL_TEST_H__
+#define __DL_TEST_H__
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Test status return codes */
+enum dl_test_status {
+	DL_TEST_PASS = 0,	/* Test passed successfully */
+	DL_TEST_SKIP,		/* Test skipped (feature unavailable, etc.) */
+	DL_TEST_FAIL,		/* Test failed */
+};
+
+/**
+ * struct dl_test - Deadline scheduler test definition
+ * @name: Short test identifier (e.g., "basic_scheduling")
+ * @description: Human-readable description of what the test validates
+ * @setup: Optional callback to prepare test environment
+ * @run: Required callback to execute test logic
+ * @cleanup: Optional callback to cleanup test resources
+ *
+ * Tests are defined by filling in this structure and registering with
+ * REGISTER_DL_TEST(). The runner will discover and execute all registered
+ * tests automatically.
+ */
+struct dl_test {
+	/**
+	 * name - The name of the test
+	 *
+	 * Short identifier used for filtering and reporting. Should be
+	 * lowercase with underscores (e.g., "bandwidth_admission").
+	 */
+	const char *name;
+
+	/**
+	 * description - Human-readable test description
+	 *
+	 * Explains what the test validates and why it's important.
+	 * Displayed when test runs unless quiet mode is enabled.
+	 */
+	const char *description;
+
+	/**
+	 * setup - Optional setup callback
+	 * @ctx: Pointer to context pointer, can be set to pass data to run()
+	 *
+	 * Called before run() to prepare test environment. Can allocate
+	 * resources, check prerequisites, etc.
+	 *
+	 * Return:
+	 * - DL_TEST_PASS: Continue to run()
+	 * - DL_TEST_SKIP: Skip this test (feature not available, etc.)
+	 * - DL_TEST_FAIL: Abort test (setup failed)
+	 *
+	 * If setup() returns SKIP or FAIL, run() and cleanup() are not called.
+	 */
+	enum dl_test_status (*setup)(void **ctx);
+
+	/**
+	 * run - Required test execution callback
+	 * @ctx: Context pointer set by setup(), or NULL if no setup
+	 *
+	 * Executes the actual test logic. This is the main test function.
+	 *
+	 * Return:
+	 * - DL_TEST_PASS: Test passed
+	 * - DL_TEST_SKIP: Test skipped (unlikely here, prefer setup())
+	 * - DL_TEST_FAIL: Test failed
+	 */
+	enum dl_test_status (*run)(void *ctx);
+
+	/**
+	 * cleanup - Optional cleanup callback
+	 * @ctx: Context pointer set by setup(), or NULL if no setup
+	 *
+	 * Called after run() to cleanup resources. Always runs if setup()
+	 * succeeded, regardless of run() result. Cannot fail the test.
+	 */
+	void (*cleanup)(void *ctx);
+};
+
+/**
+ * dl_test_register() - Register a test with the framework
+ * @test: Pointer to test structure
+ *
+ * Called by REGISTER_DL_TEST() macro. Don't call directly.
+ */
+void dl_test_register(struct dl_test *test);
+
+/**
+ * REGISTER_DL_TEST() - Register a test for auto-discovery
+ * @__test: Pointer to struct dl_test
+ *
+ * Uses ELF constructor attribute to automatically register the test
+ * when the binary loads. The runner will discover and execute all
+ * registered tests.
+ *
+ * Example:
+ *   static struct dl_test my_test = {
+ *       .name = "my_test",
+ *       .description = "Tests something important",
+ *       .run = my_test_run,
+ *   };
+ *   REGISTER_DL_TEST(&my_test);
+ */
+#define __DL_CONCAT(a, b) a##b
+#define _DL_CONCAT(a, b) __DL_CONCAT(a, b)
+
+#define REGISTER_DL_TEST(__test)					\
+	__attribute__((constructor))					\
+	static void _DL_CONCAT(___dlregister_, __LINE__)(void)		\
+	{								\
+		dl_test_register(__test);				\
+	}
+
+/* Error reporting macros */
+
+/**
+ * DL_ERR() - Print error message with file/line info
+ */
+#define DL_ERR(__fmt, ...)						\
+	do {								\
+		fprintf(stderr, "ERR: %s:%d\n", __FILE__, __LINE__);	\
+		fprintf(stderr, __fmt"\n", ##__VA_ARGS__);		\
+	} while (0)
+
+/**
+ * DL_FAIL() - Fail the test with a message
+ *
+ * Prints error message and returns DL_TEST_FAIL. Use in test run() or
+ * setup() functions.
+ */
+#define DL_FAIL(__fmt, ...)						\
+	do {								\
+		DL_ERR(__fmt, ##__VA_ARGS__);				\
+		return DL_TEST_FAIL;					\
+	} while (0)
+
+/**
+ * DL_FAIL_IF() - Conditionally fail the test
+ * @__cond: Condition to check
+ * @__fmt: printf-style format string
+ *
+ * If condition is true, fail the test with the given message.
+ */
+#define DL_FAIL_IF(__cond, __fmt, ...)					\
+	do {								\
+		if (__cond)						\
+			DL_FAIL(__fmt, ##__VA_ARGS__);			\
+	} while (0)
+
+/* Comparison assertion macros */
+
+/**
+ * DL_EQ() - Assert two values are equal
+ */
+#define DL_EQ(_x, _y) \
+	DL_FAIL_IF((_x) != (_y), \
+		   "Expected %s == %s (%lld == %lld)", \
+		   #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_NE() - Assert two values are not equal
+ */
+#define DL_NE(_x, _y) \
+	DL_FAIL_IF((_x) == (_y), \
+		   "Expected %s != %s (%lld != %lld)", \
+		   #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_LT() - Assert x < y
+ */
+#define DL_LT(_x, _y) \
+	DL_FAIL_IF((_x) >= (_y), \
+		   "Expected %s < %s (%lld < %lld)", \
+		   #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_LE() - Assert x <= y
+ */
+#define DL_LE(_x, _y) \
+	DL_FAIL_IF((_x) > (_y), \
+		   "Expected %s <= %s (%lld <= %lld)", \
+		   #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_GT() - Assert x > y
+ */
+#define DL_GT(_x, _y) \
+	DL_FAIL_IF((_x) <= (_y), \
+		   "Expected %s > %s (%lld > %lld)", \
+		   #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_GE() - Assert x >= y
+ */
+#define DL_GE(_x, _y) \
+	DL_FAIL_IF((_x) < (_y), \
+		   "Expected %s >= %s (%lld >= %lld)", \
+		   #_x, #_y, (long long)(_x), (long long)(_y))
+
+/**
+ * DL_ASSERT() - Assert condition is true
+ */
+#define DL_ASSERT(_x) \
+	DL_FAIL_IF(!(_x), "Expected %s to be true", #_x)
+
+/**
+ * DL_BUG_ON() - Fatal assertion (for framework bugs, not test failures)
+ * @__cond: Condition to check
+ * @__fmt: Error message
+ *
+ * For internal framework consistency checks. If condition is true,
+ * prints error and aborts. Use for "should never happen" cases.
+ */
+#define DL_BUG_ON(__cond, __fmt, ...)					\
+	do {								\
+		if (__cond) {						\
+			fprintf(stderr, "BUG: %s:%d: " __fmt "\n",	\
+				__FILE__, __LINE__, ##__VA_ARGS__);	\
+			abort();					\
+		}							\
+	} while (0)
+
+#endif /* __DL_TEST_H__ */
diff --git a/tools/testing/selftests/sched/deadline/runner.c b/tools/testing/selftests/sched/deadline/runner.c
new file mode 100644
index 0000000000000..358f695423ef5
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/runner.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCHED_DEADLINE Test Runner
+ *
+ * Discovers and executes all registered SCHED_DEADLINE tests.
+ * Tests are statically linked and register themselves via ELF constructors.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdbool.h>
+#include "dl_test.h"
+
+const char help_fmt[] =
+"Runner for SCHED_DEADLINE scheduler tests.\n"
+"\n"
+"All tests are statically linked and run serially. Tests require root\n"
+"privileges to set SCHED_DEADLINE scheduling policy.\n"
+"\n"
+"Usage: %s [-t TEST] [-h]\n"
+"\n"
+"  -t TEST       Only run tests whose name includes this string\n"
+"  -s            Include print output for skipped tests\n"
+"  -l            List all available tests\n"
+"  -q            Don't print the test descriptions during run\n"
+"  -h            Display this help and exit\n";
+
+static volatile int exit_req;
+static bool quiet, print_skipped, list;
+
+#define MAX_DL_TESTS 256
+
+static struct dl_test *__dl_tests[MAX_DL_TESTS];
+static unsigned int __dl_num_tests;
+
+static void sigint_handler(int sig)
+{
+	exit_req = 1;
+}
+
+static void print_test_preamble(const struct dl_test *test, bool quiet)
+{
+	printf("===== START =====\n");
+	printf("TEST: %s\n", test->name);
+	if (!quiet)
+		printf("DESCRIPTION: %s\n", test->description);
+	printf("OUTPUT:\n");
+}
+
+static const char *status_to_result(enum dl_test_status status)
+{
+	switch (status) {
+	case DL_TEST_PASS:
+	case DL_TEST_SKIP:
+		return "ok";
+	case DL_TEST_FAIL:
+		return "not ok";
+	default:
+		return "<UNKNOWN>";
+	}
+}
+
+static void print_test_result(const struct dl_test *test,
+			      enum dl_test_status status,
+			      unsigned int testnum)
+{
+	const char *result = status_to_result(status);
+	const char *directive = status == DL_TEST_SKIP ? "SKIP " : "";
+
+	printf("%s %u %s # %s\n", result, testnum, test->name, directive);
+	printf("=====  END  =====\n");
+}
+
+static bool should_skip_test(const struct dl_test *test, const char *filter)
+{
+	return filter && !strstr(test->name, filter);
+}
+
+static enum dl_test_status run_test(const struct dl_test *test)
+{
+	enum dl_test_status status;
+	void *context = NULL;
+
+	if (test->setup) {
+		status = test->setup(&context);
+		if (status != DL_TEST_PASS)
+			return status;
+	}
+
+	status = test->run(context);
+
+	if (test->cleanup)
+		test->cleanup(context);
+
+	return status;
+}
+
+static bool test_valid(const struct dl_test *test)
+{
+	if (!test) {
+		fprintf(stderr, "NULL test detected\n");
+		return false;
+	}
+
+	if (!test->name) {
+		fprintf(stderr,
+			"Test with no name found. Must specify test name.\n");
+		return false;
+	}
+
+	if (!test->description) {
+		fprintf(stderr, "Test %s requires description.\n", test->name);
+		return false;
+	}
+
+	if (!test->run) {
+		fprintf(stderr, "Test %s has no run() callback\n", test->name);
+		return false;
+	}
+
+	return true;
+}
+
+int main(int argc, char **argv)
+{
+	const char *filter = NULL;
+	unsigned int testnum = 0, i;
+	unsigned int passed = 0, skipped = 0, failed = 0;
+	int opt;
+
+	signal(SIGINT, sigint_handler);
+	signal(SIGTERM, sigint_handler);
+
+	while ((opt = getopt(argc, argv, "qslt:h")) != -1) {
+		switch (opt) {
+		case 'q':
+			quiet = true;
+			break;
+		case 's':
+			print_skipped = true;
+			break;
+		case 'l':
+			list = true;
+			break;
+		case 't':
+			filter = optarg;
+			break;
+		default:
+			fprintf(stderr, help_fmt, argv[0]);
+			return opt != 'h';
+		}
+	}
+
+	for (i = 0; i < __dl_num_tests; i++) {
+		enum dl_test_status status;
+		struct dl_test *test = __dl_tests[i];
+
+		if (list) {
+			printf("%s\n", test->name);
+			if (i == (__dl_num_tests - 1))
+				return 0;
+			continue;
+		}
+
+		if (should_skip_test(test, filter)) {
+			/*
+			 * Printing the skipped tests and their preambles can
+			 * add a lot of noise to the runner output. Printing
+			 * this is only really useful for CI, so let's skip it
+			 * by default.
+			 */
+			if (print_skipped) {
+				print_test_preamble(test, quiet);
+				print_test_result(test, DL_TEST_SKIP, ++testnum);
+			}
+			continue;
+		}
+
+		print_test_preamble(test, quiet);
+		status = run_test(test);
+		print_test_result(test, status, ++testnum);
+
+		switch (status) {
+		case DL_TEST_PASS:
+			passed++;
+			break;
+		case DL_TEST_SKIP:
+			skipped++;
+			break;
+		case DL_TEST_FAIL:
+			failed++;
+			break;
+		}
+
+		if (exit_req) {
+			fprintf(stderr, "\nInterrupted by signal\n");
+			break;
+		}
+	}
+
+	printf("\n\n=============================\n\n");
+	printf("RESULTS:\n\n");
+	printf("PASSED:  %u\n", passed);
+	printf("SKIPPED: %u\n", skipped);
+	printf("FAILED:  %u\n", failed);
+
+	return failed > 0 ? 1 : 0;
+}
+
+void dl_test_register(struct dl_test *test)
+{
+	DL_BUG_ON(!test_valid(test), "Invalid test found");
+	DL_BUG_ON(__dl_num_tests >= MAX_DL_TESTS, "Maximum tests exceeded");
+
+	__dl_tests[__dl_num_tests++] = test;
+}

-- 
2.53.0


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

* [PATCH RFC 2/7] selftests/sched: Add SCHED_DEADLINE utility library
  2026-03-06 16:10 [PATCH RFC 0/7] selftests/sched: Add comprehensive SCHED_DEADLINE test suite Juri Lelli
  2026-03-06 16:10 ` [PATCH RFC 1/7] selftests/sched: Add SCHED_DEADLINE test framework infrastructure Juri Lelli
@ 2026-03-06 16:10 ` Juri Lelli
  2026-03-11  9:39   ` Christian Loehle
  2026-03-06 16:10 ` [PATCH RFC 3/7] selftests/sched: Integrate SCHED_DEADLINE tests into kselftest framework Juri Lelli
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 21+ messages in thread
From: Juri Lelli @ 2026-03-06 16:10 UTC (permalink / raw)
  To: Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest,
	Juri Lelli

Add a comprehensive utility library for SCHED_DEADLINE scheduler tests.
This library provides reusable helper functions that simplify test
implementation and reduce code duplication across the test suite.

The utility library provides scheduling operations that wrap the
sched_setattr and sched_getattr syscalls for setting and querying
SCHED_DEADLINE parameters. These include dl_set_sched_attr() for
configuring deadline parameters, dl_get_sched_attr() for querying
scheduling attributes, dl_get_policy() for reading the policy from
/proc, and dl_is_deadline_task() for checking if a task is using
SCHED_DEADLINE. The library uses system headers for struct sched_attr
to avoid redefinition conflicts and provides full control over
SCHED_DEADLINE parameters.

Bandwidth management helpers allow tests to work within system
constraints. The dl_get_rt_bandwidth() function reads RT bandwidth
settings from /proc, while dl_calc_max_bandwidth_percent() calculates
the available bandwidth for deadline tasks based on current system
configuration.

Process management functions simplify creating and managing test
workloads. The dl_create_cpuhog() function forks and schedules a cpuhog
process by creating a child process, executing the cpuhog binary, and
setting SCHED_DEADLINE policy on the child PID after fork. It waits for
the child to start before configuring the scheduling policy. Supporting
functions include dl_cleanup_cpuhog() for terminating processes,
dl_find_cpuhogs() for locating running instances, and dl_wait_for_pid()
for synchronizing with process startup.

CPU topology helpers enable tests that manipulate CPU hotplug state.
These include dl_get_online_cpus() for counting available CPUs,
dl_get_hotpluggable_cpus() for identifying which CPUs can be
hotplugged, dl_cpu_online() and dl_cpu_offline() for controlling
hotplug state, and dl_is_cpu_online() for checking current status.

Time conversion utilities provide convenient transformations between
different time units. These include dl_ms_to_ns() and dl_us_to_ns()
for converting to nanoseconds, and dl_ns_to_ms() and dl_ns_to_us() for
converting from nanoseconds.

The library also includes the cpuhog helper program, which performs
busy looping to consume CPU cycles. This provides a controllable
workload for testing scheduler behavior under various deadline
configurations.

Assisted-by: Claude Code: claude-sonnet-4-5@20250929
Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
---
 tools/testing/selftests/sched/deadline/Makefile  |  14 +-
 tools/testing/selftests/sched/deadline/cpuhog.c  | 107 ++++++++
 tools/testing/selftests/sched/deadline/dl_util.c | 335 +++++++++++++++++++++++
 tools/testing/selftests/sched/deadline/dl_util.h | 227 +++++++++++++++
 4 files changed, 680 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
index fd57794f1a543..ea3fdfbef459e 100644
--- a/tools/testing/selftests/sched/deadline/Makefile
+++ b/tools/testing/selftests/sched/deadline/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 
-TEST_GEN_PROGS := runner
+TEST_GEN_PROGS := runner cpuhog
 
 # override lib.mk's default rules
 OVERRIDE_TARGETS := 1
@@ -11,7 +11,7 @@ CFLAGS += -Wall -O2 -g -pthread
 OUTPUT_DIR := $(OUTPUT)
 
 # Utility object files
-UTIL_OBJS :=
+UTIL_OBJS := $(OUTPUT)/dl_util.o
 
 # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c)
 # Will be populated as we add tests
@@ -21,6 +21,14 @@ TEST_OBJS :=
 $(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h | $(OUTPUT_DIR)
 	$(CC) $(CFLAGS) -o $@ runner.c $(UTIL_OBJS) $(TEST_OBJS) $(LDFLAGS)
 
+# cpuhog helper program
+$(OUTPUT)/cpuhog: cpuhog.c | $(OUTPUT_DIR)
+	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+# Utility library
+$(OUTPUT)/dl_util.o: dl_util.c dl_util.h | $(OUTPUT_DIR)
+	$(CC) $(CFLAGS) -c $< -o $@
+
 $(OUTPUT_DIR):
 	mkdir -p $@
 
@@ -29,6 +37,6 @@ $(OUTPUT_DIR):
 all: $(TEST_GEN_PROGS)
 
 clean:
-	rm -f $(OUTPUT)/runner
+	rm -f $(OUTPUT)/runner $(OUTPUT)/cpuhog
 	rm -f $(OUTPUT)/*.o
 	rm -f *.o
diff --git a/tools/testing/selftests/sched/deadline/cpuhog.c b/tools/testing/selftests/sched/deadline/cpuhog.c
new file mode 100644
index 0000000000000..55274aa19e879
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/cpuhog.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cpuhog: A simple CPU intensive program for testing scheduler behavior
+ *
+ * This program performs busy looping to consume CPU cycles, useful for
+ * testing scheduler policies like SCHED_DEADLINE.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <time.h>
+
+static int stop_flag;
+
+static void signal_handler(int sig)
+{
+	stop_flag = 1;
+}
+
+static void usage(const char *progname)
+{
+	printf("Usage: %s [options]\n", progname);
+	printf("Options:\n");
+	printf("  -t <seconds>  Run for specified seconds (default: infinite)\n");
+	printf("  -v            Verbose output\n");
+	printf("  -h            Show this help\n");
+}
+
+int main(int argc, char *argv[])
+{
+	int opt;
+	int duration = 0; /* 0 means infinite */
+	int verbose = 0;
+	time_t start_time, current_time;
+	unsigned long long iterations = 0;
+	unsigned long long last_report = 0;
+
+	while ((opt = getopt(argc, argv, "t:vh")) != -1) {
+		switch (opt) {
+		case 't':
+			duration = atoi(optarg);
+			if (duration <= 0) {
+				fprintf(stderr, "Invalid duration: %s\n", optarg);
+				return 1;
+			}
+			break;
+		case 'v':
+			verbose = 1;
+			break;
+		case 'h':
+			usage(argv[0]);
+			return 0;
+		default:
+			usage(argv[0]);
+			return 1;
+		}
+	}
+
+	/* Set up signal handlers for graceful shutdown */
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+
+	if (verbose) {
+		printf("cpuhog starting (PID: %d)\n", getpid());
+		if (duration > 0)
+			printf("Will run for %d seconds\n", duration);
+		else
+			printf("Will run until interrupted\n");
+	}
+
+	start_time = time(NULL);
+
+	/* Main busy loop */
+	while (!stop_flag) {
+		/* Simple busy work - incrementing a counter */
+		iterations++;
+
+		/* Check if we've reached the duration limit */
+		if (duration > 0) {
+			current_time = time(NULL);
+			if (current_time - start_time >= duration)
+				break;
+		}
+
+		/* Print progress every 100M iterations if verbose */
+		if (verbose && (iterations % 100000000ULL == 0)) {
+			if (iterations != last_report) {
+				printf("Completed %llu iterations\n", iterations);
+				last_report = iterations;
+			}
+		}
+	}
+
+	if (verbose) {
+		current_time = time(NULL);
+		printf("cpuhog finished after %ld seconds and %llu iterations\n",
+		       current_time - start_time, iterations);
+	}
+
+	return 0;
+}
diff --git a/tools/testing/selftests/sched/deadline/dl_util.c b/tools/testing/selftests/sched/deadline/dl_util.c
new file mode 100644
index 0000000000000..0d7c46ba877f3
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/dl_util.c
@@ -0,0 +1,335 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCHED_DEADLINE Utility Library Implementation
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <time.h>
+#include "dl_util.h"
+
+/* Syscall numbers for sched_setattr/sched_getattr */
+#ifndef __NR_sched_setattr
+#define __NR_sched_setattr 314
+#endif
+
+#ifndef __NR_sched_getattr
+#define __NR_sched_getattr 315
+#endif
+
+/*
+ * Scheduling operations
+ */
+
+static int sched_setattr(pid_t pid, const struct sched_attr *attr,
+			 unsigned int flags)
+{
+	return syscall(__NR_sched_setattr, pid, attr, flags);
+}
+
+static int sched_getattr(pid_t pid, struct sched_attr *attr,
+			 unsigned int size, unsigned int flags)
+{
+	return syscall(__NR_sched_getattr, pid, attr, size, flags);
+}
+
+int dl_set_sched_attr(pid_t pid, uint64_t runtime, uint64_t deadline,
+		      uint64_t period)
+{
+	struct sched_attr attr = {
+		.size = sizeof(attr),
+		.sched_policy = SCHED_DEADLINE,
+		.sched_flags = 0,
+		.sched_runtime = runtime,
+		.sched_deadline = deadline,
+		.sched_period = period,
+	};
+
+	return sched_setattr(pid, &attr, 0);
+}
+
+int dl_get_sched_attr(pid_t pid, struct sched_attr *attr)
+{
+	memset(attr, 0, sizeof(*attr));
+	attr->size = sizeof(*attr);
+	return sched_getattr(pid, attr, sizeof(*attr), 0);
+}
+
+int dl_get_policy(pid_t pid)
+{
+	char path[256];
+	char line[256];
+	FILE *f;
+	int policy = -1;
+
+	snprintf(path, sizeof(path), "/proc/%d/sched", pid);
+	f = fopen(path, "r");
+	if (!f)
+		return -1;
+
+	while (fgets(line, sizeof(line), f)) {
+		if (sscanf(line, " policy : %d", &policy) == 1)
+			break;
+	}
+
+	fclose(f);
+	return policy;
+}
+
+bool dl_is_deadline_task(pid_t pid)
+{
+	return dl_get_policy(pid) == SCHED_DEADLINE;
+}
+
+/*
+ * Bandwidth management
+ */
+
+static int read_proc_uint64(const char *path, uint64_t *value)
+{
+	FILE *f;
+	int ret;
+
+	f = fopen(path, "r");
+	if (!f)
+		return -1;
+
+	ret = fscanf(f, "%lu", value);
+	fclose(f);
+
+	return ret == 1 ? 0 : -1;
+}
+
+int dl_get_rt_bandwidth(uint64_t *runtime_us, uint64_t *period_us)
+{
+	int ret;
+
+	ret = read_proc_uint64("/proc/sys/kernel/sched_rt_runtime_us",
+			       runtime_us);
+	if (ret < 0)
+		return ret;
+
+	return read_proc_uint64("/proc/sys/kernel/sched_rt_period_us",
+				period_us);
+}
+
+int dl_calc_max_bandwidth_percent(void)
+{
+	uint64_t runtime_us, period_us;
+	int percent;
+
+	if (dl_get_rt_bandwidth(&runtime_us, &period_us) < 0)
+		return -1;
+
+	if (period_us == 0)
+		return -1;
+
+	percent = (runtime_us * 100) / period_us;
+	return percent > 0 ? percent : 1;
+}
+
+/*
+ * Process management
+ */
+
+pid_t dl_create_cpuhog(uint64_t runtime, uint64_t deadline, uint64_t period,
+		       int duration_secs)
+{
+	pid_t pid;
+	char duration_str[32];
+
+	pid = fork();
+	if (pid < 0)
+		return -1;
+
+	if (pid == 0) {
+		/* Child process */
+		char *args[4];
+
+		args[0] = "./cpuhog";
+		if (duration_secs > 0) {
+			args[1] = "-t";
+			snprintf(duration_str, sizeof(duration_str), "%d",
+				 duration_secs);
+			args[2] = duration_str;
+			args[3] = NULL;
+		} else {
+			args[1] = NULL;
+		}
+
+		/* Just exec - parent will set SCHED_DEADLINE */
+		execvp(args[0], args);
+		/* If exec fails, try without ./ */
+		args[0] = "cpuhog";
+		execvp(args[0], args);
+
+		fprintf(stderr, "Failed to exec cpuhog: %s\n", strerror(errno));
+		exit(1);
+	}
+
+	/* Parent process - wait for child to start then set SCHED_DEADLINE */
+	if (dl_wait_for_pid(pid, 1000) < 0) {
+		kill(pid, SIGKILL);
+		waitpid(pid, NULL, 0);
+		return -1;
+	}
+
+	/* Set SCHED_DEADLINE on the child process */
+	if (dl_set_sched_attr(pid, runtime, deadline, period) < 0) {
+		kill(pid, SIGKILL);
+		waitpid(pid, NULL, 0);
+		return -1;
+	}
+
+	return pid;
+}
+
+void dl_cleanup_cpuhog(pid_t pid)
+{
+	int i;
+
+	if (pid <= 0)
+		return;
+
+	/* Try SIGTERM first */
+	kill(pid, SIGTERM);
+
+	/* Wait up to 1 second for graceful exit */
+	for (i = 0; i < 10; i++) {
+		if (waitpid(pid, NULL, WNOHANG) == pid)
+			return;
+		usleep(100000); /* 100ms */
+	}
+
+	/* Force kill */
+	kill(pid, SIGKILL);
+	waitpid(pid, NULL, 0);
+}
+
+int dl_find_cpuhogs(pid_t *pids, int max_pids)
+{
+	FILE *f;
+	char line[256];
+	int count = 0;
+
+	f = popen("pgrep -x cpuhog", "r");
+	if (!f)
+		return -1;
+
+	while (fgets(line, sizeof(line), f) && count < max_pids) {
+		pid_t pid = atoi(line);
+
+		if (pid > 0)
+			pids[count++] = pid;
+	}
+
+	pclose(f);
+	return count;
+}
+
+int dl_wait_for_pid(pid_t pid, int timeout_ms)
+{
+	char path[256];
+	int elapsed = 0;
+	int interval = 10; /* 10ms polling interval */
+
+	snprintf(path, sizeof(path), "/proc/%d", pid);
+
+	while (elapsed < timeout_ms) {
+		if (access(path, F_OK) == 0)
+			return 0;
+
+		usleep(interval * 1000);
+		elapsed += interval;
+	}
+
+	return -1;
+}
+
+/*
+ * CPU topology operations
+ */
+
+int dl_get_online_cpus(void)
+{
+	return (int)sysconf(_SC_NPROCESSORS_ONLN);
+}
+
+int dl_get_hotpluggable_cpus(int *cpus, int max_cpus)
+{
+	int cpu, count = 0;
+	int max_cpu = (int)sysconf(_SC_NPROCESSORS_CONF);
+	char path[256];
+
+	for (cpu = 1; cpu < max_cpu && count < max_cpus; cpu++) {
+		snprintf(path, sizeof(path),
+			 "/sys/devices/system/cpu/cpu%d/online", cpu);
+
+		/* If the online file exists, the CPU is hotpluggable */
+		if (access(path, F_OK) == 0)
+			cpus[count++] = cpu;
+	}
+
+	return count;
+}
+
+static int write_cpu_online(int cpu, int online)
+{
+	char path[256];
+	FILE *f;
+	int ret;
+
+	snprintf(path, sizeof(path),
+		 "/sys/devices/system/cpu/cpu%d/online", cpu);
+
+	f = fopen(path, "w");
+	if (!f)
+		return -1;
+
+	ret = fprintf(f, "%d\n", online);
+	fclose(f);
+
+	return ret > 0 ? 0 : -1;
+}
+
+int dl_cpu_online(int cpu)
+{
+	return write_cpu_online(cpu, 1);
+}
+
+int dl_cpu_offline(int cpu)
+{
+	return write_cpu_online(cpu, 0);
+}
+
+int dl_is_cpu_online(int cpu)
+{
+	char path[256];
+	FILE *f;
+	int online = -1;
+
+	snprintf(path, sizeof(path),
+		 "/sys/devices/system/cpu/cpu%d/online", cpu);
+
+	f = fopen(path, "r");
+	if (!f) {
+		/* CPU0 often doesn't have an online file (always online) */
+		if (cpu == 0)
+			return 1;
+		return -1;
+	}
+
+	if (fscanf(f, "%d", &online) != 1)
+		online = -1;
+
+	fclose(f);
+	return online;
+}
diff --git a/tools/testing/selftests/sched/deadline/dl_util.h b/tools/testing/selftests/sched/deadline/dl_util.h
new file mode 100644
index 0000000000000..9ab9d055a95a0
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/dl_util.h
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SCHED_DEADLINE Utility Library
+ *
+ * Common helper functions for SCHED_DEADLINE scheduler tests.
+ */
+
+#ifndef __DL_UTIL_H__
+#define __DL_UTIL_H__
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <stdbool.h>
+#include <linux/sched/types.h>
+
+/* SCHED_DEADLINE policy number */
+#ifndef SCHED_DEADLINE
+#define SCHED_DEADLINE 6
+#endif
+
+/*
+ * Scheduling operations
+ */
+
+/**
+ * dl_set_sched_attr() - Set SCHED_DEADLINE parameters for a task
+ * @pid: Process ID (0 for current task)
+ * @runtime: Runtime in nanoseconds
+ * @deadline: Deadline in nanoseconds
+ * @period: Period in nanoseconds
+ *
+ * Sets the scheduling policy to SCHED_DEADLINE with the given parameters.
+ *
+ * Return: 0 on success, -1 on error (errno set)
+ */
+int dl_set_sched_attr(pid_t pid, uint64_t runtime, uint64_t deadline,
+		      uint64_t period);
+
+/**
+ * dl_get_sched_attr() - Get scheduling attributes for a task
+ * @pid: Process ID (0 for current task)
+ * @attr: Pointer to sched_attr structure to fill
+ *
+ * Return: 0 on success, -1 on error (errno set)
+ */
+int dl_get_sched_attr(pid_t pid, struct sched_attr *attr);
+
+/**
+ * dl_get_policy() - Get scheduling policy for a task
+ * @pid: Process ID
+ *
+ * Reads the policy from /proc/<pid>/sched.
+ *
+ * Return: Policy number (e.g., 6 for SCHED_DEADLINE), -1 on error
+ */
+int dl_get_policy(pid_t pid);
+
+/**
+ * dl_is_deadline_task() - Check if task is using SCHED_DEADLINE
+ * @pid: Process ID
+ *
+ * Return: true if task uses SCHED_DEADLINE, false otherwise
+ */
+bool dl_is_deadline_task(pid_t pid);
+
+/*
+ * Bandwidth management
+ */
+
+/**
+ * dl_get_rt_bandwidth() - Read RT bandwidth settings
+ * @runtime_us: Pointer to store runtime in microseconds
+ * @period_us: Pointer to store period in microseconds
+ *
+ * Reads from /proc/sys/kernel/sched_rt_runtime_us and
+ * /proc/sys/kernel/sched_rt_period_us.
+ *
+ * Return: 0 on success, -1 on error
+ */
+int dl_get_rt_bandwidth(uint64_t *runtime_us, uint64_t *period_us);
+
+/**
+ * dl_calc_max_bandwidth_percent() - Calculate available bandwidth percentage
+ *
+ * Calculates the maximum bandwidth available per CPU as a percentage,
+ * based on RT bandwidth settings.
+ *
+ * Return: Bandwidth percentage (0-100), or -1 on error
+ */
+int dl_calc_max_bandwidth_percent(void);
+
+/*
+ * Process management
+ */
+
+/**
+ * dl_create_cpuhog() - Fork and create a SCHED_DEADLINE cpuhog process
+ * @runtime: Runtime in nanoseconds
+ * @deadline: Deadline in nanoseconds
+ * @period: Period in nanoseconds
+ * @duration_secs: How long cpuhog should run (0 for infinite)
+ *
+ * Forks a cpuhog process and sets it to SCHED_DEADLINE with the given
+ * parameters. The cpuhog will run for duration_secs seconds.
+ *
+ * Return: PID of cpuhog process, -1 on error
+ */
+pid_t dl_create_cpuhog(uint64_t runtime, uint64_t deadline, uint64_t period,
+		       int duration_secs);
+
+/**
+ * dl_cleanup_cpuhog() - Kill and cleanup a cpuhog process
+ * @pid: PID of cpuhog to kill
+ *
+ * Sends SIGTERM, waits briefly, then SIGKILL if needed.
+ */
+void dl_cleanup_cpuhog(pid_t pid);
+
+/**
+ * dl_find_cpuhogs() - Find all running cpuhog processes
+ * @pids: Array to store PIDs
+ * @max_pids: Size of pids array
+ *
+ * Uses pgrep to find all processes named "cpuhog".
+ *
+ * Return: Number of cpuhog PIDs found, -1 on error
+ */
+int dl_find_cpuhogs(pid_t *pids, int max_pids);
+
+/**
+ * dl_wait_for_pid() - Wait for a process to appear
+ * @pid: Process ID to wait for
+ * @timeout_ms: Timeout in milliseconds
+ *
+ * Polls /proc/<pid> until it exists or timeout expires.
+ *
+ * Return: 0 if process appeared, -1 on timeout
+ */
+int dl_wait_for_pid(pid_t pid, int timeout_ms);
+
+/*
+ * CPU topology operations
+ */
+
+/**
+ * dl_get_online_cpus() - Get number of online CPUs
+ *
+ * Return: Number of online CPUs, -1 on error
+ */
+int dl_get_online_cpus(void);
+
+/**
+ * dl_get_hotpluggable_cpus() - Get list of hotpluggable CPUs
+ * @cpus: Array to store CPU numbers
+ * @max_cpus: Size of cpus array
+ *
+ * Returns CPUs that can be offlined (typically all except CPU0).
+ *
+ * Return: Number of hotpluggable CPUs, -1 on error
+ */
+int dl_get_hotpluggable_cpus(int *cpus, int max_cpus);
+
+/**
+ * dl_cpu_online() - Bring a CPU online
+ * @cpu: CPU number to online
+ *
+ * Writes 1 to /sys/devices/system/cpu/cpu<N>/online.
+ *
+ * Return: 0 on success, -1 on error
+ */
+int dl_cpu_online(int cpu);
+
+/**
+ * dl_cpu_offline() - Take a CPU offline
+ * @cpu: CPU number to offline
+ *
+ * Writes 0 to /sys/devices/system/cpu/cpu<N>/online.
+ *
+ * Return: 0 on success, -1 on error
+ */
+int dl_cpu_offline(int cpu);
+
+/**
+ * dl_is_cpu_online() - Check if CPU is online
+ * @cpu: CPU number
+ *
+ * Return: 1 if online, 0 if offline, -1 on error
+ */
+int dl_is_cpu_online(int cpu);
+
+/*
+ * Time conversion helpers
+ */
+
+/**
+ * dl_ms_to_ns() - Convert milliseconds to nanoseconds
+ */
+static inline uint64_t dl_ms_to_ns(uint64_t ms)
+{
+	return ms * 1000000ULL;
+}
+
+/**
+ * dl_us_to_ns() - Convert microseconds to nanoseconds
+ */
+static inline uint64_t dl_us_to_ns(uint64_t us)
+{
+	return us * 1000ULL;
+}
+
+/**
+ * dl_ns_to_us() - Convert nanoseconds to microseconds
+ */
+static inline uint64_t dl_ns_to_us(uint64_t ns)
+{
+	return ns / 1000ULL;
+}
+
+/**
+ * dl_ns_to_ms() - Convert nanoseconds to milliseconds
+ */
+static inline uint64_t dl_ns_to_ms(uint64_t ns)
+{
+	return ns / 1000000ULL;
+}
+
+#endif /* __DL_UTIL_H__ */

-- 
2.53.0


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

* [PATCH RFC 3/7] selftests/sched: Integrate SCHED_DEADLINE tests into kselftest framework
  2026-03-06 16:10 [PATCH RFC 0/7] selftests/sched: Add comprehensive SCHED_DEADLINE test suite Juri Lelli
  2026-03-06 16:10 ` [PATCH RFC 1/7] selftests/sched: Add SCHED_DEADLINE test framework infrastructure Juri Lelli
  2026-03-06 16:10 ` [PATCH RFC 2/7] selftests/sched: Add SCHED_DEADLINE utility library Juri Lelli
@ 2026-03-06 16:10 ` Juri Lelli
  2026-03-06 16:10 ` [PATCH RFC 4/7] selftests/sched: Add basic SCHED_DEADLINE functionality tests Juri Lelli
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 21+ messages in thread
From: Juri Lelli @ 2026-03-06 16:10 UTC (permalink / raw)
  To: Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest,
	Juri Lelli

Integrate the SCHED_DEADLINE test suite with the kselftest framework
by adding it to the parent sched/Makefile. This allows the tests to be
run via the standard kselftest mechanisms alongside other scheduler
tests.

The integration adds deadline/runner to TEST_PROGS for automatic
execution during test runs, and deadline/cpuhog to
TEST_GEN_PROGS_EXTENDED as a helper program that should be built but
not executed directly. Build rules recursively build the deadline/
subdirectory tests when the sched test suite is built.

The tests can now be run using standard kselftest invocations such as
'make -C tools/testing/selftests TARGETS=sched run_tests' or 'make -C
tools/testing/selftests/sched run_tests'. The deadline/runner will be
executed as part of the sched test suite, providing structured output
that integrates with the kselftest reporting infrastructure.

Assisted-by: Claude Code: claude-sonnet-4-5@20250929
Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
---
 tools/testing/selftests/sched/Makefile | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/tools/testing/selftests/sched/Makefile b/tools/testing/selftests/sched/Makefile
index 099ee9213557a..8ee7620cc214c 100644
--- a/tools/testing/selftests/sched/Makefile
+++ b/tools/testing/selftests/sched/Makefile
@@ -11,4 +11,12 @@ LDLIBS += -lpthread
 TEST_GEN_FILES := cs_prctl_test
 TEST_PROGS := cs_prctl_test
 
+# SCHED_DEADLINE tests in subdirectory
+TEST_PROGS += deadline/runner
+TEST_GEN_PROGS_EXTENDED := deadline/cpuhog
+
 include ../lib.mk
+
+# Build deadline tests
+$(OUTPUT)/deadline/runner $(OUTPUT)/deadline/cpuhog:
+	$(MAKE) -C deadline OUTPUT=$(OUTPUT)/deadline/

-- 
2.53.0


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

* [PATCH RFC 4/7] selftests/sched: Add basic SCHED_DEADLINE functionality tests
  2026-03-06 16:10 [PATCH RFC 0/7] selftests/sched: Add comprehensive SCHED_DEADLINE test suite Juri Lelli
                   ` (2 preceding siblings ...)
  2026-03-06 16:10 ` [PATCH RFC 3/7] selftests/sched: Integrate SCHED_DEADLINE tests into kselftest framework Juri Lelli
@ 2026-03-06 16:10 ` Juri Lelli
  2026-03-09  8:15   ` Gabriele Monaco
  2026-03-06 16:10 ` [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest Juri Lelli
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 21+ messages in thread
From: Juri Lelli @ 2026-03-06 16:10 UTC (permalink / raw)
  To: Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest,
	Juri Lelli

Add initial test coverage for SCHED_DEADLINE scheduler functionality.
This patch introduces two fundamental tests that validate core
SCHED_DEADLINE behavior using the test framework and utility library.

The basic_scheduling test creates a cpuhog process with SCHED_DEADLINE
policy and verifies the scheduling policy is correctly set to policy 6
(SCHED_DEADLINE). It confirms the task executes successfully for the
specified duration using reasonable parameters with 30ms runtime, 100ms
deadline, and 100ms period. This validates the fundamental operation of
setting SCHED_DEADLINE policy and running deadline-scheduled tasks.

The parameter_validation test ensures the kernel properly validates
SCHED_DEADLINE parameters. It tests that invalid parameter combinations
such as runtime greater than deadline are correctly rejected with
EINVAL, while valid parameter configurations are accepted. This confirms
the kernel's parameter validation logic is working correctly and
prevents misconfigured deadline tasks from being scheduled.

Both tests use the dl_util library for SCHED_DEADLINE operations and
follow the framework pattern with automatic registration via
REGISTER_DL_TEST(). The tests pass cleanly in VM environments with 2
tests passing, 0 skipped, and 0 failed.

Assisted-by: Claude Code: claude-sonnet-4-5@20250929
Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
---
 tools/testing/selftests/sched/deadline/Makefile |   7 +-
 tools/testing/selftests/sched/deadline/basic.c  | 127 ++++++++++++++++++++++++
 2 files changed, 132 insertions(+), 2 deletions(-)

diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
index ea3fdfbef459e..3fb4568a59e20 100644
--- a/tools/testing/selftests/sched/deadline/Makefile
+++ b/tools/testing/selftests/sched/deadline/Makefile
@@ -14,8 +14,7 @@ OUTPUT_DIR := $(OUTPUT)
 UTIL_OBJS := $(OUTPUT)/dl_util.o
 
 # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c)
-# Will be populated as we add tests
-TEST_OBJS :=
+TEST_OBJS := $(OUTPUT)/basic.o
 
 # Runner binary links utility and test objects
 $(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h | $(OUTPUT_DIR)
@@ -29,6 +28,10 @@ $(OUTPUT)/cpuhog: cpuhog.c | $(OUTPUT_DIR)
 $(OUTPUT)/dl_util.o: dl_util.c dl_util.h | $(OUTPUT_DIR)
 	$(CC) $(CFLAGS) -c $< -o $@
 
+# Test files
+$(OUTPUT)/basic.o: basic.c dl_test.h dl_util.h | $(OUTPUT_DIR)
+	$(CC) $(CFLAGS) -c $< -o $@
+
 $(OUTPUT_DIR):
 	mkdir -p $@
 
diff --git a/tools/testing/selftests/sched/deadline/basic.c b/tools/testing/selftests/sched/deadline/basic.c
new file mode 100644
index 0000000000000..f1c4e8c751b61
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/basic.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Basic SCHED_DEADLINE functionality tests
+ *
+ * Validates fundamental SCHED_DEADLINE scheduler operations including
+ * policy setup, parameter validation, and basic task execution.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <sched.h>
+#include "dl_test.h"
+#include "dl_util.h"
+
+/*
+ * Test: Basic SCHED_DEADLINE scheduling
+ *
+ * Verifies that a task can be successfully scheduled with SCHED_DEADLINE
+ * policy and executes correctly.
+ */
+static enum dl_test_status test_basic_scheduling_run(void *ctx)
+{
+	pid_t pid;
+	int policy;
+	int status;
+	uint64_t runtime_ns, deadline_ns, period_ns;
+
+	/* Use reasonable deadline parameters: 30ms/100ms/100ms */
+	runtime_ns = dl_ms_to_ns(30);
+	deadline_ns = dl_ms_to_ns(100);
+	period_ns = dl_ms_to_ns(100);
+
+	/* Create a cpuhog process with SCHED_DEADLINE */
+	pid = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 2);
+	DL_FAIL_IF(pid < 0, "Failed to create cpuhog process: %s",
+		   strerror(errno));
+
+	/* Process is now running with SCHED_DEADLINE set */
+	printf("  Created cpuhog with SCHED_DEADLINE (PID %d)\n", pid);
+
+	/* Verify it's using SCHED_DEADLINE (policy 6) */
+	policy = dl_get_policy(pid);
+	DL_FAIL_IF(policy < 0, "Failed to read scheduling policy");
+	DL_EQ(policy, SCHED_DEADLINE);
+
+	/* Alternative check using helper */
+	DL_ASSERT(dl_is_deadline_task(pid));
+
+	printf("  cpuhog running with SCHED_DEADLINE (PID %d)\n", pid);
+
+	/* Wait for cpuhog to complete (2 seconds) */
+	if (waitpid(pid, &status, 0) < 0) {
+		dl_cleanup_cpuhog(pid);
+		DL_FAIL("waitpid failed: %s", strerror(errno));
+	}
+
+	/* Verify it exited successfully */
+	DL_FAIL_IF(!WIFEXITED(status), "Process did not exit normally");
+	DL_EQ(WEXITSTATUS(status), 0);
+
+	printf("  cpuhog completed successfully\n");
+
+	return DL_TEST_PASS;
+}
+
+static struct dl_test test_basic_scheduling = {
+	.name = "basic_scheduling",
+	.description = "Verify basic SCHED_DEADLINE policy setup and execution",
+	.run = test_basic_scheduling_run,
+};
+REGISTER_DL_TEST(&test_basic_scheduling);
+
+/*
+ * Test: SCHED_DEADLINE parameter validation
+ *
+ * Verifies that the kernel correctly validates deadline parameters,
+ * rejecting invalid configurations and accepting valid ones.
+ */
+static enum dl_test_status test_parameter_validation_run(void *ctx)
+{
+	int ret;
+	uint64_t runtime_ns, deadline_ns, period_ns;
+
+	printf("  Testing invalid parameters (runtime > deadline)...\n");
+
+	/* Invalid: runtime (200ms) > deadline (100ms) */
+	runtime_ns = dl_ms_to_ns(200);
+	deadline_ns = dl_ms_to_ns(100);
+	period_ns = dl_ms_to_ns(100);
+
+	ret = dl_set_sched_attr(0, runtime_ns, deadline_ns, period_ns);
+	DL_FAIL_IF(ret == 0, "Invalid parameters were accepted (runtime > deadline)");
+	DL_FAIL_IF(errno != EINVAL, "Expected EINVAL, got %s", strerror(errno));
+
+	printf("  Invalid parameters correctly rejected with EINVAL\n");
+
+	printf("  Testing valid parameters...\n");
+
+	/* Valid: runtime (30ms) <= deadline (100ms) <= period (100ms) */
+	runtime_ns = dl_ms_to_ns(30);
+	deadline_ns = dl_ms_to_ns(100);
+	period_ns = dl_ms_to_ns(100);
+
+	ret = dl_set_sched_attr(0, runtime_ns, deadline_ns, period_ns);
+	DL_FAIL_IF(ret < 0, "Valid parameters were rejected: %s", strerror(errno));
+
+	printf("  Valid parameters correctly accepted\n");
+
+	/* Reset to normal scheduling using sched_setscheduler */
+	struct sched_param param = { .sched_priority = 0 };
+
+	sched_setscheduler(0, SCHED_OTHER, &param);
+
+	return DL_TEST_PASS;
+}
+
+static struct dl_test test_parameter_validation = {
+	.name = "parameter_validation",
+	.description = "Verify SCHED_DEADLINE parameter validation (accept/reject)",
+	.run = test_parameter_validation_run,
+};
+REGISTER_DL_TEST(&test_parameter_validation);

-- 
2.53.0


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

* [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest
  2026-03-06 16:10 [PATCH RFC 0/7] selftests/sched: Add comprehensive SCHED_DEADLINE test suite Juri Lelli
                   ` (3 preceding siblings ...)
  2026-03-06 16:10 ` [PATCH RFC 4/7] selftests/sched: Add basic SCHED_DEADLINE functionality tests Juri Lelli
@ 2026-03-06 16:10 ` Juri Lelli
  2026-03-11  9:31   ` Christian Loehle
  2026-03-06 16:10 ` [PATCH RFC 6/7] selftests/sched: Add SCHED_DEADLINE fair_server " Juri Lelli
  2026-03-06 16:10 ` [PATCH RFC 7/7] selftests/sched: Add SCHED_DEADLINE ENQUEUE_REPLENISH bug test Juri Lelli
  6 siblings, 1 reply; 21+ messages in thread
From: Juri Lelli @ 2026-03-06 16:10 UTC (permalink / raw)
  To: Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest,
	Juri Lelli

Add bandwidth admission control tests for SCHED_DEADLINE scheduler.
These tests validate that the kernel properly enforces global bandwidth
limits and correctly admits or rejects deadline tasks based on available
system capacity.

The bandwidth_admission test verifies that N tasks can run
simultaneously at the maximum available bandwidth per CPU. This maximum
is calculated as the RT bandwidth limit minus any DL server bandwidth
allocations, ensuring that tasks can fully utilize the available
deadline scheduling capacity without being rejected by admission
control.

The bandwidth_overflow test verifies that the kernel correctly rejects
tasks that would exceed the available global bandwidth. This ensures the
admission control mechanism prevents overcommitment of deadline
resources, which is critical for maintaining temporal isolation and
schedulability guarantees.

The implementation includes automatic detection of DL server bandwidth
allocations by scanning /sys/kernel/debug/sched/*_server directories.
This detects servers such as fair_server and any future additions,
ensuring tests adapt automatically to system configuration changes.
Available bandwidth is calculated by reading cpu0 configuration across
all servers, with the assumption of symmetric systems where all CPUs
have identical configuration.

Assisted-by: Claude Code: claude-sonnet-4-5@20250929
Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
---
 tools/testing/selftests/sched/deadline/Makefile    |   5 +-
 tools/testing/selftests/sched/deadline/bandwidth.c | 270 +++++++++++++++++++++
 tools/testing/selftests/sched/deadline/dl_util.c   |  73 +++++-
 tools/testing/selftests/sched/deadline/dl_util.h   |  12 +-
 4 files changed, 355 insertions(+), 5 deletions(-)

diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
index 3fb4568a59e20..daa2f5d14e947 100644
--- a/tools/testing/selftests/sched/deadline/Makefile
+++ b/tools/testing/selftests/sched/deadline/Makefile
@@ -14,7 +14,7 @@ OUTPUT_DIR := $(OUTPUT)
 UTIL_OBJS := $(OUTPUT)/dl_util.o
 
 # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c)
-TEST_OBJS := $(OUTPUT)/basic.o
+TEST_OBJS := $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o
 
 # Runner binary links utility and test objects
 $(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h | $(OUTPUT_DIR)
@@ -32,6 +32,9 @@ $(OUTPUT)/dl_util.o: dl_util.c dl_util.h | $(OUTPUT_DIR)
 $(OUTPUT)/basic.o: basic.c dl_test.h dl_util.h | $(OUTPUT_DIR)
 	$(CC) $(CFLAGS) -c $< -o $@
 
+$(OUTPUT)/bandwidth.o: bandwidth.c dl_test.h dl_util.h | $(OUTPUT_DIR)
+	$(CC) $(CFLAGS) -c $< -o $@
+
 $(OUTPUT_DIR):
 	mkdir -p $@
 
diff --git a/tools/testing/selftests/sched/deadline/bandwidth.c b/tools/testing/selftests/sched/deadline/bandwidth.c
new file mode 100644
index 0000000000000..72755a200db22
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/bandwidth.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCHED_DEADLINE bandwidth admission control tests
+ *
+ * Validates that the kernel correctly enforces bandwidth limits for
+ * SCHED_DEADLINE tasks, including per-CPU bandwidth replication and
+ * overflow rejection.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include "dl_test.h"
+#include "dl_util.h"
+
+/*
+ * Test: Bandwidth admission control with max bandwidth per CPU
+ *
+ * Verifies that SCHED_DEADLINE bandwidth is replicated per CPU, allowing
+ * one task per CPU to use the maximum available bandwidth (typically 95%).
+ */
+static enum dl_test_status test_bandwidth_admission_run(void *ctx)
+{
+	uint64_t rt_runtime_us, rt_period_us;
+	int max_bw_percent;
+	uint64_t runtime_ns, deadline_ns, period_ns;
+	int num_cpus, i;
+	pid_t *pids = NULL;
+	int started = 0, running = 0;
+	enum dl_test_status ret = DL_TEST_FAIL;
+
+	/* Get RT bandwidth settings */
+	DL_FAIL_IF(dl_get_rt_bandwidth(&rt_runtime_us, &rt_period_us) < 0,
+		   "Failed to read RT bandwidth settings");
+
+	printf("  RT bandwidth: runtime=%luµs, period=%luµs (%.0f%%)\n",
+	       rt_runtime_us, rt_period_us,
+	       (double)rt_runtime_us * 100.0 / rt_period_us);
+
+	/* Show server overhead */
+	int server_overhead = dl_get_server_bandwidth_overhead();
+
+	if (server_overhead > 0)
+		printf("  DL server overhead: %d%% per CPU\n", server_overhead);
+
+	/* Calculate maximum bandwidth percentage */
+	max_bw_percent = dl_calc_max_bandwidth_percent();
+	DL_FAIL_IF(max_bw_percent < 0, "Failed to calculate max bandwidth");
+
+	printf("  Available bandwidth per CPU: %d%%\n", max_bw_percent);
+
+	/* Calculate task parameters: 100ms period for easy calculation */
+	period_ns = dl_ms_to_ns(100);  /* 100ms */
+	runtime_ns = (period_ns * max_bw_percent) / 100;
+	deadline_ns = period_ns;
+
+	printf("  Task params: runtime=%lums, deadline=%lums, period=%lums\n",
+	       dl_ns_to_ms(runtime_ns), dl_ns_to_ms(deadline_ns),
+	       dl_ns_to_ms(period_ns));
+
+	/* Get number of CPUs */
+	num_cpus = dl_get_online_cpus();
+	DL_FAIL_IF(num_cpus <= 0, "Failed to get number of CPUs");
+
+	printf("  Number of online CPUs: %d\n", num_cpus);
+
+	/* Allocate PID array */
+	pids = calloc(num_cpus, sizeof(pid_t));
+	DL_FAIL_IF(!pids, "Failed to allocate PID array");
+
+	/* Start one cpuhog per CPU at max bandwidth */
+	printf("  Starting %d cpuhog tasks at max bandwidth...\n", num_cpus);
+
+	for (i = 0; i < num_cpus; i++) {
+		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
+		if (pids[i] < 0) {
+			printf("  Task %d failed to start: %s\n",
+			       i + 1, strerror(errno));
+			goto cleanup;
+		}
+		started++;
+	}
+
+	/* Brief wait for tasks to settle */
+	usleep(500000);  /* 500ms */
+
+	/* Verify all tasks are running with SCHED_DEADLINE */
+	for (i = 0; i < started; i++) {
+		if (pids[i] <= 0)
+			continue;
+
+		if (kill(pids[i], 0) < 0) {
+			printf("  Task PID %d died unexpectedly\n", pids[i]);
+			continue;
+		}
+
+		if (dl_is_deadline_task(pids[i]))
+			running++;
+	}
+
+	printf("  Started %d/%d tasks, %d running with SCHED_DEADLINE\n",
+	       started, num_cpus, running);
+
+	/* Test passes if we started all N tasks and they're all running */
+	if (started == num_cpus && running == num_cpus) {
+		printf("  SUCCESS: All %d tasks running at max bandwidth\n",
+		       num_cpus);
+		ret = DL_TEST_PASS;
+	} else if (started != num_cpus) {
+		DL_ERR("Only started %d/%d tasks", started, num_cpus);
+		ret = DL_TEST_FAIL;
+	} else {
+		DL_ERR("Started %d tasks but only %d using SCHED_DEADLINE",
+		       started, running);
+		ret = DL_TEST_FAIL;
+	}
+
+cleanup:
+	/* Cleanup all started tasks */
+	for (i = 0; i < started; i++) {
+		if (pids[i] > 0)
+			dl_cleanup_cpuhog(pids[i]);
+	}
+
+	free(pids);
+	return ret;
+}
+
+static struct dl_test test_bandwidth_admission = {
+	.name = "bandwidth_admission",
+	.description = "Verify per-CPU bandwidth replication (N tasks at max bandwidth)",
+	.run = test_bandwidth_admission_run,
+};
+REGISTER_DL_TEST(&test_bandwidth_admission);
+
+/*
+ * Test: Bandwidth admission control overflow rejection
+ *
+ * Verifies that the kernel rejects tasks that would exceed available
+ * bandwidth on a CPU. Creates N-1 tasks at max bandwidth, then attempts
+ * to create one more at slightly higher bandwidth (should fail).
+ */
+static enum dl_test_status test_bandwidth_overflow_run(void *ctx)
+{
+	uint64_t rt_runtime_us, rt_period_us;
+	int max_bw_percent;
+	uint64_t runtime_ns, deadline_ns, period_ns;
+	uint64_t overflow_runtime_ns;
+	int num_cpus, i;
+	int target_tasks;
+	pid_t *pids = NULL;
+	pid_t overflow_pid;
+	int started = 0;
+	enum dl_test_status ret = DL_TEST_FAIL;
+
+	/* Get RT bandwidth settings */
+	DL_FAIL_IF(dl_get_rt_bandwidth(&rt_runtime_us, &rt_period_us) < 0,
+		   "Failed to read RT bandwidth settings");
+
+	printf("  RT bandwidth: runtime=%luµs, period=%luµs (%.0f%%)\n",
+	       rt_runtime_us, rt_period_us,
+	       (double)rt_runtime_us * 100.0 / rt_period_us);
+
+	/* Show server overhead */
+	int server_overhead = dl_get_server_bandwidth_overhead();
+
+	if (server_overhead > 0)
+		printf("  DL server overhead: %d%% per CPU\n", server_overhead);
+
+	/* Calculate maximum bandwidth percentage */
+	max_bw_percent = dl_calc_max_bandwidth_percent();
+	DL_FAIL_IF(max_bw_percent < 0, "Failed to calculate max bandwidth");
+
+	printf("  Available bandwidth per CPU: %d%%\n", max_bw_percent);
+
+	/* Get number of CPUs */
+	num_cpus = dl_get_online_cpus();
+	DL_FAIL_IF(num_cpus <= 0, "Failed to get number of CPUs");
+
+	if (num_cpus < 2) {
+		printf("  Need at least 2 CPUs for this test (have %d)\n",
+		       num_cpus);
+		return DL_TEST_SKIP;
+	}
+
+	printf("  Number of online CPUs: %d\n", num_cpus);
+
+	/* Calculate task parameters */
+	period_ns = dl_ms_to_ns(100);  /* 100ms */
+	runtime_ns = (period_ns * max_bw_percent) / 100;
+	deadline_ns = period_ns;
+
+	printf("  Task params: runtime=%lums, deadline=%lums, period=%lums\n",
+	       dl_ns_to_ms(runtime_ns), dl_ns_to_ms(deadline_ns),
+	       dl_ns_to_ms(period_ns));
+
+	/* Start N-1 tasks at max bandwidth */
+	target_tasks = num_cpus - 1;
+	pids = calloc(target_tasks, sizeof(pid_t));
+	DL_FAIL_IF(!pids, "Failed to allocate PID array");
+
+	printf("  Starting %d tasks at max bandwidth...\n", target_tasks);
+
+	for (i = 0; i < target_tasks; i++) {
+		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
+		if (pids[i] < 0) {
+			printf("  Task %d failed to start: %s\n",
+			       i + 1, strerror(errno));
+			goto cleanup;
+		}
+		started++;
+	}
+
+	printf("  Successfully started %d/%d tasks\n", started, target_tasks);
+
+	/* Brief wait */
+	usleep(500000);  /* 500ms */
+
+	/* Try to start one more task at max+1% bandwidth (should fail) */
+	overflow_runtime_ns = (runtime_ns * 101) / 100;  /* Add 1% */
+
+	printf("  Attempting overflow task with runtime=%lums (+1%%)...\n",
+	       dl_ns_to_ms(overflow_runtime_ns));
+
+	overflow_pid = dl_create_cpuhog(overflow_runtime_ns, deadline_ns,
+					period_ns, 0);
+
+	if (overflow_pid < 0) {
+		/* Expected: admission control rejected it */
+		printf("  Overflow task correctly rejected: %s\n",
+		       strerror(errno));
+		ret = DL_TEST_PASS;
+	} else {
+		/* Unexpected: it was admitted */
+		usleep(100000);  /* 100ms */
+
+		if (kill(overflow_pid, 0) == 0) {
+			printf("  ERROR: Overflow task admitted and running\n");
+			dl_cleanup_cpuhog(overflow_pid);
+			ret = DL_TEST_FAIL;
+		} else {
+			/* It was admitted but died - still wrong */
+			printf("  ERROR: Overflow task admitted but died\n");
+			ret = DL_TEST_FAIL;
+		}
+	}
+
+cleanup:
+	/* Cleanup all tasks */
+	for (i = 0; i < started; i++) {
+		if (pids[i] > 0)
+			dl_cleanup_cpuhog(pids[i]);
+	}
+
+	free(pids);
+	return ret;
+}
+
+static struct dl_test test_bandwidth_overflow = {
+	.name = "bandwidth_overflow",
+	.description = "Verify bandwidth overflow rejection (N-1 + overflow fails)",
+	.run = test_bandwidth_overflow_run,
+};
+REGISTER_DL_TEST(&test_bandwidth_overflow);
diff --git a/tools/testing/selftests/sched/deadline/dl_util.c b/tools/testing/selftests/sched/deadline/dl_util.c
index 0d7c46ba877f3..6727d622d72d3 100644
--- a/tools/testing/selftests/sched/deadline/dl_util.c
+++ b/tools/testing/selftests/sched/deadline/dl_util.c
@@ -14,6 +14,8 @@
 #include <sys/wait.h>
 #include <signal.h>
 #include <time.h>
+#include <glob.h>
+#include <dirent.h>
 #include "dl_util.h"
 
 /* Syscall numbers for sched_setattr/sched_getattr */
@@ -121,10 +123,65 @@ int dl_get_rt_bandwidth(uint64_t *runtime_us, uint64_t *period_us)
 				period_us);
 }
 
+int dl_get_server_bandwidth_overhead(void)
+{
+	glob_t globbuf;
+	char pattern[512];
+	size_t i;
+	int total_overhead = 0;
+
+	/* Find all *_server directories */
+	snprintf(pattern, sizeof(pattern),
+		 "/sys/kernel/debug/sched/*_server");
+
+	if (glob(pattern, 0, NULL, &globbuf) != 0) {
+		/* No servers found - not an error, just no overhead */
+		return 0;
+	}
+
+	/*
+	 * Sum overhead from cpu0 across all servers.
+	 * Assumes symmetric system where all CPUs have identical server
+	 * configuration. Reading only cpu0 represents the per-CPU overhead.
+	 */
+	for (i = 0; i < globbuf.gl_pathc; i++) {
+		char runtime_path[512];
+		char period_path[512];
+		char *server_path = globbuf.gl_pathv[i];
+		uint64_t runtime_ns = 0, period_ns = 0;
+		int percent;
+
+		/* Build paths to cpu0 runtime and period files */
+		snprintf(runtime_path, sizeof(runtime_path),
+			 "%s/cpu0/runtime", server_path);
+		snprintf(period_path, sizeof(period_path),
+			 "%s/cpu0/period", server_path);
+
+		/* Read runtime and period for cpu0 */
+		if (read_proc_uint64(runtime_path, &runtime_ns) < 0)
+			continue;
+		if (read_proc_uint64(period_path, &period_ns) < 0)
+			continue;
+
+		if (period_ns == 0)
+			continue;
+
+		/* Calculate percentage for this server */
+		percent = (runtime_ns * 100) / period_ns;
+
+		/* Accumulate overhead from all servers */
+		total_overhead += percent;
+	}
+
+	globfree(&globbuf);
+	return total_overhead;
+}
+
 int dl_calc_max_bandwidth_percent(void)
 {
 	uint64_t runtime_us, period_us;
-	int percent;
+	int rt_percent, server_overhead;
+	int available_percent;
 
 	if (dl_get_rt_bandwidth(&runtime_us, &period_us) < 0)
 		return -1;
@@ -132,8 +189,18 @@ int dl_calc_max_bandwidth_percent(void)
 	if (period_us == 0)
 		return -1;
 
-	percent = (runtime_us * 100) / period_us;
-	return percent > 0 ? percent : 1;
+	/* Calculate RT bandwidth percentage */
+	rt_percent = (runtime_us * 100) / period_us;
+
+	/* Get server overhead */
+	server_overhead = dl_get_server_bandwidth_overhead();
+	if (server_overhead < 0)
+		server_overhead = 0;
+
+	/* Available bandwidth = RT bandwidth - server overhead */
+	available_percent = rt_percent - server_overhead;
+
+	return available_percent > 0 ? available_percent : 1;
 }
 
 /*
diff --git a/tools/testing/selftests/sched/deadline/dl_util.h b/tools/testing/selftests/sched/deadline/dl_util.h
index 9ab9d055a95a0..f8046eb0cbd3b 100644
--- a/tools/testing/selftests/sched/deadline/dl_util.h
+++ b/tools/testing/selftests/sched/deadline/dl_util.h
@@ -79,11 +79,21 @@ bool dl_is_deadline_task(pid_t pid);
  */
 int dl_get_rt_bandwidth(uint64_t *runtime_us, uint64_t *period_us);
 
+/**
+ * dl_get_server_bandwidth_overhead() - Calculate total DL server overhead per CPU
+ *
+ * Scans /sys/kernel/debug/sched/ for server directories (fair_server, etc.) and
+ * calculates the total bandwidth reserved by all DL servers per CPU.
+ *
+ * Return: Bandwidth percentage overhead per CPU (0-100), or -1 on error
+ */
+int dl_get_server_bandwidth_overhead(void);
+
 /**
  * dl_calc_max_bandwidth_percent() - Calculate available bandwidth percentage
  *
  * Calculates the maximum bandwidth available per CPU as a percentage,
- * based on RT bandwidth settings.
+ * based on RT bandwidth settings minus DL server overhead (fair_server, etc.).
  *
  * Return: Bandwidth percentage (0-100), or -1 on error
  */

-- 
2.53.0


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

* [PATCH RFC 6/7] selftests/sched: Add SCHED_DEADLINE fair_server tests to kselftest
  2026-03-06 16:10 [PATCH RFC 0/7] selftests/sched: Add comprehensive SCHED_DEADLINE test suite Juri Lelli
                   ` (4 preceding siblings ...)
  2026-03-06 16:10 ` [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest Juri Lelli
@ 2026-03-06 16:10 ` Juri Lelli
  2026-03-06 16:10 ` [PATCH RFC 7/7] selftests/sched: Add SCHED_DEADLINE ENQUEUE_REPLENISH bug test Juri Lelli
  6 siblings, 0 replies; 21+ messages in thread
From: Juri Lelli @ 2026-03-06 16:10 UTC (permalink / raw)
  To: Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest,
	Juri Lelli

Add tests validating fair_server bandwidth management and CPU protection
behavior. The fair_server is a DEADLINE server that provides CPU time to
CFS tasks while enforcing bandwidth limits within the RT bandwidth
allocation.

The fair_server_bandwidth_validation test validates that the kernel
enforces per-CPU RT bandwidth limits when configuring fair_server
runtime. It attempts to set all CPUs to 101% of the per-CPU RT bandwidth
and verifies that at least one write is rejected, ensuring the kernel
prevents misconfiguration that would exceed available bandwidth.

The fair_server_cpu_protection test verifies that CFS tasks receive
their allocated fair_server CPU time even when competing with
high-priority SCHED_FIFO tasks on the same CPU. It measures actual CPU
usage and validates it falls within expected tolerance of ±50%, ensuring
the fair_server provides the bandwidth protection that CFS tasks rely
on.

Helper functions are added to dl_util for fair_server management. These
include dl_fair_server_exists() to check if the fair_server interface is
available, dl_get_fair_server_settings() to read per-CPU runtime and
period values, dl_set_fair_server_runtime() to write per-CPU runtime
configuration, dl_set_rt_bandwidth() to configure system RT bandwidth
limits, and dl_get_process_cpu_time() to read process CPU time from
/proc/PID/stat for validation purposes.

Assisted-by: Claude Code: claude-sonnet-4-5@20250929
Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
---
 tools/testing/selftests/sched/deadline/Makefile    |   5 +-
 tools/testing/selftests/sched/deadline/dl_util.c   | 128 ++++++++++
 tools/testing/selftests/sched/deadline/dl_util.h   |  57 +++++
 .../testing/selftests/sched/deadline/fair_server.c | 260 +++++++++++++++++++++
 4 files changed, 449 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
index daa2f5d14e947..e7e16c610ee58 100644
--- a/tools/testing/selftests/sched/deadline/Makefile
+++ b/tools/testing/selftests/sched/deadline/Makefile
@@ -14,7 +14,7 @@ OUTPUT_DIR := $(OUTPUT)
 UTIL_OBJS := $(OUTPUT)/dl_util.o
 
 # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c)
-TEST_OBJS := $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o
+TEST_OBJS := $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o $(OUTPUT)/fair_server.o
 
 # Runner binary links utility and test objects
 $(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h | $(OUTPUT_DIR)
@@ -35,6 +35,9 @@ $(OUTPUT)/basic.o: basic.c dl_test.h dl_util.h | $(OUTPUT_DIR)
 $(OUTPUT)/bandwidth.o: bandwidth.c dl_test.h dl_util.h | $(OUTPUT_DIR)
 	$(CC) $(CFLAGS) -c $< -o $@
 
+$(OUTPUT)/fair_server.o: fair_server.c dl_test.h dl_util.h | $(OUTPUT_DIR)
+	$(CC) $(CFLAGS) -c $< -o $@
+
 $(OUTPUT_DIR):
 	mkdir -p $@
 
diff --git a/tools/testing/selftests/sched/deadline/dl_util.c b/tools/testing/selftests/sched/deadline/dl_util.c
index 6727d622d72d3..ca34eee964d61 100644
--- a/tools/testing/selftests/sched/deadline/dl_util.c
+++ b/tools/testing/selftests/sched/deadline/dl_util.c
@@ -203,6 +203,80 @@ int dl_calc_max_bandwidth_percent(void)
 	return available_percent > 0 ? available_percent : 1;
 }
 
+static int write_proc_uint64(const char *path, uint64_t value)
+{
+	FILE *f;
+	int ret;
+
+	f = fopen(path, "w");
+	if (!f)
+		return -1;
+
+	ret = fprintf(f, "%lu\n", value);
+	if (ret < 0) {
+		fclose(f);
+		return -1;
+	}
+
+	/* fclose() flushes and may return error if kernel write fails */
+	if (fclose(f) != 0)
+		return -1;
+
+	return 0;
+}
+
+int dl_set_rt_bandwidth(uint64_t runtime_us, uint64_t period_us)
+{
+	int ret;
+
+	ret = write_proc_uint64("/proc/sys/kernel/sched_rt_runtime_us",
+				runtime_us);
+	if (ret < 0)
+		return ret;
+
+	return write_proc_uint64("/proc/sys/kernel/sched_rt_period_us",
+				 period_us);
+}
+
+bool dl_fair_server_exists(void)
+{
+	return access("/sys/kernel/debug/sched/fair_server", F_OK) == 0;
+}
+
+int dl_get_fair_server_settings(int cpu, uint64_t *runtime_ns,
+				 uint64_t *period_ns)
+{
+	char runtime_path[256];
+	char period_path[256];
+	int ret;
+
+	snprintf(runtime_path, sizeof(runtime_path),
+		 "/sys/kernel/debug/sched/fair_server/cpu%d/runtime", cpu);
+
+	ret = read_proc_uint64(runtime_path, runtime_ns);
+	if (ret < 0)
+		return ret;
+
+	/* period_ns is optional */
+	if (period_ns) {
+		snprintf(period_path, sizeof(period_path),
+			 "/sys/kernel/debug/sched/fair_server/cpu%d/period", cpu);
+		return read_proc_uint64(period_path, period_ns);
+	}
+
+	return 0;
+}
+
+int dl_set_fair_server_runtime(int cpu, uint64_t runtime_ns)
+{
+	char path[256];
+
+	snprintf(path, sizeof(path),
+		 "/sys/kernel/debug/sched/fair_server/cpu%d/runtime", cpu);
+
+	return write_proc_uint64(path, runtime_ns);
+}
+
 /*
  * Process management
  */
@@ -321,6 +395,60 @@ int dl_wait_for_pid(pid_t pid, int timeout_ms)
 	return -1;
 }
 
+uint64_t dl_get_process_cpu_time(pid_t pid)
+{
+	char path[256];
+	char line[1024];
+	FILE *f;
+	uint64_t utime = 0, stime = 0;
+	int i;
+	char *p, *token, *saveptr;
+
+	snprintf(path, sizeof(path), "/proc/%d/stat", pid);
+	f = fopen(path, "r");
+	if (!f)
+		return 0;
+
+	if (!fgets(line, sizeof(line), f)) {
+		fclose(f);
+		return 0;
+	}
+
+	fclose(f);
+
+	/*
+	 * Parse /proc/PID/stat format:
+	 * pid (comm) state ppid ... utime stime ...
+	 *
+	 * The comm field (field 2) can contain spaces and is enclosed in
+	 * parentheses. Find the last ')' to skip past it, then parse the
+	 * remaining space-separated fields.
+	 *
+	 * After the closing ')', fields are:
+	 * 1=state 2=ppid 3=pgrp 4=sid 5=tty_nr 6=tty_pgrp 7=flags
+	 * 8=min_flt 9=cmin_flt 10=maj_flt 11=cmaj_flt 12=utime 13=stime
+	 */
+	p = strrchr(line, ')');
+	if (!p)
+		return 0;
+
+	/* Skip past ') ' */
+	p += 2;
+
+	/* Tokenize remaining fields */
+	token = strtok_r(p, " ", &saveptr);
+	for (i = 1; token && i <= 13; i++) {
+		if (i == 12)
+			utime = strtoull(token, NULL, 10);
+		else if (i == 13)
+			stime = strtoull(token, NULL, 10);
+
+		token = strtok_r(NULL, " ", &saveptr);
+	}
+
+	return utime + stime;
+}
+
 /*
  * CPU topology operations
  */
diff --git a/tools/testing/selftests/sched/deadline/dl_util.h b/tools/testing/selftests/sched/deadline/dl_util.h
index f8046eb0cbd3b..511cc92ef1e3e 100644
--- a/tools/testing/selftests/sched/deadline/dl_util.h
+++ b/tools/testing/selftests/sched/deadline/dl_util.h
@@ -99,6 +99,52 @@ int dl_get_server_bandwidth_overhead(void);
  */
 int dl_calc_max_bandwidth_percent(void);
 
+/**
+ * dl_set_rt_bandwidth() - Set RT bandwidth settings
+ * @runtime_us: Runtime in microseconds
+ * @period_us: Period in microseconds
+ *
+ * Writes to /proc/sys/kernel/sched_rt_runtime_us and
+ * /proc/sys/kernel/sched_rt_period_us. Requires root privileges.
+ *
+ * Return: 0 on success, -1 on error
+ */
+int dl_set_rt_bandwidth(uint64_t runtime_us, uint64_t period_us);
+
+/**
+ * dl_get_fair_server_settings() - Read fair_server settings for a CPU
+ * @cpu: CPU number
+ * @runtime_ns: Pointer to store runtime in nanoseconds
+ * @period_ns: Pointer to store period in nanoseconds
+ *
+ * Reads from /sys/kernel/debug/sched/fair_server/cpuN/runtime and period.
+ *
+ * Return: 0 on success, -1 on error (including if fair_server doesn't exist)
+ */
+int dl_get_fair_server_settings(int cpu, uint64_t *runtime_ns,
+				uint64_t *period_ns);
+
+/**
+ * dl_set_fair_server_runtime() - Set fair_server runtime for a CPU
+ * @cpu: CPU number
+ * @runtime_ns: Runtime in nanoseconds
+ *
+ * Writes to /sys/kernel/debug/sched/fair_server/cpuN/runtime.
+ * Requires appropriate permissions.
+ *
+ * Return: 0 on success, -1 on error
+ */
+int dl_set_fair_server_runtime(int cpu, uint64_t runtime_ns);
+
+/**
+ * dl_fair_server_exists() - Check if fair_server interface exists
+ *
+ * Checks if /sys/kernel/debug/sched/fair_server directory exists.
+ *
+ * Return: true if fair_server interface exists, false otherwise
+ */
+bool dl_fair_server_exists(void);
+
 /*
  * Process management
  */
@@ -148,6 +194,17 @@ int dl_find_cpuhogs(pid_t *pids, int max_pids);
  */
 int dl_wait_for_pid(pid_t pid, int timeout_ms);
 
+/**
+ * dl_get_process_cpu_time() - Get total CPU time for a process
+ * @pid: Process ID
+ *
+ * Reads utime and stime from /proc/<pid>/stat and returns total CPU
+ * time in clock ticks.
+ *
+ * Return: Total CPU ticks used, or 0 on error
+ */
+uint64_t dl_get_process_cpu_time(pid_t pid);
+
 /*
  * CPU topology operations
  */
diff --git a/tools/testing/selftests/sched/deadline/fair_server.c b/tools/testing/selftests/sched/deadline/fair_server.c
new file mode 100644
index 0000000000000..dbff6296090f2
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/fair_server.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCHED_DEADLINE fair_server tests
+ *
+ * Validates fair_server bandwidth management and CPU protection behavior.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <sched.h>
+#include "dl_test.h"
+#include "dl_util.h"
+
+/*
+ * Test: Fair server bandwidth validation
+ *
+ * Verifies that the kernel rejects attempts to set fair_server bandwidth
+ * that exceeds available RT bandwidth, and preserves the original value.
+ */
+static enum dl_test_status test_fair_server_bandwidth_validation_run(void *ctx)
+{
+	uint64_t rt_runtime_us, rt_period_us;
+	uint64_t fair_runtime_ns, fair_period_ns;
+	uint64_t excessive_runtime_ns;
+	uint64_t *original_runtimes = NULL;
+	int num_cpus, i;
+	int write_succeeded = 0;
+	int write_failed = 0;
+
+	/* Check if fair_server interface exists */
+	if (!dl_fair_server_exists()) {
+		printf("  Fair server interface not found\n");
+		return DL_TEST_SKIP;
+	}
+
+	/* Read RT bandwidth settings */
+	DL_FAIL_IF(dl_get_rt_bandwidth(&rt_runtime_us, &rt_period_us) < 0,
+		   "Failed to read RT bandwidth settings");
+
+	printf("  RT bandwidth: %luµs / %luµs per CPU\n",
+	       rt_runtime_us, rt_period_us);
+
+	num_cpus = dl_get_online_cpus();
+	DL_FAIL_IF(num_cpus <= 0, "Failed to get number of CPUs");
+
+	printf("  Number of online CPUs: %d\n", num_cpus);
+
+	/* Read current fair_server settings for cpu0 to get period */
+	DL_FAIL_IF(dl_get_fair_server_settings(0, &fair_runtime_ns,
+					       &fair_period_ns) < 0,
+		   "Failed to read fair_server settings");
+
+	printf("  Fair server period: %luns\n", fair_period_ns);
+
+	/* Save original runtimes for all CPUs */
+	original_runtimes = calloc(num_cpus, sizeof(uint64_t));
+	DL_FAIL_IF(!original_runtimes, "Failed to allocate memory");
+
+	for (i = 0; i < num_cpus; i++) {
+		if (dl_get_fair_server_settings(i, &original_runtimes[i],
+						NULL) < 0) {
+			printf("  Warning: Cannot read CPU %d settings\n", i);
+			original_runtimes[i] = 0;
+		}
+	}
+
+	/*
+	 * Try to set each CPU's fair_server to 101% of RT bandwidth per CPU.
+	 * This should exceed the per-CPU RT bandwidth limit and fail.
+	 */
+	excessive_runtime_ns = (rt_runtime_us * 101 / 100) * 1000;
+
+	/* Scale to fair_server period if different from RT period */
+	if (fair_period_ns != rt_period_us * 1000)
+		excessive_runtime_ns = excessive_runtime_ns * fair_period_ns /
+				       (rt_period_us * 1000);
+
+	printf("  Attempting to set all CPUs to %luns (101%% of RT bandwidth)\n",
+	       excessive_runtime_ns);
+
+	for (i = 0; i < num_cpus; i++) {
+		if (dl_set_fair_server_runtime(i, excessive_runtime_ns) == 0) {
+			write_succeeded++;
+		} else {
+			write_failed++;
+			printf("  CPU %d write rejected: %s\n", i, strerror(errno));
+		}
+	}
+
+	printf("  Result: %d writes succeeded, %d failed\n",
+	       write_succeeded, write_failed);
+
+	/* Restore original values */
+	for (i = 0; i < num_cpus; i++) {
+		if (original_runtimes[i] > 0)
+			dl_set_fair_server_runtime(i, original_runtimes[i]);
+	}
+
+	free(original_runtimes);
+
+	/*
+	 * Test passes if at least one write was rejected,
+	 * showing bandwidth limit enforcement.
+	 */
+	if (write_failed > 0) {
+		printf("  SUCCESS: Bandwidth limit enforced (%d writes rejected)\n",
+		       write_failed);
+		return DL_TEST_PASS;
+	}
+
+	printf("  FAIL: All writes accepted, no bandwidth limit enforcement\n");
+	return DL_TEST_FAIL;
+}
+
+static struct dl_test test_fair_server_bandwidth_validation = {
+	.name = "fair_server_bandwidth_validation",
+	.description = "Verify fair_server bandwidth validation against RT bandwidth",
+	.run = test_fair_server_bandwidth_validation_run,
+};
+REGISTER_DL_TEST(&test_fair_server_bandwidth_validation);
+
+/*
+ * Test: Fair server CPU protection under FIFO competition
+ *
+ * Verifies that fair_server provides CPU time to CFS tasks even when
+ * competing with high-priority FIFO tasks on the same CPU.
+ */
+static enum dl_test_status test_fair_server_cpu_protection_run(void *ctx)
+{
+	uint64_t fair_runtime_ns, fair_period_ns;
+	uint64_t initial_time, final_time, cpu_ticks_used;
+	uint64_t ticks_per_sec, test_duration = 12;
+	pid_t cfs_pid, fifo_pid;
+	int test_cpu = 2;
+	int expected_percent, cpu_percent;
+	int min_expected, max_expected;
+	cpu_set_t cpuset;
+	struct sched_param param;
+
+	/* Check if fair_server interface exists */
+	if (!dl_fair_server_exists()) {
+		printf("  Fair server interface not found\n");
+		return DL_TEST_SKIP;
+	}
+
+	/* Read fair_server settings */
+	DL_FAIL_IF(dl_get_fair_server_settings(test_cpu, &fair_runtime_ns,
+					       &fair_period_ns) < 0,
+		   "Failed to read fair_server settings");
+
+	expected_percent = (fair_runtime_ns * 100) / fair_period_ns;
+
+	printf("  Fair server (CPU %d): %luns / %luns (%d%%)\n",
+	       test_cpu, fair_runtime_ns, fair_period_ns, expected_percent);
+
+	ticks_per_sec = sysconf(_SC_CLK_TCK);
+
+	/* Fork CFS cpuhog */
+	cfs_pid = fork();
+	if (cfs_pid < 0) {
+		DL_ERR("Failed to fork CFS task");
+		return DL_TEST_FAIL;
+	}
+
+	if (cfs_pid == 0) {
+		/* Child: CFS cpuhog pinned to test_cpu */
+		CPU_ZERO(&cpuset);
+		CPU_SET(test_cpu, &cpuset);
+		sched_setaffinity(0, sizeof(cpuset), &cpuset);
+
+		execl("./cpuhog", "cpuhog", "-t", "20", NULL);
+		exit(1);
+	}
+
+	/* Wait for CFS task to stabilize */
+	sleep(2);
+
+	printf("  Measuring baseline CPU time...\n");
+	initial_time = dl_get_process_cpu_time(cfs_pid);
+
+	/* Fork FIFO cpuhog */
+	fifo_pid = fork();
+	if (fifo_pid < 0) {
+		kill(cfs_pid, SIGKILL);
+		waitpid(cfs_pid, NULL, 0);
+		DL_ERR("Failed to fork FIFO task");
+		return DL_TEST_FAIL;
+	}
+
+	if (fifo_pid == 0) {
+		/* Child: FIFO cpuhog pinned to test_cpu */
+		CPU_ZERO(&cpuset);
+		CPU_SET(test_cpu, &cpuset);
+		sched_setaffinity(0, sizeof(cpuset), &cpuset);
+
+		param.sched_priority = 50;
+		sched_setscheduler(0, SCHED_FIFO, &param);
+
+		execl("./cpuhog", "cpuhog", "-t", "20", NULL);
+		exit(1);
+	}
+
+	printf("  Starting FIFO competition for %lus...\n", test_duration);
+
+	/* Wait for test duration */
+	sleep(test_duration);
+
+	printf("  Measuring final CPU time...\n");
+	final_time = dl_get_process_cpu_time(cfs_pid);
+
+	/* Cleanup */
+	kill(cfs_pid, SIGKILL);
+	kill(fifo_pid, SIGKILL);
+	waitpid(cfs_pid, NULL, 0);
+	waitpid(fifo_pid, NULL, 0);
+
+	/* Calculate CPU usage */
+	cpu_ticks_used = final_time - initial_time;
+	cpu_percent = (cpu_ticks_used * 100) / (test_duration * ticks_per_sec);
+
+	printf("  CPU ticks used: %lu / %lu\n",
+	       cpu_ticks_used, test_duration * ticks_per_sec);
+	printf("  CFS task CPU usage: %d%%\n", cpu_percent);
+
+	/* Allow ±50% tolerance (e.g., 5% ± 50% = 2.5% - 7.5%) */
+	min_expected = expected_percent * 50 / 100;
+	max_expected = expected_percent * 150 / 100;
+
+	if (min_expected < 1)
+		min_expected = 1;
+
+	printf("  Expected range: %d%% - %d%%\n", min_expected, max_expected);
+
+	if (cpu_percent >= min_expected && cpu_percent <= max_expected) {
+		printf("  SUCCESS: CFS task received %d%% CPU\n", cpu_percent);
+		return DL_TEST_PASS;
+	} else if (cpu_percent < min_expected) {
+		printf("  FAIL: CFS task received only %d%% (below %d%%)\n",
+		       cpu_percent, min_expected);
+		return DL_TEST_FAIL;
+	}
+
+	printf("  FAIL: CFS task received %d%% (above %d%%)\n",
+	       cpu_percent, max_expected);
+	return DL_TEST_FAIL;
+}
+
+static struct dl_test test_fair_server_cpu_protection = {
+	.name = "fair_server_cpu_protection",
+	.description = "Verify fair_server provides CPU protection under FIFO competition",
+	.run = test_fair_server_cpu_protection_run,
+};
+REGISTER_DL_TEST(&test_fair_server_cpu_protection);

-- 
2.53.0


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

* [PATCH RFC 7/7] selftests/sched: Add SCHED_DEADLINE ENQUEUE_REPLENISH bug test
  2026-03-06 16:10 [PATCH RFC 0/7] selftests/sched: Add comprehensive SCHED_DEADLINE test suite Juri Lelli
                   ` (5 preceding siblings ...)
  2026-03-06 16:10 ` [PATCH RFC 6/7] selftests/sched: Add SCHED_DEADLINE fair_server " Juri Lelli
@ 2026-03-06 16:10 ` Juri Lelli
  6 siblings, 0 replies; 21+ messages in thread
From: Juri Lelli @ 2026-03-06 16:10 UTC (permalink / raw)
  To: Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest,
	Juri Lelli

Add a test that validates the ENQUEUE_REPLENISH flag is correctly set
during PI boosting after a task is changed via sched_setscheduler().

The test reproduces a specific scenario where:
1. Task B (DEADLINE, short deadline) holds a PI mutex
2. Task A (DEADLINE, long deadline) blocks on Task B's mutex
3. Task B doesn't inherit from Task A (B has higher priority)
4. Task B is changed from SCHED_DEADLINE to SCHED_IDLE via setscheduler
5. Task B should now inherit DEADLINE from Task A with ENQUEUE_REPLENISH

Without the fix, the ENQUEUE_REPLENISH flag is missing when Task B
inherits the DEADLINE attributes from Task A, causing bandwidth
accounting corruption and potential system hangs.

The test uses pthreads with PI mutexes to orchestrate the scenario
and includes a timeout mechanism to detect if the bug causes a hang.

Assisted-by: Claude Code: claude-sonnet-4-5@20250929
Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
---
 tools/testing/selftests/sched/deadline/Makefile    |   5 +-
 .../selftests/sched/deadline/replenish_bug.c       | 337 +++++++++++++++++++++
 2 files changed, 341 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
index e7e16c610ee58..aa7752da1bdcf 100644
--- a/tools/testing/selftests/sched/deadline/Makefile
+++ b/tools/testing/selftests/sched/deadline/Makefile
@@ -14,7 +14,7 @@ OUTPUT_DIR := $(OUTPUT)
 UTIL_OBJS := $(OUTPUT)/dl_util.o
 
 # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c)
-TEST_OBJS := $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o $(OUTPUT)/fair_server.o
+TEST_OBJS := $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o $(OUTPUT)/fair_server.o $(OUTPUT)/replenish_bug.o
 
 # Runner binary links utility and test objects
 $(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h | $(OUTPUT_DIR)
@@ -38,6 +38,9 @@ $(OUTPUT)/bandwidth.o: bandwidth.c dl_test.h dl_util.h | $(OUTPUT_DIR)
 $(OUTPUT)/fair_server.o: fair_server.c dl_test.h dl_util.h | $(OUTPUT_DIR)
 	$(CC) $(CFLAGS) -c $< -o $@
 
+$(OUTPUT)/replenish_bug.o: replenish_bug.c dl_test.h dl_util.h | $(OUTPUT_DIR)
+	$(CC) $(CFLAGS) -c $< -o $@
+
 $(OUTPUT_DIR):
 	mkdir -p $@
 
diff --git a/tools/testing/selftests/sched/deadline/replenish_bug.c b/tools/testing/selftests/sched/deadline/replenish_bug.c
new file mode 100644
index 0000000000000..016007d04c95a
--- /dev/null
+++ b/tools/testing/selftests/sched/deadline/replenish_bug.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCHED_DEADLINE ENQUEUE_REPLENISH Bug Test
+ *
+ * Reproduces the scenario where:
+ * 1. Task B (DEADLINE, short deadline) holds a PI mutex
+ * 2. Task A (DEADLINE, long deadline) blocks on Task B's mutex
+ * 3. Task B doesn't inherit from Task A (B has shorter deadline = higher priority)
+ * 4. sched_setscheduler() changes Task B from DEADLINE to IDLE
+ * 5. Task B should now inherit DEADLINE from Task A with ENQUEUE_REPLENISH
+ *
+ * Without the fix, ENQUEUE_REPLENISH flag is missing, causing:
+ * "DL de-boosted task PID X: REPLENISH flag missing"
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sched.h>
+#include <errno.h>
+#include <stdint.h>
+#include <time.h>
+#include <signal.h>
+#include "dl_test.h"
+#include "dl_util.h"
+
+/* Thread context for the test */
+struct replenish_test_ctx {
+	pthread_mutex_t pi_mutex;
+	pthread_barrier_t barrier;
+	pthread_t holder;
+	pthread_t waiter;
+	volatile int holder_ready;
+	volatile int waiter_blocked;
+	volatile int test_done;
+	volatile int timeout_occurred;
+	volatile pid_t holder_tid;
+	volatile pid_t waiter_tid;
+};
+
+/* Timeout handler */
+static void timeout_handler(int sig)
+{
+	printf("\n\n!!! TIMEOUT !!!\n");
+	printf("Test appears to have hung - likely due to the bug being triggered!\n");
+	printf("This indicates the ENQUEUE_REPLENISH bug corrupted bandwidth accounting.\n");
+	printf("\nCheck kernel log:\n");
+	printf("  sudo dmesg | tail -50\n");
+	printf("\nLook for:\n");
+	printf("  'REPLENISH flag missing'\n");
+	printf("  'dl_runtime_exceeded' or bandwidth warnings\n");
+}
+
+static void print_sched_info(const char *label, pid_t tid)
+{
+	struct sched_attr attr = {0};
+
+	if (dl_get_sched_attr(tid, &attr) == 0) {
+		printf("[%s] TID %d: policy=%u prio=%d",
+		       label, tid, attr.sched_policy, attr.sched_priority);
+		if (attr.sched_policy == SCHED_DEADLINE) {
+			printf(" runtime=%llu deadline=%llu period=%llu",
+			       (unsigned long long)attr.sched_runtime,
+			       (unsigned long long)attr.sched_deadline,
+			       (unsigned long long)attr.sched_period);
+		}
+		printf("\n");
+	}
+}
+
+static int set_sched_idle(pid_t tid)
+{
+	struct sched_param param = {0};
+	return sched_setscheduler(tid, SCHED_IDLE, &param);
+}
+
+/*
+ * Thread B: DEADLINE task (SHORT deadline) that holds the PI mutex
+ * This will be setscheduled to IDLE, triggering the bug
+ */
+static void *holder_thread(void *arg)
+{
+	struct replenish_test_ctx *ctx = arg;
+
+	ctx->holder_tid = gettid();
+	printf("\n=== HOLDER (Task B) thread started (TID %d) ===\n",
+	       ctx->holder_tid);
+
+	/* Set to DEADLINE with a SHORT deadline (high priority) */
+	if (dl_set_sched_attr(ctx->holder_tid, dl_ms_to_ns(5),
+			      dl_ms_to_ns(30), dl_ms_to_ns(60)) < 0) {
+		perror("holder: dl_set_sched_attr");
+		return NULL;
+	}
+
+	print_sched_info("HOLDER-INIT", ctx->holder_tid);
+
+	/* Lock the mutex */
+	pthread_mutex_lock(&ctx->pi_mutex);
+	printf("[HOLDER] TID %d: Locked PI mutex\n", ctx->holder_tid);
+
+	/* Signal we're ready */
+	ctx->holder_ready = 1;
+
+	/* Wait at barrier */
+	pthread_barrier_wait(&ctx->barrier);
+
+	/* Keep holding the mutex while waiter blocks and gets setscheduled */
+	while (!ctx->test_done)
+		usleep(10000); /* 10ms */
+
+	printf("[HOLDER] TID %d: Unlocking PI mutex\n", ctx->holder_tid);
+	pthread_mutex_unlock(&ctx->pi_mutex);
+
+	printf("[HOLDER] TID %d: Exiting\n", ctx->holder_tid);
+	return NULL;
+}
+
+/*
+ * Thread A: DEADLINE task (LONG deadline) that will block on the mutex
+ * This is the pi_task that holder will inherit from after setscheduler
+ */
+static void *waiter_thread(void *arg)
+{
+	struct replenish_test_ctx *ctx = arg;
+
+	ctx->waiter_tid = gettid();
+	printf("\n=== WAITER (Task A) thread started (TID %d) ===\n",
+	       ctx->waiter_tid);
+
+	/* Set to DEADLINE with a LONG deadline (low priority) */
+	if (dl_set_sched_attr(ctx->waiter_tid, dl_ms_to_ns(10),
+			      dl_ms_to_ns(50), dl_ms_to_ns(100)) < 0) {
+		perror("waiter: dl_set_sched_attr");
+		return NULL;
+	}
+
+	print_sched_info("WAITER-INIT", ctx->waiter_tid);
+
+	/* Wait for holder to lock the mutex */
+	while (!ctx->holder_ready)
+		usleep(1000);
+
+	/* Wait at barrier */
+	pthread_barrier_wait(&ctx->barrier);
+
+	printf("[WAITER] TID %d: Attempting to lock PI mutex (will block)...\n",
+	       ctx->waiter_tid);
+
+	/* This will block because holder has the lock */
+	ctx->waiter_blocked = 1;
+	pthread_mutex_lock(&ctx->pi_mutex);
+
+	/* Eventually we get the lock */
+	printf("[WAITER] TID %d: Acquired PI mutex\n", ctx->waiter_tid);
+	print_sched_info("WAITER-AFTER", ctx->waiter_tid);
+
+	pthread_mutex_unlock(&ctx->pi_mutex);
+	printf("[WAITER] TID %d: Unlocked PI mutex\n", ctx->waiter_tid);
+	printf("[WAITER] TID %d: Exiting\n", ctx->waiter_tid);
+
+	return NULL;
+}
+
+/*
+ * Test: DEADLINE ENQUEUE_REPLENISH Bug
+ *
+ * Verifies that when a SCHED_DEADLINE task holding a PI mutex is changed
+ * to SCHED_IDLE while a lower-priority DEADLINE task is blocked on that
+ * mutex, the ENQUEUE_REPLENISH flag is correctly set during PI boosting.
+ */
+static enum dl_test_status test_replenish_bug_run(void *arg)
+{
+	struct replenish_test_ctx *ctx = arg;
+	struct sigaction sa;
+
+	printf("======================================\n");
+	printf("DEADLINE ENQUEUE_REPLENISH Bug Test\n");
+	printf("======================================\n");
+	printf("Timeout: 5 seconds\n");
+	printf("\nThis test reproduces the scenario where:\n");
+	printf("1. Task B (DEADLINE, short deadline) holds a PI mutex\n");
+	printf("2. Task A (DEADLINE, long deadline) blocks on Task B's mutex\n");
+	printf("3. Task B doesn't inherit from A (B has higher priority)\n");
+	printf("4. Task B gets setscheduled to SCHED_IDLE (while A still blocked)\n");
+	printf("5. Task B should now inherit from A with ENQUEUE_REPLENISH\n");
+	printf("\nWithout fix: Missing ENQUEUE_REPLENISH flag causes WARNING\n");
+	printf("\nCheck dmesg for:\n");
+	printf("  'DL de-boosted task PID X: REPLENISH flag missing'\n");
+	printf("\nNOTE: If test hangs and times out, the bug was triggered!\n");
+	printf("======================================\n\n");
+
+	/* Set up timeout handler */
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = timeout_handler;
+	sigaction(SIGALRM, &sa, NULL);
+
+	/* Set timeout (5 seconds) */
+	alarm(5);
+
+	/* Initialize barrier for 2 threads */
+	DL_FAIL_IF(pthread_barrier_init(&ctx->barrier, NULL, 2) != 0,
+		   "pthread_barrier_init failed");
+
+	/* Create holder thread (will lock mutex) */
+	if (pthread_create(&ctx->holder, NULL, holder_thread, ctx) != 0) {
+		pthread_barrier_destroy(&ctx->barrier);
+		DL_FAIL("pthread_create holder failed: %s", strerror(errno));
+	}
+
+	/* Create waiter thread (will block on mutex) */
+	if (pthread_create(&ctx->waiter, NULL, waiter_thread, ctx) != 0) {
+		pthread_barrier_destroy(&ctx->barrier);
+		DL_FAIL("pthread_create waiter failed: %s", strerror(errno));
+	}
+
+	/* Give threads time to start */
+	sleep(1);
+
+	/* Wait for waiter to block on the mutex */
+	printf("\n[MAIN] Waiting for waiter to block on mutex...\n");
+	while (!ctx->waiter_blocked)
+		usleep(1000);
+
+	/* Give it a moment to actually block */
+	usleep(50000); /* 50ms */
+
+	printf("\n[MAIN] Holder TID: %d\n", ctx->holder_tid);
+	print_sched_info("HOLDER-HOLDING", ctx->holder_tid);
+
+	/*
+	 * THE BUG TRIGGER:
+	 * Holder (Task B) is DEADLINE with short deadline (high priority).
+	 * Waiter (Task A) is DEADLINE with long deadline (low priority), blocked.
+	 * Holder didn't inherit from waiter (holder has higher priority).
+	 * Now change HOLDER from DEADLINE to SCHED_IDLE.
+	 * Holder should inherit DEADLINE from waiter with ENQUEUE_REPLENISH,
+	 * but without the fix, it doesn't.
+	 */
+	printf("\n[MAIN] *** Changing HOLDER (Task B) from SCHED_DEADLINE to SCHED_IDLE ***\n");
+	printf("[MAIN] *** This triggers the bug! ***\n");
+
+	if (set_sched_idle(ctx->holder_tid) < 0) {
+		ctx->test_done = 1;
+		pthread_join(ctx->holder, NULL);
+		pthread_join(ctx->waiter, NULL);
+		pthread_barrier_destroy(&ctx->barrier);
+		DL_FAIL("set_sched_idle failed: %s", strerror(errno));
+	}
+
+	printf("[MAIN] Successfully changed holder to SCHED_IDLE\n");
+	print_sched_info("HOLDER-SETSCHEDULED", ctx->holder_tid);
+
+	/* Let the scenario play out */
+	usleep(100000); /* 100ms */
+
+	/* Signal threads to finish */
+	ctx->test_done = 1;
+
+	/* Wait for threads */
+	pthread_join(ctx->holder, NULL);
+	pthread_join(ctx->waiter, NULL);
+
+	/* Cancel the alarm - we completed successfully */
+	alarm(0);
+
+	pthread_barrier_destroy(&ctx->barrier);
+
+	DL_FAIL_IF(ctx->timeout_occurred, "Test timed out - bug was triggered!");
+
+	printf("\n======================================\n");
+	printf("Test completed successfully!\n");
+	printf("======================================\n");
+	printf("\nNo timeout occurred - fix appears to be working.\n");
+	printf("\nCheck kernel log:\n");
+	printf("  sudo dmesg | tail -50\n");
+	printf("\nLook for:\n");
+	printf("  'DL de-boosted task PID X: REPLENISH flag missing'\n");
+	printf("  'dl_runtime_exceeded' or bandwidth warnings\n");
+	printf("\n");
+
+	return DL_TEST_PASS;
+}
+
+static enum dl_test_status test_replenish_bug_setup(void **ctx_ptr)
+{
+	struct replenish_test_ctx *ctx;
+	pthread_mutexattr_t attr;
+
+	ctx = calloc(1, sizeof(*ctx));
+	DL_FAIL_IF(!ctx, "Failed to allocate test context");
+
+	/* Initialize PI mutex */
+	if (pthread_mutexattr_init(&attr) != 0) {
+		free(ctx);
+		DL_FAIL("pthread_mutexattr_init failed");
+	}
+
+	if (pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT) != 0) {
+		pthread_mutexattr_destroy(&attr);
+		free(ctx);
+		DL_FAIL("pthread_mutexattr_setprotocol failed");
+	}
+
+	if (pthread_mutex_init(&ctx->pi_mutex, &attr) != 0) {
+		pthread_mutexattr_destroy(&attr);
+		free(ctx);
+		DL_FAIL("pthread_mutex_init failed");
+	}
+
+	pthread_mutexattr_destroy(&attr);
+
+	*ctx_ptr = ctx;
+	return DL_TEST_PASS;
+}
+
+static void test_replenish_bug_cleanup(void *arg)
+{
+	struct replenish_test_ctx *ctx = arg;
+
+	if (ctx) {
+		pthread_mutex_destroy(&ctx->pi_mutex);
+		free(ctx);
+	}
+}
+
+static struct dl_test test_replenish_bug = {
+	.name = "replenish_bug",
+	.description = "Verify ENQUEUE_REPLENISH flag is set during PI boosting after setscheduler",
+	.setup = test_replenish_bug_setup,
+	.run = test_replenish_bug_run,
+	.cleanup = test_replenish_bug_cleanup,
+};
+REGISTER_DL_TEST(&test_replenish_bug);

-- 
2.53.0


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

* Re: [PATCH RFC 4/7] selftests/sched: Add basic SCHED_DEADLINE functionality tests
  2026-03-06 16:10 ` [PATCH RFC 4/7] selftests/sched: Add basic SCHED_DEADLINE functionality tests Juri Lelli
@ 2026-03-09  8:15   ` Gabriele Monaco
  2026-03-09  9:11     ` Juri Lelli
  0 siblings, 1 reply; 21+ messages in thread
From: Gabriele Monaco @ 2026-03-09  8:15 UTC (permalink / raw)
  To: Juri Lelli, Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Tommaso Cucinotta, Luca Abeni,
	linux-kernel, linux-kselftest

Hello,

On Fri, 2026-03-06 at 17:10 +0100, Juri Lelli wrote:
> Add initial test coverage for SCHED_DEADLINE scheduler functionality.
> This patch introduces two fundamental tests that validate core
> SCHED_DEADLINE behavior using the test framework and utility library.
> 
> The basic_scheduling test creates a cpuhog process with SCHED_DEADLINE
> policy and verifies the scheduling policy is correctly set to policy 6
> (SCHED_DEADLINE). It confirms the task executes successfully for the
> specified duration using reasonable parameters with 30ms runtime, 100ms
> deadline, and 100ms period. This validates the fundamental operation of
> setting SCHED_DEADLINE policy and running deadline-scheduled tasks.
> 
> The parameter_validation test ensures the kernel properly validates
> SCHED_DEADLINE parameters. It tests that invalid parameter combinations
> such as runtime greater than deadline are correctly rejected with
> EINVAL, while valid parameter configurations are accepted. This confirms
> the kernel's parameter validation logic is working correctly and
> prevents misconfigured deadline tasks from being scheduled.
> 
> Both tests use the dl_util library for SCHED_DEADLINE operations and
> follow the framework pattern with automatic registration via
> REGISTER_DL_TEST(). The tests pass cleanly in VM environments with 2
> tests passing, 0 skipped, and 0 failed.
> 
> Assisted-by: Claude Code: claude-sonnet-4-5@20250929
> Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
> ---
...
>  
> +# Test files
> +$(OUTPUT)/basic.o: basic.c dl_test.h dl_util.h | $(OUTPUT_DIR)
> +	$(CC) $(CFLAGS) -c $< -o $@
> +

This seems a constant in all tests, what about just having make do the trick:

  $(OUTPUT)/%.o: %.c dl_test.h dl_util.h | $(OUTPUT_DIR)
  	$(CC) $(CFLAGS) -c $< -o $@

The rest looks neat.

Thanks,
Gabriele

>  $(OUTPUT_DIR):
>  	mkdir -p $@
>  
> diff --git a/tools/testing/selftests/sched/deadline/basic.c
> b/tools/testing/selftests/sched/deadline/basic.c
> new file mode 100644
> index 0000000000000..f1c4e8c751b61
> --- /dev/null
> +++ b/tools/testing/selftests/sched/deadline/basic.c
> @@ -0,0 +1,127 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Basic SCHED_DEADLINE functionality tests
> + *
> + * Validates fundamental SCHED_DEADLINE scheduler operations including
> + * policy setup, parameter validation, and basic task execution.
> + */
> +
> +#define _GNU_SOURCE
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <errno.h>
> +#include <sched.h>
> +#include "dl_test.h"
> +#include "dl_util.h"
> +
> +/*
> + * Test: Basic SCHED_DEADLINE scheduling
> + *
> + * Verifies that a task can be successfully scheduled with SCHED_DEADLINE
> + * policy and executes correctly.
> + */
> +static enum dl_test_status test_basic_scheduling_run(void *ctx)
> +{
> +	pid_t pid;
> +	int policy;
> +	int status;
> +	uint64_t runtime_ns, deadline_ns, period_ns;
> +
> +	/* Use reasonable deadline parameters: 30ms/100ms/100ms */
> +	runtime_ns = dl_ms_to_ns(30);
> +	deadline_ns = dl_ms_to_ns(100);
> +	period_ns = dl_ms_to_ns(100);
> +
> +	/* Create a cpuhog process with SCHED_DEADLINE */
> +	pid = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 2);
> +	DL_FAIL_IF(pid < 0, "Failed to create cpuhog process: %s",
> +		   strerror(errno));
> +
> +	/* Process is now running with SCHED_DEADLINE set */
> +	printf("  Created cpuhog with SCHED_DEADLINE (PID %d)\n", pid);
> +
> +	/* Verify it's using SCHED_DEADLINE (policy 6) */
> +	policy = dl_get_policy(pid);
> +	DL_FAIL_IF(policy < 0, "Failed to read scheduling policy");
> +	DL_EQ(policy, SCHED_DEADLINE);
> +
> +	/* Alternative check using helper */
> +	DL_ASSERT(dl_is_deadline_task(pid));
> +
> +	printf("  cpuhog running with SCHED_DEADLINE (PID %d)\n", pid);
> +
> +	/* Wait for cpuhog to complete (2 seconds) */
> +	if (waitpid(pid, &status, 0) < 0) {
> +		dl_cleanup_cpuhog(pid);
> +		DL_FAIL("waitpid failed: %s", strerror(errno));
> +	}
> +
> +	/* Verify it exited successfully */
> +	DL_FAIL_IF(!WIFEXITED(status), "Process did not exit normally");
> +	DL_EQ(WEXITSTATUS(status), 0);
> +
> +	printf("  cpuhog completed successfully\n");
> +
> +	return DL_TEST_PASS;
> +}
> +
> +static struct dl_test test_basic_scheduling = {
> +	.name = "basic_scheduling",
> +	.description = "Verify basic SCHED_DEADLINE policy setup and
> execution",
> +	.run = test_basic_scheduling_run,
> +};
> +REGISTER_DL_TEST(&test_basic_scheduling);
> +
> +/*
> + * Test: SCHED_DEADLINE parameter validation
> + *
> + * Verifies that the kernel correctly validates deadline parameters,
> + * rejecting invalid configurations and accepting valid ones.
> + */
> +static enum dl_test_status test_parameter_validation_run(void *ctx)
> +{
> +	int ret;
> +	uint64_t runtime_ns, deadline_ns, period_ns;
> +
> +	printf("  Testing invalid parameters (runtime > deadline)...\n");
> +
> +	/* Invalid: runtime (200ms) > deadline (100ms) */
> +	runtime_ns = dl_ms_to_ns(200);
> +	deadline_ns = dl_ms_to_ns(100);
> +	period_ns = dl_ms_to_ns(100);
> +
> +	ret = dl_set_sched_attr(0, runtime_ns, deadline_ns, period_ns);
> +	DL_FAIL_IF(ret == 0, "Invalid parameters were accepted (runtime >
> deadline)");
> +	DL_FAIL_IF(errno != EINVAL, "Expected EINVAL, got %s",
> strerror(errno));
> +
> +	printf("  Invalid parameters correctly rejected with EINVAL\n");
> +
> +	printf("  Testing valid parameters...\n");
> +
> +	/* Valid: runtime (30ms) <= deadline (100ms) <= period (100ms) */
> +	runtime_ns = dl_ms_to_ns(30);
> +	deadline_ns = dl_ms_to_ns(100);
> +	period_ns = dl_ms_to_ns(100);
> +
> +	ret = dl_set_sched_attr(0, runtime_ns, deadline_ns, period_ns);
> +	DL_FAIL_IF(ret < 0, "Valid parameters were rejected: %s",
> strerror(errno));
> +
> +	printf("  Valid parameters correctly accepted\n");
> +
> +	/* Reset to normal scheduling using sched_setscheduler */
> +	struct sched_param param = { .sched_priority = 0 };
> +
> +	sched_setscheduler(0, SCHED_OTHER, &param);
> +
> +	return DL_TEST_PASS;
> +}
> +
> +static struct dl_test test_parameter_validation = {
> +	.name = "parameter_validation",
> +	.description = "Verify SCHED_DEADLINE parameter validation
> (accept/reject)",
> +	.run = test_parameter_validation_run,
> +};
> +REGISTER_DL_TEST(&test_parameter_validation);


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

* Re: [PATCH RFC 1/7] selftests/sched: Add SCHED_DEADLINE test framework infrastructure
  2026-03-06 16:10 ` [PATCH RFC 1/7] selftests/sched: Add SCHED_DEADLINE test framework infrastructure Juri Lelli
@ 2026-03-09  8:20   ` Gabriele Monaco
  2026-03-09  9:10     ` Juri Lelli
  0 siblings, 1 reply; 21+ messages in thread
From: Gabriele Monaco @ 2026-03-09  8:20 UTC (permalink / raw)
  To: Juri Lelli, Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Tommaso Cucinotta, Luca Abeni,
	linux-kernel, linux-kselftest

On Fri, 2026-03-06 at 17:10 +0100, Juri Lelli wrote:
> Add the foundational infrastructure for SCHED_DEADLINE scheduler tests
> following the pattern established by sched_ext selftests. This provides
> a clean, extensible framework for testing SCHED_DEADLINE functionality.
> 
> The framework uses ELF constructors for automatic test registration,
> allowing new tests to be added simply by defining a struct dl_test and
> calling REGISTER_DL_TEST(). The runner discovers all registered tests
> at runtime and executes them serially, providing a simple and consistent
> way to expand test coverage without modifying the test runner itself.
> 
> The framework provides a complete test lifecycle with setup, run, and
> cleanup phases for each test. Tests can be filtered by name, listed for
> inspection, or run in quiet mode for automated testing environments. The
> framework includes assertion macros such as DL_EQ and DL_FAIL_IF to
> simplify test authoring and provide consistent error reporting. Signal
> handling ensures graceful interruption and cleanup when tests are
> terminated early.
> 
> This commit establishes the framework without any actual tests. Tests
> will be added in subsequent patches. The framework design is inspired by
> tools/testing/selftests/sched_ext/ but tailored for SCHED_DEADLINE
> testing needs.
> 
> Assisted-by: Claude Code: claude-sonnet-4-5@20250929
> Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
> ---
>  tools/testing/selftests/sched/deadline/.gitignore |   3 +
>  tools/testing/selftests/sched/deadline/Makefile   |  34 ++++
>  tools/testing/selftests/sched/deadline/dl_test.h  | 238
> ++++++++++++++++++++++
>  tools/testing/selftests/sched/deadline/runner.c   | 219 ++++++++++++++++++++
>  4 files changed, 494 insertions(+)
> 

I wonder if this runner couldn't be made generic, something like not mentioning
DL in any of the helpers, keep runner.c in tools/testing/selftests/sched/ and
have a tools/testing/selftests/sched/deadline/Makefile glue all required objects
together for the deadline tests (or anything else).

That would probably make adding new tests much easier without code duplication.

Just a thought, haven't tested it.
Thanks,
Gabriele

> diff --git a/tools/testing/selftests/sched/deadline/.gitignore
> b/tools/testing/selftests/sched/deadline/.gitignore
> new file mode 100644
> index 0000000000000..503a1a968f952
> --- /dev/null
> +++ b/tools/testing/selftests/sched/deadline/.gitignore
> @@ -0,0 +1,3 @@
> +runner
> +cpuhog
> +*.o
> diff --git a/tools/testing/selftests/sched/deadline/Makefile
> b/tools/testing/selftests/sched/deadline/Makefile
> new file mode 100644
> index 0000000000000..fd57794f1a543
> --- /dev/null
> +++ b/tools/testing/selftests/sched/deadline/Makefile
> @@ -0,0 +1,34 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +TEST_GEN_PROGS := runner
> +
> +# override lib.mk's default rules
> +OVERRIDE_TARGETS := 1
> +include ../../lib.mk
> +
> +CFLAGS += -Wall -O2 -g -pthread
> +
> +OUTPUT_DIR := $(OUTPUT)
> +
> +# Utility object files
> +UTIL_OBJS :=
> +
> +# Test object files (all .c files except runner.c, dl_util.c, cpuhog.c)
> +# Will be populated as we add tests
> +TEST_OBJS :=
> +
> +# Runner binary links utility and test objects
> +$(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h |
> $(OUTPUT_DIR)
> +	$(CC) $(CFLAGS) -o $@ runner.c $(UTIL_OBJS) $(TEST_OBJS) $(LDFLAGS)
> +
> +$(OUTPUT_DIR):
> +	mkdir -p $@
> +
> +.PHONY: all clean
> +
> +all: $(TEST_GEN_PROGS)
> +
> +clean:
> +	rm -f $(OUTPUT)/runner
> +	rm -f $(OUTPUT)/*.o
> +	rm -f *.o
> diff --git a/tools/testing/selftests/sched/deadline/dl_test.h
> b/tools/testing/selftests/sched/deadline/dl_test.h
> new file mode 100644
> index 0000000000000..545fae1af6631
> --- /dev/null
> +++ b/tools/testing/selftests/sched/deadline/dl_test.h
> @@ -0,0 +1,238 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * SCHED_DEADLINE Test Framework
> + *
> + * Provides infrastructure for testing SCHED_DEADLINE scheduler
> functionality.
> + * Tests register themselves using REGISTER_DL_TEST() macro and are
> + * automatically discovered by the runner at runtime.
> + */
> +
> +#ifndef __DL_TEST_H__
> +#define __DL_TEST_H__
> +
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <stdlib.h>
> +
> +/* Test status return codes */
> +enum dl_test_status {
> +	DL_TEST_PASS = 0,	/* Test passed successfully */
> +	DL_TEST_SKIP,		/* Test skipped (feature unavailable, etc.)
> */
> +	DL_TEST_FAIL,		/* Test failed */
> +};
> +
> +/**
> + * struct dl_test - Deadline scheduler test definition
> + * @name: Short test identifier (e.g., "basic_scheduling")
> + * @description: Human-readable description of what the test validates
> + * @setup: Optional callback to prepare test environment
> + * @run: Required callback to execute test logic
> + * @cleanup: Optional callback to cleanup test resources
> + *
> + * Tests are defined by filling in this structure and registering with
> + * REGISTER_DL_TEST(). The runner will discover and execute all registered
> + * tests automatically.
> + */
> +struct dl_test {
> +	/**
> +	 * name - The name of the test
> +	 *
> +	 * Short identifier used for filtering and reporting. Should be
> +	 * lowercase with underscores (e.g., "bandwidth_admission").
> +	 */
> +	const char *name;
> +
> +	/**
> +	 * description - Human-readable test description
> +	 *
> +	 * Explains what the test validates and why it's important.
> +	 * Displayed when test runs unless quiet mode is enabled.
> +	 */
> +	const char *description;
> +
> +	/**
> +	 * setup - Optional setup callback
> +	 * @ctx: Pointer to context pointer, can be set to pass data to run()
> +	 *
> +	 * Called before run() to prepare test environment. Can allocate
> +	 * resources, check prerequisites, etc.
> +	 *
> +	 * Return:
> +	 * - DL_TEST_PASS: Continue to run()
> +	 * - DL_TEST_SKIP: Skip this test (feature not available, etc.)
> +	 * - DL_TEST_FAIL: Abort test (setup failed)
> +	 *
> +	 * If setup() returns SKIP or FAIL, run() and cleanup() are not
> called.
> +	 */
> +	enum dl_test_status (*setup)(void **ctx);
> +
> +	/**
> +	 * run - Required test execution callback
> +	 * @ctx: Context pointer set by setup(), or NULL if no setup
> +	 *
> +	 * Executes the actual test logic. This is the main test function.
> +	 *
> +	 * Return:
> +	 * - DL_TEST_PASS: Test passed
> +	 * - DL_TEST_SKIP: Test skipped (unlikely here, prefer setup())
> +	 * - DL_TEST_FAIL: Test failed
> +	 */
> +	enum dl_test_status (*run)(void *ctx);
> +
> +	/**
> +	 * cleanup - Optional cleanup callback
> +	 * @ctx: Context pointer set by setup(), or NULL if no setup
> +	 *
> +	 * Called after run() to cleanup resources. Always runs if setup()
> +	 * succeeded, regardless of run() result. Cannot fail the test.
> +	 */
> +	void (*cleanup)(void *ctx);
> +};
> +
> +/**
> + * dl_test_register() - Register a test with the framework
> + * @test: Pointer to test structure
> + *
> + * Called by REGISTER_DL_TEST() macro. Don't call directly.
> + */
> +void dl_test_register(struct dl_test *test);
> +
> +/**
> + * REGISTER_DL_TEST() - Register a test for auto-discovery
> + * @__test: Pointer to struct dl_test
> + *
> + * Uses ELF constructor attribute to automatically register the test
> + * when the binary loads. The runner will discover and execute all
> + * registered tests.
> + *
> + * Example:
> + *   static struct dl_test my_test = {
> + *       .name = "my_test",
> + *       .description = "Tests something important",
> + *       .run = my_test_run,
> + *   };
> + *   REGISTER_DL_TEST(&my_test);
> + */
> +#define __DL_CONCAT(a, b) a##b
> +#define _DL_CONCAT(a, b) __DL_CONCAT(a, b)
> +
> +#define REGISTER_DL_TEST(__test)					\
> +	__attribute__((constructor))					\
> +	static void _DL_CONCAT(___dlregister_, __LINE__)(void)		\
> +	{								\
> +		dl_test_register(__test);				\
> +	}
> +
> +/* Error reporting macros */
> +
> +/**
> + * DL_ERR() - Print error message with file/line info
> + */
> +#define DL_ERR(__fmt, ...)						\
> +	do {								\
> +		fprintf(stderr, "ERR: %s:%d\n", __FILE__, __LINE__);	\
> +		fprintf(stderr, __fmt"\n", ##__VA_ARGS__);		\
> +	} while (0)
> +
> +/**
> + * DL_FAIL() - Fail the test with a message
> + *
> + * Prints error message and returns DL_TEST_FAIL. Use in test run() or
> + * setup() functions.
> + */
> +#define DL_FAIL(__fmt, ...)						\
> +	do {								\
> +		DL_ERR(__fmt, ##__VA_ARGS__);				\
> +		return DL_TEST_FAIL;					\
> +	} while (0)
> +
> +/**
> + * DL_FAIL_IF() - Conditionally fail the test
> + * @__cond: Condition to check
> + * @__fmt: printf-style format string
> + *
> + * If condition is true, fail the test with the given message.
> + */
> +#define DL_FAIL_IF(__cond, __fmt, ...)					\
> +	do {								\
> +		if (__cond)						\
> +			DL_FAIL(__fmt, ##__VA_ARGS__);			\
> +	} while (0)
> +
> +/* Comparison assertion macros */
> +
> +/**
> + * DL_EQ() - Assert two values are equal
> + */
> +#define DL_EQ(_x, _y) \
> +	DL_FAIL_IF((_x) != (_y), \
> +		   "Expected %s == %s (%lld == %lld)", \
> +		   #_x, #_y, (long long)(_x), (long long)(_y))
> +
> +/**
> + * DL_NE() - Assert two values are not equal
> + */
> +#define DL_NE(_x, _y) \
> +	DL_FAIL_IF((_x) == (_y), \
> +		   "Expected %s != %s (%lld != %lld)", \
> +		   #_x, #_y, (long long)(_x), (long long)(_y))
> +
> +/**
> + * DL_LT() - Assert x < y
> + */
> +#define DL_LT(_x, _y) \
> +	DL_FAIL_IF((_x) >= (_y), \
> +		   "Expected %s < %s (%lld < %lld)", \
> +		   #_x, #_y, (long long)(_x), (long long)(_y))
> +
> +/**
> + * DL_LE() - Assert x <= y
> + */
> +#define DL_LE(_x, _y) \
> +	DL_FAIL_IF((_x) > (_y), \
> +		   "Expected %s <= %s (%lld <= %lld)", \
> +		   #_x, #_y, (long long)(_x), (long long)(_y))
> +
> +/**
> + * DL_GT() - Assert x > y
> + */
> +#define DL_GT(_x, _y) \
> +	DL_FAIL_IF((_x) <= (_y), \
> +		   "Expected %s > %s (%lld > %lld)", \
> +		   #_x, #_y, (long long)(_x), (long long)(_y))
> +
> +/**
> + * DL_GE() - Assert x >= y
> + */
> +#define DL_GE(_x, _y) \
> +	DL_FAIL_IF((_x) < (_y), \
> +		   "Expected %s >= %s (%lld >= %lld)", \
> +		   #_x, #_y, (long long)(_x), (long long)(_y))
> +
> +/**
> + * DL_ASSERT() - Assert condition is true
> + */
> +#define DL_ASSERT(_x) \
> +	DL_FAIL_IF(!(_x), "Expected %s to be true", #_x)
> +
> +/**
> + * DL_BUG_ON() - Fatal assertion (for framework bugs, not test failures)
> + * @__cond: Condition to check
> + * @__fmt: Error message
> + *
> + * For internal framework consistency checks. If condition is true,
> + * prints error and aborts. Use for "should never happen" cases.
> + */
> +#define DL_BUG_ON(__cond, __fmt, ...)					\
> +	do {								\
> +		if (__cond) {						\
> +			fprintf(stderr, "BUG: %s:%d: " __fmt "\n",	\
> +				__FILE__, __LINE__, ##__VA_ARGS__);	\
> +			abort();					\
> +		}							\
> +	} while (0)
> +
> +#endif /* __DL_TEST_H__ */
> diff --git a/tools/testing/selftests/sched/deadline/runner.c
> b/tools/testing/selftests/sched/deadline/runner.c
> new file mode 100644
> index 0000000000000..358f695423ef5
> --- /dev/null
> +++ b/tools/testing/selftests/sched/deadline/runner.c
> @@ -0,0 +1,219 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * SCHED_DEADLINE Test Runner
> + *
> + * Discovers and executes all registered SCHED_DEADLINE tests.
> + * Tests are statically linked and register themselves via ELF constructors.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <signal.h>
> +#include <string.h>
> +#include <stdbool.h>
> +#include "dl_test.h"
> +
> +const char help_fmt[] =
> +"Runner for SCHED_DEADLINE scheduler tests.\n"
> +"\n"
> +"All tests are statically linked and run serially. Tests require root\n"
> +"privileges to set SCHED_DEADLINE scheduling policy.\n"
> +"\n"
> +"Usage: %s [-t TEST] [-h]\n"
> +"\n"
> +"  -t TEST       Only run tests whose name includes this string\n"
> +"  -s            Include print output for skipped tests\n"
> +"  -l            List all available tests\n"
> +"  -q            Don't print the test descriptions during run\n"
> +"  -h            Display this help and exit\n";
> +
> +static volatile int exit_req;
> +static bool quiet, print_skipped, list;
> +
> +#define MAX_DL_TESTS 256
> +
> +static struct dl_test *__dl_tests[MAX_DL_TESTS];
> +static unsigned int __dl_num_tests;
> +
> +static void sigint_handler(int sig)
> +{
> +	exit_req = 1;
> +}
> +
> +static void print_test_preamble(const struct dl_test *test, bool quiet)
> +{
> +	printf("===== START =====\n");
> +	printf("TEST: %s\n", test->name);
> +	if (!quiet)
> +		printf("DESCRIPTION: %s\n", test->description);
> +	printf("OUTPUT:\n");
> +}
> +
> +static const char *status_to_result(enum dl_test_status status)
> +{
> +	switch (status) {
> +	case DL_TEST_PASS:
> +	case DL_TEST_SKIP:
> +		return "ok";
> +	case DL_TEST_FAIL:
> +		return "not ok";
> +	default:
> +		return "<UNKNOWN>";
> +	}
> +}
> +
> +static void print_test_result(const struct dl_test *test,
> +			      enum dl_test_status status,
> +			      unsigned int testnum)
> +{
> +	const char *result = status_to_result(status);
> +	const char *directive = status == DL_TEST_SKIP ? "SKIP " : "";
> +
> +	printf("%s %u %s # %s\n", result, testnum, test->name, directive);
> +	printf("=====  END  =====\n");
> +}
> +
> +static bool should_skip_test(const struct dl_test *test, const char *filter)
> +{
> +	return filter && !strstr(test->name, filter);
> +}
> +
> +static enum dl_test_status run_test(const struct dl_test *test)
> +{
> +	enum dl_test_status status;
> +	void *context = NULL;
> +
> +	if (test->setup) {
> +		status = test->setup(&context);
> +		if (status != DL_TEST_PASS)
> +			return status;
> +	}
> +
> +	status = test->run(context);
> +
> +	if (test->cleanup)
> +		test->cleanup(context);
> +
> +	return status;
> +}
> +
> +static bool test_valid(const struct dl_test *test)
> +{
> +	if (!test) {
> +		fprintf(stderr, "NULL test detected\n");
> +		return false;
> +	}
> +
> +	if (!test->name) {
> +		fprintf(stderr,
> +			"Test with no name found. Must specify test
> name.\n");
> +		return false;
> +	}
> +
> +	if (!test->description) {
> +		fprintf(stderr, "Test %s requires description.\n", test-
> >name);
> +		return false;
> +	}
> +
> +	if (!test->run) {
> +		fprintf(stderr, "Test %s has no run() callback\n", test-
> >name);
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	const char *filter = NULL;
> +	unsigned int testnum = 0, i;
> +	unsigned int passed = 0, skipped = 0, failed = 0;
> +	int opt;
> +
> +	signal(SIGINT, sigint_handler);
> +	signal(SIGTERM, sigint_handler);
> +
> +	while ((opt = getopt(argc, argv, "qslt:h")) != -1) {
> +		switch (opt) {
> +		case 'q':
> +			quiet = true;
> +			break;
> +		case 's':
> +			print_skipped = true;
> +			break;
> +		case 'l':
> +			list = true;
> +			break;
> +		case 't':
> +			filter = optarg;
> +			break;
> +		default:
> +			fprintf(stderr, help_fmt, argv[0]);
> +			return opt != 'h';
> +		}
> +	}
> +
> +	for (i = 0; i < __dl_num_tests; i++) {
> +		enum dl_test_status status;
> +		struct dl_test *test = __dl_tests[i];
> +
> +		if (list) {
> +			printf("%s\n", test->name);
> +			if (i == (__dl_num_tests - 1))
> +				return 0;
> +			continue;
> +		}
> +
> +		if (should_skip_test(test, filter)) {
> +			/*
> +			 * Printing the skipped tests and their preambles can
> +			 * add a lot of noise to the runner output. Printing
> +			 * this is only really useful for CI, so let's skip
> it
> +			 * by default.
> +			 */
> +			if (print_skipped) {
> +				print_test_preamble(test, quiet);
> +				print_test_result(test, DL_TEST_SKIP,
> ++testnum);
> +			}
> +			continue;
> +		}
> +
> +		print_test_preamble(test, quiet);
> +		status = run_test(test);
> +		print_test_result(test, status, ++testnum);
> +
> +		switch (status) {
> +		case DL_TEST_PASS:
> +			passed++;
> +			break;
> +		case DL_TEST_SKIP:
> +			skipped++;
> +			break;
> +		case DL_TEST_FAIL:
> +			failed++;
> +			break;
> +		}
> +
> +		if (exit_req) {
> +			fprintf(stderr, "\nInterrupted by signal\n");
> +			break;
> +		}
> +	}
> +
> +	printf("\n\n=============================\n\n");
> +	printf("RESULTS:\n\n");
> +	printf("PASSED:  %u\n", passed);
> +	printf("SKIPPED: %u\n", skipped);
> +	printf("FAILED:  %u\n", failed);
> +
> +	return failed > 0 ? 1 : 0;
> +}
> +
> +void dl_test_register(struct dl_test *test)
> +{
> +	DL_BUG_ON(!test_valid(test), "Invalid test found");
> +	DL_BUG_ON(__dl_num_tests >= MAX_DL_TESTS, "Maximum tests exceeded");
> +
> +	__dl_tests[__dl_num_tests++] = test;
> +}


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

* Re: [PATCH RFC 1/7] selftests/sched: Add SCHED_DEADLINE test framework infrastructure
  2026-03-09  8:20   ` Gabriele Monaco
@ 2026-03-09  9:10     ` Juri Lelli
  0 siblings, 0 replies; 21+ messages in thread
From: Juri Lelli @ 2026-03-09  9:10 UTC (permalink / raw)
  To: Gabriele Monaco
  Cc: Shuah Khan, Peter Zijlstra, Ingo Molnar, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Valentin Schneider,
	Clark Williams, Tommaso Cucinotta, Luca Abeni, linux-kernel,
	linux-kselftest

On 09/03/26 09:20, Gabriele Monaco wrote:
> On Fri, 2026-03-06 at 17:10 +0100, Juri Lelli wrote:
> > Add the foundational infrastructure for SCHED_DEADLINE scheduler tests
> > following the pattern established by sched_ext selftests. This provides
> > a clean, extensible framework for testing SCHED_DEADLINE functionality.
> > 
> > The framework uses ELF constructors for automatic test registration,
> > allowing new tests to be added simply by defining a struct dl_test and
> > calling REGISTER_DL_TEST(). The runner discovers all registered tests
> > at runtime and executes them serially, providing a simple and consistent
> > way to expand test coverage without modifying the test runner itself.
> > 
> > The framework provides a complete test lifecycle with setup, run, and
> > cleanup phases for each test. Tests can be filtered by name, listed for
> > inspection, or run in quiet mode for automated testing environments. The
> > framework includes assertion macros such as DL_EQ and DL_FAIL_IF to
> > simplify test authoring and provide consistent error reporting. Signal
> > handling ensures graceful interruption and cleanup when tests are
> > terminated early.
> > 
> > This commit establishes the framework without any actual tests. Tests
> > will be added in subsequent patches. The framework design is inspired by
> > tools/testing/selftests/sched_ext/ but tailored for SCHED_DEADLINE
> > testing needs.
> > 
> > Assisted-by: Claude Code: claude-sonnet-4-5@20250929
> > Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
> > ---
> >  tools/testing/selftests/sched/deadline/.gitignore |   3 +
> >  tools/testing/selftests/sched/deadline/Makefile   |  34 ++++
> >  tools/testing/selftests/sched/deadline/dl_test.h  | 238
> > ++++++++++++++++++++++
> >  tools/testing/selftests/sched/deadline/runner.c   | 219 ++++++++++++++++++++
> >  4 files changed, 494 insertions(+)
> > 
> 
> I wonder if this runner couldn't be made generic, something like not mentioning
> DL in any of the helpers, keep runner.c in tools/testing/selftests/sched/ and
> have a tools/testing/selftests/sched/deadline/Makefile glue all required objects
> together for the deadline tests (or anything else).
> 
> That would probably make adding new tests much easier without code duplication.

Yeah, guess we can do that. I started this with DEADLINE in mind, but if
people think the infra might be of help for adding tests for other
classes/policies, we can definitely restructure as you suggest.

Thanks for taking a look!

Best,
Juri


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

* Re: [PATCH RFC 4/7] selftests/sched: Add basic SCHED_DEADLINE functionality tests
  2026-03-09  8:15   ` Gabriele Monaco
@ 2026-03-09  9:11     ` Juri Lelli
  0 siblings, 0 replies; 21+ messages in thread
From: Juri Lelli @ 2026-03-09  9:11 UTC (permalink / raw)
  To: Gabriele Monaco
  Cc: Shuah Khan, Peter Zijlstra, Ingo Molnar, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Valentin Schneider,
	Clark Williams, Tommaso Cucinotta, Luca Abeni, linux-kernel,
	linux-kselftest

On 09/03/26 09:15, Gabriele Monaco wrote:
> Hello,
> 
> On Fri, 2026-03-06 at 17:10 +0100, Juri Lelli wrote:
> > Add initial test coverage for SCHED_DEADLINE scheduler functionality.
> > This patch introduces two fundamental tests that validate core
> > SCHED_DEADLINE behavior using the test framework and utility library.
> > 
> > The basic_scheduling test creates a cpuhog process with SCHED_DEADLINE
> > policy and verifies the scheduling policy is correctly set to policy 6
> > (SCHED_DEADLINE). It confirms the task executes successfully for the
> > specified duration using reasonable parameters with 30ms runtime, 100ms
> > deadline, and 100ms period. This validates the fundamental operation of
> > setting SCHED_DEADLINE policy and running deadline-scheduled tasks.
> > 
> > The parameter_validation test ensures the kernel properly validates
> > SCHED_DEADLINE parameters. It tests that invalid parameter combinations
> > such as runtime greater than deadline are correctly rejected with
> > EINVAL, while valid parameter configurations are accepted. This confirms
> > the kernel's parameter validation logic is working correctly and
> > prevents misconfigured deadline tasks from being scheduled.
> > 
> > Both tests use the dl_util library for SCHED_DEADLINE operations and
> > follow the framework pattern with automatic registration via
> > REGISTER_DL_TEST(). The tests pass cleanly in VM environments with 2
> > tests passing, 0 skipped, and 0 failed.
> > 
> > Assisted-by: Claude Code: claude-sonnet-4-5@20250929
> > Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
> > ---
> ...
> >  
> > +# Test files
> > +$(OUTPUT)/basic.o: basic.c dl_test.h dl_util.h | $(OUTPUT_DIR)
> > +	$(CC) $(CFLAGS) -c $< -o $@
> > +
> 
> This seems a constant in all tests, what about just having make do the trick:
> 
>   $(OUTPUT)/%.o: %.c dl_test.h dl_util.h | $(OUTPUT_DIR)
>   	$(CC) $(CFLAGS) -c $< -o $@

That should work, thanks!

> The rest looks neat.

Cool. Thanks again for reviewing.

Best,
Juri


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

* Re: [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest
  2026-03-06 16:10 ` [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest Juri Lelli
@ 2026-03-11  9:31   ` Christian Loehle
  2026-03-11 13:23     ` Juri Lelli
  0 siblings, 1 reply; 21+ messages in thread
From: Christian Loehle @ 2026-03-11  9:31 UTC (permalink / raw)
  To: Juri Lelli, Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest

On 3/6/26 16:10, Juri Lelli wrote:
> Add bandwidth admission control tests for SCHED_DEADLINE scheduler.
> These tests validate that the kernel properly enforces global bandwidth
> limits and correctly admits or rejects deadline tasks based on available
> system capacity.
> 
> The bandwidth_admission test verifies that N tasks can run
> simultaneously at the maximum available bandwidth per CPU. This maximum
> is calculated as the RT bandwidth limit minus any DL server bandwidth
> allocations, ensuring that tasks can fully utilize the available
> deadline scheduling capacity without being rejected by admission
> control.
> 
> The bandwidth_overflow test verifies that the kernel correctly rejects
> tasks that would exceed the available global bandwidth. This ensures the
> admission control mechanism prevents overcommitment of deadline
> resources, which is critical for maintaining temporal isolation and
> schedulability guarantees.
> 
> The implementation includes automatic detection of DL server bandwidth
> allocations by scanning /sys/kernel/debug/sched/*_server directories.
> This detects servers such as fair_server and any future additions,
> ensuring tests adapt automatically to system configuration changes.
> Available bandwidth is calculated by reading cpu0 configuration across
> all servers, with the assumption of symmetric systems where all CPUs
> have identical configuration.
> 
> Assisted-by: Claude Code: claude-sonnet-4-5@20250929
> Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
> ---
>  tools/testing/selftests/sched/deadline/Makefile    |   5 +-
>  tools/testing/selftests/sched/deadline/bandwidth.c | 270 +++++++++++++++++++++
>  tools/testing/selftests/sched/deadline/dl_util.c   |  73 +++++-
>  tools/testing/selftests/sched/deadline/dl_util.h   |  12 +-
>  4 files changed, 355 insertions(+), 5 deletions(-)
> 
> diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
> index 3fb4568a59e20..daa2f5d14e947 100644
> --- a/tools/testing/selftests/sched/deadline/Makefile
> +++ b/tools/testing/selftests/sched/deadline/Makefile
> @@ -14,7 +14,7 @@ OUTPUT_DIR := $(OUTPUT)
>  UTIL_OBJS := $(OUTPUT)/dl_util.o
>  
>  # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c)
> -TEST_OBJS := $(OUTPUT)/basic.o
> +TEST_OBJS := $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o
>  
>  # Runner binary links utility and test objects
>  $(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h | $(OUTPUT_DIR)
> @@ -32,6 +32,9 @@ $(OUTPUT)/dl_util.o: dl_util.c dl_util.h | $(OUTPUT_DIR)
>  $(OUTPUT)/basic.o: basic.c dl_test.h dl_util.h | $(OUTPUT_DIR)
>  	$(CC) $(CFLAGS) -c $< -o $@
>  
> +$(OUTPUT)/bandwidth.o: bandwidth.c dl_test.h dl_util.h | $(OUTPUT_DIR)
> +	$(CC) $(CFLAGS) -c $< -o $@
> +
>  $(OUTPUT_DIR):
>  	mkdir -p $@
>  
> diff --git a/tools/testing/selftests/sched/deadline/bandwidth.c b/tools/testing/selftests/sched/deadline/bandwidth.c
> new file mode 100644
> index 0000000000000..72755a200db22
> --- /dev/null
> +++ b/tools/testing/selftests/sched/deadline/bandwidth.c
> @@ -0,0 +1,270 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * SCHED_DEADLINE bandwidth admission control tests
> + *
> + * Validates that the kernel correctly enforces bandwidth limits for
> + * SCHED_DEADLINE tasks, including per-CPU bandwidth replication and
> + * overflow rejection.
> + */
> +
> +#define _GNU_SOURCE
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <signal.h>
> +#include <errno.h>
> +#include <string.h>
> +#include "dl_test.h"
> +#include "dl_util.h"
> +
> +/*
> + * Test: Bandwidth admission control with max bandwidth per CPU
> + *
> + * Verifies that SCHED_DEADLINE bandwidth is replicated per CPU, allowing
> + * one task per CPU to use the maximum available bandwidth (typically 95%).
> + */
> +static enum dl_test_status test_bandwidth_admission_run(void *ctx)
> +{
> +	uint64_t rt_runtime_us, rt_period_us;
> +	int max_bw_percent;
> +	uint64_t runtime_ns, deadline_ns, period_ns;
> +	int num_cpus, i;
> +	pid_t *pids = NULL;
> +	int started = 0, running = 0;
> +	enum dl_test_status ret = DL_TEST_FAIL;
> +
> +	/* Get RT bandwidth settings */
> +	DL_FAIL_IF(dl_get_rt_bandwidth(&rt_runtime_us, &rt_period_us) < 0,
> +		   "Failed to read RT bandwidth settings");
> +
> +	printf("  RT bandwidth: runtime=%luµs, period=%luµs (%.0f%%)\n",
> +	       rt_runtime_us, rt_period_us,
> +	       (double)rt_runtime_us * 100.0 / rt_period_us);
> +
> +	/* Show server overhead */
> +	int server_overhead = dl_get_server_bandwidth_overhead();
> +
> +	if (server_overhead > 0)
> +		printf("  DL server overhead: %d%% per CPU\n", server_overhead);
> +
> +	/* Calculate maximum bandwidth percentage */
> +	max_bw_percent = dl_calc_max_bandwidth_percent();
> +	DL_FAIL_IF(max_bw_percent < 0, "Failed to calculate max bandwidth");
> +
> +	printf("  Available bandwidth per CPU: %d%%\n", max_bw_percent);
> +
> +	/* Calculate task parameters: 100ms period for easy calculation */
> +	period_ns = dl_ms_to_ns(100);  /* 100ms */
> +	runtime_ns = (period_ns * max_bw_percent) / 100;
> +	deadline_ns = period_ns;
> +
> +	printf("  Task params: runtime=%lums, deadline=%lums, period=%lums\n",
> +	       dl_ns_to_ms(runtime_ns), dl_ns_to_ms(deadline_ns),
> +	       dl_ns_to_ms(period_ns));
> +
> +	/* Get number of CPUs */
> +	num_cpus = dl_get_online_cpus();
> +	DL_FAIL_IF(num_cpus <= 0, "Failed to get number of CPUs");
> +
> +	printf("  Number of online CPUs: %d\n", num_cpus);
> +
> +	/* Allocate PID array */
> +	pids = calloc(num_cpus, sizeof(pid_t));
> +	DL_FAIL_IF(!pids, "Failed to allocate PID array");
> +
> +	/* Start one cpuhog per CPU at max bandwidth */
> +	printf("  Starting %d cpuhog tasks at max bandwidth...\n", num_cpus);
> +
> +	for (i = 0; i < num_cpus; i++) {
> +		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
> +		if (pids[i] < 0) {
> +			printf("  Task %d failed to start: %s\n",
> +			       i + 1, strerror(errno));
> +			goto cleanup;
> +		}
> +		started++;
> +	}

Would it be okay to just have one task per max-cap CPU to make this pass on HMP?
Or something more sophisticated?

> +
> +	/* Brief wait for tasks to settle */
> +	usleep(500000);  /* 500ms */
> +
> +	/* Verify all tasks are running with SCHED_DEADLINE */
> +	for (i = 0; i < started; i++) {
> +		if (pids[i] <= 0)
> +			continue;
> +
> +		if (kill(pids[i], 0) < 0) {
> +			printf("  Task PID %d died unexpectedly\n", pids[i]);
> +			continue;
> +		}
> +
> +		if (dl_is_deadline_task(pids[i]))
> +			running++;
> +	}
> +
> +	printf("  Started %d/%d tasks, %d running with SCHED_DEADLINE\n",
> +	       started, num_cpus, running);
> +
> +	/* Test passes if we started all N tasks and they're all running */
> +	if (started == num_cpus && running == num_cpus) {
> +		printf("  SUCCESS: All %d tasks running at max bandwidth\n",
> +		       num_cpus);
> +		ret = DL_TEST_PASS;
> +	} else if (started != num_cpus) {
> +		DL_ERR("Only started %d/%d tasks", started, num_cpus);
> +		ret = DL_TEST_FAIL;
> +	} else {
> +		DL_ERR("Started %d tasks but only %d using SCHED_DEADLINE",
> +		       started, running);
> +		ret = DL_TEST_FAIL;
> +	}
> +
> +cleanup:
> +	/* Cleanup all started tasks */
> +	for (i = 0; i < started; i++) {
> +		if (pids[i] > 0)
> +			dl_cleanup_cpuhog(pids[i]);
> +	}
> +
> +	free(pids);
> +	return ret;
> +}
> +
> +static struct dl_test test_bandwidth_admission = {
> +	.name = "bandwidth_admission",
> +	.description = "Verify per-CPU bandwidth replication (N tasks at max bandwidth)",
> +	.run = test_bandwidth_admission_run,
> +};
> +REGISTER_DL_TEST(&test_bandwidth_admission);
> +
> +/*
> + * Test: Bandwidth admission control overflow rejection
> + *
> + * Verifies that the kernel rejects tasks that would exceed available
> + * bandwidth on a CPU. Creates N-1 tasks at max bandwidth, then attempts
> + * to create one more at slightly higher bandwidth (should fail).
> + */
> +static enum dl_test_status test_bandwidth_overflow_run(void *ctx)
> +{
> +	uint64_t rt_runtime_us, rt_period_us;
> +	int max_bw_percent;
> +	uint64_t runtime_ns, deadline_ns, period_ns;
> +	uint64_t overflow_runtime_ns;
> +	int num_cpus, i;
> +	int target_tasks;
> +	pid_t *pids = NULL;
> +	pid_t overflow_pid;
> +	int started = 0;
> +	enum dl_test_status ret = DL_TEST_FAIL;
> +
> +	/* Get RT bandwidth settings */
> +	DL_FAIL_IF(dl_get_rt_bandwidth(&rt_runtime_us, &rt_period_us) < 0,
> +		   "Failed to read RT bandwidth settings");
> +
> +	printf("  RT bandwidth: runtime=%luµs, period=%luµs (%.0f%%)\n",
> +	       rt_runtime_us, rt_period_us,
> +	       (double)rt_runtime_us * 100.0 / rt_period_us);
> +
> +	/* Show server overhead */
> +	int server_overhead = dl_get_server_bandwidth_overhead();
> +
> +	if (server_overhead > 0)
> +		printf("  DL server overhead: %d%% per CPU\n", server_overhead);
> +
> +	/* Calculate maximum bandwidth percentage */
> +	max_bw_percent = dl_calc_max_bandwidth_percent();
> +	DL_FAIL_IF(max_bw_percent < 0, "Failed to calculate max bandwidth");
> +
> +	printf("  Available bandwidth per CPU: %d%%\n", max_bw_percent);
> +
> +	/* Get number of CPUs */
> +	num_cpus = dl_get_online_cpus();
> +	DL_FAIL_IF(num_cpus <= 0, "Failed to get number of CPUs");
> +
> +	if (num_cpus < 2) {
> +		printf("  Need at least 2 CPUs for this test (have %d)\n",
> +		       num_cpus);
> +		return DL_TEST_SKIP;
> +	}
> +
> +	printf("  Number of online CPUs: %d\n", num_cpus);
> +
> +	/* Calculate task parameters */
> +	period_ns = dl_ms_to_ns(100);  /* 100ms */
> +	runtime_ns = (period_ns * max_bw_percent) / 100;
> +	deadline_ns = period_ns;
> +
> +	printf("  Task params: runtime=%lums, deadline=%lums, period=%lums\n",
> +	       dl_ns_to_ms(runtime_ns), dl_ns_to_ms(deadline_ns),
> +	       dl_ns_to_ms(period_ns));
> +
> +	/* Start N-1 tasks at max bandwidth */
> +	target_tasks = num_cpus - 1;
> +	pids = calloc(target_tasks, sizeof(pid_t));
> +	DL_FAIL_IF(!pids, "Failed to allocate PID array");
> +
> +	printf("  Starting %d tasks at max bandwidth...\n", target_tasks);
> +
> +	for (i = 0; i < target_tasks; i++) {
> +		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
> +		if (pids[i] < 0) {
> +			printf("  Task %d failed to start: %s\n",
> +			       i + 1, strerror(errno));
> +			goto cleanup;
> +		}
> +		started++;
> +	}
> +
> +	printf("  Successfully started %d/%d tasks\n", started, target_tasks);
> +
> +	/* Brief wait */
> +	usleep(500000);  /* 500ms */
> +
> +	/* Try to start one more task at max+1% bandwidth (should fail) */
> +	overflow_runtime_ns = (runtime_ns * 101) / 100;  /* Add 1% */
> +
> +	printf("  Attempting overflow task with runtime=%lums (+1%%)...\n",
> +	       dl_ns_to_ms(overflow_runtime_ns));
> +
> +	overflow_pid = dl_create_cpuhog(overflow_runtime_ns, deadline_ns,
> +					period_ns, 0);
> +
> +	if (overflow_pid < 0) {
> +		/* Expected: admission control rejected it */
> +		printf("  Overflow task correctly rejected: %s\n",
> +		       strerror(errno));
> +		ret = DL_TEST_PASS;
> +	} else {
> +		/* Unexpected: it was admitted */
> +		usleep(100000);  /* 100ms */
> +
> +		if (kill(overflow_pid, 0) == 0) {
> +			printf("  ERROR: Overflow task admitted and running\n");
> +			dl_cleanup_cpuhog(overflow_pid);
> +			ret = DL_TEST_FAIL;
> +		} else {
> +			/* It was admitted but died - still wrong */
> +			printf("  ERROR: Overflow task admitted but died\n");
> +			ret = DL_TEST_FAIL;
> +		}
> +	}
> +
> +cleanup:
> +	/* Cleanup all tasks */
> +	for (i = 0; i < started; i++) {
> +		if (pids[i] > 0)
> +			dl_cleanup_cpuhog(pids[i]);
> +	}
> +
> +	free(pids);
> +	return ret;
> +}
> +
> +static struct dl_test test_bandwidth_overflow = {
> +	.name = "bandwidth_overflow",
> +	.description = "Verify bandwidth overflow rejection (N-1 + overflow fails)",
> +	.run = test_bandwidth_overflow_run,
> +};
> +REGISTER_DL_TEST(&test_bandwidth_overflow);
> diff --git a/tools/testing/selftests/sched/deadline/dl_util.c b/tools/testing/selftests/sched/deadline/dl_util.c
> index 0d7c46ba877f3..6727d622d72d3 100644
> --- a/tools/testing/selftests/sched/deadline/dl_util.c
> +++ b/tools/testing/selftests/sched/deadline/dl_util.c
> @@ -14,6 +14,8 @@
>  #include <sys/wait.h>
>  #include <signal.h>
>  #include <time.h>
> +#include <glob.h>
> +#include <dirent.h>
>  #include "dl_util.h"
>  
>  /* Syscall numbers for sched_setattr/sched_getattr */
> @@ -121,10 +123,65 @@ int dl_get_rt_bandwidth(uint64_t *runtime_us, uint64_t *period_us)
>  				period_us);
>  }
>  
> +int dl_get_server_bandwidth_overhead(void)
> +{
> +	glob_t globbuf;
> +	char pattern[512];
> +	size_t i;
> +	int total_overhead = 0;
> +
> +	/* Find all *_server directories */
> +	snprintf(pattern, sizeof(pattern),
> +		 "/sys/kernel/debug/sched/*_server");
> +
> +	if (glob(pattern, 0, NULL, &globbuf) != 0) {
> +		/* No servers found - not an error, just no overhead */
> +		return 0;
> +	}
> +
> +	/*
> +	 * Sum overhead from cpu0 across all servers.
> +	 * Assumes symmetric system where all CPUs have identical server
> +	 * configuration. Reading only cpu0 represents the per-CPU overhead.
> +	 */
> +	for (i = 0; i < globbuf.gl_pathc; i++) {
> +		char runtime_path[512];
> +		char period_path[512];
> +		char *server_path = globbuf.gl_pathv[i];
> +		uint64_t runtime_ns = 0, period_ns = 0;
> +		int percent;
> +
> +		/* Build paths to cpu0 runtime and period files */
> +		snprintf(runtime_path, sizeof(runtime_path),
> +			 "%s/cpu0/runtime", server_path);
> +		snprintf(period_path, sizeof(period_path),
> +			 "%s/cpu0/period", server_path);
> +
> +		/* Read runtime and period for cpu0 */
> +		if (read_proc_uint64(runtime_path, &runtime_ns) < 0)
> +			continue;
> +		if (read_proc_uint64(period_path, &period_ns) < 0)
> +			continue;
> +
> +		if (period_ns == 0)
> +			continue;
> +
> +		/* Calculate percentage for this server */
> +		percent = (runtime_ns * 100) / period_ns;
> +
> +		/* Accumulate overhead from all servers */
> +		total_overhead += percent;
> +	}
> +
> +	globfree(&globbuf);
> +	return total_overhead;
> +}
> +
>  int dl_calc_max_bandwidth_percent(void)
>  {
>  	uint64_t runtime_us, period_us;
> -	int percent;
> +	int rt_percent, server_overhead;
> +	int available_percent;
>  
>  	if (dl_get_rt_bandwidth(&runtime_us, &period_us) < 0)
>  		return -1;
> @@ -132,8 +189,18 @@ int dl_calc_max_bandwidth_percent(void)
>  	if (period_us == 0)
>  		return -1;
>  
> -	percent = (runtime_us * 100) / period_us;
> -	return percent > 0 ? percent : 1;
> +	/* Calculate RT bandwidth percentage */
> +	rt_percent = (runtime_us * 100) / period_us;
> +
> +	/* Get server overhead */
> +	server_overhead = dl_get_server_bandwidth_overhead();
> +	if (server_overhead < 0)
> +		server_overhead = 0;
> +
> +	/* Available bandwidth = RT bandwidth - server overhead */
> +	available_percent = rt_percent - server_overhead;
> +
> +	return available_percent > 0 ? available_percent : 1;
>  }
>  
>  /*
> diff --git a/tools/testing/selftests/sched/deadline/dl_util.h b/tools/testing/selftests/sched/deadline/dl_util.h
> index 9ab9d055a95a0..f8046eb0cbd3b 100644
> --- a/tools/testing/selftests/sched/deadline/dl_util.h
> +++ b/tools/testing/selftests/sched/deadline/dl_util.h
> @@ -79,11 +79,21 @@ bool dl_is_deadline_task(pid_t pid);
>   */
>  int dl_get_rt_bandwidth(uint64_t *runtime_us, uint64_t *period_us);
>  
> +/**
> + * dl_get_server_bandwidth_overhead() - Calculate total DL server overhead per CPU
> + *
> + * Scans /sys/kernel/debug/sched/ for server directories (fair_server, etc.) and
> + * calculates the total bandwidth reserved by all DL servers per CPU.
> + *
> + * Return: Bandwidth percentage overhead per CPU (0-100), or -1 on error
> + */
> +int dl_get_server_bandwidth_overhead(void);
> +
>  /**
>   * dl_calc_max_bandwidth_percent() - Calculate available bandwidth percentage
>   *
>   * Calculates the maximum bandwidth available per CPU as a percentage,
> - * based on RT bandwidth settings.
> + * based on RT bandwidth settings minus DL server overhead (fair_server, etc.).
>   *
>   * Return: Bandwidth percentage (0-100), or -1 on error
>   */
> 


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

* Re: [PATCH RFC 2/7] selftests/sched: Add SCHED_DEADLINE utility library
  2026-03-06 16:10 ` [PATCH RFC 2/7] selftests/sched: Add SCHED_DEADLINE utility library Juri Lelli
@ 2026-03-11  9:39   ` Christian Loehle
  2026-03-11 13:15     ` Juri Lelli
  0 siblings, 1 reply; 21+ messages in thread
From: Christian Loehle @ 2026-03-11  9:39 UTC (permalink / raw)
  To: Juri Lelli, Shuah Khan, Peter Zijlstra, Ingo Molnar
  Cc: Vincent Guittot, Dietmar Eggemann, Steven Rostedt,
	Valentin Schneider, Clark Williams, Gabriele Monaco,
	Tommaso Cucinotta, Luca Abeni, linux-kernel, linux-kselftest

On 3/6/26 16:10, Juri Lelli wrote:
> Add a comprehensive utility library for SCHED_DEADLINE scheduler tests.
> This library provides reusable helper functions that simplify test
> implementation and reduce code duplication across the test suite.
> 
> The utility library provides scheduling operations that wrap the
> sched_setattr and sched_getattr syscalls for setting and querying
> SCHED_DEADLINE parameters. These include dl_set_sched_attr() for
> configuring deadline parameters, dl_get_sched_attr() for querying
> scheduling attributes, dl_get_policy() for reading the policy from
> /proc, and dl_is_deadline_task() for checking if a task is using
> SCHED_DEADLINE. The library uses system headers for struct sched_attr
> to avoid redefinition conflicts and provides full control over
> SCHED_DEADLINE parameters.
> 
> Bandwidth management helpers allow tests to work within system
> constraints. The dl_get_rt_bandwidth() function reads RT bandwidth
> settings from /proc, while dl_calc_max_bandwidth_percent() calculates
> the available bandwidth for deadline tasks based on current system
> configuration.
> 
> Process management functions simplify creating and managing test
> workloads. The dl_create_cpuhog() function forks and schedules a cpuhog
> process by creating a child process, executing the cpuhog binary, and
> setting SCHED_DEADLINE policy on the child PID after fork. It waits for
> the child to start before configuring the scheduling policy. Supporting
> functions include dl_cleanup_cpuhog() for terminating processes,
> dl_find_cpuhogs() for locating running instances, and dl_wait_for_pid()
> for synchronizing with process startup.
> 
> CPU topology helpers enable tests that manipulate CPU hotplug state.
> These include dl_get_online_cpus() for counting available CPUs,
> dl_get_hotpluggable_cpus() for identifying which CPUs can be
> hotplugged, dl_cpu_online() and dl_cpu_offline() for controlling
> hotplug state, and dl_is_cpu_online() for checking current status.
> 
> Time conversion utilities provide convenient transformations between
> different time units. These include dl_ms_to_ns() and dl_us_to_ns()
> for converting to nanoseconds, and dl_ns_to_ms() and dl_ns_to_us() for
> converting from nanoseconds.
> 
> The library also includes the cpuhog helper program, which performs
> busy looping to consume CPU cycles. This provides a controllable
> workload for testing scheduler behavior under various deadline
> configurations.
> 
> Assisted-by: Claude Code: claude-sonnet-4-5@20250929
> Signed-off-by: Juri Lelli <juri.lelli@redhat.com>
> ---
>  tools/testing/selftests/sched/deadline/Makefile  |  14 +-
>  tools/testing/selftests/sched/deadline/cpuhog.c  | 107 ++++++++
>  tools/testing/selftests/sched/deadline/dl_util.c | 335 +++++++++++++++++++++++
>  tools/testing/selftests/sched/deadline/dl_util.h | 227 +++++++++++++++
>  4 files changed, 680 insertions(+), 3 deletions(-)
> 
> diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
> index fd57794f1a543..ea3fdfbef459e 100644
> --- a/tools/testing/selftests/sched/deadline/Makefile
> +++ b/tools/testing/selftests/sched/deadline/Makefile
> @@ -1,6 +1,6 @@
>  # SPDX-License-Identifier: GPL-2.0
>  
> -TEST_GEN_PROGS := runner
> +TEST_GEN_PROGS := runner cpuhog
>  

This runs cpuhog as a test as well which prints misleading timeouts.


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

* Re: [PATCH RFC 2/7] selftests/sched: Add SCHED_DEADLINE utility library
  2026-03-11  9:39   ` Christian Loehle
@ 2026-03-11 13:15     ` Juri Lelli
  0 siblings, 0 replies; 21+ messages in thread
From: Juri Lelli @ 2026-03-11 13:15 UTC (permalink / raw)
  To: Christian Loehle
  Cc: Shuah Khan, Peter Zijlstra, Ingo Molnar, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Valentin Schneider,
	Clark Williams, Gabriele Monaco, Tommaso Cucinotta, Luca Abeni,
	linux-kernel, linux-kselftest

On 11/03/26 09:39, Christian Loehle wrote:
> On 3/6/26 16:10, Juri Lelli wrote:

...

> > diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
> > index fd57794f1a543..ea3fdfbef459e 100644
> > --- a/tools/testing/selftests/sched/deadline/Makefile
> > +++ b/tools/testing/selftests/sched/deadline/Makefile
> > @@ -1,6 +1,6 @@
> >  # SPDX-License-Identifier: GPL-2.0
> >  
> > -TEST_GEN_PROGS := runner
> > +TEST_GEN_PROGS := runner cpuhog
> >  
> 
> This runs cpuhog as a test as well which prints misleading timeouts.
> 

Yeah, will fix/remove.

Thanks!
Juri


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

* Re: [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest
  2026-03-11  9:31   ` Christian Loehle
@ 2026-03-11 13:23     ` Juri Lelli
  2026-03-11 13:44       ` Christian Loehle
  0 siblings, 1 reply; 21+ messages in thread
From: Juri Lelli @ 2026-03-11 13:23 UTC (permalink / raw)
  To: Christian Loehle
  Cc: Shuah Khan, Peter Zijlstra, Ingo Molnar, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Valentin Schneider,
	Clark Williams, Gabriele Monaco, Tommaso Cucinotta, Luca Abeni,
	linux-kernel, linux-kselftest

On 11/03/26 09:31, Christian Loehle wrote:
> On 3/6/26 16:10, Juri Lelli wrote:

...

> > +	/* Start one cpuhog per CPU at max bandwidth */
> > +	printf("  Starting %d cpuhog tasks at max bandwidth...\n", num_cpus);
> > +
> > +	for (i = 0; i < num_cpus; i++) {
> > +		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
> > +		if (pids[i] < 0) {
> > +			printf("  Task %d failed to start: %s\n",
> > +			       i + 1, strerror(errno));
> > +			goto cleanup;
> > +		}
> > +		started++;
> > +	}
> 
> Would it be okay to just have one task per max-cap CPU to make this pass on HMP?
> Or something more sophisticated?
> 

On HMP we should probably have max bandwidth hogs on big CPUs and then
scale runtime (bandwidth) considering smaller CPUs capacities. Cannot
quickly check atm, but that info (max cap per-CPU) is available
somewhere in sys or proc, is it?

Thanks,
Juri


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

* Re: [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest
  2026-03-11 13:23     ` Juri Lelli
@ 2026-03-11 13:44       ` Christian Loehle
  2026-03-11 14:26         ` Christian Loehle
  0 siblings, 1 reply; 21+ messages in thread
From: Christian Loehle @ 2026-03-11 13:44 UTC (permalink / raw)
  To: Juri Lelli
  Cc: Shuah Khan, Peter Zijlstra, Ingo Molnar, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Valentin Schneider,
	Clark Williams, Gabriele Monaco, Tommaso Cucinotta, Luca Abeni,
	linux-kernel, linux-kselftest

[-- Attachment #1: Type: text/plain, Size: 1054 bytes --]

On 3/11/26 13:23, Juri Lelli wrote:
> On 11/03/26 09:31, Christian Loehle wrote:
>> On 3/6/26 16:10, Juri Lelli wrote:
> 
> ...
> 
>>> +	/* Start one cpuhog per CPU at max bandwidth */
>>> +	printf("  Starting %d cpuhog tasks at max bandwidth...\n", num_cpus);
>>> +
>>> +	for (i = 0; i < num_cpus; i++) {
>>> +		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
>>> +		if (pids[i] < 0) {
>>> +			printf("  Task %d failed to start: %s\n",
>>> +			       i + 1, strerror(errno));
>>> +			goto cleanup;
>>> +		}
>>> +		started++;
>>> +	}
>>
>> Would it be okay to just have one task per max-cap CPU to make this pass on HMP?
>> Or something more sophisticated?
>>
> 
> On HMP we should probably have max bandwidth hogs on big CPUs and then
> scale runtime (bandwidth) considering smaller CPUs capacities. Cannot
> quickly check atm, but that info (max cap per-CPU) is available
> somewhere in sys or proc, is it?

Yes it's here:
/sys/devices/system/cpu/cpu0/cpu_capacity

FWIW I've attached the two patches to get a pass out of arm64 HMP.

[-- Attachment #2: 0001-selftests-sched-Account-for-asymmetric-CPU-bw.patch --]
[-- Type: text/x-patch, Size: 16762 bytes --]

From 49f24f00c6af2b7e856f7a7223a19fc87e2a4aaf Mon Sep 17 00:00:00 2001
From: Christian Loehle <christian.loehle@arm.com>
Date: Wed, 11 Mar 2026 13:31:05 +0000
Subject: [PATCH 1/2] selftests/sched: Account for asymmetric CPU bw

The bandwidth tests previously assumed a symmetric system, starting one
max-bandwidth task per online CPU. On HMP systems this overcounts
available bandwidth, since small CPUs have less capacity than 1024.

Fix this by reading cpu_capacity for each online CPU from sysfs and
computing the number of expected tasks as total root-domain bandwidth
divided by one max-capacity task's bandwidth. Also read DL server
overhead per-CPU from debugfs so it is correctly subtracted before
computing available bandwidth.

Signed-off-by: Christian Loehle <christian.loehle@arm.com>
---
 .../selftests/sched/deadline/bandwidth.c      | 363 ++++++++++++------
 1 file changed, 252 insertions(+), 111 deletions(-)

diff --git a/tools/testing/selftests/sched/deadline/bandwidth.c b/tools/testing/selftests/sched/deadline/bandwidth.c
index 72755a200db2..f931b6bddac6 100644
--- a/tools/testing/selftests/sched/deadline/bandwidth.c
+++ b/tools/testing/selftests/sched/deadline/bandwidth.c
@@ -3,8 +3,8 @@
  * SCHED_DEADLINE bandwidth admission control tests
  *
  * Validates that the kernel correctly enforces bandwidth limits for
- * SCHED_DEADLINE tasks, including per-CPU bandwidth replication and
- * overflow rejection.
+ * SCHED_DEADLINE tasks, including capacity-scaled bandwidth replication
+ * and overflow rejection.
  */
 
 #define _GNU_SOURCE
@@ -16,69 +16,248 @@
 #include <signal.h>
 #include <errno.h>
 #include <string.h>
+#include <glob.h>
 #include "dl_test.h"
 #include "dl_util.h"
 
-/*
- * Test: Bandwidth admission control with max bandwidth per CPU
- *
- * Verifies that SCHED_DEADLINE bandwidth is replicated per CPU, allowing
- * one task per CPU to use the maximum available bandwidth (typically 95%).
- */
-static enum dl_test_status test_bandwidth_admission_run(void *ctx)
+#define DL_BW_SCALE 1000000000ULL
+#define DL_DEFAULT_CPU_CAPACITY 1024UL
+
+struct dl_bw_test_config {
+	uint64_t rt_runtime_us;
+	uint64_t rt_period_us;
+	uint64_t rt_bw_scaled;
+	uint64_t total_server_bw_scaled;
+	uint64_t max_cpu_server_bw_scaled;
+	uint64_t runtime_ns;
+	uint64_t deadline_ns;
+	uint64_t period_ns;
+	uint64_t task_bw_scaled;
+	unsigned long total_cpu_capacity;
+	unsigned long max_cpu_capacity;
+	int num_cpus;
+	int max_cpu;
+	int expected_tasks;
+};
+
+static int bw_read_uint64(const char *path, uint64_t *value)
 {
-	uint64_t rt_runtime_us, rt_period_us;
-	int max_bw_percent;
-	uint64_t runtime_ns, deadline_ns, period_ns;
-	int num_cpus, i;
-	pid_t *pids = NULL;
-	int started = 0, running = 0;
-	enum dl_test_status ret = DL_TEST_FAIL;
+	FILE *f;
+	int ret;
+
+	f = fopen(path, "r");
+	if (!f)
+		return -1;
+
+	ret = fscanf(f, "%lu", value);
+	fclose(f);
+
+	return ret == 1 ? 0 : -1;
+}
+
+static int bw_is_cpu_online(int cpu)
+{
+	char path[256];
+	int online;
+
+	online = dl_is_cpu_online(cpu);
+	if (online >= 0)
+		return online;
+
+	snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d", cpu);
+	return access(path, F_OK) == 0;
+}
+
+static int bw_get_cpu_capacity(int cpu, unsigned long *capacity)
+{
+	char path[256];
+	uint64_t value;
 
-	/* Get RT bandwidth settings */
-	DL_FAIL_IF(dl_get_rt_bandwidth(&rt_runtime_us, &rt_period_us) < 0,
+	snprintf(path, sizeof(path),
+		 "/sys/devices/system/cpu/cpu%d/cpu_capacity", cpu);
+
+	if (bw_read_uint64(path, &value) < 0)
+		value = DL_DEFAULT_CPU_CAPACITY;
+
+	if (!value)
+		value = DL_DEFAULT_CPU_CAPACITY;
+
+	*capacity = value;
+	return 0;
+}
+
+static uint64_t bw_get_server_bw_scaled(int cpu)
+{
+	glob_t globbuf;
+	char pattern[512];
+	size_t i;
+	uint64_t total_bw = 0;
+
+	snprintf(pattern, sizeof(pattern), "/sys/kernel/debug/sched/*_server");
+	if (glob(pattern, 0, NULL, &globbuf) != 0)
+		return 0;
+
+	for (i = 0; i < globbuf.gl_pathc; i++) {
+		char runtime_path[512];
+		char period_path[512];
+		uint64_t runtime_ns, period_ns;
+
+		snprintf(runtime_path, sizeof(runtime_path), "%s/cpu%d/runtime",
+			 globbuf.gl_pathv[i], cpu);
+		snprintf(period_path, sizeof(period_path), "%s/cpu%d/period",
+			 globbuf.gl_pathv[i], cpu);
+
+		if (bw_read_uint64(runtime_path, &runtime_ns) < 0)
+			continue;
+		if (bw_read_uint64(period_path, &period_ns) < 0)
+			continue;
+		if (!period_ns)
+			continue;
+
+		total_bw += ((__uint128_t)runtime_ns * DL_BW_SCALE) / period_ns;
+	}
+
+	globfree(&globbuf);
+	return total_bw;
+}
+
+static double bw_scaled_to_percent(uint64_t scaled)
+{
+	return (double)scaled * 100.0 / DL_BW_SCALE;
+}
+
+static enum dl_test_status bw_prepare_test(struct dl_bw_test_config *cfg)
+{
+	int cpu;
+	int max_cpus;
+	__uint128_t total_rt_bw;
+	__uint128_t total_server_bw;
+	__uint128_t total_available_bw;
+	__uint128_t task_bw_capacity;
+
+	memset(cfg, 0, sizeof(*cfg));
+	cfg->max_cpu = -1;
+
+	DL_FAIL_IF(dl_get_rt_bandwidth(&cfg->rt_runtime_us,
+			       &cfg->rt_period_us) < 0,
 		   "Failed to read RT bandwidth settings");
+	DL_FAIL_IF(!cfg->rt_period_us, "RT bandwidth period is zero");
 
-	printf("  RT bandwidth: runtime=%luµs, period=%luµs (%.0f%%)\n",
-	       rt_runtime_us, rt_period_us,
-	       (double)rt_runtime_us * 100.0 / rt_period_us);
+	cfg->rt_bw_scaled = ((__uint128_t)cfg->rt_runtime_us * DL_BW_SCALE) /
+				    cfg->rt_period_us;
+	cfg->period_ns = dl_ms_to_ns(100);
+	cfg->deadline_ns = cfg->period_ns;
 
-	/* Show server overhead */
-	int server_overhead = dl_get_server_bandwidth_overhead();
+	max_cpus = (int)sysconf(_SC_NPROCESSORS_CONF);
+	DL_FAIL_IF(max_cpus <= 0, "Failed to read configured CPU count");
 
-	if (server_overhead > 0)
-		printf("  DL server overhead: %d%% per CPU\n", server_overhead);
+	for (cpu = 0; cpu < max_cpus; cpu++) {
+		unsigned long capacity;
+		uint64_t server_bw;
 
-	/* Calculate maximum bandwidth percentage */
-	max_bw_percent = dl_calc_max_bandwidth_percent();
-	DL_FAIL_IF(max_bw_percent < 0, "Failed to calculate max bandwidth");
+		if (!bw_is_cpu_online(cpu))
+			continue;
 
-	printf("  Available bandwidth per CPU: %d%%\n", max_bw_percent);
+		bw_get_cpu_capacity(cpu, &capacity);
+		server_bw = bw_get_server_bw_scaled(cpu);
 
-	/* Calculate task parameters: 100ms period for easy calculation */
-	period_ns = dl_ms_to_ns(100);  /* 100ms */
-	runtime_ns = (period_ns * max_bw_percent) / 100;
-	deadline_ns = period_ns;
+		cfg->num_cpus++;
+		cfg->total_cpu_capacity += capacity;
+		cfg->total_server_bw_scaled += server_bw;
+
+		if (capacity > cfg->max_cpu_capacity) {
+			cfg->max_cpu_capacity = capacity;
+			cfg->max_cpu_server_bw_scaled = server_bw;
+			cfg->max_cpu = cpu;
+		}
+	}
+
+	DL_FAIL_IF(cfg->num_cpus <= 0, "Failed to find online CPUs");
+	DL_FAIL_IF(!cfg->max_cpu_capacity,
+		   "Failed to determine maximum CPU capacity");
+	DL_FAIL_IF(cfg->rt_bw_scaled <= cfg->max_cpu_server_bw_scaled,
+		   "DL servers leave no bandwidth for tasks on max-capacity CPU");
+
+	cfg->runtime_ns = ((__uint128_t)cfg->period_ns *
+			  (cfg->rt_bw_scaled - cfg->max_cpu_server_bw_scaled)) /
+			 DL_BW_SCALE;
+	DL_FAIL_IF(!cfg->runtime_ns, "Calculated task runtime is zero");
+
+	cfg->task_bw_scaled = ((__uint128_t)cfg->runtime_ns * DL_BW_SCALE) /
+			      cfg->period_ns;
+	DL_FAIL_IF(!cfg->task_bw_scaled, "Calculated task bandwidth is zero");
+
+	total_rt_bw = (__uint128_t)cfg->total_cpu_capacity * cfg->rt_bw_scaled;
+	total_server_bw = (__uint128_t)cfg->max_cpu_capacity *
+			  cfg->total_server_bw_scaled;
+	DL_FAIL_IF(total_rt_bw <= total_server_bw,
+		   "DL servers consume all root-domain bandwidth");
+
+	total_available_bw = total_rt_bw - total_server_bw;
+	task_bw_capacity = (__uint128_t)cfg->max_cpu_capacity *
+			   cfg->task_bw_scaled;
+
+	DL_FAIL_IF(!task_bw_capacity, "Calculated task capacity is zero");
+
+	cfg->expected_tasks = total_available_bw / task_bw_capacity;
+	DL_FAIL_IF(cfg->expected_tasks <= 0,
+		   "Calculated task count is zero");
+
+	return DL_TEST_PASS;
+}
+
+static void bw_print_test_config(const struct dl_bw_test_config *cfg)
+{
+	printf("  RT bandwidth: runtime=%luµs, period=%luµs (%.0f%%)\n",
+	       cfg->rt_runtime_us, cfg->rt_period_us,
+	       (double)cfg->rt_runtime_us * 100.0 / cfg->rt_period_us);
+	printf("  Number of online CPUs: %d\n", cfg->num_cpus);
+	printf("  Equivalent max-capacity CPUs: %.2f\n",
+	       (double)cfg->total_cpu_capacity / cfg->max_cpu_capacity);
+	printf("  Max-capacity CPU: %d (capacity=%lu)\n",
+	       cfg->max_cpu, cfg->max_cpu_capacity);
+
+	if (cfg->max_cpu_server_bw_scaled) {
+		printf("  DL server overhead on max-capacity CPU: %.2f%%\n",
+		       bw_scaled_to_percent(cfg->max_cpu_server_bw_scaled));
+	}
 
 	printf("  Task params: runtime=%lums, deadline=%lums, period=%lums\n",
-	       dl_ns_to_ms(runtime_ns), dl_ns_to_ms(deadline_ns),
-	       dl_ns_to_ms(period_ns));
+	       dl_ns_to_ms(cfg->runtime_ns), dl_ns_to_ms(cfg->deadline_ns),
+	       dl_ns_to_ms(cfg->period_ns));
+	printf("  Expected tasks at max-capacity bandwidth: %d\n",
+	       cfg->expected_tasks);
+}
 
-	/* Get number of CPUs */
-	num_cpus = dl_get_online_cpus();
-	DL_FAIL_IF(num_cpus <= 0, "Failed to get number of CPUs");
+/*
+ * Test: Bandwidth admission control with max-capacity task bandwidth
+ *
+ * Verifies that SCHED_DEADLINE bandwidth is replicated across the root
+ * domain capacity, allowing one max-capacity task per equivalent CPU.
+ */
+static enum dl_test_status test_bandwidth_admission_run(void *ctx)
+{
+	struct dl_bw_test_config cfg;
+	int i;
+	pid_t *pids = NULL;
+	int started = 0, running = 0;
+	enum dl_test_status ret = DL_TEST_FAIL;
 
-	printf("  Number of online CPUs: %d\n", num_cpus);
+	DL_FAIL_IF(bw_prepare_test(&cfg) != DL_TEST_PASS,
+		   "Failed to prepare bandwidth test parameters");
+	bw_print_test_config(&cfg);
 
 	/* Allocate PID array */
-	pids = calloc(num_cpus, sizeof(pid_t));
+	pids = calloc(cfg.expected_tasks, sizeof(pid_t));
 	DL_FAIL_IF(!pids, "Failed to allocate PID array");
 
-	/* Start one cpuhog per CPU at max bandwidth */
-	printf("  Starting %d cpuhog tasks at max bandwidth...\n", num_cpus);
+	/* Start one cpuhog per equivalent max-capacity CPU */
+	printf("  Starting %d cpuhog tasks at max-capacity bandwidth...\n",
+	       cfg.expected_tasks);
 
-	for (i = 0; i < num_cpus; i++) {
-		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
+	for (i = 0; i < cfg.expected_tasks; i++) {
+		pids[i] = dl_create_cpuhog(cfg.runtime_ns, cfg.deadline_ns,
+					  cfg.period_ns, 0);
 		if (pids[i] < 0) {
 			printf("  Task %d failed to start: %s\n",
 			       i + 1, strerror(errno));
@@ -105,15 +284,15 @@ static enum dl_test_status test_bandwidth_admission_run(void *ctx)
 	}
 
 	printf("  Started %d/%d tasks, %d running with SCHED_DEADLINE\n",
-	       started, num_cpus, running);
+	       started, cfg.expected_tasks, running);
 
-	/* Test passes if we started all N tasks and they're all running */
-	if (started == num_cpus && running == num_cpus) {
-		printf("  SUCCESS: All %d tasks running at max bandwidth\n",
-		       num_cpus);
+	/* Test passes if we started all expected tasks and they're all running */
+	if (started == cfg.expected_tasks && running == cfg.expected_tasks) {
+		printf("  SUCCESS: All %d tasks running at max-capacity bandwidth\n",
+		       cfg.expected_tasks);
 		ret = DL_TEST_PASS;
-	} else if (started != num_cpus) {
-		DL_ERR("Only started %d/%d tasks", started, num_cpus);
+	} else if (started != cfg.expected_tasks) {
+		DL_ERR("Only started %d/%d tasks", started, cfg.expected_tasks);
 		ret = DL_TEST_FAIL;
 	} else {
 		DL_ERR("Started %d tasks but only %d using SCHED_DEADLINE",
@@ -134,7 +313,7 @@ static enum dl_test_status test_bandwidth_admission_run(void *ctx)
 
 static struct dl_test test_bandwidth_admission = {
 	.name = "bandwidth_admission",
-	.description = "Verify per-CPU bandwidth replication (N tasks at max bandwidth)",
+	.description = "Verify capacity-scaled bandwidth replication (N equivalent tasks at max bandwidth)",
 	.run = test_bandwidth_admission_run,
 };
 REGISTER_DL_TEST(&test_bandwidth_admission);
@@ -142,73 +321,34 @@ REGISTER_DL_TEST(&test_bandwidth_admission);
 /*
  * Test: Bandwidth admission control overflow rejection
  *
- * Verifies that the kernel rejects tasks that would exceed available
- * bandwidth on a CPU. Creates N-1 tasks at max bandwidth, then attempts
- * to create one more at slightly higher bandwidth (should fail).
+ * Verifies that the kernel rejects tasks that would exceed the available
+ * root-domain bandwidth. Creates all equivalent max-capacity tasks, then
+ * attempts to create one more at slightly higher bandwidth (should fail).
  */
 static enum dl_test_status test_bandwidth_overflow_run(void *ctx)
 {
-	uint64_t rt_runtime_us, rt_period_us;
-	int max_bw_percent;
-	uint64_t runtime_ns, deadline_ns, period_ns;
+	struct dl_bw_test_config cfg;
 	uint64_t overflow_runtime_ns;
-	int num_cpus, i;
-	int target_tasks;
+	int i;
 	pid_t *pids = NULL;
 	pid_t overflow_pid;
 	int started = 0;
 	enum dl_test_status ret = DL_TEST_FAIL;
 
-	/* Get RT bandwidth settings */
-	DL_FAIL_IF(dl_get_rt_bandwidth(&rt_runtime_us, &rt_period_us) < 0,
-		   "Failed to read RT bandwidth settings");
-
-	printf("  RT bandwidth: runtime=%luµs, period=%luµs (%.0f%%)\n",
-	       rt_runtime_us, rt_period_us,
-	       (double)rt_runtime_us * 100.0 / rt_period_us);
-
-	/* Show server overhead */
-	int server_overhead = dl_get_server_bandwidth_overhead();
-
-	if (server_overhead > 0)
-		printf("  DL server overhead: %d%% per CPU\n", server_overhead);
-
-	/* Calculate maximum bandwidth percentage */
-	max_bw_percent = dl_calc_max_bandwidth_percent();
-	DL_FAIL_IF(max_bw_percent < 0, "Failed to calculate max bandwidth");
-
-	printf("  Available bandwidth per CPU: %d%%\n", max_bw_percent);
-
-	/* Get number of CPUs */
-	num_cpus = dl_get_online_cpus();
-	DL_FAIL_IF(num_cpus <= 0, "Failed to get number of CPUs");
-
-	if (num_cpus < 2) {
-		printf("  Need at least 2 CPUs for this test (have %d)\n",
-		       num_cpus);
-		return DL_TEST_SKIP;
-	}
-
-	printf("  Number of online CPUs: %d\n", num_cpus);
-
-	/* Calculate task parameters */
-	period_ns = dl_ms_to_ns(100);  /* 100ms */
-	runtime_ns = (period_ns * max_bw_percent) / 100;
-	deadline_ns = period_ns;
-
-	printf("  Task params: runtime=%lums, deadline=%lums, period=%lums\n",
-	       dl_ns_to_ms(runtime_ns), dl_ns_to_ms(deadline_ns),
-	       dl_ns_to_ms(period_ns));
+	DL_FAIL_IF(bw_prepare_test(&cfg) != DL_TEST_PASS,
+		   "Failed to prepare bandwidth test parameters");
+	bw_print_test_config(&cfg);
 
-	/* Start N-1 tasks at max bandwidth */
-	target_tasks = num_cpus - 1;
-	pids = calloc(target_tasks, sizeof(pid_t));
+	/* Start all expected tasks at max-capacity bandwidth */
+	pids = calloc(cfg.expected_tasks, sizeof(pid_t));
 	DL_FAIL_IF(!pids, "Failed to allocate PID array");
 
-	printf("  Starting %d tasks at max bandwidth...\n", target_tasks);
+	printf("  Starting %d tasks at max-capacity bandwidth...\n",
+	       cfg.expected_tasks);
 
-	for (i = 0; i < target_tasks; i++) {
-		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
+	for (i = 0; i < cfg.expected_tasks; i++) {
+		pids[i] = dl_create_cpuhog(cfg.runtime_ns, cfg.deadline_ns,
+					  cfg.period_ns, 0);
 		if (pids[i] < 0) {
 			printf("  Task %d failed to start: %s\n",
 			       i + 1, strerror(errno));
@@ -217,19 +357,20 @@ static enum dl_test_status test_bandwidth_overflow_run(void *ctx)
 		started++;
 	}
 
-	printf("  Successfully started %d/%d tasks\n", started, target_tasks);
+	printf("  Successfully started %d/%d tasks\n",
+	       started, cfg.expected_tasks);
 
 	/* Brief wait */
 	usleep(500000);  /* 500ms */
 
 	/* Try to start one more task at max+1% bandwidth (should fail) */
-	overflow_runtime_ns = (runtime_ns * 101) / 100;  /* Add 1% */
+	overflow_runtime_ns = (cfg.runtime_ns * 101) / 100;
 
 	printf("  Attempting overflow task with runtime=%lums (+1%%)...\n",
 	       dl_ns_to_ms(overflow_runtime_ns));
 
-	overflow_pid = dl_create_cpuhog(overflow_runtime_ns, deadline_ns,
-					period_ns, 0);
+	overflow_pid = dl_create_cpuhog(overflow_runtime_ns, cfg.deadline_ns,
+					cfg.period_ns, 0);
 
 	if (overflow_pid < 0) {
 		/* Expected: admission control rejected it */
@@ -264,7 +405,7 @@ static enum dl_test_status test_bandwidth_overflow_run(void *ctx)
 
 static struct dl_test test_bandwidth_overflow = {
 	.name = "bandwidth_overflow",
-	.description = "Verify bandwidth overflow rejection (N-1 + overflow fails)",
+	.description = "Verify bandwidth overflow rejection beyond equivalent capacity",
 	.run = test_bandwidth_overflow_run,
 };
 REGISTER_DL_TEST(&test_bandwidth_overflow);
-- 
2.34.1


[-- Attachment #3: 0002-selftests-sched-Remove-cpuhog-from-test-binaries.patch --]
[-- Type: text/x-patch, Size: 1726 bytes --]

From babc2081f598a59ba4bcebe9b4741f4cfa0f67ef Mon Sep 17 00:00:00 2001
From: Christian Loehle <christian.loehle@arm.com>
Date: Wed, 11 Mar 2026 13:32:35 +0000
Subject: [PATCH 2/2] selftests/sched: Remove cpuhog from test binaries

cpuhog is a helper binary invoked by the test runner, not a standalone
test. Listing it in TEST_GEN_PROGS causes the kselftest framework to
execute it directly as a test case. Move it to TEST_GEN_FILES so it
is built but not run independently.

Also drop the hand-written clean target and use EXTRA_CLEAN instead,
deferring to lib.mk.

Signed-off-by: Christian Loehle <christian.loehle@arm.com>
---
 tools/testing/selftests/sched/deadline/Makefile | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/tools/testing/selftests/sched/deadline/Makefile b/tools/testing/selftests/sched/deadline/Makefile
index aa7752da1bdc..22cdffa4d08a 100644
--- a/tools/testing/selftests/sched/deadline/Makefile
+++ b/tools/testing/selftests/sched/deadline/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
-TEST_GEN_PROGS := runner cpuhog
+TEST_GEN_PROGS := runner
+TEST_GEN_FILES := cpuhog
 
 # override lib.mk's default rules
 OVERRIDE_TARGETS := 1
@@ -9,6 +10,7 @@ include ../../lib.mk
 CFLAGS += -Wall -O2 -g -pthread
 
 OUTPUT_DIR := $(OUTPUT)
+EXTRA_CLEAN += $(OUTPUT)/*.o *.o
 
 # Utility object files
 UTIL_OBJS := $(OUTPUT)/dl_util.o
@@ -44,11 +46,6 @@ $(OUTPUT)/replenish_bug.o: replenish_bug.c dl_test.h dl_util.h | $(OUTPUT_DIR)
 $(OUTPUT_DIR):
 	mkdir -p $@
 
-.PHONY: all clean
+.PHONY: all
 
-all: $(TEST_GEN_PROGS)
-
-clean:
-	rm -f $(OUTPUT)/runner $(OUTPUT)/cpuhog
-	rm -f $(OUTPUT)/*.o
-	rm -f *.o
+all: $(TEST_GEN_PROGS) $(TEST_GEN_FILES)
-- 
2.34.1


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

* Re: [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest
  2026-03-11 13:44       ` Christian Loehle
@ 2026-03-11 14:26         ` Christian Loehle
  2026-03-12 10:43           ` Christian Loehle
  0 siblings, 1 reply; 21+ messages in thread
From: Christian Loehle @ 2026-03-11 14:26 UTC (permalink / raw)
  To: Juri Lelli
  Cc: Shuah Khan, Peter Zijlstra, Ingo Molnar, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Valentin Schneider,
	Clark Williams, Gabriele Monaco, Tommaso Cucinotta, Luca Abeni,
	linux-kernel, linux-kselftest

On 3/11/26 13:44, Christian Loehle wrote:
> On 3/11/26 13:23, Juri Lelli wrote:
>> On 11/03/26 09:31, Christian Loehle wrote:
>>> On 3/6/26 16:10, Juri Lelli wrote:
>>
>> ...
>>
>>>> +	/* Start one cpuhog per CPU at max bandwidth */
>>>> +	printf("  Starting %d cpuhog tasks at max bandwidth...\n", num_cpus);
>>>> +
>>>> +	for (i = 0; i < num_cpus; i++) {
>>>> +		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
>>>> +		if (pids[i] < 0) {
>>>> +			printf("  Task %d failed to start: %s\n",
>>>> +			       i + 1, strerror(errno));
>>>> +			goto cleanup;
>>>> +		}
>>>> +		started++;
>>>> +	}
>>>
>>> Would it be okay to just have one task per max-cap CPU to make this pass on HMP?
>>> Or something more sophisticated?
>>>
>>
>> On HMP we should probably have max bandwidth hogs on big CPUs and then
>> scale runtime (bandwidth) considering smaller CPUs capacities. Cannot
>> quickly check atm, but that info (max cap per-CPU) is available
>> somewhere in sys or proc, is it?
> 
> Yes it's here:
> /sys/devices/system/cpu/cpu0/cpu_capacity
> 
> FWIW I've attached the two patches to get a pass out of arm64 HMP.

Wait nevermind, this isn't right, this would expect a 10 CPU system with
[1024, 128, 128, 128, 128, 128, 128, 128, 128, 128]
= 2176
would allow for 2 1024-equivalent hogs, but that is obviously wrong as
the capacity -> bandwidth calculation must be capped in practice by
only summing the k-highest-cap-CPUs if there's only k deadline-tasks.

Let me go and read how this is actually supposed to work.

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

* Re: [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest
  2026-03-11 14:26         ` Christian Loehle
@ 2026-03-12 10:43           ` Christian Loehle
  2026-03-12 11:30             ` Christian Loehle
  0 siblings, 1 reply; 21+ messages in thread
From: Christian Loehle @ 2026-03-12 10:43 UTC (permalink / raw)
  To: Juri Lelli
  Cc: Shuah Khan, Peter Zijlstra, Ingo Molnar, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Valentin Schneider,
	Clark Williams, Gabriele Monaco, Tommaso Cucinotta, Luca Abeni,
	linux-kernel, linux-kselftest

On 3/11/26 14:26, Christian Loehle wrote:
> On 3/11/26 13:44, Christian Loehle wrote:
>> On 3/11/26 13:23, Juri Lelli wrote:
>>> On 11/03/26 09:31, Christian Loehle wrote:
>>>> On 3/6/26 16:10, Juri Lelli wrote:
>>>
>>> ...
>>>
>>>>> +	/* Start one cpuhog per CPU at max bandwidth */
>>>>> +	printf("  Starting %d cpuhog tasks at max bandwidth...\n", num_cpus);
>>>>> +
>>>>> +	for (i = 0; i < num_cpus; i++) {
>>>>> +		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
>>>>> +		if (pids[i] < 0) {
>>>>> +			printf("  Task %d failed to start: %s\n",
>>>>> +			       i + 1, strerror(errno));
>>>>> +			goto cleanup;
>>>>> +		}
>>>>> +		started++;
>>>>> +	}
>>>>
>>>> Would it be okay to just have one task per max-cap CPU to make this pass on HMP?
>>>> Or something more sophisticated?
>>>>
>>>
>>> On HMP we should probably have max bandwidth hogs on big CPUs and then
>>> scale runtime (bandwidth) considering smaller CPUs capacities. Cannot
>>> quickly check atm, but that info (max cap per-CPU) is available
>>> somewhere in sys or proc, is it?
>>
>> Yes it's here:
>> /sys/devices/system/cpu/cpu0/cpu_capacity
>>
>> FWIW I've attached the two patches to get a pass out of arm64 HMP.
> 
> Wait nevermind, this isn't right, this would expect a 10 CPU system with
> [1024, 128, 128, 128, 128, 128, 128, 128, 128, 128]
> = 2176
> would allow for 2 1024-equivalent hogs, but that is obviously wrong as
> the capacity -> bandwidth calculation must be capped in practice by
> only summing the k-highest-cap-CPUs if there's only k deadline-tasks.
> 
> Let me go and read how this is actually supposed to work.

Nevermind the nevermind, it's a bit counterintuitive because we specifically
test this edgecase here but my original proposal is fine...

if you're still taking suggestions, I think a test with hotplugging and
bandwidth would be nice, too:

-Fill to max, verify extra admission fails.
-Kill one task, offline one CPU, verify offline succeeds.
-Try respawn while CPU is offline, verify admission fails.
-Online CPU again, verify respawn succeeds.

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

* Re: [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest
  2026-03-12 10:43           ` Christian Loehle
@ 2026-03-12 11:30             ` Christian Loehle
  2026-03-12 14:13               ` Juri Lelli
  0 siblings, 1 reply; 21+ messages in thread
From: Christian Loehle @ 2026-03-12 11:30 UTC (permalink / raw)
  To: Juri Lelli
  Cc: Shuah Khan, Peter Zijlstra, Ingo Molnar, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Valentin Schneider,
	Clark Williams, Gabriele Monaco, Tommaso Cucinotta, Luca Abeni,
	linux-kernel, linux-kselftest

[-- Attachment #1: Type: text/plain, Size: 3372 bytes --]

On 3/12/26 10:43, Christian Loehle wrote:
> On 3/11/26 14:26, Christian Loehle wrote:
>> On 3/11/26 13:44, Christian Loehle wrote:
>>> On 3/11/26 13:23, Juri Lelli wrote:
>>>> On 11/03/26 09:31, Christian Loehle wrote:
>>>>> On 3/6/26 16:10, Juri Lelli wrote:
>>>>
>>>> ...
>>>>
>>>>>> +	/* Start one cpuhog per CPU at max bandwidth */
>>>>>> +	printf("  Starting %d cpuhog tasks at max bandwidth...\n", num_cpus);
>>>>>> +
>>>>>> +	for (i = 0; i < num_cpus; i++) {
>>>>>> +		pids[i] = dl_create_cpuhog(runtime_ns, deadline_ns, period_ns, 0);
>>>>>> +		if (pids[i] < 0) {
>>>>>> +			printf("  Task %d failed to start: %s\n",
>>>>>> +			       i + 1, strerror(errno));
>>>>>> +			goto cleanup;
>>>>>> +		}
>>>>>> +		started++;
>>>>>> +	}
>>>>>
>>>>> Would it be okay to just have one task per max-cap CPU to make this pass on HMP?
>>>>> Or something more sophisticated?
>>>>>
>>>>
>>>> On HMP we should probably have max bandwidth hogs on big CPUs and then
>>>> scale runtime (bandwidth) considering smaller CPUs capacities. Cannot
>>>> quickly check atm, but that info (max cap per-CPU) is available
>>>> somewhere in sys or proc, is it?
>>>
>>> Yes it's here:
>>> /sys/devices/system/cpu/cpu0/cpu_capacity
>>>
>>> FWIW I've attached the two patches to get a pass out of arm64 HMP.
>>
>> Wait nevermind, this isn't right, this would expect a 10 CPU system with
>> [1024, 128, 128, 128, 128, 128, 128, 128, 128, 128]
>> = 2176
>> would allow for 2 1024-equivalent hogs, but that is obviously wrong as
>> the capacity -> bandwidth calculation must be capped in practice by
>> only summing the k-highest-cap-CPUs if there's only k deadline-tasks.
>>
>> Let me go and read how this is actually supposed to work.
> 
> Nevermind the nevermind, it's a bit counterintuitive because we specifically
> test this edgecase here but my original proposal is fine...
> 
> if you're still taking suggestions, I think a test with hotplugging and
> bandwidth would be nice, too:
> 
> -Fill to max, verify extra admission fails.
> -Kill one task, offline one CPU, verify offline succeeds.
> -Try respawn while CPU is offline, verify admission fails.
> -Online CPU again, verify respawn succeeds.
> 

For completeness, although I'm sure you can come up with something equally good:

# ===== START =====
# TEST: bandwidth_hotplug_accounting
# DESCRIPTION: Verify capacity-scaled bandwidth accounting across CPU hotplug
# OUTPUT:
#   RT bandwidth: runtime=950000µs, period=1000000µs (95%)
#   Number of online CPUs: 12
#   Equivalent max-capacity CPUs: 8.47
#   Max-capacity CPU: 0 (capacity=1024)
#   DL server overhead on max-capacity CPU: 10.00%
#   Task params: runtime=85ms, deadline=100ms, period=100ms
#   Expected tasks at max-capacity bandwidth: 8
#   Selected hotpluggable CPU: 1
#   Expected tasks after offline: 7
#   Starting 8 tasks at max-capacity bandwidth...
#   Verifying additional task is rejected...
#   Additional task correctly rejected: Device or resource busy
#   Killing one task before CPU offline...
#   Offlining CPU 1...
#   Trying to respawn one task with CPU offline (expect reject)...
#   Respawn correctly rejected: Device or resource busy
#   Onlining CPU 1...
#   Trying to respawn one task with CPU online (expect success)...
#   SUCCESS: Hotplug accounting matched capacity-scaled expectations
# ok 5 bandwidth_hotplug_accounting # 
# =====  END  =====


[-- Attachment #2: 0001-selftests-sched-Add-a-hotplug-bandwidth-selftest.patch --]
[-- Type: text/x-patch, Size: 9059 bytes --]

From 788e3761ccfd2143f9ca0ee2981865bb5b60a6a7 Mon Sep 17 00:00:00 2001
From: Christian Loehle <christian.loehle@arm.com>
Date: Thu, 12 Mar 2026 11:28:25 +0000
Subject: [PATCH] selftests/sched: Add a hotplug-bandwidth selftest

Bandwidth is recalculated when CPU hotplugging, so add a test for:
-Spawn all capacity-computed tasks.
-Verify one more is rejected.
-Kill one task.
-Offline selected hotpluggable CPU.
-Verify respawn fails.
-Online CPU.
-Verify respawn succeeds.

Signed-off-by: Christian Loehle <christian.loehle@arm.com>
---
 .../selftests/sched/deadline/bandwidth.c      | 266 ++++++++++++++++++
 1 file changed, 266 insertions(+)

diff --git a/tools/testing/selftests/sched/deadline/bandwidth.c b/tools/testing/selftests/sched/deadline/bandwidth.c
index f931b6bddac6..6dd5e61bb4fb 100644
--- a/tools/testing/selftests/sched/deadline/bandwidth.c
+++ b/tools/testing/selftests/sched/deadline/bandwidth.c
@@ -37,6 +37,7 @@ struct dl_bw_test_config {
 	unsigned long max_cpu_capacity;
 	int num_cpus;
 	int max_cpu;
+	int max_cpu_count;
 	int expected_tasks;
 };
 
@@ -126,6 +127,104 @@ static double bw_scaled_to_percent(uint64_t scaled)
 	return (double)scaled * 100.0 / DL_BW_SCALE;
 }
 
+static int bw_expected_tasks_for_totals(const struct dl_bw_test_config *cfg,
+					unsigned long total_cpu_capacity,
+					uint64_t total_server_bw_scaled)
+{
+	__uint128_t total_rt_bw;
+	__uint128_t total_server_bw;
+	__uint128_t total_available_bw;
+	__uint128_t task_bw_capacity;
+
+	total_rt_bw = (__uint128_t)total_cpu_capacity * cfg->rt_bw_scaled;
+	total_server_bw = (__uint128_t)cfg->max_cpu_capacity *
+			  total_server_bw_scaled;
+	if (total_rt_bw <= total_server_bw)
+		return 0;
+
+	total_available_bw = total_rt_bw - total_server_bw;
+	task_bw_capacity = (__uint128_t)cfg->max_cpu_capacity *
+			   cfg->task_bw_scaled;
+	if (!task_bw_capacity)
+		return 0;
+
+	return total_available_bw / task_bw_capacity;
+}
+
+static int bw_wait_cpu_state(int cpu, int online, int timeout_ms)
+{
+	int waited_ms = 0;
+
+	while (waited_ms < timeout_ms) {
+		if (dl_is_cpu_online(cpu) == online)
+			return 0;
+
+		usleep(10000); /* 10ms */
+		waited_ms += 10;
+	}
+
+	return -1;
+}
+
+static int bw_find_hotplug_cpu_for_offline_test(
+		const struct dl_bw_test_config *cfg,
+		int *expected_tasks_after_offline)
+{
+	int max_cpus;
+	int hotplug_count;
+	int *hotplug_cpus;
+	int i;
+	int selected_cpu = -1;
+
+	max_cpus = (int)sysconf(_SC_NPROCESSORS_CONF);
+	if (max_cpus <= 0)
+		return -1;
+
+	hotplug_cpus = calloc(max_cpus, sizeof(int));
+	if (!hotplug_cpus)
+		return -1;
+
+	hotplug_count = dl_get_hotpluggable_cpus(hotplug_cpus, max_cpus);
+	if (hotplug_count <= 0)
+		goto out;
+
+	for (i = 0; i < hotplug_count; i++) {
+		int cpu = hotplug_cpus[i];
+		unsigned long capacity;
+		uint64_t server_bw;
+		int expected_after;
+
+		if (!bw_is_cpu_online(cpu))
+			continue;
+
+		bw_get_cpu_capacity(cpu, &capacity);
+		server_bw = bw_get_server_bw_scaled(cpu);
+
+		if (cfg->total_cpu_capacity <= capacity)
+			continue;
+		if (cfg->total_server_bw_scaled < server_bw)
+			continue;
+
+		if (capacity == cfg->max_cpu_capacity && cfg->max_cpu_count == 1)
+			continue;
+
+		expected_after = bw_expected_tasks_for_totals(cfg,
+					cfg->total_cpu_capacity - capacity,
+					cfg->total_server_bw_scaled - server_bw);
+
+		if (expected_after == cfg->expected_tasks - 1) {
+			selected_cpu = cpu;
+			if (expected_tasks_after_offline)
+				*expected_tasks_after_offline = expected_after;
+			break;
+		}
+	}
+
+out:
+	free(hotplug_cpus);
+	return selected_cpu;
+}
+
 static enum dl_test_status bw_prepare_test(struct dl_bw_test_config *cfg)
 {
 	int cpu;
@@ -169,6 +268,9 @@ static enum dl_test_status bw_prepare_test(struct dl_bw_test_config *cfg)
 			cfg->max_cpu_capacity = capacity;
 			cfg->max_cpu_server_bw_scaled = server_bw;
 			cfg->max_cpu = cpu;
+			cfg->max_cpu_count = 1;
+		} else if (capacity == cfg->max_cpu_capacity) {
+			cfg->max_cpu_count++;
 		}
 	}
 
@@ -409,3 +511,167 @@ static struct dl_test test_bandwidth_overflow = {
 	.run = test_bandwidth_overflow_run,
 };
 REGISTER_DL_TEST(&test_bandwidth_overflow);
+
+/*
+ * Test: Capacity-scaled bandwidth accounting across CPU hotplug
+ *
+ * Verifies that admission accounting tracks CPU offline/online transitions on
+ * asymmetric systems using the same capacity-scaled math as admission:
+ *
+ * 1) Spawn all expected max-capacity tasks.
+ * 2) Verify one more task cannot be admitted.
+ * 3) Kill one task and offline one selected hotpluggable CPU.
+ * 4) Verify respawn fails while CPU is offline.
+ * 5) Online the CPU and verify respawn succeeds.
+ */
+static enum dl_test_status test_bandwidth_hotplug_accounting_run(void *ctx)
+{
+	struct dl_bw_test_config cfg;
+	int expected_after_offline = -1;
+	int hotplug_cpu;
+	pid_t *pids = NULL;
+	pid_t probe_pid;
+	int started = 0;
+	int i;
+	bool cpu_offlined = false;
+	enum dl_test_status ret = DL_TEST_FAIL;
+
+	DL_FAIL_IF(bw_prepare_test(&cfg) != DL_TEST_PASS,
+		   "Failed to prepare bandwidth test parameters");
+	bw_print_test_config(&cfg);
+
+	if (cfg.expected_tasks < 2) {
+		printf("  Need at least 2 expected tasks for hotplug test (have %d)\n",
+		       cfg.expected_tasks);
+		return DL_TEST_SKIP;
+	}
+
+	hotplug_cpu = bw_find_hotplug_cpu_for_offline_test(&cfg,
+						   &expected_after_offline);
+	if (hotplug_cpu < 0) {
+		printf("  No suitable hotpluggable CPU found for exact offline/online transition\n");
+		return DL_TEST_SKIP;
+	}
+
+	printf("  Selected hotpluggable CPU: %d\n", hotplug_cpu);
+	printf("  Expected tasks after offline: %d\n", expected_after_offline);
+
+	pids = calloc(cfg.expected_tasks + 1, sizeof(pid_t));
+	DL_FAIL_IF(!pids, "Failed to allocate PID array");
+
+	printf("  Starting %d tasks at max-capacity bandwidth...\n",
+	       cfg.expected_tasks);
+
+	for (i = 0; i < cfg.expected_tasks; i++) {
+		pids[i] = dl_create_cpuhog(cfg.runtime_ns, cfg.deadline_ns,
+					  cfg.period_ns, 0);
+		if (pids[i] < 0) {
+			printf("  Task %d failed to start: %s\n",
+			       i + 1, strerror(errno));
+			goto cleanup;
+		}
+		started++;
+	}
+
+	usleep(500000);
+
+	printf("  Verifying additional task is rejected...\n");
+	probe_pid = dl_create_cpuhog(cfg.runtime_ns, cfg.deadline_ns,
+				     cfg.period_ns, 0);
+	if (probe_pid >= 0) {
+		printf("  ERROR: Additional task admitted at saturation\n");
+		dl_cleanup_cpuhog(probe_pid);
+		goto cleanup;
+	}
+
+	printf("  Additional task correctly rejected: %s\n", strerror(errno));
+
+	printf("  Killing one task before CPU offline...\n");
+	dl_cleanup_cpuhog(pids[started - 1]);
+	pids[started - 1] = 0;
+	started--;
+
+	usleep(200000);
+
+	printf("  Offlining CPU %d...\n", hotplug_cpu);
+	if (dl_cpu_offline(hotplug_cpu) < 0) {
+		DL_ERR("Failed to offline CPU %d: %s", hotplug_cpu, strerror(errno));
+		goto cleanup;
+	}
+
+	if (bw_wait_cpu_state(hotplug_cpu, 0, 3000) < 0) {
+		DL_ERR("CPU %d did not transition to offline state", hotplug_cpu);
+		goto cleanup;
+	}
+
+	cpu_offlined = true;
+	usleep(300000);
+
+	printf("  Trying to respawn one task with CPU offline (expect reject)...\n");
+	probe_pid = dl_create_cpuhog(cfg.runtime_ns, cfg.deadline_ns,
+				     cfg.period_ns, 0);
+	if (probe_pid >= 0) {
+		printf("  ERROR: Respawn admitted while CPU is offline\n");
+		dl_cleanup_cpuhog(probe_pid);
+		goto cleanup;
+	}
+
+	printf("  Respawn correctly rejected: %s\n", strerror(errno));
+
+	printf("  Onlining CPU %d...\n", hotplug_cpu);
+	if (dl_cpu_online(hotplug_cpu) < 0) {
+		DL_ERR("Failed to online CPU %d: %s", hotplug_cpu, strerror(errno));
+		goto cleanup;
+	}
+
+	if (bw_wait_cpu_state(hotplug_cpu, 1, 5000) < 0) {
+		DL_ERR("CPU %d did not transition to online state", hotplug_cpu);
+		goto cleanup;
+	}
+
+	cpu_offlined = false;
+	usleep(300000);
+
+	printf("  Trying to respawn one task with CPU online (expect success)...\n");
+	pids[started] = dl_create_cpuhog(cfg.runtime_ns, cfg.deadline_ns,
+					 cfg.period_ns, 0);
+	if (pids[started] < 0) {
+		DL_ERR("Respawn failed after CPU online: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	if (!dl_is_deadline_task(pids[started])) {
+		DL_ERR("Respawned task is not running with SCHED_DEADLINE");
+		dl_cleanup_cpuhog(pids[started]);
+		pids[started] = 0;
+		goto cleanup;
+	}
+
+	started++;
+	printf("  SUCCESS: Hotplug accounting matched capacity-scaled expectations\n");
+	ret = DL_TEST_PASS;
+
+cleanup:
+	for (i = 0; i < started; i++) {
+		if (pids && pids[i] > 0)
+			dl_cleanup_cpuhog(pids[i]);
+	}
+
+	if (cpu_offlined) {
+		if (dl_cpu_online(hotplug_cpu) < 0)
+			printf("  WARN: Failed to restore CPU %d online: %s\n",
+			       hotplug_cpu, strerror(errno));
+		else
+			bw_wait_cpu_state(hotplug_cpu, 1, 5000);
+	}
+
+	free(pids);
+	return ret;
+}
+
+static struct dl_test test_bandwidth_hotplug_accounting = {
+	.name = "bandwidth_hotplug_accounting",
+	.description = "Verify capacity-scaled bandwidth accounting across CPU hotplug",
+	.run = test_bandwidth_hotplug_accounting_run,
+};
+REGISTER_DL_TEST(&test_bandwidth_hotplug_accounting);
-- 
2.34.1


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

* Re: [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest
  2026-03-12 11:30             ` Christian Loehle
@ 2026-03-12 14:13               ` Juri Lelli
  0 siblings, 0 replies; 21+ messages in thread
From: Juri Lelli @ 2026-03-12 14:13 UTC (permalink / raw)
  To: Christian Loehle
  Cc: Shuah Khan, Peter Zijlstra, Ingo Molnar, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Valentin Schneider,
	Clark Williams, Gabriele Monaco, Tommaso Cucinotta, Luca Abeni,
	linux-kernel, linux-kselftest

Hello,

On 12/03/26 11:30, Christian Loehle wrote:

...

> For completeness, although I'm sure you can come up with something equally good:

Thanks a lot for all this! Will definitely incorporate it in next
versions!

Best,
Juri


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

end of thread, other threads:[~2026-03-12 14:13 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-06 16:10 [PATCH RFC 0/7] selftests/sched: Add comprehensive SCHED_DEADLINE test suite Juri Lelli
2026-03-06 16:10 ` [PATCH RFC 1/7] selftests/sched: Add SCHED_DEADLINE test framework infrastructure Juri Lelli
2026-03-09  8:20   ` Gabriele Monaco
2026-03-09  9:10     ` Juri Lelli
2026-03-06 16:10 ` [PATCH RFC 2/7] selftests/sched: Add SCHED_DEADLINE utility library Juri Lelli
2026-03-11  9:39   ` Christian Loehle
2026-03-11 13:15     ` Juri Lelli
2026-03-06 16:10 ` [PATCH RFC 3/7] selftests/sched: Integrate SCHED_DEADLINE tests into kselftest framework Juri Lelli
2026-03-06 16:10 ` [PATCH RFC 4/7] selftests/sched: Add basic SCHED_DEADLINE functionality tests Juri Lelli
2026-03-09  8:15   ` Gabriele Monaco
2026-03-09  9:11     ` Juri Lelli
2026-03-06 16:10 ` [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest Juri Lelli
2026-03-11  9:31   ` Christian Loehle
2026-03-11 13:23     ` Juri Lelli
2026-03-11 13:44       ` Christian Loehle
2026-03-11 14:26         ` Christian Loehle
2026-03-12 10:43           ` Christian Loehle
2026-03-12 11:30             ` Christian Loehle
2026-03-12 14:13               ` Juri Lelli
2026-03-06 16:10 ` [PATCH RFC 6/7] selftests/sched: Add SCHED_DEADLINE fair_server " Juri Lelli
2026-03-06 16:10 ` [PATCH RFC 7/7] selftests/sched: Add SCHED_DEADLINE ENQUEUE_REPLENISH bug test Juri Lelli

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