aboutsummaryrefslogtreecommitdiff
path: root/testcases/kernel/sched/eas/sched_prio_3_fifo.c
blob: 29f704f92ce9f55955576ac11579b78b5a1e2d73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/*
 * 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();

	tracefs_write("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 */
	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");

	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 */
	tracefs_write("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,
	.setup = trace_setup,
	.cleanup = trace_cleanup,
};