From nobody Thu Apr 9 16:34:30 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D3EA336606C for ; Fri, 6 Mar 2026 16:13:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813612; cv=none; b=dZvVAIpXaeGdzl6v16QtQACbzBmB0mGtJ/ZLhw7V+uv4v95Ly81qfrYJpavUub3E13eioaABUNM2QKPVONsrmwm8Ms7dm+xIPwq5IXdDZ0v1AEjbHJD3WEVDKoS96JDIgHs5EAemLjuhT1FcJnGuvADKmuxPHni0JWOR9238Tkw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813612; c=relaxed/simple; bh=jJq0hYOd7KM1DJayyjkcFArrTtHSIxiUDjXD0vFf6hg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=L3aDQ/MzWSMnWqEdmh9jrpGbwkKpKJpId4IHmvti0vJbK9eu89S8lZajm+6VIMHC/oZhlP7IQNLHvUkceLhugroz+hh5pIt5Jxkg5UAQJoJ9FUBNwn44kyVV07JaFsJ60/zupNeIWjJcObc41oCmcuF3MClhM8Ij4bh+Uq69+oc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=DPWEGuHY; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="DPWEGuHY" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1772813609; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=pp3EhGkHs8clYca1XkHqcOG+uzD0A0gPqGEyEfk/OdY=; b=DPWEGuHY58IdftcC+tU9+1TtAWG8oBcb6zGJ7jgJKjangfQ4Lbb9SZcVxVFGeQXuZI/H3y J2LdrFCBfPQtN8SVfPQMc0onCLhlKG8FbU0zOGO+Y1hOxENhoqEbFbjRs5pUFgG8hhFn/y qKhJYaS77iy0mtdKNlU9v8n0QouuYgM= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-332-e0XU-W0eOUuW0XdYOlTQ4Q-1; Fri, 06 Mar 2026 11:13:26 -0500 X-MC-Unique: e0XU-W0eOUuW0XdYOlTQ4Q-1 X-Mimecast-MFC-AGG-ID: e0XU-W0eOUuW0XdYOlTQ4Q_1772813604 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E1E7D19560A7; Fri, 6 Mar 2026 16:13:23 +0000 (UTC) Received: from jlelli-thinkpadt14gen4.remote.csb (unknown [10.45.224.3]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 0EA7E3003E9F; Fri, 6 Mar 2026 16:13:18 +0000 (UTC) From: Juri Lelli Date: Fri, 06 Mar 2026 17:10:06 +0100 Subject: [PATCH RFC 1/7] selftests/sched: Add SCHED_DEADLINE test framework infrastructure Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260306-upstream-deadline-kselftests-v1-1-2b23ef74c46a@redhat.com> References: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> In-Reply-To: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> 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@vger.kernel.org, linux-kselftest@vger.kernel.org, Juri Lelli X-Developer-Signature: v=1; a=ed25519-sha256; t=1772813592; l=15557; i=juri.lelli@redhat.com; s=20250626; h=from:subject:message-id; bh=jJq0hYOd7KM1DJayyjkcFArrTtHSIxiUDjXD0vFf6hg=; b=TuoorXqdQfHAcRC2W81zD5RaDXkXUUkj8Utgj3E343lMw4pIHfeW9g69s7RF5GLQ/iuVH+T/v 64q/nqNGrAUCKBLMycRtMD9FbE88o/NjdYB8VDlr5piMdO35HuYpV3K X-Developer-Key: i=juri.lelli@redhat.com; a=ed25519; pk=kSwf88oiY/PYrNMRL/tjuBPiSGzc+U3bD13Zag6wO5Q= X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 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 --- 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/test= ing/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/testin= g/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 :=3D runner + +# override lib.mk's default rules +OVERRIDE_TARGETS :=3D 1 +include ../../lib.mk + +CFLAGS +=3D -Wall -O2 -g -pthread + +OUTPUT_DIR :=3D $(OUTPUT) + +# Utility object files +UTIL_OBJS :=3D + +# Test object files (all .c files except runner.c, dl_util.c, cpuhog.c) +# Will be populated as we add tests +TEST_OBJS :=3D + +# 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/testi= ng/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 functional= ity. + * 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 +#include +#include +#include +#include +#include + +/* Test status return codes */ +enum dl_test_status { + DL_TEST_PASS =3D 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 =3D { + * .name =3D "my_test", + * .description =3D "Tests something important", + * .run =3D 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) !=3D (_y), \ + "Expected %s =3D=3D %s (%lld =3D=3D %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) =3D=3D (_y), \ + "Expected %s !=3D %s (%lld !=3D %lld)", \ + #_x, #_y, (long long)(_x), (long long)(_y)) + +/** + * DL_LT() - Assert x < y + */ +#define DL_LT(_x, _y) \ + DL_FAIL_IF((_x) >=3D (_y), \ + "Expected %s < %s (%lld < %lld)", \ + #_x, #_y, (long long)(_x), (long long)(_y)) + +/** + * DL_LE() - Assert x <=3D y + */ +#define DL_LE(_x, _y) \ + DL_FAIL_IF((_x) > (_y), \ + "Expected %s <=3D %s (%lld <=3D %lld)", \ + #_x, #_y, (long long)(_x), (long long)(_y)) + +/** + * DL_GT() - Assert x > y + */ +#define DL_GT(_x, _y) \ + DL_FAIL_IF((_x) <=3D (_y), \ + "Expected %s > %s (%lld > %lld)", \ + #_x, #_y, (long long)(_x), (long long)(_y)) + +/** + * DL_GE() - Assert x >=3D y + */ +#define DL_GE(_x, _y) \ + DL_FAIL_IF((_x) < (_y), \ + "Expected %s >=3D %s (%lld >=3D %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/testin= g/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 constructor= s. + */ + +#include +#include +#include +#include +#include +#include +#include "dl_test.h" + +const char help_fmt[] =3D +"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 =3D 1; +} + +static void print_test_preamble(const struct dl_test *test, bool quiet) +{ + printf("=3D=3D=3D=3D=3D START =3D=3D=3D=3D=3D\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 ""; + } +} + +static void print_test_result(const struct dl_test *test, + enum dl_test_status status, + unsigned int testnum) +{ + const char *result =3D status_to_result(status); + const char *directive =3D status =3D=3D DL_TEST_SKIP ? "SKIP " : ""; + + printf("%s %u %s # %s\n", result, testnum, test->name, directive); + printf("=3D=3D=3D=3D=3D END =3D=3D=3D=3D=3D\n"); +} + +static bool should_skip_test(const struct dl_test *test, const char *filte= r) +{ + 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 =3D NULL; + + if (test->setup) { + status =3D test->setup(&context); + if (status !=3D DL_TEST_PASS) + return status; + } + + status =3D 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 =3D NULL; + unsigned int testnum =3D 0, i; + unsigned int passed =3D 0, skipped =3D 0, failed =3D 0; + int opt; + + signal(SIGINT, sigint_handler); + signal(SIGTERM, sigint_handler); + + while ((opt =3D getopt(argc, argv, "qslt:h")) !=3D -1) { + switch (opt) { + case 'q': + quiet =3D true; + break; + case 's': + print_skipped =3D true; + break; + case 'l': + list =3D true; + break; + case 't': + filter =3D optarg; + break; + default: + fprintf(stderr, help_fmt, argv[0]); + return opt !=3D 'h'; + } + } + + for (i =3D 0; i < __dl_num_tests; i++) { + enum dl_test_status status; + struct dl_test *test =3D __dl_tests[i]; + + if (list) { + printf("%s\n", test->name); + if (i =3D=3D (__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 =3D 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=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D\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 >=3D MAX_DL_TESTS, "Maximum tests exceeded"); + + __dl_tests[__dl_num_tests++] =3D test; +} --=20 2.53.0 From nobody Thu Apr 9 16:34:30 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7AB3632D0DE for ; Fri, 6 Mar 2026 16:13:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813616; cv=none; b=GzwQtVvbNWgo7vtMEpsKQnmLf3zhkYnn/JGqNdzqW2SDJ+K9t+LFBUvqiDZnxv00I1yiozo2HLRlj5iiIOBGndZAbXLZlkkBb3nnR83xs0z8siC5XXgNK7WOhh6fIdjzHC23RGUohkmgZO7i2jm/8lpceTdZxUP0MsQ9WjSXMAQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813616; c=relaxed/simple; bh=/bidsxXu9CvWMPjW/tUtwL0K6J6dII3HoXBK9F7Km5U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pwJI3rJ1aoXkfbXjx2orU8JSl3ajeq42FWbNhD3Q1ucH+cV+HW7C+GvU03IxLeb2MT9OLL5aNW/OXNp8CY/vl1RV45Q+OSB/hL5XqPuEROiWCdNxLNVmi1rBs7uXk/hagy8ECm05Wf//KyWxOJH6QvFTwEEeBsU+jjQfggSBBEU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=euzjiDwg; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="euzjiDwg" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1772813613; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=2Ylt4OCIDgmiM5S9qcgc4QXEM8ZZkVzorMJ3ZH7qWgQ=; b=euzjiDwgIkgdvhvRcGzI9h2tWDeuikaQ6TWnNenCakiW4eKIbQsbUfbWGBJ4K3DolTBmsB GG162bzkBbq9ptFONuY66sYizCY3vHHbr1JYlAppqUQX8b3Qr83IqZS2VthRyOdN+iM4zD 5pFWzCWEEY1Z0SCUxkFJXpAMUVtjayk= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-425-EfuozfBdN0amGDcXGDL7Jw-1; Fri, 06 Mar 2026 11:13:30 -0500 X-MC-Unique: EfuozfBdN0amGDcXGDL7Jw-1 X-Mimecast-MFC-AGG-ID: EfuozfBdN0amGDcXGDL7Jw_1772813609 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 94320180035D; Fri, 6 Mar 2026 16:13:28 +0000 (UTC) Received: from jlelli-thinkpadt14gen4.remote.csb (unknown [10.45.224.3]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 5F3DB3003EA4; Fri, 6 Mar 2026 16:13:24 +0000 (UTC) From: Juri Lelli Date: Fri, 06 Mar 2026 17:10:07 +0100 Subject: [PATCH RFC 2/7] selftests/sched: Add SCHED_DEADLINE utility library Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260306-upstream-deadline-kselftests-v1-2-2b23ef74c46a@redhat.com> References: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> In-Reply-To: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> 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@vger.kernel.org, linux-kselftest@vger.kernel.org, Juri Lelli X-Developer-Signature: v=1; a=ed25519-sha256; t=1772813592; l=20039; i=juri.lelli@redhat.com; s=20250626; h=from:subject:message-id; bh=/bidsxXu9CvWMPjW/tUtwL0K6J6dII3HoXBK9F7Km5U=; b=rS2otwo+UDqDqk2Nf71HjWKywF81dS0wcrYaNuR8O1fv6kTMWjBi2Utz+CoDEO74JTyQW6Htv bBsOiMjFpwgD+JPJSq9p1B9l/a3doFRByY28KnELM5DaGd8GThi7Sww X-Developer-Key: i=juri.lelli@redhat.com; a=ed25519; pk=kSwf88oiY/PYrNMRL/tjuBPiSGzc+U3bD13Zag6wO5Q= X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 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 --- 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/testin= g/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 =20 -TEST_GEN_PROGS :=3D runner +TEST_GEN_PROGS :=3D runner cpuhog =20 # override lib.mk's default rules OVERRIDE_TARGETS :=3D 1 @@ -11,7 +11,7 @@ CFLAGS +=3D -Wall -O2 -g -pthread OUTPUT_DIR :=3D $(OUTPUT) =20 # Utility object files -UTIL_OBJS :=3D +UTIL_OBJS :=3D $(OUTPUT)/dl_util.o =20 # 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 :=3D $(OUTPUT)/runner: runner.c $(UTIL_OBJS) $(TEST_OBJS) dl_test.h | $(OUTPUT_= DIR) $(CC) $(CFLAGS) -o $@ runner.c $(UTIL_OBJS) $(TEST_OBJS) $(LDFLAGS) =20 +# 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 $@ =20 @@ -29,6 +37,6 @@ $(OUTPUT_DIR): all: $(TEST_GEN_PROGS) =20 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/testin= g/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 +#include +#include +#include +#include +#include +#include +#include + +static int stop_flag; + +static void signal_handler(int sig) +{ + stop_flag =3D 1; +} + +static void usage(const char *progname) +{ + printf("Usage: %s [options]\n", progname); + printf("Options:\n"); + printf(" -t 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 =3D 0; /* 0 means infinite */ + int verbose =3D 0; + time_t start_time, current_time; + unsigned long long iterations =3D 0; + unsigned long long last_report =3D 0; + + while ((opt =3D getopt(argc, argv, "t:vh")) !=3D -1) { + switch (opt) { + case 't': + duration =3D atoi(optarg); + if (duration <=3D 0) { + fprintf(stderr, "Invalid duration: %s\n", optarg); + return 1; + } + break; + case 'v': + verbose =3D 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 =3D 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 =3D time(NULL); + if (current_time - start_time >=3D duration) + break; + } + + /* Print progress every 100M iterations if verbose */ + if (verbose && (iterations % 100000000ULL =3D=3D 0)) { + if (iterations !=3D last_report) { + printf("Completed %llu iterations\n", iterations); + last_report =3D iterations; + } + } + } + + if (verbose) { + current_time =3D 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/testi= ng/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 =3D { + .size =3D sizeof(attr), + .sched_policy =3D SCHED_DEADLINE, + .sched_flags =3D 0, + .sched_runtime =3D runtime, + .sched_deadline =3D deadline, + .sched_period =3D 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 =3D 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 =3D -1; + + snprintf(path, sizeof(path), "/proc/%d/sched", pid); + f =3D fopen(path, "r"); + if (!f) + return -1; + + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, " policy : %d", &policy) =3D=3D 1) + break; + } + + fclose(f); + return policy; +} + +bool dl_is_deadline_task(pid_t pid) +{ + return dl_get_policy(pid) =3D=3D SCHED_DEADLINE; +} + +/* + * Bandwidth management + */ + +static int read_proc_uint64(const char *path, uint64_t *value) +{ + FILE *f; + int ret; + + f =3D fopen(path, "r"); + if (!f) + return -1; + + ret =3D fscanf(f, "%lu", value); + fclose(f); + + return ret =3D=3D 1 ? 0 : -1; +} + +int dl_get_rt_bandwidth(uint64_t *runtime_us, uint64_t *period_us) +{ + int ret; + + ret =3D 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 =3D=3D 0) + return -1; + + percent =3D (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 perio= d, + int duration_secs) +{ + pid_t pid; + char duration_str[32]; + + pid =3D fork(); + if (pid < 0) + return -1; + + if (pid =3D=3D 0) { + /* Child process */ + char *args[4]; + + args[0] =3D "./cpuhog"; + if (duration_secs > 0) { + args[1] =3D "-t"; + snprintf(duration_str, sizeof(duration_str), "%d", + duration_secs); + args[2] =3D duration_str; + args[3] =3D NULL; + } else { + args[1] =3D NULL; + } + + /* Just exec - parent will set SCHED_DEADLINE */ + execvp(args[0], args); + /* If exec fails, try without ./ */ + args[0] =3D "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 <=3D 0) + return; + + /* Try SIGTERM first */ + kill(pid, SIGTERM); + + /* Wait up to 1 second for graceful exit */ + for (i =3D 0; i < 10; i++) { + if (waitpid(pid, NULL, WNOHANG) =3D=3D 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 =3D 0; + + f =3D popen("pgrep -x cpuhog", "r"); + if (!f) + return -1; + + while (fgets(line, sizeof(line), f) && count < max_pids) { + pid_t pid =3D atoi(line); + + if (pid > 0) + pids[count++] =3D pid; + } + + pclose(f); + return count; +} + +int dl_wait_for_pid(pid_t pid, int timeout_ms) +{ + char path[256]; + int elapsed =3D 0; + int interval =3D 10; /* 10ms polling interval */ + + snprintf(path, sizeof(path), "/proc/%d", pid); + + while (elapsed < timeout_ms) { + if (access(path, F_OK) =3D=3D 0) + return 0; + + usleep(interval * 1000); + elapsed +=3D 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 =3D 0; + int max_cpu =3D (int)sysconf(_SC_NPROCESSORS_CONF); + char path[256]; + + for (cpu =3D 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) =3D=3D 0) + cpus[count++] =3D 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 =3D fopen(path, "w"); + if (!f) + return -1; + + ret =3D 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 =3D -1; + + snprintf(path, sizeof(path), + "/sys/devices/system/cpu/cpu%d/online", cpu); + + f =3D fopen(path, "r"); + if (!f) { + /* CPU0 often doesn't have an online file (always online) */ + if (cpu =3D=3D 0) + return 1; + return -1; + } + + if (fscanf(f, "%d", &online) !=3D 1) + online =3D -1; + + fclose(f); + return online; +} diff --git a/tools/testing/selftests/sched/deadline/dl_util.h b/tools/testi= ng/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 +#include +#include +#include + +/* 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//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 percent= age + * + * 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 perio= d, + 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/ 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/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/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__ */ --=20 2.53.0 From nobody Thu Apr 9 16:34:30 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3469137B41F for ; Fri, 6 Mar 2026 16:13:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813619; cv=none; b=t27iPeD7O4ix98xXx+l+iQ2+5Qfiis45qSjaEiritigGEBlmOFRvnGQ6KJsyNnz1IRXZdf5i3A8kypk26E49exuCjXAm+Gd3vJNvBWm0EyqyFG2pD/veASY/GD9MzndzP0VJ3eQd/4fT3HaiWgPfTdML1huqyYrJXfH1VA+ywoQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813619; c=relaxed/simple; bh=MXOtCZMxsfDHijoxtxkoRjFvX0AqG7us86qHveio4sU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=cwne3Ig4ZoS2/OTD8WJtMwXEmj3XLYaHKASjbPyf6VIpG0gYvpDzL9AJ8z66ePvYEdKC87Ip7lr/L34QKjPxw/lLYD905pK9Ib+c+vx4TqQA5LJno4DHpHnLKOFzzeElMryqEsG6EubcmPow0OGXpxIVK6zkVGpJSlDb4BxkM0A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=SXu/DHJ/; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="SXu/DHJ/" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1772813616; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=jYUhUcPVkT/WRHMuV6chDisV9g6arbR69yzhwqgrfkE=; b=SXu/DHJ/1sb0O/nqkDLlWngxI7IrMBaO3+2rLSUnKSKwSgLHlHMF0FCCP0k4rp6+Exhhs3 3nG3WsmSMXVfMQ0o/NSm1g9LQp7lGmapbFMXPXQDI4ZSDkEMzUSobvc3u61dRHJwuVWWF9 e07FPcdskBBQHT9tFws8g/mOQB0g5hU= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-663-8LnrB05qN7ih9snMrK1IoA-1; Fri, 06 Mar 2026 11:13:34 -0500 X-MC-Unique: 8LnrB05qN7ih9snMrK1IoA-1 X-Mimecast-MFC-AGG-ID: 8LnrB05qN7ih9snMrK1IoA_1772813613 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id D18C71956080; Fri, 6 Mar 2026 16:13:32 +0000 (UTC) Received: from jlelli-thinkpadt14gen4.remote.csb (unknown [10.45.224.3]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1AAED3003E9F; Fri, 6 Mar 2026 16:13:28 +0000 (UTC) From: Juri Lelli Date: Fri, 06 Mar 2026 17:10:08 +0100 Subject: [PATCH RFC 3/7] selftests/sched: Integrate SCHED_DEADLINE tests into kselftest framework Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260306-upstream-deadline-kselftests-v1-3-2b23ef74c46a@redhat.com> References: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> In-Reply-To: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> 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@vger.kernel.org, linux-kselftest@vger.kernel.org, Juri Lelli X-Developer-Signature: v=1; a=ed25519-sha256; t=1772813592; l=1708; i=juri.lelli@redhat.com; s=20250626; h=from:subject:message-id; bh=MXOtCZMxsfDHijoxtxkoRjFvX0AqG7us86qHveio4sU=; b=TifsLXEcWcIHmDxSHhpItoP9zVsWID3JNnDfIMM1CYQG55byQVh0vo//tvnlRupLWM1NR5Zkz 7mBexaxN5APCWFo/AFVtO0tyZrvk8sNRNT4eaPQqn1JZOcFViw1HREH X-Developer-Key: i=juri.lelli@redhat.com; a=ed25519; pk=kSwf88oiY/PYrNMRL/tjuBPiSGzc+U3bD13Zag6wO5Q= X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 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=3Dsched 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 --- tools/testing/selftests/sched/Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/testing/selftests/sched/Makefile b/tools/testing/selftes= ts/sched/Makefile index 099ee9213557a..8ee7620cc214c 100644 --- a/tools/testing/selftests/sched/Makefile +++ b/tools/testing/selftests/sched/Makefile @@ -11,4 +11,12 @@ LDLIBS +=3D -lpthread TEST_GEN_FILES :=3D cs_prctl_test TEST_PROGS :=3D cs_prctl_test =20 +# SCHED_DEADLINE tests in subdirectory +TEST_PROGS +=3D deadline/runner +TEST_GEN_PROGS_EXTENDED :=3D deadline/cpuhog + include ../lib.mk + +# Build deadline tests +$(OUTPUT)/deadline/runner $(OUTPUT)/deadline/cpuhog: + $(MAKE) -C deadline OUTPUT=3D$(OUTPUT)/deadline/ --=20 2.53.0 From nobody Thu Apr 9 16:34:30 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0DDF13ECBE3 for ; Fri, 6 Mar 2026 16:13:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813623; cv=none; b=rqUb1+hfO3w4CPDsZhuJT5h9yMZyy6lrZcUbI/cl3hEQMpwKkh3n+cB4qkRJzAjbTwIQjYB1GCtwEq26ISrtE/yJLRvKGr5CjLJPzTQoUSM6pOk7rxmPxXuFXwuk9mALnDGf/Zt4e6wP9ClanWdHo+SNpnaZLEmA/32Iq10FAiE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813623; c=relaxed/simple; bh=NxSdl5yGIkYpf0qnKogIYBNzpYELmbJSNhIZrqL4g1c=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=VNCOiJMYZKwfrriqmEM235VEMhD3v042KCZm3jZhtZCqntj4Aqzv+S+3DWr2gR2lc4FpT45h0163aYCKDf1lMXfq8xj6cIwdJhcBYc3FQWu6mvL938x84uj4lCZ0AJUaOMAwQVwY1VtFaHAZEqcntWCUDr7HBIvg4ATo426CulM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=eM4ZuQjj; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="eM4ZuQjj" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1772813621; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=l7hWemiWFfPKWWobIMiiiRloN5fsIthWM22POlNE8Ec=; b=eM4ZuQjjkA6O1Jxq6RTimwvyD7lgjV/OdNe4zt8rrEL8DDAFCN/5UdQg5/Cox1QkZATjLj qxIcGKFjLB1QRHhZKVzhLmI9JUIRjuVGTCHXygDccc95Wr4Ki0yyOIvWo2CXIuq5bU/p87 qVybD8VmvLwQIKkiQlZlcLz1PkvZjoE= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-37-bekmns86N1-GgHKOeWKv7g-1; Fri, 06 Mar 2026 11:13:39 -0500 X-MC-Unique: bekmns86N1-GgHKOeWKv7g-1 X-Mimecast-MFC-AGG-ID: bekmns86N1-GgHKOeWKv7g_1772813618 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id C22CF1956088; Fri, 6 Mar 2026 16:13:37 +0000 (UTC) Received: from jlelli-thinkpadt14gen4.remote.csb (unknown [10.45.224.3]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 4FD063003EAA; Fri, 6 Mar 2026 16:13:33 +0000 (UTC) From: Juri Lelli Date: Fri, 06 Mar 2026 17:10:09 +0100 Subject: [PATCH RFC 4/7] selftests/sched: Add basic SCHED_DEADLINE functionality tests Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260306-upstream-deadline-kselftests-v1-4-2b23ef74c46a@redhat.com> References: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> In-Reply-To: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> 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@vger.kernel.org, linux-kselftest@vger.kernel.org, Juri Lelli X-Developer-Signature: v=1; a=ed25519-sha256; t=1772813592; l=6864; i=juri.lelli@redhat.com; s=20250626; h=from:subject:message-id; bh=NxSdl5yGIkYpf0qnKogIYBNzpYELmbJSNhIZrqL4g1c=; b=O3lG0UKvF6dRYEVcR+3ACS063EsyNoD82ch/w+fP6i7nEyws9Qofqfuv/ZMg0h/ML3Gnrkgk5 vLpJ9zb0YcVA74fQnJ9rJRDzyaJxfR4Eid0K/5F1P7Kbp8Vfm3ljS1x X-Developer-Key: i=juri.lelli@redhat.com; a=ed25519; pk=kSwf88oiY/PYrNMRL/tjuBPiSGzc+U3bD13Zag6wO5Q= X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 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 --- 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/testin= g/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 :=3D $(OUTPUT) UTIL_OBJS :=3D $(OUTPUT)/dl_util.o =20 # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c) -# Will be populated as we add tests -TEST_OBJS :=3D +TEST_OBJS :=3D $(OUTPUT)/basic.o =20 # 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 $@ =20 +# Test files +$(OUTPUT)/basic.o: basic.c dl_test.h dl_util.h | $(OUTPUT_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + $(OUTPUT_DIR): mkdir -p $@ =20 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 +#include +#include +#include +#include +#include +#include +#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 =3D dl_ms_to_ns(30); + deadline_ns =3D dl_ms_to_ns(100); + period_ns =3D dl_ms_to_ns(100); + + /* Create a cpuhog process with SCHED_DEADLINE */ + pid =3D 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 =3D 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 =3D { + .name =3D "basic_scheduling", + .description =3D "Verify basic SCHED_DEADLINE policy setup and execution", + .run =3D 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 =3D dl_ms_to_ns(200); + deadline_ns =3D dl_ms_to_ns(100); + period_ns =3D dl_ms_to_ns(100); + + ret =3D dl_set_sched_attr(0, runtime_ns, deadline_ns, period_ns); + DL_FAIL_IF(ret =3D=3D 0, "Invalid parameters were accepted (runtime > dea= dline)"); + DL_FAIL_IF(errno !=3D EINVAL, "Expected EINVAL, got %s", strerror(errno)); + + printf(" Invalid parameters correctly rejected with EINVAL\n"); + + printf(" Testing valid parameters...\n"); + + /* Valid: runtime (30ms) <=3D deadline (100ms) <=3D period (100ms) */ + runtime_ns =3D dl_ms_to_ns(30); + deadline_ns =3D dl_ms_to_ns(100); + period_ns =3D dl_ms_to_ns(100); + + ret =3D 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 =3D { .sched_priority =3D 0 }; + + sched_setscheduler(0, SCHED_OTHER, ¶m); + + return DL_TEST_PASS; +} + +static struct dl_test test_parameter_validation =3D { + .name =3D "parameter_validation", + .description =3D "Verify SCHED_DEADLINE parameter validation (accept/reje= ct)", + .run =3D test_parameter_validation_run, +}; +REGISTER_DL_TEST(&test_parameter_validation); --=20 2.53.0 From nobody Thu Apr 9 16:34:30 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 637783ED5A5 for ; Fri, 6 Mar 2026 16:13:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813630; cv=none; b=UhJr8cQuajHEhvh2ZCRHiqhVesGZFuQcR6K5g3nUgDWLR8N6YrXyClQgKCSVir27S9g2wiTjFsmg2j/va3WmMcZ1oh289//9Rp7JeXZXq8dfHhqtXbiiAG3Mrgu4l8Pdf7BkNPmoci71SLlLjI/ABcIR4mhqFWfjoAz2meUaQYU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813630; c=relaxed/simple; bh=c+nosA64my82VuvtHhRSENadcM8bygk8YaP37ZrIbtc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Y36tl3GSXNzadLyJA7ryC5Eans2oIWyrOUhe8ZO6+28M9UMWoqmYChdGVjnH+Q8Kn4ibkpS/OeLj3q9ZDBcqHLxpno2hRPw2C8VGJ35iiZdJY2VFyo7HGm+o2y4Rn5m+ksI1owbJVoPKIpMjMra6VA1oS1OUrmUlICGRXPOG3EI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=DU4Yur9p; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="DU4Yur9p" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1772813627; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=I5tCSkJzqS2CBeUq3YRalzS1+ZaMONJC407Te+Tws5g=; b=DU4Yur9p6OTmd1sJXPDKjCtQ8dO2frUA1Py9PfYrVDOmYy9dGLQEDHoL7+16QlLBQfixo2 vArhSNmXxpXFNEW1adV9PFJrQsfLddnNKCtmC7nwLHTdISHcjRnOTCZCB/7egB/9RZKmOe Ji2T7aD9VvWTzt2BY9HN8O4FNvax8M8= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-312-NUXAxrOnOye7BSS1giRzzQ-1; Fri, 06 Mar 2026 11:13:44 -0500 X-MC-Unique: NUXAxrOnOye7BSS1giRzzQ-1 X-Mimecast-MFC-AGG-ID: NUXAxrOnOye7BSS1giRzzQ_1772813622 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 876201800617; Fri, 6 Mar 2026 16:13:42 +0000 (UTC) Received: from jlelli-thinkpadt14gen4.remote.csb (unknown [10.45.224.3]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 40B283003E9F; Fri, 6 Mar 2026 16:13:38 +0000 (UTC) From: Juri Lelli Date: Fri, 06 Mar 2026 17:10:10 +0100 Subject: [PATCH RFC 5/7] selftests/sched: Add SCHED_DEADLINE bandwidth tests to kselftest Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260306-upstream-deadline-kselftests-v1-5-2b23ef74c46a@redhat.com> References: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> In-Reply-To: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> 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@vger.kernel.org, linux-kselftest@vger.kernel.org, Juri Lelli X-Developer-Signature: v=1; a=ed25519-sha256; t=1772813592; l=15558; i=juri.lelli@redhat.com; s=20250626; h=from:subject:message-id; bh=c+nosA64my82VuvtHhRSENadcM8bygk8YaP37ZrIbtc=; b=+tqgSbZ+dEVXlXND/J21MMTlysjK2IspaIp6VswIizTc1+6nFoq6HDCfStNAvv3q944kYrepW 9doWjG4LLLOBb6u5alKooRkhpa+BS+YKFkiUhhLvsVWZu9y8NsE5sFs X-Developer-Key: i=juri.lelli@redhat.com; a=ed25519; pk=kSwf88oiY/PYrNMRL/tjuBPiSGzc+U3bD13Zag6wO5Q= X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 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 --- 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/testin= g/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 :=3D $(OUTPUT) UTIL_OBJS :=3D $(OUTPUT)/dl_util.o =20 # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c) -TEST_OBJS :=3D $(OUTPUT)/basic.o +TEST_OBJS :=3D $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o =20 # 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 $@ =20 +$(OUTPUT)/bandwidth.o: bandwidth.c dl_test.h dl_util.h | $(OUTPUT_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + $(OUTPUT_DIR): mkdir -p $@ =20 diff --git a/tools/testing/selftests/sched/deadline/bandwidth.c b/tools/tes= ting/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 +#include +#include +#include +#include +#include +#include +#include +#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 =3D NULL; + int started =3D 0, running =3D 0; + enum dl_test_status ret =3D 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=3D%lu=C2=B5s, period=3D%lu=C2=B5s (%.0f%%= )\n", + rt_runtime_us, rt_period_us, + (double)rt_runtime_us * 100.0 / rt_period_us); + + /* Show server overhead */ + int server_overhead =3D 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 =3D 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 =3D dl_ms_to_ns(100); /* 100ms */ + runtime_ns =3D (period_ns * max_bw_percent) / 100; + deadline_ns =3D period_ns; + + printf(" Task params: runtime=3D%lums, deadline=3D%lums, period=3D%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 =3D dl_get_online_cpus(); + DL_FAIL_IF(num_cpus <=3D 0, "Failed to get number of CPUs"); + + printf(" Number of online CPUs: %d\n", num_cpus); + + /* Allocate PID array */ + pids =3D 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 =3D 0; i < num_cpus; i++) { + pids[i] =3D 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 =3D 0; i < started; i++) { + if (pids[i] <=3D 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 =3D=3D num_cpus && running =3D=3D num_cpus) { + printf(" SUCCESS: All %d tasks running at max bandwidth\n", + num_cpus); + ret =3D DL_TEST_PASS; + } else if (started !=3D num_cpus) { + DL_ERR("Only started %d/%d tasks", started, num_cpus); + ret =3D DL_TEST_FAIL; + } else { + DL_ERR("Started %d tasks but only %d using SCHED_DEADLINE", + started, running); + ret =3D DL_TEST_FAIL; + } + +cleanup: + /* Cleanup all started tasks */ + for (i =3D 0; i < started; i++) { + if (pids[i] > 0) + dl_cleanup_cpuhog(pids[i]); + } + + free(pids); + return ret; +} + +static struct dl_test test_bandwidth_admission =3D { + .name =3D "bandwidth_admission", + .description =3D "Verify per-CPU bandwidth replication (N tasks at max ba= ndwidth)", + .run =3D 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 =3D NULL; + pid_t overflow_pid; + int started =3D 0; + enum dl_test_status ret =3D 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=3D%lu=C2=B5s, period=3D%lu=C2=B5s (%.0f%%= )\n", + rt_runtime_us, rt_period_us, + (double)rt_runtime_us * 100.0 / rt_period_us); + + /* Show server overhead */ + int server_overhead =3D 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 =3D 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 =3D dl_get_online_cpus(); + DL_FAIL_IF(num_cpus <=3D 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 =3D dl_ms_to_ns(100); /* 100ms */ + runtime_ns =3D (period_ns * max_bw_percent) / 100; + deadline_ns =3D period_ns; + + printf(" Task params: runtime=3D%lums, deadline=3D%lums, period=3D%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 =3D num_cpus - 1; + pids =3D 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 =3D 0; i < target_tasks; i++) { + pids[i] =3D 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 =3D (runtime_ns * 101) / 100; /* Add 1% */ + + printf(" Attempting overflow task with runtime=3D%lums (+1%%)...\n", + dl_ns_to_ms(overflow_runtime_ns)); + + overflow_pid =3D 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 =3D DL_TEST_PASS; + } else { + /* Unexpected: it was admitted */ + usleep(100000); /* 100ms */ + + if (kill(overflow_pid, 0) =3D=3D 0) { + printf(" ERROR: Overflow task admitted and running\n"); + dl_cleanup_cpuhog(overflow_pid); + ret =3D DL_TEST_FAIL; + } else { + /* It was admitted but died - still wrong */ + printf(" ERROR: Overflow task admitted but died\n"); + ret =3D DL_TEST_FAIL; + } + } + +cleanup: + /* Cleanup all tasks */ + for (i =3D 0; i < started; i++) { + if (pids[i] > 0) + dl_cleanup_cpuhog(pids[i]); + } + + free(pids); + return ret; +} + +static struct dl_test test_bandwidth_overflow =3D { + .name =3D "bandwidth_overflow", + .description =3D "Verify bandwidth overflow rejection (N-1 + overflow fai= ls)", + .run =3D test_bandwidth_overflow_run, +}; +REGISTER_DL_TEST(&test_bandwidth_overflow); diff --git a/tools/testing/selftests/sched/deadline/dl_util.c b/tools/testi= ng/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 #include #include +#include +#include #include "dl_util.h" =20 /* 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); } =20 +int dl_get_server_bandwidth_overhead(void) +{ + glob_t globbuf; + char pattern[512]; + size_t i; + int total_overhead =3D 0; + + /* Find all *_server directories */ + snprintf(pattern, sizeof(pattern), + "/sys/kernel/debug/sched/*_server"); + + if (glob(pattern, 0, NULL, &globbuf) !=3D 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 =3D 0; i < globbuf.gl_pathc; i++) { + char runtime_path[512]; + char period_path[512]; + char *server_path =3D globbuf.gl_pathv[i]; + uint64_t runtime_ns =3D 0, period_ns =3D 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 =3D=3D 0) + continue; + + /* Calculate percentage for this server */ + percent =3D (runtime_ns * 100) / period_ns; + + /* Accumulate overhead from all servers */ + total_overhead +=3D 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; =20 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 =3D=3D 0) return -1; =20 - percent =3D (runtime_us * 100) / period_us; - return percent > 0 ? percent : 1; + /* Calculate RT bandwidth percentage */ + rt_percent =3D (runtime_us * 100) / period_us; + + /* Get server overhead */ + server_overhead =3D dl_get_server_bandwidth_overhead(); + if (server_overhead < 0) + server_overhead =3D 0; + + /* Available bandwidth =3D RT bandwidth - server overhead */ + available_percent =3D rt_percent - server_overhead; + + return available_percent > 0 ? available_percent : 1; } =20 /* diff --git a/tools/testing/selftests/sched/deadline/dl_util.h b/tools/testi= ng/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); =20 +/** + * 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 percent= age * * 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, e= tc.). * * Return: Bandwidth percentage (0-100), or -1 on error */ --=20 2.53.0 From nobody Thu Apr 9 16:34:30 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 285903ED13B for ; Fri, 6 Mar 2026 16:13:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813635; cv=none; b=We7SFlVXtgRrOpaMdn1ena9wKZ/MI+rPrS1Ck89ecrAR4FmEF4gfnFM+TUrsaDrcDs2DBU0Msj+L7MvgDLu557DpNoHIN+0P6RtANzRep85FSLIUmZegCzm2uTPESRkEHu8EtOs7xgAwtrNwaoiWewYreCZB9eEoaIfqTpHfPXo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813635; c=relaxed/simple; bh=uzQ/71TbI/xC0SluDKVSineqo7fx9SyqzH08CIY9xrU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=JnpIaTdbtC3lJGSnPBI6o1MFz896mxrA8IdJ3Ji6NpW8CUJ4YfOIzsEuL5763Ov1Qaam+KL8AcjnEcKQzzP3ByghKie63VktiDT3OaT+5vkSIvZOrcreHxRIIAdClVxtxeOgM++UBEKCsWEcGhCkjfXI7nR2DPO1dsm6MvVBpLk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=cs9Y+kYB; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="cs9Y+kYB" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1772813632; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=wUiQN3/xeDKX4GTd8FkJSzSxG+77HaSzzy/t9hhyH7I=; b=cs9Y+kYB8khwsMJcB7MJnPEdGQFaUwq5eBgFMqgoiKHSSuHivJ5yf+pWO70TD3eordLGv4 5gMxMv5/pLQSdfHWBKT09uWrx36pmreldYF/1h+v1L+tfYph3l+wKRjnI0X48/KCqP+K5j /9uy7GxamyRCRUVvOb6ABRbyAzbB2rU= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-292-7DX5BfwfOcyon7Tzyud-dQ-1; Fri, 06 Mar 2026 11:13:48 -0500 X-MC-Unique: 7DX5BfwfOcyon7Tzyud-dQ-1 X-Mimecast-MFC-AGG-ID: 7DX5BfwfOcyon7Tzyud-dQ_1772813627 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id F20451956063; Fri, 6 Mar 2026 16:13:46 +0000 (UTC) Received: from jlelli-thinkpadt14gen4.remote.csb (unknown [10.45.224.3]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 0655F3003EA4; Fri, 6 Mar 2026 16:13:42 +0000 (UTC) From: Juri Lelli Date: Fri, 06 Mar 2026 17:10:11 +0100 Subject: [PATCH RFC 6/7] selftests/sched: Add SCHED_DEADLINE fair_server tests to kselftest Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260306-upstream-deadline-kselftests-v1-6-2b23ef74c46a@redhat.com> References: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> In-Reply-To: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> 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@vger.kernel.org, linux-kselftest@vger.kernel.org, Juri Lelli X-Developer-Signature: v=1; a=ed25519-sha256; t=1772813592; l=17061; i=juri.lelli@redhat.com; s=20250626; h=from:subject:message-id; bh=uzQ/71TbI/xC0SluDKVSineqo7fx9SyqzH08CIY9xrU=; b=C+balqmaus+gVnblMXYi/9lfNPiwZKogMybxCP+h8DXR3qidbAGhECBAN0QSsZuBXVSh90ZlI VGM9tiKTB7WDO9daMSaIS4GnH/Xl1dX7HARdVOXK4uILM4PQVelRSyB X-Developer-Key: i=juri.lelli@redhat.com; a=ed25519; pk=kSwf88oiY/PYrNMRL/tjuBPiSGzc+U3bD13Zag6wO5Q= X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 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 =C2=B150%, ensuri= ng 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 --- 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/testin= g/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 :=3D $(OUTPUT) UTIL_OBJS :=3D $(OUTPUT)/dl_util.o =20 # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c) -TEST_OBJS :=3D $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o +TEST_OBJS :=3D $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o $(OUTPUT)/fair_serv= er.o =20 # 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 $@ =20 +$(OUTPUT)/fair_server.o: fair_server.c dl_test.h dl_util.h | $(OUTPUT_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + $(OUTPUT_DIR): mkdir -p $@ =20 diff --git a/tools/testing/selftests/sched/deadline/dl_util.c b/tools/testi= ng/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; } =20 +static int write_proc_uint64(const char *path, uint64_t value) +{ + FILE *f; + int ret; + + f =3D fopen(path, "w"); + if (!f) + return -1; + + ret =3D 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) !=3D 0) + return -1; + + return 0; +} + +int dl_set_rt_bandwidth(uint64_t runtime_us, uint64_t period_us) +{ + int ret; + + ret =3D 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) =3D=3D 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 =3D 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; } =20 +uint64_t dl_get_process_cpu_time(pid_t pid) +{ + char path[256]; + char line[1024]; + FILE *f; + uint64_t utime =3D 0, stime =3D 0; + int i; + char *p, *token, *saveptr; + + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + f =3D 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=3Dstate 2=3Dppid 3=3Dpgrp 4=3Dsid 5=3Dtty_nr 6=3Dtty_pgrp 7=3Dflags + * 8=3Dmin_flt 9=3Dcmin_flt 10=3Dmaj_flt 11=3Dcmaj_flt 12=3Dutime 13=3Dst= ime + */ + p =3D strrchr(line, ')'); + if (!p) + return 0; + + /* Skip past ') ' */ + p +=3D 2; + + /* Tokenize remaining fields */ + token =3D strtok_r(p, " ", &saveptr); + for (i =3D 1; token && i <=3D 13; i++) { + if (i =3D=3D 12) + utime =3D strtoull(token, NULL, 10); + else if (i =3D=3D 13) + stime =3D strtoull(token, NULL, 10); + + token =3D strtok_r(NULL, " ", &saveptr); + } + + return utime + stime; +} + /* * CPU topology operations */ diff --git a/tools/testing/selftests/sched/deadline/dl_util.h b/tools/testi= ng/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); =20 +/** + * 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 exi= st) + */ +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); =20 +/** + * dl_get_process_cpu_time() - Get total CPU time for a process + * @pid: Process ID + * + * Reads utime and stime from /proc//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/t= esting/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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 =3D NULL; + int num_cpus, i; + int write_succeeded =3D 0; + int write_failed =3D 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=C2=B5s / %lu=C2=B5s per CPU\n", + rt_runtime_us, rt_period_us); + + num_cpus =3D dl_get_online_cpus(); + DL_FAIL_IF(num_cpus <=3D 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 =3D calloc(num_cpus, sizeof(uint64_t)); + DL_FAIL_IF(!original_runtimes, "Failed to allocate memory"); + + for (i =3D 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] =3D 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 =3D (rt_runtime_us * 101 / 100) * 1000; + + /* Scale to fair_server period if different from RT period */ + if (fair_period_ns !=3D rt_period_us * 1000) + excessive_runtime_ns =3D 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 =3D 0; i < num_cpus; i++) { + if (dl_set_fair_server_runtime(i, excessive_runtime_ns) =3D=3D 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 =3D 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 =3D { + .name =3D "fair_server_bandwidth_validation", + .description =3D "Verify fair_server bandwidth validation against RT band= width", + .run =3D 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 =3D 12; + pid_t cfs_pid, fifo_pid; + int test_cpu =3D 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 =3D (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 =3D sysconf(_SC_CLK_TCK); + + /* Fork CFS cpuhog */ + cfs_pid =3D fork(); + if (cfs_pid < 0) { + DL_ERR("Failed to fork CFS task"); + return DL_TEST_FAIL; + } + + if (cfs_pid =3D=3D 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 =3D dl_get_process_cpu_time(cfs_pid); + + /* Fork FIFO cpuhog */ + fifo_pid =3D 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 =3D=3D 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 =3D 50; + sched_setscheduler(0, SCHED_FIFO, ¶m); + + 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 =3D 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 =3D final_time - initial_time; + cpu_percent =3D (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 =C2=B150% tolerance (e.g., 5% =C2=B1 50% =3D 2.5% - 7.5%) */ + min_expected =3D expected_percent * 50 / 100; + max_expected =3D expected_percent * 150 / 100; + + if (min_expected < 1) + min_expected =3D 1; + + printf(" Expected range: %d%% - %d%%\n", min_expected, max_expected); + + if (cpu_percent >=3D min_expected && cpu_percent <=3D 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 =3D { + .name =3D "fair_server_cpu_protection", + .description =3D "Verify fair_server provides CPU protection under FIFO c= ompetition", + .run =3D test_fair_server_cpu_protection_run, +}; +REGISTER_DL_TEST(&test_fair_server_cpu_protection); --=20 2.53.0 From nobody Thu Apr 9 16:34:30 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4C7843ED126 for ; Fri, 6 Mar 2026 16:13:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813641; cv=none; b=FTvWfDWrtAOMls6/JlvJ+E43RW48CQNXjc1d33Ed1I4r1TK75GskrKrbQu6Jft7uNfgCapOW05Y2AhNglL3zOwOALZz+UCQtCgtiLTdR5NPIAZLjAnW3o/UJTkYqIaKShNfvNSefYP2UnAB8Yj7y1zkLhes3JksbfqC4mT9G7ig= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772813641; c=relaxed/simple; bh=kMp7gN8TjkP2k3+jNTSWaAT7mbX3pXIHeLkFSCzkd+Y=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AQTFtRebRYmUzrXrpjlUKHVCIhnljvspH3OYAfcrYQHw4j6zkhzVKeL5aMiOEHgH/XorWAA+OQ1taTVlC/4+8i99+VxPUKa1i6Ms9loS7bmfy2El88ThjQj/enVNMhPTv0+Z13wDuLbCyNTfGg/oFnjL07adf32zNNKDG6QDYUk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Ufap7k2A; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Ufap7k2A" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1772813638; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=dyP6TuTAdyVw9tObShEUUYGxABrxESU5roDfSR8x5uw=; b=Ufap7k2Atc0r7mlmPqHppoBVO2SjPFgaWITN4HN7L1+DOFbKIJApzlssw687S3fBhNiEB+ x50NXsfNmc3jwRtuLSMxFOv//HPHG+OPEGZyCvAKBfuBnBHvE45IuzlZe3wKZdDQzuP8W7 FmyQfewAHE1joiuesKoNJWEeotmnbTs= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-208-jA-8r4eYMhybIqmlUbiijA-1; Fri, 06 Mar 2026 11:13:53 -0500 X-MC-Unique: jA-8r4eYMhybIqmlUbiijA-1 X-Mimecast-MFC-AGG-ID: jA-8r4eYMhybIqmlUbiijA_1772813631 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 50AF619560B7; Fri, 6 Mar 2026 16:13:51 +0000 (UTC) Received: from jlelli-thinkpadt14gen4.remote.csb (unknown [10.45.224.3]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6F2713003EA4; Fri, 6 Mar 2026 16:13:47 +0000 (UTC) From: Juri Lelli Date: Fri, 06 Mar 2026 17:10:12 +0100 Subject: [PATCH RFC 7/7] selftests/sched: Add SCHED_DEADLINE ENQUEUE_REPLENISH bug test Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260306-upstream-deadline-kselftests-v1-7-2b23ef74c46a@redhat.com> References: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> In-Reply-To: <20260306-upstream-deadline-kselftests-v1-0-2b23ef74c46a@redhat.com> 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@vger.kernel.org, linux-kselftest@vger.kernel.org, Juri Lelli X-Developer-Signature: v=1; a=ed25519-sha256; t=1772813592; l=13442; i=juri.lelli@redhat.com; s=20250626; h=from:subject:message-id; bh=kMp7gN8TjkP2k3+jNTSWaAT7mbX3pXIHeLkFSCzkd+Y=; b=ZAAAyp5RP/gamBNYATHDyqTx8Xhm8WsIu33ehw/BcDovVxncG2kqEjYKzXzolDrHdfmpUPWkH UiRkwNkA4XRCEyUlgiKGrvXvB+q6WNSDKsFbh5ynvozhZ3k9xuhCfs+ X-Developer-Key: i=juri.lelli@redhat.com; a=ed25519; pk=kSwf88oiY/PYrNMRL/tjuBPiSGzc+U3bD13Zag6wO5Q= X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 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 --- 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/testin= g/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 :=3D $(OUTPUT) UTIL_OBJS :=3D $(OUTPUT)/dl_util.o =20 # Test object files (all .c files except runner.c, dl_util.c, cpuhog.c) -TEST_OBJS :=3D $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o $(OUTPUT)/fair_serv= er.o +TEST_OBJS :=3D $(OUTPUT)/basic.o $(OUTPUT)/bandwidth.o $(OUTPUT)/fair_serv= er.o $(OUTPUT)/replenish_bug.o =20 # 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 $@ =20 +$(OUTPUT)/replenish_bug.o: replenish_bug.c dl_test.h dl_util.h | $(OUTPUT_= DIR) + $(CC) $(CFLAGS) -c $< -o $@ + $(OUTPUT_DIR): mkdir -p $@ =20 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 =3D highe= r 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 acco= unting.\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 =3D {0}; + + if (dl_get_sched_attr(tid, &attr) =3D=3D 0) { + printf("[%s] TID %d: policy=3D%u prio=3D%d", + label, tid, attr.sched_policy, attr.sched_priority); + if (attr.sched_policy =3D=3D SCHED_DEADLINE) { + printf(" runtime=3D%llu deadline=3D%llu period=3D%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 =3D {0}; + return sched_setscheduler(tid, SCHED_IDLE, ¶m); +} + +/* + * 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 =3D arg; + + ctx->holder_tid =3D gettid(); + printf("\n=3D=3D=3D HOLDER (Task B) thread started (TID %d) =3D=3D=3D\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 =3D 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 =3D arg; + + ctx->waiter_tid =3D gettid(); + printf("\n=3D=3D=3D WAITER (Task A) thread started (TID %d) =3D=3D=3D\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 =3D 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 =3D arg; + struct sigaction sa; + + printf("=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D\n"); + printf("DEADLINE ENQUEUE_REPLENISH Bug Test\n"); + printf("=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D\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("=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D\n\n"); + + /* Set up timeout handler */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler =3D 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) !=3D 0, + "pthread_barrier_init failed"); + + /* Create holder thread (will lock mutex) */ + if (pthread_create(&ctx->holder, NULL, holder_thread, ctx) !=3D 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) !=3D 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 SCHE= D_IDLE ***\n"); + printf("[MAIN] *** This triggers the bug! ***\n"); + + if (set_sched_idle(ctx->holder_tid) < 0) { + ctx->test_done =3D 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 =3D 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=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D\n"); + printf("Test completed successfully!\n"); + printf("=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D\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 =3D calloc(1, sizeof(*ctx)); + DL_FAIL_IF(!ctx, "Failed to allocate test context"); + + /* Initialize PI mutex */ + if (pthread_mutexattr_init(&attr) !=3D 0) { + free(ctx); + DL_FAIL("pthread_mutexattr_init failed"); + } + + if (pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT) !=3D 0) { + pthread_mutexattr_destroy(&attr); + free(ctx); + DL_FAIL("pthread_mutexattr_setprotocol failed"); + } + + if (pthread_mutex_init(&ctx->pi_mutex, &attr) !=3D 0) { + pthread_mutexattr_destroy(&attr); + free(ctx); + DL_FAIL("pthread_mutex_init failed"); + } + + pthread_mutexattr_destroy(&attr); + + *ctx_ptr =3D ctx; + return DL_TEST_PASS; +} + +static void test_replenish_bug_cleanup(void *arg) +{ + struct replenish_test_ctx *ctx =3D arg; + + if (ctx) { + pthread_mutex_destroy(&ctx->pi_mutex); + free(ctx); + } +} + +static struct dl_test test_replenish_bug =3D { + .name =3D "replenish_bug", + .description =3D "Verify ENQUEUE_REPLENISH flag is set during PI boosting= after setscheduler", + .setup =3D test_replenish_bug_setup, + .run =3D test_replenish_bug_run, + .cleanup =3D test_replenish_bug_cleanup, +}; +REGISTER_DL_TEST(&test_replenish_bug); --=20 2.53.0