1be621a76SAndrea Righi // SPDX-License-Identifier: GPL-2.0 2be621a76SAndrea Righi /* 3be621a76SAndrea Righi * Copyright (c) 2025 NVIDIA Corporation. 4be621a76SAndrea Righi */ 5be621a76SAndrea Righi #define _GNU_SOURCE 6be621a76SAndrea Righi #include <stdio.h> 7be621a76SAndrea Righi #include <stdlib.h> 8be621a76SAndrea Righi #include <unistd.h> 9be621a76SAndrea Righi #include <sched.h> 10be621a76SAndrea Righi #include <sys/prctl.h> 11be621a76SAndrea Righi #include <sys/types.h> 12be621a76SAndrea Righi #include <sys/wait.h> 13be621a76SAndrea Righi #include <time.h> 14be621a76SAndrea Righi #include <linux/sched.h> 15be621a76SAndrea Righi #include <signal.h> 16be621a76SAndrea Righi #include <bpf/bpf.h> 17be621a76SAndrea Righi #include <scx/common.h> 18be621a76SAndrea Righi #include <unistd.h> 19be621a76SAndrea Righi #include "rt_stall.bpf.skel.h" 20be621a76SAndrea Righi #include "scx_test.h" 21be621a76SAndrea Righi #include "../kselftest.h" 22be621a76SAndrea Righi 23be621a76SAndrea Righi #define CORE_ID 0 /* CPU to pin tasks to */ 24be621a76SAndrea Righi #define RUN_TIME 5 /* How long to run the test in seconds */ 25be621a76SAndrea Righi 26*0b82cc33SIhor Solodrai /* Signal the parent that setup is complete by writing to a pipe */ 27*0b82cc33SIhor Solodrai static void signal_ready(int fd) 28*0b82cc33SIhor Solodrai { 29*0b82cc33SIhor Solodrai char c = 1; 30*0b82cc33SIhor Solodrai 31*0b82cc33SIhor Solodrai if (write(fd, &c, 1) != 1) { 32*0b82cc33SIhor Solodrai perror("write to ready pipe"); 33*0b82cc33SIhor Solodrai exit(EXIT_FAILURE); 34*0b82cc33SIhor Solodrai } 35*0b82cc33SIhor Solodrai close(fd); 36*0b82cc33SIhor Solodrai } 37*0b82cc33SIhor Solodrai 38*0b82cc33SIhor Solodrai /* Wait for a child to signal readiness via a pipe */ 39*0b82cc33SIhor Solodrai static void wait_ready(int fd) 40*0b82cc33SIhor Solodrai { 41*0b82cc33SIhor Solodrai char c; 42*0b82cc33SIhor Solodrai 43*0b82cc33SIhor Solodrai if (read(fd, &c, 1) != 1) { 44*0b82cc33SIhor Solodrai perror("read from ready pipe"); 45*0b82cc33SIhor Solodrai exit(EXIT_FAILURE); 46*0b82cc33SIhor Solodrai } 47*0b82cc33SIhor Solodrai close(fd); 48*0b82cc33SIhor Solodrai } 49*0b82cc33SIhor Solodrai 50be621a76SAndrea Righi /* Simple busy-wait function for test tasks */ 51be621a76SAndrea Righi static void process_func(void) 52be621a76SAndrea Righi { 53be621a76SAndrea Righi while (1) { 54be621a76SAndrea Righi /* Busy wait */ 55be621a76SAndrea Righi for (volatile unsigned long i = 0; i < 10000000UL; i++) 56be621a76SAndrea Righi ; 57be621a76SAndrea Righi } 58be621a76SAndrea Righi } 59be621a76SAndrea Righi 60be621a76SAndrea Righi /* Set CPU affinity to a specific core */ 61be621a76SAndrea Righi static void set_affinity(int cpu) 62be621a76SAndrea Righi { 63be621a76SAndrea Righi cpu_set_t mask; 64be621a76SAndrea Righi 65be621a76SAndrea Righi CPU_ZERO(&mask); 66be621a76SAndrea Righi CPU_SET(cpu, &mask); 67be621a76SAndrea Righi if (sched_setaffinity(0, sizeof(mask), &mask) != 0) { 68be621a76SAndrea Righi perror("sched_setaffinity"); 69be621a76SAndrea Righi exit(EXIT_FAILURE); 70be621a76SAndrea Righi } 71be621a76SAndrea Righi } 72be621a76SAndrea Righi 73be621a76SAndrea Righi /* Set task scheduling policy and priority */ 74be621a76SAndrea Righi static void set_sched(int policy, int priority) 75be621a76SAndrea Righi { 76be621a76SAndrea Righi struct sched_param param; 77be621a76SAndrea Righi 78be621a76SAndrea Righi param.sched_priority = priority; 79be621a76SAndrea Righi if (sched_setscheduler(0, policy, ¶m) != 0) { 80be621a76SAndrea Righi perror("sched_setscheduler"); 81be621a76SAndrea Righi exit(EXIT_FAILURE); 82be621a76SAndrea Righi } 83be621a76SAndrea Righi } 84be621a76SAndrea Righi 85be621a76SAndrea Righi /* Get process runtime from /proc/<pid>/stat */ 86be621a76SAndrea Righi static float get_process_runtime(int pid) 87be621a76SAndrea Righi { 88be621a76SAndrea Righi char path[256]; 89be621a76SAndrea Righi FILE *file; 90be621a76SAndrea Righi long utime, stime; 91be621a76SAndrea Righi int fields; 92be621a76SAndrea Righi 93be621a76SAndrea Righi snprintf(path, sizeof(path), "/proc/%d/stat", pid); 94be621a76SAndrea Righi file = fopen(path, "r"); 95be621a76SAndrea Righi if (file == NULL) { 96be621a76SAndrea Righi perror("Failed to open stat file"); 97be621a76SAndrea Righi return -1; 98be621a76SAndrea Righi } 99be621a76SAndrea Righi 100be621a76SAndrea Righi /* Skip the first 13 fields and read the 14th and 15th */ 101be621a76SAndrea Righi fields = fscanf(file, 102be621a76SAndrea Righi "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", 103be621a76SAndrea Righi &utime, &stime); 104be621a76SAndrea Righi fclose(file); 105be621a76SAndrea Righi 106be621a76SAndrea Righi if (fields != 2) { 107be621a76SAndrea Righi fprintf(stderr, "Failed to read stat file\n"); 108be621a76SAndrea Righi return -1; 109be621a76SAndrea Righi } 110be621a76SAndrea Righi 111be621a76SAndrea Righi /* Calculate the total time spent in the process */ 112be621a76SAndrea Righi long total_time = utime + stime; 113be621a76SAndrea Righi long ticks_per_second = sysconf(_SC_CLK_TCK); 114be621a76SAndrea Righi float runtime_seconds = total_time * 1.0 / ticks_per_second; 115be621a76SAndrea Righi 116be621a76SAndrea Righi return runtime_seconds; 117be621a76SAndrea Righi } 118be621a76SAndrea Righi 119be621a76SAndrea Righi static enum scx_test_status setup(void **ctx) 120be621a76SAndrea Righi { 121be621a76SAndrea Righi struct rt_stall *skel; 122be621a76SAndrea Righi 123be621a76SAndrea Righi skel = rt_stall__open(); 124be621a76SAndrea Righi SCX_FAIL_IF(!skel, "Failed to open"); 125be621a76SAndrea Righi SCX_ENUM_INIT(skel); 126be621a76SAndrea Righi SCX_FAIL_IF(rt_stall__load(skel), "Failed to load skel"); 127be621a76SAndrea Righi 128be621a76SAndrea Righi *ctx = skel; 129be621a76SAndrea Righi 130be621a76SAndrea Righi return SCX_TEST_PASS; 131be621a76SAndrea Righi } 132be621a76SAndrea Righi 133be621a76SAndrea Righi static bool sched_stress_test(bool is_ext) 134be621a76SAndrea Righi { 135be621a76SAndrea Righi /* 136be621a76SAndrea Righi * We're expecting the EXT task to get around 5% of CPU time when 137be621a76SAndrea Righi * competing with the RT task (small 1% fluctuations are expected). 138be621a76SAndrea Righi * 139be621a76SAndrea Righi * However, the EXT task should get at least 4% of the CPU to prove 140be621a76SAndrea Righi * that the EXT deadline server is working correctly. A percentage 141be621a76SAndrea Righi * less than 4% indicates a bug where RT tasks can potentially 142be621a76SAndrea Righi * stall SCHED_EXT tasks, causing the test to fail. 143be621a76SAndrea Righi */ 144be621a76SAndrea Righi const float expected_min_ratio = 0.04; /* 4% */ 145be621a76SAndrea Righi const char *class_str = is_ext ? "EXT" : "FAIR"; 146be621a76SAndrea Righi 147be621a76SAndrea Righi float ext_runtime, rt_runtime, actual_ratio; 148be621a76SAndrea Righi int ext_pid, rt_pid; 149*0b82cc33SIhor Solodrai int ext_ready[2], rt_ready[2]; 150be621a76SAndrea Righi 151be621a76SAndrea Righi ksft_print_header(); 152be621a76SAndrea Righi ksft_set_plan(1); 153be621a76SAndrea Righi 154*0b82cc33SIhor Solodrai if (pipe(ext_ready) || pipe(rt_ready)) { 155*0b82cc33SIhor Solodrai perror("pipe"); 156*0b82cc33SIhor Solodrai ksft_exit_fail(); 157*0b82cc33SIhor Solodrai } 158*0b82cc33SIhor Solodrai 159be621a76SAndrea Righi /* Create and set up a EXT task */ 160be621a76SAndrea Righi ext_pid = fork(); 161be621a76SAndrea Righi if (ext_pid == 0) { 162*0b82cc33SIhor Solodrai close(ext_ready[0]); 163*0b82cc33SIhor Solodrai close(rt_ready[0]); 164*0b82cc33SIhor Solodrai close(rt_ready[1]); 165be621a76SAndrea Righi set_affinity(CORE_ID); 166*0b82cc33SIhor Solodrai signal_ready(ext_ready[1]); 167be621a76SAndrea Righi process_func(); 168be621a76SAndrea Righi exit(0); 169be621a76SAndrea Righi } else if (ext_pid < 0) { 170be621a76SAndrea Righi perror("fork task"); 171be621a76SAndrea Righi ksft_exit_fail(); 172be621a76SAndrea Righi } 173be621a76SAndrea Righi 174be621a76SAndrea Righi /* Create an RT task */ 175be621a76SAndrea Righi rt_pid = fork(); 176be621a76SAndrea Righi if (rt_pid == 0) { 177*0b82cc33SIhor Solodrai close(ext_ready[0]); 178*0b82cc33SIhor Solodrai close(ext_ready[1]); 179*0b82cc33SIhor Solodrai close(rt_ready[0]); 180be621a76SAndrea Righi set_affinity(CORE_ID); 181be621a76SAndrea Righi set_sched(SCHED_FIFO, 50); 182*0b82cc33SIhor Solodrai signal_ready(rt_ready[1]); 183be621a76SAndrea Righi process_func(); 184be621a76SAndrea Righi exit(0); 185be621a76SAndrea Righi } else if (rt_pid < 0) { 186be621a76SAndrea Righi perror("fork for RT task"); 187be621a76SAndrea Righi ksft_exit_fail(); 188be621a76SAndrea Righi } 189be621a76SAndrea Righi 190*0b82cc33SIhor Solodrai /* 191*0b82cc33SIhor Solodrai * Wait for both children to complete their setup (affinity and 192*0b82cc33SIhor Solodrai * scheduling policy) before starting the measurement window. 193*0b82cc33SIhor Solodrai * This prevents flaky failures caused by the RT child's setup 194*0b82cc33SIhor Solodrai * time eating into the measurement period. 195*0b82cc33SIhor Solodrai */ 196*0b82cc33SIhor Solodrai close(ext_ready[1]); 197*0b82cc33SIhor Solodrai close(rt_ready[1]); 198*0b82cc33SIhor Solodrai wait_ready(ext_ready[0]); 199*0b82cc33SIhor Solodrai wait_ready(rt_ready[0]); 200*0b82cc33SIhor Solodrai 201be621a76SAndrea Righi /* Let the processes run for the specified time */ 202be621a76SAndrea Righi sleep(RUN_TIME); 203be621a76SAndrea Righi 204be621a76SAndrea Righi /* Get runtime for the EXT task */ 205be621a76SAndrea Righi ext_runtime = get_process_runtime(ext_pid); 206be621a76SAndrea Righi if (ext_runtime == -1) 207be621a76SAndrea Righi ksft_exit_fail_msg("Error getting runtime for %s task (PID %d)\n", 208be621a76SAndrea Righi class_str, ext_pid); 209be621a76SAndrea Righi ksft_print_msg("Runtime of %s task (PID %d) is %f seconds\n", 210be621a76SAndrea Righi class_str, ext_pid, ext_runtime); 211be621a76SAndrea Righi 212be621a76SAndrea Righi /* Get runtime for the RT task */ 213be621a76SAndrea Righi rt_runtime = get_process_runtime(rt_pid); 214be621a76SAndrea Righi if (rt_runtime == -1) 215be621a76SAndrea Righi ksft_exit_fail_msg("Error getting runtime for RT task (PID %d)\n", rt_pid); 216be621a76SAndrea Righi ksft_print_msg("Runtime of RT task (PID %d) is %f seconds\n", rt_pid, rt_runtime); 217be621a76SAndrea Righi 218be621a76SAndrea Righi /* Kill the processes */ 219be621a76SAndrea Righi kill(ext_pid, SIGKILL); 220be621a76SAndrea Righi kill(rt_pid, SIGKILL); 221be621a76SAndrea Righi waitpid(ext_pid, NULL, 0); 222be621a76SAndrea Righi waitpid(rt_pid, NULL, 0); 223be621a76SAndrea Righi 224be621a76SAndrea Righi /* Verify that the scx task got enough runtime */ 225be621a76SAndrea Righi actual_ratio = ext_runtime / (ext_runtime + rt_runtime); 226be621a76SAndrea Righi ksft_print_msg("%s task got %.2f%% of total runtime\n", 227be621a76SAndrea Righi class_str, actual_ratio * 100); 228be621a76SAndrea Righi 229be621a76SAndrea Righi if (actual_ratio >= expected_min_ratio) { 230be621a76SAndrea Righi ksft_test_result_pass("PASS: %s task got more than %.2f%% of runtime\n", 231be621a76SAndrea Righi class_str, expected_min_ratio * 100); 232be621a76SAndrea Righi return true; 233be621a76SAndrea Righi } 234be621a76SAndrea Righi ksft_test_result_fail("FAIL: %s task got less than %.2f%% of runtime\n", 235be621a76SAndrea Righi class_str, expected_min_ratio * 100); 236be621a76SAndrea Righi return false; 237be621a76SAndrea Righi } 238be621a76SAndrea Righi 239be621a76SAndrea Righi static enum scx_test_status run(void *ctx) 240be621a76SAndrea Righi { 241be621a76SAndrea Righi struct rt_stall *skel = ctx; 242be621a76SAndrea Righi struct bpf_link *link = NULL; 243be621a76SAndrea Righi bool res; 244be621a76SAndrea Righi int i; 245be621a76SAndrea Righi 246be621a76SAndrea Righi /* 247be621a76SAndrea Righi * Test if the dl_server is working both with and without the 248be621a76SAndrea Righi * sched_ext scheduler attached. 249be621a76SAndrea Righi * 250be621a76SAndrea Righi * This ensures all the scenarios are covered: 251be621a76SAndrea Righi * - fair_server stop -> ext_server start 252be621a76SAndrea Righi * - ext_server stop -> fair_server stop 253be621a76SAndrea Righi */ 254be621a76SAndrea Righi for (i = 0; i < 4; i++) { 255be621a76SAndrea Righi bool is_ext = i % 2; 256be621a76SAndrea Righi 257be621a76SAndrea Righi if (is_ext) { 258be621a76SAndrea Righi memset(&skel->data->uei, 0, sizeof(skel->data->uei)); 259be621a76SAndrea Righi link = bpf_map__attach_struct_ops(skel->maps.rt_stall_ops); 260be621a76SAndrea Righi SCX_FAIL_IF(!link, "Failed to attach scheduler"); 261be621a76SAndrea Righi } 262be621a76SAndrea Righi res = sched_stress_test(is_ext); 263be621a76SAndrea Righi if (is_ext) { 264be621a76SAndrea Righi SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_NONE)); 265be621a76SAndrea Righi bpf_link__destroy(link); 266be621a76SAndrea Righi } 267be621a76SAndrea Righi 268be621a76SAndrea Righi if (!res) 269be621a76SAndrea Righi ksft_exit_fail(); 270be621a76SAndrea Righi } 271be621a76SAndrea Righi 272be621a76SAndrea Righi return SCX_TEST_PASS; 273be621a76SAndrea Righi } 274be621a76SAndrea Righi 275be621a76SAndrea Righi static void cleanup(void *ctx) 276be621a76SAndrea Righi { 277be621a76SAndrea Righi struct rt_stall *skel = ctx; 278be621a76SAndrea Righi 279be621a76SAndrea Righi rt_stall__destroy(skel); 280be621a76SAndrea Righi } 281be621a76SAndrea Righi 282be621a76SAndrea Righi struct scx_test rt_stall = { 283be621a76SAndrea Righi .name = "rt_stall", 284be621a76SAndrea Righi .description = "Verify that RT tasks cannot stall SCHED_EXT tasks", 285be621a76SAndrea Righi .setup = setup, 286be621a76SAndrea Righi .run = run, 287be621a76SAndrea Righi .cleanup = cleanup, 288be621a76SAndrea Righi }; 289be621a76SAndrea Righi REGISTER_SCX_TEST(&rt_stall) 290