| /* |
| * Copyright (c) 2018 Google, Inc. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| * |
| * A DL task runs. Its execution pattern is checked to see that the constraints |
| * it has been given (runtime, period, deadline) are satisfied. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <pthread.h> |
| #include <sched.h> |
| #include <semaphore.h> |
| #include <time.h> |
| |
| #include "tst_test.h" |
| #include "tst_safe_file_ops.h" |
| #include "tst_safe_pthread.h" |
| |
| #include "lapi/syscalls.h" |
| #include "lapi/sched.h" |
| #include "trace_parse.h" |
| #include "util.h" |
| |
| #define TRACE_EVENTS "sched_switch" |
| |
| static int dl_task_tid; |
| |
| /* |
| * It is not possible to restrict CPU affinity on SCHED_DEADLINE tasks, so we do |
| * not force the CFS and DL tasks to be on the same CPU in this test. CPUsets |
| * can be used to do this, perhaps implement that later. |
| */ |
| |
| static void *dl_fn(void *arg LTP_ATTRIBUTE_UNUSED) |
| { |
| struct sched_attr attr; |
| struct timespec ts; |
| uint64_t now_usec, end_usec; |
| |
| attr.size = sizeof(attr); |
| attr.sched_flags = 0; |
| attr.sched_nice = 0; |
| attr.sched_priority = 0; |
| |
| attr.sched_policy = SCHED_DEADLINE; |
| attr.sched_runtime = 5000000; |
| attr.sched_period = 20000000; |
| attr.sched_deadline = 10000000; |
| |
| tracefs_write("trace_marker", "DL START"); |
| ERROR_CHECK(sched_setattr(0, &attr, 0)); |
| |
| dl_task_tid = gettid(); |
| |
| if (clock_gettime(CLOCK_MONOTONIC, &ts)) { |
| printf("clock_gettime() reported an error\n"); |
| return NULL; |
| } |
| now_usec = (ts.tv_sec) * USEC_PER_SEC + (ts.tv_nsec / 1000); |
| end_usec = now_usec + 3 * USEC_PER_SEC; |
| while (now_usec < end_usec) { |
| /* Run for 5ms */ |
| burn(5000, 0); |
| |
| /* Wait until next 20ms boundary. sched_yield() for DL tasks |
| * throttles the task until its next period. */ |
| sched_yield(); |
| if (clock_gettime(CLOCK_MONOTONIC, &ts)) { |
| printf("clock_gettime() reported an error\n"); |
| return NULL; |
| } |
| now_usec = (ts.tv_sec) * USEC_PER_SEC + (ts.tv_nsec / 1000); |
| } |
| |
| return NULL; |
| } |
| |
| static int parse_results(void) |
| { |
| int i; |
| unsigned long long next_period_ts_us = 0; |
| unsigned long long next_deadline_ts_us = 0; |
| unsigned long long start_ts_us = 0; |
| unsigned long long period_exec_time_us = 0; |
| int periods_parsed = 0; |
| |
| for (i = 0; i < num_trace_records; i++) { |
| if (trace[i].event_type == TRACE_RECORD_SCHED_SWITCH) { |
| struct trace_sched_switch *t = trace[i].event_data; |
| if (t->prev_pid == dl_task_tid) { |
| unsigned long long end_ts_us; |
| if (!start_ts_us) { |
| printf("Trace parse error, " |
| "start_ts_us = 0\n"); |
| return -1; |
| } |
| if (TS_TO_USEC(trace[i].ts) > |
| next_period_ts_us) { |
| printf("Task ran past end of " |
| "period!\n"); |
| return -1; |
| } |
| end_ts_us = TS_TO_USEC(trace[i].ts); |
| if (end_ts_us > next_deadline_ts_us) |
| end_ts_us = next_deadline_ts_us; |
| if (start_ts_us > next_deadline_ts_us) |
| start_ts_us = next_deadline_ts_us; |
| period_exec_time_us += |
| (end_ts_us - start_ts_us); |
| start_ts_us = 0; |
| } |
| } |
| if (next_period_ts_us && |
| TS_TO_USEC(trace[i].ts) > next_period_ts_us) { |
| if (start_ts_us) { |
| printf("Task was running across period boundary!\n"); |
| return -1; |
| } |
| if (period_exec_time_us < 5000) { |
| printf("Missed deadline at %llu!\n", |
| next_deadline_ts_us); |
| return -1; |
| } |
| periods_parsed++; |
| next_deadline_ts_us += 20000; |
| next_period_ts_us += 20000; |
| period_exec_time_us = 0; |
| } |
| if (trace[i].event_type == TRACE_RECORD_SCHED_SWITCH) { |
| struct trace_sched_switch *t = trace[i].event_data; |
| if (t->next_pid == dl_task_tid) { |
| |
| if (start_ts_us) { |
| printf("Trace parse error, " |
| "start_ts_us != 0\n"); |
| return -1; |
| } |
| start_ts_us = TS_TO_USEC(trace[i].ts); |
| if (!next_period_ts_us) { |
| /* |
| * Initialize period and deadline the |
| * first time the DL task runs. |
| */ |
| next_period_ts_us = start_ts_us + 20000; |
| next_deadline_ts_us = start_ts_us + |
| 10000; |
| } |
| } |
| } |
| } |
| if (periods_parsed >= 149) |
| printf("%d periods parsed successfully.\n", periods_parsed); |
| else |
| printf("Only %d periods parsed successfully.\n", |
| periods_parsed); |
| return 0; |
| } |
| |
| static void run(void) |
| { |
| pthread_t dl_thread; |
| pthread_attr_t dl_thread_attrs; |
| struct sched_param dl_thread_sched_params; |
| |
| ERROR_CHECK(pthread_attr_init(&dl_thread_attrs)); |
| ERROR_CHECK(pthread_attr_setinheritsched(&dl_thread_attrs, |
| PTHREAD_EXPLICIT_SCHED)); |
| ERROR_CHECK(pthread_attr_setschedpolicy(&dl_thread_attrs, |
| SCHED_FIFO)); |
| dl_thread_sched_params.sched_priority = 80; |
| ERROR_CHECK(pthread_attr_setschedparam(&dl_thread_attrs, |
| &dl_thread_sched_params)); |
| |
| /* configure and enable tracing */ |
| tracefs_write("tracing_on", "0"); |
| tracefs_write("buffer_size_kb", "16384"); |
| tracefs_write("set_event", TRACE_EVENTS); |
| tracefs_write("trace", "\n"); |
| tracefs_write("tracing_on", "1"); |
| |
| SAFE_PTHREAD_CREATE(&dl_thread, NULL, dl_fn, NULL); |
| SAFE_PTHREAD_JOIN(dl_thread, NULL); |
| |
| /* disable tracing */ |
| tracefs_write("tracing_on", "0"); |
| LOAD_TRACE(); |
| |
| if (parse_results()) |
| tst_res(TFAIL, "DL task did not execute as expected.\n"); |
| else |
| tst_res(TPASS, "DL task ran as expected.\n"); |
| } |
| |
| static struct tst_test test = { |
| .setup = trace_setup, |
| .test_all = run, |
| .cleanup = trace_cleanup, |
| }; |