1/*
2 * udelay() test kernel module
3 *
4 * Test is executed by writing and reading to /sys/kernel/debug/udelay_test
5 * Tests are configured by writing: USECS ITERATIONS
6 * Tests are executed by reading from the same file.
7 * Specifying usecs of 0 or negative values will run multiples tests.
8 *
9 * Copyright (C) 2014 Google, Inc.
10 *
11 * This software is licensed under the terms of the GNU General Public
12 * License version 2, as published by the Free Software Foundation, and
13 * may be copied, distributed, and modified under those terms.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 */
20
21#include <linux/debugfs.h>
22#include <linux/delay.h>
23#include <linux/ktime.h>
24#include <linux/module.h>
25#include <linux/uaccess.h>
26
27#define DEFAULT_ITERATIONS 100
28
29#define DEBUGFS_FILENAME "udelay_test"
30
31static DEFINE_MUTEX(udelay_test_lock);
32static struct dentry *udelay_test_debugfs_file;
33static int udelay_test_usecs;
34static int udelay_test_iterations = DEFAULT_ITERATIONS;
35
36static int udelay_test_single(struct seq_file *s, int usecs, uint32_t iters)
37{
38	int min = 0, max = 0, fail_count = 0;
39	uint64_t sum = 0;
40	uint64_t avg;
41	int i;
42	/* Allow udelay to be up to 0.5% fast */
43	int allowed_error_ns = usecs * 5;
44
45	for (i = 0; i < iters; ++i) {
46		struct timespec ts1, ts2;
47		int time_passed;
48
49		ktime_get_ts(&ts1);
50		udelay(usecs);
51		ktime_get_ts(&ts2);
52		time_passed = timespec_to_ns(&ts2) - timespec_to_ns(&ts1);
53
54		if (i == 0 || time_passed < min)
55			min = time_passed;
56		if (i == 0 || time_passed > max)
57			max = time_passed;
58		if ((time_passed + allowed_error_ns) / 1000 < usecs)
59			++fail_count;
60		WARN_ON(time_passed < 0);
61		sum += time_passed;
62	}
63
64	avg = sum;
65	do_div(avg, iters);
66	seq_printf(s, "%d usecs x %d: exp=%d allowed=%d min=%d avg=%lld max=%d",
67			usecs, iters, usecs * 1000,
68			(usecs * 1000) - allowed_error_ns, min, avg, max);
69	if (fail_count)
70		seq_printf(s, " FAIL=%d", fail_count);
71	seq_puts(s, "\n");
72
73	return 0;
74}
75
76static int udelay_test_show(struct seq_file *s, void *v)
77{
78	int usecs;
79	int iters;
80	int ret = 0;
81
82	mutex_lock(&udelay_test_lock);
83	usecs = udelay_test_usecs;
84	iters = udelay_test_iterations;
85	mutex_unlock(&udelay_test_lock);
86
87	if (usecs > 0 && iters > 0) {
88		return udelay_test_single(s, usecs, iters);
89	} else if (usecs == 0) {
90		struct timespec ts;
91
92		ktime_get_ts(&ts);
93		seq_printf(s, "udelay() test (lpj=%ld kt=%ld.%09ld)\n",
94				loops_per_jiffy, ts.tv_sec, ts.tv_nsec);
95		seq_puts(s, "usage:\n");
96		seq_puts(s, "echo USECS [ITERS] > " DEBUGFS_FILENAME "\n");
97		seq_puts(s, "cat " DEBUGFS_FILENAME "\n");
98	}
99
100	return ret;
101}
102
103static int udelay_test_open(struct inode *inode, struct file *file)
104{
105	return single_open(file, udelay_test_show, inode->i_private);
106}
107
108static ssize_t udelay_test_write(struct file *file, const char __user *buf,
109		size_t count, loff_t *pos)
110{
111	char lbuf[32];
112	int ret;
113	int usecs;
114	int iters;
115
116	if (count >= sizeof(lbuf))
117		return -EINVAL;
118
119	if (copy_from_user(lbuf, buf, count))
120		return -EFAULT;
121	lbuf[count] = '\0';
122
123	ret = sscanf(lbuf, "%d %d", &usecs, &iters);
124	if (ret < 1)
125		return -EINVAL;
126	else if (ret < 2)
127		iters = DEFAULT_ITERATIONS;
128
129	mutex_lock(&udelay_test_lock);
130	udelay_test_usecs = usecs;
131	udelay_test_iterations = iters;
132	mutex_unlock(&udelay_test_lock);
133
134	return count;
135}
136
137static const struct file_operations udelay_test_debugfs_ops = {
138	.owner = THIS_MODULE,
139	.open = udelay_test_open,
140	.read = seq_read,
141	.write = udelay_test_write,
142	.llseek = seq_lseek,
143	.release = single_release,
144};
145
146static int __init udelay_test_init(void)
147{
148	mutex_lock(&udelay_test_lock);
149	udelay_test_debugfs_file = debugfs_create_file(DEBUGFS_FILENAME,
150			S_IRUSR, NULL, NULL, &udelay_test_debugfs_ops);
151	mutex_unlock(&udelay_test_lock);
152
153	return 0;
154}
155
156module_init(udelay_test_init);
157
158static void __exit udelay_test_exit(void)
159{
160	mutex_lock(&udelay_test_lock);
161	debugfs_remove(udelay_test_debugfs_file);
162	mutex_unlock(&udelay_test_lock);
163}
164
165module_exit(udelay_test_exit);
166
167MODULE_AUTHOR("David Riley <davidriley@chromium.org>");
168MODULE_LICENSE("GPL");
169