/*
* Copyright (c) 2018 Google, Inc.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Six RT FIFO tasks are created and affined to the same CPU. They execute
* with a particular pattern of overlapping eligibility to run. The resulting
* execution pattern is checked to see that the tasks execute as expected given
* their priorities.
*/
#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 "trace_parse.h"
#include "util.h"
#define TRACE_EVENTS "sched_wakeup sched_switch sched_process_exit"
static int rt_task_tids[6];
/*
* Create two of each RT FIFO task at each priority level. Ensure that
* - higher priority RT tasks preempt lower priority RT tasks
* - newly woken RT tasks at the same priority level do not preempt currently
* running RT tasks
*
* Affine all tasks to CPU 0.
* Have rt_low_fn 1 run first. It wakes up rt_low_fn 2, which should not run
* until rt_low_fn 1 sleeps/exits.
* rt_low_fn2 wakes rt_med_fn1. rt_med_fn1 should run immediately, then sleep,
* allowing rt_low_fn2 to complete.
* rt_med_fn1 wakes rt_med_fn2, which should not run until rt_med_fn 2
* sleeps/exits... (etc)
*/
static sem_t sem_high_b;
static sem_t sem_high_a;
static sem_t sem_med_b;
static sem_t sem_med_a;
static sem_t sem_low_b;
static sem_t sem_low_a;
enum {
RT_LOW_FN_A_TID = 0,
RT_LOW_FN_B_TID,
RT_MED_FN_A_TID,
RT_MED_FN_B_TID,
RT_HIGH_FN_A_TID,
RT_HIGH_FN_B_TID,
};
struct expected_event {
int event_type;
/*
* If sched_wakeup, pid being woken.
* If sched_switch, pid being switched to.
*/
int event_data;
};
static struct expected_event events[] = {
/* rt_low_fn_a wakes up rt_low_fn_b:
* sched_wakeup(rt_low_fn_b) */
{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
.event_data = RT_LOW_FN_B_TID},
/* TODO: Expect an event for the exit of rt_low_fn_a. */
/* 3ms goes by, then rt_low_fn_a exits and rt_low_fn_b starts running */
{ .event_type = TRACE_RECORD_SCHED_SWITCH,
.event_data = RT_LOW_FN_B_TID},
/* rt_low_fn_b wakes rt_med_fn_a which runs immediately */
{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
.event_data = RT_MED_FN_A_TID},
{ .event_type = TRACE_RECORD_SCHED_SWITCH,
.event_data = RT_MED_FN_A_TID},
/* rt_med_fn_a sleeps, allowing rt_low_fn_b time to exit */
{ .event_type = TRACE_RECORD_SCHED_SWITCH,
.event_data = RT_LOW_FN_B_TID},
/* TODO: Expect an event for the exit of rt_low_fn_b. */
{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
.event_data = RT_MED_FN_A_TID},
{ .event_type = TRACE_RECORD_SCHED_SWITCH,
.event_data = RT_MED_FN_A_TID},
/* rt_med_fn_a wakes rt_med_fn_b */
{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
.event_data = RT_MED_FN_B_TID},
/* 3ms goes by, then rt_med_fn_a exits and rt_med_fn_b starts running */
/* TODO: Expect an event for the exit of rt_med_fn_a */
{ .event_type = TRACE_RECORD_SCHED_SWITCH,
.event_data = RT_MED_FN_B_TID},
/* rt_med_fn_b wakes up rt_high_fn_a which runs immediately */
{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
.event_data = RT_HIGH_FN_A_TID},
{ .event_type = TRACE_RECORD_SCHED_SWITCH,
.event_data = RT_HIGH_FN_A_TID},
/* rt_high_fn_a sleeps, allowing rt_med_fn_b time to exit */
{ .event_type = TRACE_RECORD_SCHED_SWITCH,
.event_data = RT_MED_FN_B_TID},
/* TODO: Expect an event for the exit of rt_med_fn_b */
{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
.event_data = RT_HIGH_FN_A_TID},
{ .event_type = TRACE_RECORD_SCHED_SWITCH,
.event_data = RT_HIGH_FN_A_TID},
/* rt_high_fn_a wakes up rt_high_fn_b */
{ .event_type = TRACE_RECORD_SCHED_WAKEUP,
.event_data = RT_HIGH_FN_B_TID},
/* 3ms goes by, then rt_high_fn_a exits and rt_high_fn_b starts running */
/* TODO: Expect an event for the exit of rt_high_fn_a */
{ .event_type = TRACE_RECORD_SCHED_SWITCH,
.event_data = RT_HIGH_FN_B_TID},
};
static void *rt_high_fn_b(void *arg LTP_ATTRIBUTE_UNUSED)
{
rt_task_tids[RT_HIGH_FN_B_TID] = gettid();
affine(0);
/* Wait for rt_high_fn_a to wake us up. */
sem_wait(&sem_high_b);
/* Run after rt_high_fn_a exits. */
return NULL;
}
static void *rt_high_fn_a(void *arg LTP_ATTRIBUTE_UNUSED)
{
rt_task_tids[RT_HIGH_FN_A_TID] = gettid();
affine(0);
/* Wait for rt_med_fn_b to wake us up. */
sem_wait(&sem_high_a);
/* Sleep, allowing rt_med_fn_b a chance to exit. */
usleep(1000);
/* Wake up rt_high_fn_b. We should continue to run though. */
sem_post(&sem_high_b);
/* Busy wait for just a bit. */
burn(3000, 0);
return NULL;
}
static void *rt_med_fn_b(void *arg LTP_ATTRIBUTE_UNUSED)
{
rt_task_tids[RT_MED_FN_B_TID] = gettid();
affine(0);
/* Wait for rt_med_fn_a to wake us up. */
sem_wait(&sem_med_b);
/* Run after rt_med_fn_a exits. */
/* This will wake up rt_high_fn_a which will run immediately, preempting
* us. */
sem_post(&sem_high_a);
return NULL;
}
static void *rt_med_fn_a(void *arg LTP_ATTRIBUTE_UNUSED)
{
rt_task_tids[RT_MED_FN_A_TID] = gettid();
affine(0);
/* Wait for rt_low_fn_b to wake us up. */
sem_wait(&sem_med_a);
/* Sleep, allowing rt_low_fn_b a chance to exit. */
usleep(3000);
/* Wake up rt_med_fn_b. We should continue to run though. */
sem_post(&sem_med_b);
/* Busy wait for just a bit. */
burn(3000, 0);
return NULL;
}
static void *rt_low_fn_b(void *arg LTP_ATTRIBUTE_UNUSED)
{
rt_task_tids[RT_LOW_FN_B_TID] = gettid();
affine(0);
/* Wait for rt_low_fn_a to wake us up. */
sem_wait(&sem_low_b);
/* Run after rt_low_fn_a exits. */
/* This will wake up rt_med_fn_a which will run immediately, preempting
* us. */
sem_post(&sem_med_a);
/* So the previous sem_post isn't actually causing a sched_switch
* to med_a immediately - this is running a bit longer and exiting.
* Delay here. */
burn(1000, 0);
return NULL;
}
/* Put real task tids into the expected events. */
static void fixup_expected_events(void)
{
int i;
int size = sizeof(events)/sizeof(struct expected_event);
for (i = 0; i < size; i++)
events[i].event_data = rt_task_tids[events[i].event_data];
}
static void *rt_low_fn_a(void *arg LTP_ATTRIBUTE_UNUSED)
{
rt_task_tids[RT_LOW_FN_A_TID] = gettid();
affine(0);
/* Give all other tasks a chance to set their tids and block. */
usleep(3000);
fixup_expected_events();
SAFE_FILE_PRINTF(TRACING_DIR "trace_marker", "TEST START");
/* Wake up rt_low_fn_b. We should continue to run though. */
sem_post(&sem_low_b);
/* Busy wait for just a bit. */
burn(3000, 0);
return NULL;
}
/* Returns whether the given tid is a tid of one of the RT tasks in this
* testcase. */
static int rt_tid(int tid)
{
int i;
for (i = 0; i < 6; i++)
if (rt_task_tids[i] == tid)
return 1;
return 0;
}
static int parse_results(void)
{
int i;
int test_start = 0;
int event_idx = 0;
int events_size = sizeof(events)/sizeof(struct expected_event);
for (i = 0; i < num_trace_records; i++) {
if (trace[i].event_type == TRACE_RECORD_TRACING_MARK_WRITE &&
!strcmp(trace[i].event_data, "TEST START"))
test_start = 1;
if (!test_start)
continue;
if (trace[i].event_type != TRACE_RECORD_SCHED_WAKEUP &&
trace[i].event_type != TRACE_RECORD_SCHED_SWITCH)
continue;
if (trace[i].event_type == TRACE_RECORD_SCHED_SWITCH) {
struct trace_sched_switch *t = trace[i].event_data;
if (!rt_tid(t->next_pid))
continue;
if (events[event_idx].event_type !=
TRACE_RECORD_SCHED_SWITCH ||
events[event_idx].event_data !=
t->next_pid) {
printf("Test case failed, expecting event "
"index %d type %d for tid %d, "
"got sched switch to tid %d\n",
event_idx,
events[event_idx].event_type,
events[event_idx].event_data,
t->next_pid);
return -1;
}
event_idx++;
}
if (trace[i].event_type == TRACE_RECORD_SCHED_WAKEUP) {
struct trace_sched_wakeup *t = trace[i].event_data;
if (!rt_tid(t->pid))
continue;
if (events[event_idx].event_type !=
TRACE_RECORD_SCHED_WAKEUP ||
events[event_idx].event_data !=
t->pid) {
printf("Test case failed, expecting event "
"index %d type %d for tid %d, "
"got sched wakeup to tid %d\n",
event_idx,
events[event_idx].event_type,
events[event_idx].event_data,
t->pid);
return -1;
}
event_idx++;
}
if (event_idx == events_size)
break;
}
if (event_idx != events_size) {
printf("Test case failed, "
"did not complete all expected events.\n");
printf("Next expected event: event type %d for tid %d\n",
events[event_idx].event_type,
events[event_idx].event_data);
return -1;
}
return 0;
}
static void create_rt_thread(int prio, void *fn, pthread_t *rt_thread)
{
pthread_attr_t rt_thread_attrs;
struct sched_param rt_thread_sched_params;
ERROR_CHECK(pthread_attr_init(&rt_thread_attrs));
ERROR_CHECK(pthread_attr_setinheritsched(&rt_thread_attrs,
PTHREAD_EXPLICIT_SCHED));
ERROR_CHECK(pthread_attr_setschedpolicy(&rt_thread_attrs,
SCHED_FIFO));
rt_thread_sched_params.sched_priority = prio;
ERROR_CHECK(pthread_attr_setschedparam(&rt_thread_attrs,
&rt_thread_sched_params));
SAFE_PTHREAD_CREATE(rt_thread, &rt_thread_attrs, fn, NULL);
}
static void run(void)
{
pthread_t rt_low_a, rt_low_b;
pthread_t rt_med_a, rt_med_b;
pthread_t rt_high_a, rt_high_b;
sem_init(&sem_high_b, 0, 0);
sem_init(&sem_high_a, 0, 0);
sem_init(&sem_med_b, 0, 0);
sem_init(&sem_med_a, 0, 0);
sem_init(&sem_low_b, 0, 0);
sem_init(&sem_low_a, 0, 0);
/* configure and enable tracing */
SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0");
SAFE_FILE_PRINTF(TRACING_DIR "buffer_size_kb", "16384");
SAFE_FILE_PRINTF(TRACING_DIR "set_event", TRACE_EVENTS);
SAFE_FILE_PRINTF(TRACING_DIR "trace", "\n");
SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "1");
create_rt_thread(70, rt_low_fn_a, &rt_low_a);
create_rt_thread(70, rt_low_fn_b, &rt_low_b);
create_rt_thread(75, rt_med_fn_a, &rt_med_a);
create_rt_thread(75, rt_med_fn_b, &rt_med_b);
create_rt_thread(80, rt_high_fn_a, &rt_high_a);
create_rt_thread(80, rt_high_fn_b, &rt_high_b);
SAFE_PTHREAD_JOIN(rt_low_a, NULL);
SAFE_PTHREAD_JOIN(rt_low_b, NULL);
SAFE_PTHREAD_JOIN(rt_med_a, NULL);
SAFE_PTHREAD_JOIN(rt_med_b, NULL);
SAFE_PTHREAD_JOIN(rt_high_a, NULL);
SAFE_PTHREAD_JOIN(rt_high_b, NULL);
/* disable tracing */
SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0");
LOAD_TRACE();
if (parse_results())
tst_res(TFAIL, "RT FIFO tasks did not execute in the expected "
"pattern.\n");
else
tst_res(TPASS, "RT FIFO tasks executed in the expected "
"pattern.\n");
}
static struct tst_test test = {
.test_all = run,
.cleanup = trace_cleanup,
};