diff options
Diffstat (limited to 'litmus/sched_trace.c')
-rw-r--r-- | litmus/sched_trace.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/litmus/sched_trace.c b/litmus/sched_trace.c new file mode 100644 index 00000000000..f4171fddbbb --- /dev/null +++ b/litmus/sched_trace.c | |||
@@ -0,0 +1,252 @@ | |||
1 | /* | ||
2 | * sched_trace.c -- record scheduling events to a byte stream. | ||
3 | */ | ||
4 | #include <linux/spinlock.h> | ||
5 | #include <linux/mutex.h> | ||
6 | |||
7 | #include <linux/fs.h> | ||
8 | #include <linux/slab.h> | ||
9 | #include <linux/miscdevice.h> | ||
10 | #include <asm/uaccess.h> | ||
11 | #include <linux/module.h> | ||
12 | #include <linux/sysrq.h> | ||
13 | |||
14 | #include <linux/kfifo.h> | ||
15 | |||
16 | #include <litmus/sched_trace.h> | ||
17 | #include <litmus/litmus.h> | ||
18 | |||
19 | #define SCHED_TRACE_NAME "litmus/log" | ||
20 | |||
21 | /* Compute size of TRACE() buffer */ | ||
22 | #define LITMUS_TRACE_BUF_SIZE (1 << CONFIG_SCHED_DEBUG_TRACE_SHIFT) | ||
23 | |||
24 | /* Max length of one read from the buffer */ | ||
25 | #define MAX_READ_LEN (64 * 1024) | ||
26 | |||
27 | /* Max length for one write --- by TRACE() --- to the buffer. This is used to | ||
28 | * allocate a per-cpu buffer for printf() formatting. */ | ||
29 | #define MSG_SIZE 255 | ||
30 | |||
31 | |||
32 | static DEFINE_MUTEX(reader_mutex); | ||
33 | static atomic_t reader_cnt = ATOMIC_INIT(0); | ||
34 | static DEFINE_KFIFO(debug_buffer, char, LITMUS_TRACE_BUF_SIZE); | ||
35 | |||
36 | |||
37 | static DEFINE_RAW_SPINLOCK(log_buffer_lock); | ||
38 | static DEFINE_PER_CPU(char[MSG_SIZE], fmt_buffer); | ||
39 | |||
40 | /* | ||
41 | * sched_trace_log_message - Write to the trace buffer (log_buffer) | ||
42 | * | ||
43 | * This is the only function accessing the log_buffer from inside the | ||
44 | * kernel for writing. | ||
45 | * Concurrent access to sched_trace_log_message must be serialized using | ||
46 | * log_buffer_lock | ||
47 | * The maximum length of a formatted message is 255 | ||
48 | */ | ||
49 | void sched_trace_log_message(const char* fmt, ...) | ||
50 | { | ||
51 | unsigned long flags; | ||
52 | va_list args; | ||
53 | size_t len; | ||
54 | char* buf; | ||
55 | |||
56 | if (!atomic_read(&reader_cnt)) | ||
57 | /* early exit if nobody is listening */ | ||
58 | return; | ||
59 | |||
60 | va_start(args, fmt); | ||
61 | local_irq_save(flags); | ||
62 | |||
63 | /* format message */ | ||
64 | buf = __get_cpu_var(fmt_buffer); | ||
65 | len = vscnprintf(buf, MSG_SIZE, fmt, args); | ||
66 | |||
67 | raw_spin_lock(&log_buffer_lock); | ||
68 | /* Don't copy the trailing null byte, we don't want null bytes in a | ||
69 | * text file. | ||
70 | */ | ||
71 | kfifo_in(&debug_buffer, buf, len); | ||
72 | raw_spin_unlock(&log_buffer_lock); | ||
73 | |||
74 | local_irq_restore(flags); | ||
75 | va_end(args); | ||
76 | } | ||
77 | |||
78 | |||
79 | /* | ||
80 | * log_read - Read the trace buffer | ||
81 | * | ||
82 | * This function is called as a file operation from userspace. | ||
83 | * Readers can sleep. Access is serialized through reader_mutex | ||
84 | */ | ||
85 | static ssize_t log_read(struct file *filp, | ||
86 | char __user *to, size_t len, | ||
87 | loff_t *f_pos) | ||
88 | { | ||
89 | /* we ignore f_pos, this is strictly sequential */ | ||
90 | |||
91 | ssize_t error = -EINVAL; | ||
92 | char* mem; | ||
93 | |||
94 | if (mutex_lock_interruptible(&reader_mutex)) { | ||
95 | error = -ERESTARTSYS; | ||
96 | goto out; | ||
97 | } | ||
98 | |||
99 | if (len > MAX_READ_LEN) | ||
100 | len = MAX_READ_LEN; | ||
101 | |||
102 | mem = kmalloc(len, GFP_KERNEL); | ||
103 | if (!mem) { | ||
104 | error = -ENOMEM; | ||
105 | goto out_unlock; | ||
106 | } | ||
107 | |||
108 | error = kfifo_out(&debug_buffer, mem, len); | ||
109 | while (!error) { | ||
110 | set_current_state(TASK_INTERRUPTIBLE); | ||
111 | schedule_timeout(110); | ||
112 | if (signal_pending(current)) | ||
113 | error = -ERESTARTSYS; | ||
114 | else | ||
115 | error = kfifo_out(&debug_buffer, mem, len); | ||
116 | } | ||
117 | |||
118 | if (error > 0 && copy_to_user(to, mem, error)) | ||
119 | error = -EFAULT; | ||
120 | |||
121 | kfree(mem); | ||
122 | out_unlock: | ||
123 | mutex_unlock(&reader_mutex); | ||
124 | out: | ||
125 | return error; | ||
126 | } | ||
127 | |||
128 | /* | ||
129 | * Enable redirection of printk() messages to the trace buffer. | ||
130 | * Defined in kernel/printk.c | ||
131 | */ | ||
132 | extern int trace_override; | ||
133 | extern int trace_recurse; | ||
134 | |||
135 | /* | ||
136 | * log_open - open the global log message ring buffer. | ||
137 | */ | ||
138 | static int log_open(struct inode *in, struct file *filp) | ||
139 | { | ||
140 | int error = -EINVAL; | ||
141 | |||
142 | if (mutex_lock_interruptible(&reader_mutex)) { | ||
143 | error = -ERESTARTSYS; | ||
144 | goto out; | ||
145 | } | ||
146 | |||
147 | atomic_inc(&reader_cnt); | ||
148 | error = 0; | ||
149 | |||
150 | printk(KERN_DEBUG | ||
151 | "sched_trace kfifo with buffer starting at: 0x%p\n", | ||
152 | debug_buffer.buf); | ||
153 | |||
154 | /* override printk() */ | ||
155 | trace_override++; | ||
156 | |||
157 | mutex_unlock(&reader_mutex); | ||
158 | out: | ||
159 | return error; | ||
160 | } | ||
161 | |||
162 | static int log_release(struct inode *in, struct file *filp) | ||
163 | { | ||
164 | int error = -EINVAL; | ||
165 | |||
166 | if (mutex_lock_interruptible(&reader_mutex)) { | ||
167 | error = -ERESTARTSYS; | ||
168 | goto out; | ||
169 | } | ||
170 | |||
171 | atomic_dec(&reader_cnt); | ||
172 | |||
173 | /* release printk() overriding */ | ||
174 | trace_override--; | ||
175 | |||
176 | printk(KERN_DEBUG "sched_trace kfifo released\n"); | ||
177 | |||
178 | mutex_unlock(&reader_mutex); | ||
179 | out: | ||
180 | return error; | ||
181 | } | ||
182 | |||
183 | /* | ||
184 | * log_fops - The file operations for accessing the global LITMUS log message | ||
185 | * buffer. | ||
186 | * | ||
187 | * Except for opening the device file it uses the same operations as trace_fops. | ||
188 | */ | ||
189 | static struct file_operations log_fops = { | ||
190 | .owner = THIS_MODULE, | ||
191 | .open = log_open, | ||
192 | .release = log_release, | ||
193 | .read = log_read, | ||
194 | }; | ||
195 | |||
196 | static struct miscdevice litmus_log_dev = { | ||
197 | .name = SCHED_TRACE_NAME, | ||
198 | .minor = MISC_DYNAMIC_MINOR, | ||
199 | .fops = &log_fops, | ||
200 | }; | ||
201 | |||
202 | #ifdef CONFIG_MAGIC_SYSRQ | ||
203 | void dump_trace_buffer(int max) | ||
204 | { | ||
205 | char line[80]; | ||
206 | int len; | ||
207 | int count = 0; | ||
208 | |||
209 | /* potential, but very unlikely, race... */ | ||
210 | trace_recurse = 1; | ||
211 | while ((max == 0 || count++ < max) && | ||
212 | (len = kfifo_out(&debug_buffer, line, sizeof(line - 1))) > 0) { | ||
213 | line[len] = '\0'; | ||
214 | printk("%s", line); | ||
215 | } | ||
216 | trace_recurse = 0; | ||
217 | } | ||
218 | |||
219 | static void sysrq_dump_trace_buffer(int key) | ||
220 | { | ||
221 | dump_trace_buffer(100); | ||
222 | } | ||
223 | |||
224 | static struct sysrq_key_op sysrq_dump_trace_buffer_op = { | ||
225 | .handler = sysrq_dump_trace_buffer, | ||
226 | .help_msg = "dump-trace-buffer(Y)", | ||
227 | .action_msg = "writing content of TRACE() buffer", | ||
228 | }; | ||
229 | #endif | ||
230 | |||
231 | static int __init init_sched_trace(void) | ||
232 | { | ||
233 | printk("Initializing TRACE() device\n"); | ||
234 | |||
235 | #ifdef CONFIG_MAGIC_SYSRQ | ||
236 | /* offer some debugging help */ | ||
237 | if (!register_sysrq_key('y', &sysrq_dump_trace_buffer_op)) | ||
238 | printk("Registered dump-trace-buffer(Y) magic sysrq.\n"); | ||
239 | else | ||
240 | printk("Could not register dump-trace-buffer(Y) magic sysrq.\n"); | ||
241 | #endif | ||
242 | |||
243 | return misc_register(&litmus_log_dev); | ||
244 | } | ||
245 | |||
246 | static void __exit exit_sched_trace(void) | ||
247 | { | ||
248 | misc_deregister(&litmus_log_dev); | ||
249 | } | ||
250 | |||
251 | module_init(init_sched_trace); | ||
252 | module_exit(exit_sched_trace); | ||