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