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

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

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

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

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

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

-- 
2.53.0


Reply via email to