1/*
2 * RT-Mutex-tester: scriptable tester for rt mutexes
3 *
4 * started by Thomas Gleixner:
5 *
6 *  Copyright (C) 2006, Timesys Corp., Thomas Gleixner <tglx@timesys.com>
7 *
8 */
9#include <linux/kthread.h>
10#include <linux/module.h>
11#include <linux/sched.h>
12#include <linux/smp_lock.h>
13#include <linux/spinlock.h>
14#include <linux/sysdev.h>
15#include <linux/timer.h>
16#include <linux/freezer.h>
17
18#include "rtmutex.h"
19
20#define MAX_RT_TEST_THREADS	8
21#define MAX_RT_TEST_MUTEXES	8
22
23static spinlock_t rttest_lock;
24static atomic_t rttest_event;
25
26struct test_thread_data {
27	int			opcode;
28	int			opdata;
29	int			mutexes[MAX_RT_TEST_MUTEXES];
30	int			bkl;
31	int			event;
32	struct sys_device	sysdev;
33};
34
35static struct test_thread_data thread_data[MAX_RT_TEST_THREADS];
36static struct task_struct *threads[MAX_RT_TEST_THREADS];
37static struct rt_mutex mutexes[MAX_RT_TEST_MUTEXES];
38
39enum test_opcodes {
40	RTTEST_NOP = 0,
41	RTTEST_SCHEDOT,		/* 1 Sched other, data = nice */
42	RTTEST_SCHEDRT,		/* 2 Sched fifo, data = prio */
43	RTTEST_LOCK,		/* 3 Lock uninterruptible, data = lockindex */
44	RTTEST_LOCKNOWAIT,	/* 4 Lock uninterruptible no wait in wakeup, data = lockindex */
45	RTTEST_LOCKINT,		/* 5 Lock interruptible, data = lockindex */
46	RTTEST_LOCKINTNOWAIT,	/* 6 Lock interruptible no wait in wakeup, data = lockindex */
47	RTTEST_LOCKCONT,	/* 7 Continue locking after the wakeup delay */
48	RTTEST_UNLOCK,		/* 8 Unlock, data = lockindex */
49	RTTEST_LOCKBKL,		/* 9 Lock BKL */
50	RTTEST_UNLOCKBKL,	/* 10 Unlock BKL */
51	RTTEST_SIGNAL,		/* 11 Signal other test thread, data = thread id */
52	RTTEST_RESETEVENT = 98,	/* 98 Reset event counter */
53	RTTEST_RESET = 99,	/* 99 Reset all pending operations */
54};
55
56static int handle_op(struct test_thread_data *td, int lockwakeup)
57{
58	int i, id, ret = -EINVAL;
59
60	switch(td->opcode) {
61
62	case RTTEST_NOP:
63		return 0;
64
65	case RTTEST_LOCKCONT:
66		td->mutexes[td->opdata] = 1;
67		td->event = atomic_add_return(1, &rttest_event);
68		return 0;
69
70	case RTTEST_RESET:
71		for (i = 0; i < MAX_RT_TEST_MUTEXES; i++) {
72			if (td->mutexes[i] == 4) {
73				rt_mutex_unlock(&mutexes[i]);
74				td->mutexes[i] = 0;
75			}
76		}
77
78		if (!lockwakeup && td->bkl == 4) {
79			unlock_kernel();
80			td->bkl = 0;
81		}
82		return 0;
83
84	case RTTEST_RESETEVENT:
85		atomic_set(&rttest_event, 0);
86		return 0;
87
88	default:
89		if (lockwakeup)
90			return ret;
91	}
92
93	switch(td->opcode) {
94
95	case RTTEST_LOCK:
96	case RTTEST_LOCKNOWAIT:
97		id = td->opdata;
98		if (id < 0 || id >= MAX_RT_TEST_MUTEXES)
99			return ret;
100
101		td->mutexes[id] = 1;
102		td->event = atomic_add_return(1, &rttest_event);
103		rt_mutex_lock(&mutexes[id]);
104		td->event = atomic_add_return(1, &rttest_event);
105		td->mutexes[id] = 4;
106		return 0;
107
108	case RTTEST_LOCKINT:
109	case RTTEST_LOCKINTNOWAIT:
110		id = td->opdata;
111		if (id < 0 || id >= MAX_RT_TEST_MUTEXES)
112			return ret;
113
114		td->mutexes[id] = 1;
115		td->event = atomic_add_return(1, &rttest_event);
116		ret = rt_mutex_lock_interruptible(&mutexes[id], 0);
117		td->event = atomic_add_return(1, &rttest_event);
118		td->mutexes[id] = ret ? 0 : 4;
119		return ret ? -EINTR : 0;
120
121	case RTTEST_UNLOCK:
122		id = td->opdata;
123		if (id < 0 || id >= MAX_RT_TEST_MUTEXES || td->mutexes[id] != 4)
124			return ret;
125
126		td->event = atomic_add_return(1, &rttest_event);
127		rt_mutex_unlock(&mutexes[id]);
128		td->event = atomic_add_return(1, &rttest_event);
129		td->mutexes[id] = 0;
130		return 0;
131
132	case RTTEST_LOCKBKL:
133		if (td->bkl)
134			return 0;
135		td->bkl = 1;
136		lock_kernel();
137		td->bkl = 4;
138		return 0;
139
140	case RTTEST_UNLOCKBKL:
141		if (td->bkl != 4)
142			break;
143		unlock_kernel();
144		td->bkl = 0;
145		return 0;
146
147	default:
148		break;
149	}
150	return ret;
151}
152
153/*
154 * Schedule replacement for rtsem_down(). Only called for threads with
155 * PF_MUTEX_TESTER set.
156 *
157 * This allows us to have finegrained control over the event flow.
158 *
159 */
160void schedule_rt_mutex_test(struct rt_mutex *mutex)
161{
162	int tid, op, dat;
163	struct test_thread_data *td;
164
165	/* We have to lookup the task */
166	for (tid = 0; tid < MAX_RT_TEST_THREADS; tid++) {
167		if (threads[tid] == current)
168			break;
169	}
170
171	BUG_ON(tid == MAX_RT_TEST_THREADS);
172
173	td = &thread_data[tid];
174
175	op = td->opcode;
176	dat = td->opdata;
177
178	switch (op) {
179	case RTTEST_LOCK:
180	case RTTEST_LOCKINT:
181	case RTTEST_LOCKNOWAIT:
182	case RTTEST_LOCKINTNOWAIT:
183		if (mutex != &mutexes[dat])
184			break;
185
186		if (td->mutexes[dat] != 1)
187			break;
188
189		td->mutexes[dat] = 2;
190		td->event = atomic_add_return(1, &rttest_event);
191		break;
192
193	case RTTEST_LOCKBKL:
194	default:
195		break;
196	}
197
198	schedule();
199
200
201	switch (op) {
202	case RTTEST_LOCK:
203	case RTTEST_LOCKINT:
204		if (mutex != &mutexes[dat])
205			return;
206
207		if (td->mutexes[dat] != 2)
208			return;
209
210		td->mutexes[dat] = 3;
211		td->event = atomic_add_return(1, &rttest_event);
212		break;
213
214	case RTTEST_LOCKNOWAIT:
215	case RTTEST_LOCKINTNOWAIT:
216		if (mutex != &mutexes[dat])
217			return;
218
219		if (td->mutexes[dat] != 2)
220			return;
221
222		td->mutexes[dat] = 1;
223		td->event = atomic_add_return(1, &rttest_event);
224		return;
225
226	case RTTEST_LOCKBKL:
227		return;
228	default:
229		return;
230	}
231
232	td->opcode = 0;
233
234	for (;;) {
235		set_current_state(TASK_INTERRUPTIBLE);
236
237		if (td->opcode > 0) {
238			int ret;
239
240			set_current_state(TASK_RUNNING);
241			ret = handle_op(td, 1);
242			set_current_state(TASK_INTERRUPTIBLE);
243			if (td->opcode == RTTEST_LOCKCONT)
244				break;
245			td->opcode = ret;
246		}
247
248		/* Wait for the next command to be executed */
249		schedule();
250	}
251
252	/* Restore previous command and data */
253	td->opcode = op;
254	td->opdata = dat;
255}
256
257static int test_func(void *data)
258{
259	struct test_thread_data *td = data;
260	int ret;
261
262	current->flags |= PF_MUTEX_TESTER;
263	set_freezable();
264	allow_signal(SIGHUP);
265
266	for(;;) {
267
268		set_current_state(TASK_INTERRUPTIBLE);
269
270		if (td->opcode > 0) {
271			set_current_state(TASK_RUNNING);
272			ret = handle_op(td, 0);
273			set_current_state(TASK_INTERRUPTIBLE);
274			td->opcode = ret;
275		}
276
277		/* Wait for the next command to be executed */
278		schedule();
279		try_to_freeze();
280
281		if (signal_pending(current))
282			flush_signals(current);
283
284		if(kthread_should_stop())
285			break;
286	}
287	return 0;
288}
289
290/**
291 * sysfs_test_command - interface for test commands
292 * @dev:	thread reference
293 * @buf:	command for actual step
294 * @count:	length of buffer
295 *
296 * command syntax:
297 *
298 * opcode:data
299 */
300static ssize_t sysfs_test_command(struct sys_device *dev, struct sysdev_attribute *attr,
301				  const char *buf, size_t count)
302{
303	struct sched_param schedpar;
304	struct test_thread_data *td;
305	char cmdbuf[32];
306	int op, dat, tid, ret;
307
308	td = container_of(dev, struct test_thread_data, sysdev);
309	tid = td->sysdev.id;
310
311	/* strings from sysfs write are not 0 terminated! */
312	if (count >= sizeof(cmdbuf))
313		return -EINVAL;
314
315	/* strip of \n: */
316	if (buf[count-1] == '\n')
317		count--;
318	if (count < 1)
319		return -EINVAL;
320
321	memcpy(cmdbuf, buf, count);
322	cmdbuf[count] = 0;
323
324	if (sscanf(cmdbuf, "%d:%d", &op, &dat) != 2)
325		return -EINVAL;
326
327	switch (op) {
328	case RTTEST_SCHEDOT:
329		schedpar.sched_priority = 0;
330		ret = sched_setscheduler(threads[tid], SCHED_NORMAL, &schedpar);
331		if (ret)
332			return ret;
333		set_user_nice(current, 0);
334		break;
335
336	case RTTEST_SCHEDRT:
337		schedpar.sched_priority = dat;
338		ret = sched_setscheduler(threads[tid], SCHED_FIFO, &schedpar);
339		if (ret)
340			return ret;
341		break;
342
343	case RTTEST_SIGNAL:
344		send_sig(SIGHUP, threads[tid], 0);
345		break;
346
347	default:
348		if (td->opcode > 0)
349			return -EBUSY;
350		td->opdata = dat;
351		td->opcode = op;
352		wake_up_process(threads[tid]);
353	}
354
355	return count;
356}
357
358/**
359 * sysfs_test_status - sysfs interface for rt tester
360 * @dev:	thread to query
361 * @buf:	char buffer to be filled with thread status info
362 */
363static ssize_t sysfs_test_status(struct sys_device *dev, struct sysdev_attribute *attr,
364				 char *buf)
365{
366	struct test_thread_data *td;
367	struct task_struct *tsk;
368	char *curr = buf;
369	int i;
370
371	td = container_of(dev, struct test_thread_data, sysdev);
372	tsk = threads[td->sysdev.id];
373
374	spin_lock(&rttest_lock);
375
376	curr += sprintf(curr,
377		"O: %4d, E:%8d, S: 0x%08lx, P: %4d, N: %4d, B: %p, K: %d, M:",
378		td->opcode, td->event, tsk->state,
379			(MAX_RT_PRIO - 1) - tsk->prio,
380			(MAX_RT_PRIO - 1) - tsk->normal_prio,
381		tsk->pi_blocked_on, td->bkl);
382
383	for (i = MAX_RT_TEST_MUTEXES - 1; i >=0 ; i--)
384		curr += sprintf(curr, "%d", td->mutexes[i]);
385
386	spin_unlock(&rttest_lock);
387
388	curr += sprintf(curr, ", T: %p, R: %p\n", tsk,
389			mutexes[td->sysdev.id].owner);
390
391	return curr - buf;
392}
393
394static SYSDEV_ATTR(status, 0600, sysfs_test_status, NULL);
395static SYSDEV_ATTR(command, 0600, NULL, sysfs_test_command);
396
397static struct sysdev_class rttest_sysclass = {
398	.name = "rttest",
399};
400
401static int init_test_thread(int id)
402{
403	thread_data[id].sysdev.cls = &rttest_sysclass;
404	thread_data[id].sysdev.id = id;
405
406	threads[id] = kthread_run(test_func, &thread_data[id], "rt-test-%d", id);
407	if (IS_ERR(threads[id]))
408		return PTR_ERR(threads[id]);
409
410	return sysdev_register(&thread_data[id].sysdev);
411}
412
413static int init_rttest(void)
414{
415	int ret, i;
416
417	spin_lock_init(&rttest_lock);
418
419	for (i = 0; i < MAX_RT_TEST_MUTEXES; i++)
420		rt_mutex_init(&mutexes[i]);
421
422	ret = sysdev_class_register(&rttest_sysclass);
423	if (ret)
424		return ret;
425
426	for (i = 0; i < MAX_RT_TEST_THREADS; i++) {
427		ret = init_test_thread(i);
428		if (ret)
429			break;
430		ret = sysdev_create_file(&thread_data[i].sysdev, &attr_status);
431		if (ret)
432			break;
433		ret = sysdev_create_file(&thread_data[i].sysdev, &attr_command);
434		if (ret)
435			break;
436	}
437
438	printk("Initializing RT-Tester: %s\n", ret ? "Failed" : "OK" );
439
440	return ret;
441}
442
443device_initcall(init_rttest);
444