| /* |
| * 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, |
| }; |