diff options
Diffstat (limited to 'litmus/litmus.c')
-rw-r--r-- | litmus/litmus.c | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/litmus/litmus.c b/litmus/litmus.c new file mode 100644 index 000000000000..7417a8fbda74 --- /dev/null +++ b/litmus/litmus.c | |||
@@ -0,0 +1,576 @@ | |||
1 | /* | ||
2 | * litmus.c -- Implementation of the LITMUS syscalls, | ||
3 | * the LITMUS intialization code, | ||
4 | * and the procfs interface.. | ||
5 | */ | ||
6 | #include <asm/uaccess.h> | ||
7 | #include <linux/uaccess.h> | ||
8 | #include <linux/sysrq.h> | ||
9 | #include <linux/sched.h> | ||
10 | #include <linux/module.h> | ||
11 | #include <linux/slab.h> | ||
12 | #include <linux/reboot.h> | ||
13 | #include <linux/stop_machine.h> | ||
14 | |||
15 | #include <litmus/litmus.h> | ||
16 | #include <litmus/bheap.h> | ||
17 | #include <litmus/trace.h> | ||
18 | #include <litmus/rt_domain.h> | ||
19 | #include <litmus/litmus_proc.h> | ||
20 | #include <litmus/sched_trace.h> | ||
21 | |||
22 | #ifdef CONFIG_SCHED_CPU_AFFINITY | ||
23 | #include <litmus/affinity.h> | ||
24 | #endif | ||
25 | |||
26 | /* Number of RT tasks that exist in the system */ | ||
27 | atomic_t rt_task_count = ATOMIC_INIT(0); | ||
28 | |||
29 | #ifdef CONFIG_RELEASE_MASTER | ||
30 | /* current master CPU for handling timer IRQs */ | ||
31 | atomic_t release_master_cpu = ATOMIC_INIT(NO_CPU); | ||
32 | #endif | ||
33 | |||
34 | static struct kmem_cache * bheap_node_cache; | ||
35 | extern struct kmem_cache * release_heap_cache; | ||
36 | |||
37 | struct bheap_node* bheap_node_alloc(int gfp_flags) | ||
38 | { | ||
39 | return kmem_cache_alloc(bheap_node_cache, gfp_flags); | ||
40 | } | ||
41 | |||
42 | void bheap_node_free(struct bheap_node* hn) | ||
43 | { | ||
44 | kmem_cache_free(bheap_node_cache, hn); | ||
45 | } | ||
46 | |||
47 | struct release_heap* release_heap_alloc(int gfp_flags); | ||
48 | void release_heap_free(struct release_heap* rh); | ||
49 | |||
50 | /* | ||
51 | * sys_set_task_rt_param | ||
52 | * @pid: Pid of the task which scheduling parameters must be changed | ||
53 | * @param: New real-time extension parameters such as the execution cost and | ||
54 | * period | ||
55 | * Syscall for manipulating with task rt extension params | ||
56 | * Returns EFAULT if param is NULL. | ||
57 | * ESRCH if pid is not corrsponding | ||
58 | * to a valid task. | ||
59 | * EINVAL if either period or execution cost is <=0 | ||
60 | * EPERM if pid is a real-time task | ||
61 | * 0 if success | ||
62 | * | ||
63 | * Only non-real-time tasks may be configured with this system call | ||
64 | * to avoid races with the scheduler. In practice, this means that a | ||
65 | * task's parameters must be set _before_ calling sys_prepare_rt_task() | ||
66 | * | ||
67 | * find_task_by_vpid() assumes that we are in the same namespace of the | ||
68 | * target. | ||
69 | */ | ||
70 | asmlinkage long sys_set_rt_task_param(pid_t pid, struct rt_task __user * param) | ||
71 | { | ||
72 | struct rt_task tp; | ||
73 | struct task_struct *target; | ||
74 | int retval = -EINVAL; | ||
75 | |||
76 | printk("Setting up rt task parameters for process %d.\n", pid); | ||
77 | |||
78 | if (pid < 0 || param == 0) { | ||
79 | goto out; | ||
80 | } | ||
81 | if (copy_from_user(&tp, param, sizeof(tp))) { | ||
82 | retval = -EFAULT; | ||
83 | goto out; | ||
84 | } | ||
85 | |||
86 | /* Task search and manipulation must be protected */ | ||
87 | read_lock_irq(&tasklist_lock); | ||
88 | if (!(target = find_task_by_vpid(pid))) { | ||
89 | retval = -ESRCH; | ||
90 | goto out_unlock; | ||
91 | } | ||
92 | |||
93 | if (is_realtime(target)) { | ||
94 | /* The task is already a real-time task. | ||
95 | * We cannot not allow parameter changes at this point. | ||
96 | */ | ||
97 | retval = -EBUSY; | ||
98 | goto out_unlock; | ||
99 | } | ||
100 | |||
101 | /* set relative deadline to be implicit if left unspecified */ | ||
102 | if (tp.relative_deadline == 0) | ||
103 | tp.relative_deadline = tp.period; | ||
104 | |||
105 | if (tp.exec_cost <= 0) | ||
106 | goto out_unlock; | ||
107 | if (tp.period <= 0) | ||
108 | goto out_unlock; | ||
109 | if (!cpu_online(tp.cpu)) | ||
110 | goto out_unlock; | ||
111 | if (min(tp.relative_deadline, tp.period) < tp.exec_cost) /*density check*/ | ||
112 | { | ||
113 | printk(KERN_INFO "litmus: real-time task %d rejected " | ||
114 | "because task density > 1.0\n", pid); | ||
115 | goto out_unlock; | ||
116 | } | ||
117 | if (tp.cls != RT_CLASS_HARD && | ||
118 | tp.cls != RT_CLASS_SOFT && | ||
119 | tp.cls != RT_CLASS_BEST_EFFORT) | ||
120 | { | ||
121 | printk(KERN_INFO "litmus: real-time task %d rejected " | ||
122 | "because its class is invalid\n", pid); | ||
123 | goto out_unlock; | ||
124 | } | ||
125 | if (tp.budget_policy != NO_ENFORCEMENT && | ||
126 | tp.budget_policy != QUANTUM_ENFORCEMENT && | ||
127 | tp.budget_policy != PRECISE_ENFORCEMENT) | ||
128 | { | ||
129 | printk(KERN_INFO "litmus: real-time task %d rejected " | ||
130 | "because unsupported budget enforcement policy " | ||
131 | "specified (%d)\n", | ||
132 | pid, tp.budget_policy); | ||
133 | goto out_unlock; | ||
134 | } | ||
135 | |||
136 | target->rt_param.task_params = tp; | ||
137 | |||
138 | retval = 0; | ||
139 | out_unlock: | ||
140 | read_unlock_irq(&tasklist_lock); | ||
141 | out: | ||
142 | return retval; | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * Getter of task's RT params | ||
147 | * returns EINVAL if param or pid is NULL | ||
148 | * returns ESRCH if pid does not correspond to a valid task | ||
149 | * returns EFAULT if copying of parameters has failed. | ||
150 | * | ||
151 | * find_task_by_vpid() assumes that we are in the same namespace of the | ||
152 | * target. | ||
153 | */ | ||
154 | asmlinkage long sys_get_rt_task_param(pid_t pid, struct rt_task __user * param) | ||
155 | { | ||
156 | int retval = -EINVAL; | ||
157 | struct task_struct *source; | ||
158 | struct rt_task lp; | ||
159 | if (param == 0 || pid < 0) | ||
160 | goto out; | ||
161 | read_lock(&tasklist_lock); | ||
162 | if (!(source = find_task_by_vpid(pid))) { | ||
163 | retval = -ESRCH; | ||
164 | goto out_unlock; | ||
165 | } | ||
166 | lp = source->rt_param.task_params; | ||
167 | read_unlock(&tasklist_lock); | ||
168 | /* Do copying outside the lock */ | ||
169 | retval = | ||
170 | copy_to_user(param, &lp, sizeof(lp)) ? -EFAULT : 0; | ||
171 | return retval; | ||
172 | out_unlock: | ||
173 | read_unlock(&tasklist_lock); | ||
174 | out: | ||
175 | return retval; | ||
176 | |||
177 | } | ||
178 | |||
179 | /* | ||
180 | * This is the crucial function for periodic task implementation, | ||
181 | * It checks if a task is periodic, checks if such kind of sleep | ||
182 | * is permitted and calls plugin-specific sleep, which puts the | ||
183 | * task into a wait array. | ||
184 | * returns 0 on successful wakeup | ||
185 | * returns EPERM if current conditions do not permit such sleep | ||
186 | * returns EINVAL if current task is not able to go to sleep | ||
187 | */ | ||
188 | asmlinkage long sys_complete_job(void) | ||
189 | { | ||
190 | int retval = -EPERM; | ||
191 | if (!is_realtime(current)) { | ||
192 | retval = -EINVAL; | ||
193 | goto out; | ||
194 | } | ||
195 | /* Task with negative or zero period cannot sleep */ | ||
196 | if (get_rt_period(current) <= 0) { | ||
197 | retval = -EINVAL; | ||
198 | goto out; | ||
199 | } | ||
200 | /* The plugin has to put the task into an | ||
201 | * appropriate queue and call schedule | ||
202 | */ | ||
203 | retval = litmus->complete_job(); | ||
204 | out: | ||
205 | return retval; | ||
206 | } | ||
207 | |||
208 | /* This is an "improved" version of sys_complete_job that | ||
209 | * addresses the problem of unintentionally missing a job after | ||
210 | * an overrun. | ||
211 | * | ||
212 | * returns 0 on successful wakeup | ||
213 | * returns EPERM if current conditions do not permit such sleep | ||
214 | * returns EINVAL if current task is not able to go to sleep | ||
215 | */ | ||
216 | asmlinkage long sys_wait_for_job_release(unsigned int job) | ||
217 | { | ||
218 | int retval = -EPERM; | ||
219 | if (!is_realtime(current)) { | ||
220 | retval = -EINVAL; | ||
221 | goto out; | ||
222 | } | ||
223 | |||
224 | /* Task with negative or zero period cannot sleep */ | ||
225 | if (get_rt_period(current) <= 0) { | ||
226 | retval = -EINVAL; | ||
227 | goto out; | ||
228 | } | ||
229 | |||
230 | retval = 0; | ||
231 | |||
232 | /* first wait until we have "reached" the desired job | ||
233 | * | ||
234 | * This implementation has at least two problems: | ||
235 | * | ||
236 | * 1) It doesn't gracefully handle the wrap around of | ||
237 | * job_no. Since LITMUS is a prototype, this is not much | ||
238 | * of a problem right now. | ||
239 | * | ||
240 | * 2) It is theoretically racy if a job release occurs | ||
241 | * between checking job_no and calling sleep_next_period(). | ||
242 | * A proper solution would requiring adding another callback | ||
243 | * in the plugin structure and testing the condition with | ||
244 | * interrupts disabled. | ||
245 | * | ||
246 | * FIXME: At least problem 2 should be taken care of eventually. | ||
247 | */ | ||
248 | while (!retval && job > current->rt_param.job_params.job_no) | ||
249 | /* If the last job overran then job <= job_no and we | ||
250 | * don't send the task to sleep. | ||
251 | */ | ||
252 | retval = litmus->complete_job(); | ||
253 | out: | ||
254 | return retval; | ||
255 | } | ||
256 | |||
257 | /* This is a helper syscall to query the current job sequence number. | ||
258 | * | ||
259 | * returns 0 on successful query | ||
260 | * returns EPERM if task is not a real-time task. | ||
261 | * returns EFAULT if &job is not a valid pointer. | ||
262 | */ | ||
263 | asmlinkage long sys_query_job_no(unsigned int __user *job) | ||
264 | { | ||
265 | int retval = -EPERM; | ||
266 | if (is_realtime(current)) | ||
267 | retval = put_user(current->rt_param.job_params.job_no, job); | ||
268 | |||
269 | return retval; | ||
270 | } | ||
271 | |||
272 | /* sys_null_call() is only used for determining raw system call | ||
273 | * overheads (kernel entry, kernel exit). It has no useful side effects. | ||
274 | * If ts is non-NULL, then the current Feather-Trace time is recorded. | ||
275 | */ | ||
276 | asmlinkage long sys_null_call(cycles_t __user *ts) | ||
277 | { | ||
278 | long ret = 0; | ||
279 | cycles_t now; | ||
280 | |||
281 | if (ts) { | ||
282 | now = get_cycles(); | ||
283 | ret = put_user(now, ts); | ||
284 | } | ||
285 | |||
286 | return ret; | ||
287 | } | ||
288 | |||
289 | /* p is a real-time task. Re-init its state as a best-effort task. */ | ||
290 | static void reinit_litmus_state(struct task_struct* p, int restore) | ||
291 | { | ||
292 | struct rt_task user_config = {}; | ||
293 | void* ctrl_page = NULL; | ||
294 | |||
295 | if (restore) { | ||
296 | /* Safe user-space provided configuration data. | ||
297 | * and allocated page. */ | ||
298 | user_config = p->rt_param.task_params; | ||
299 | ctrl_page = p->rt_param.ctrl_page; | ||
300 | } | ||
301 | |||
302 | /* We probably should not be inheriting any task's priority | ||
303 | * at this point in time. | ||
304 | */ | ||
305 | WARN_ON(p->rt_param.inh_task); | ||
306 | |||
307 | /* Cleanup everything else. */ | ||
308 | memset(&p->rt_param, 0, sizeof(p->rt_param)); | ||
309 | |||
310 | /* Restore preserved fields. */ | ||
311 | if (restore) { | ||
312 | p->rt_param.task_params = user_config; | ||
313 | p->rt_param.ctrl_page = ctrl_page; | ||
314 | } | ||
315 | } | ||
316 | |||
317 | long litmus_admit_task(struct task_struct* tsk) | ||
318 | { | ||
319 | long retval = 0; | ||
320 | |||
321 | BUG_ON(is_realtime(tsk)); | ||
322 | |||
323 | tsk_rt(tsk)->heap_node = NULL; | ||
324 | tsk_rt(tsk)->rel_heap = NULL; | ||
325 | |||
326 | if (get_rt_relative_deadline(tsk) == 0 || | ||
327 | get_exec_cost(tsk) > | ||
328 | min(get_rt_relative_deadline(tsk), get_rt_period(tsk)) ) { | ||
329 | TRACE_TASK(tsk, | ||
330 | "litmus admit: invalid task parameters " | ||
331 | "(e = %lu, p = %lu, d = %lu)\n", | ||
332 | get_exec_cost(tsk), get_rt_period(tsk), | ||
333 | get_rt_relative_deadline(tsk)); | ||
334 | retval = -EINVAL; | ||
335 | goto out; | ||
336 | } | ||
337 | |||
338 | if (!cpu_online(get_partition(tsk))) { | ||
339 | TRACE_TASK(tsk, "litmus admit: cpu %d is not online\n", | ||
340 | get_partition(tsk)); | ||
341 | retval = -EINVAL; | ||
342 | goto out; | ||
343 | } | ||
344 | |||
345 | INIT_LIST_HEAD(&tsk_rt(tsk)->list); | ||
346 | |||
347 | /* allocate heap node for this task */ | ||
348 | tsk_rt(tsk)->heap_node = bheap_node_alloc(GFP_ATOMIC); | ||
349 | tsk_rt(tsk)->rel_heap = release_heap_alloc(GFP_ATOMIC); | ||
350 | |||
351 | if (!tsk_rt(tsk)->heap_node || !tsk_rt(tsk)->rel_heap) { | ||
352 | printk(KERN_WARNING "litmus: no more heap node memory!?\n"); | ||
353 | |||
354 | retval = -ENOMEM; | ||
355 | goto out; | ||
356 | } else { | ||
357 | bheap_node_init(&tsk_rt(tsk)->heap_node, tsk); | ||
358 | } | ||
359 | |||
360 | preempt_disable(); | ||
361 | |||
362 | retval = litmus->admit_task(tsk); | ||
363 | |||
364 | if (!retval) { | ||
365 | sched_trace_task_name(tsk); | ||
366 | sched_trace_task_param(tsk); | ||
367 | atomic_inc(&rt_task_count); | ||
368 | } | ||
369 | |||
370 | preempt_enable(); | ||
371 | |||
372 | out: | ||
373 | if (retval) { | ||
374 | bheap_node_free(tsk_rt(tsk)->heap_node); | ||
375 | release_heap_free(tsk_rt(tsk)->rel_heap); | ||
376 | } | ||
377 | return retval; | ||
378 | } | ||
379 | |||
380 | void litmus_exit_task(struct task_struct* tsk) | ||
381 | { | ||
382 | if (is_realtime(tsk)) { | ||
383 | sched_trace_task_completion(tsk, 1); | ||
384 | |||
385 | litmus->task_exit(tsk); | ||
386 | |||
387 | BUG_ON(bheap_node_in_heap(tsk_rt(tsk)->heap_node)); | ||
388 | bheap_node_free(tsk_rt(tsk)->heap_node); | ||
389 | release_heap_free(tsk_rt(tsk)->rel_heap); | ||
390 | |||
391 | atomic_dec(&rt_task_count); | ||
392 | reinit_litmus_state(tsk, 1); | ||
393 | } | ||
394 | } | ||
395 | |||
396 | static int do_plugin_switch(void *_plugin) | ||
397 | { | ||
398 | int ret; | ||
399 | struct sched_plugin* plugin = _plugin; | ||
400 | |||
401 | /* don't switch if there are active real-time tasks */ | ||
402 | if (atomic_read(&rt_task_count) == 0) { | ||
403 | ret = litmus->deactivate_plugin(); | ||
404 | if (0 != ret) | ||
405 | goto out; | ||
406 | ret = plugin->activate_plugin(); | ||
407 | if (0 != ret) { | ||
408 | printk(KERN_INFO "Can't activate %s (%d).\n", | ||
409 | plugin->plugin_name, ret); | ||
410 | plugin = &linux_sched_plugin; | ||
411 | } | ||
412 | printk(KERN_INFO "Switching to LITMUS^RT plugin %s.\n", plugin->plugin_name); | ||
413 | litmus = plugin; | ||
414 | } else | ||
415 | ret = -EBUSY; | ||
416 | out: | ||
417 | return ret; | ||
418 | } | ||
419 | |||
420 | /* Switching a plugin in use is tricky. | ||
421 | * We must watch out that no real-time tasks exists | ||
422 | * (and that none is created in parallel) and that the plugin is not | ||
423 | * currently in use on any processor (in theory). | ||
424 | */ | ||
425 | int switch_sched_plugin(struct sched_plugin* plugin) | ||
426 | { | ||
427 | BUG_ON(!plugin); | ||
428 | |||
429 | if (atomic_read(&rt_task_count) == 0) | ||
430 | return stop_machine(do_plugin_switch, plugin, NULL); | ||
431 | else | ||
432 | return -EBUSY; | ||
433 | } | ||
434 | |||
435 | /* Called upon fork. | ||
436 | * p is the newly forked task. | ||
437 | */ | ||
438 | void litmus_fork(struct task_struct* p) | ||
439 | { | ||
440 | if (is_realtime(p)) { | ||
441 | /* clean out any litmus related state, don't preserve anything */ | ||
442 | reinit_litmus_state(p, 0); | ||
443 | /* Don't let the child be a real-time task. */ | ||
444 | p->sched_reset_on_fork = 1; | ||
445 | } else | ||
446 | /* non-rt tasks might have ctrl_page set */ | ||
447 | tsk_rt(p)->ctrl_page = NULL; | ||
448 | |||
449 | /* od tables are never inherited across a fork */ | ||
450 | p->od_table = NULL; | ||
451 | } | ||
452 | |||
453 | /* Called upon execve(). | ||
454 | * current is doing the exec. | ||
455 | * Don't let address space specific stuff leak. | ||
456 | */ | ||
457 | void litmus_exec(void) | ||
458 | { | ||
459 | struct task_struct* p = current; | ||
460 | |||
461 | if (is_realtime(p)) { | ||
462 | WARN_ON(p->rt_param.inh_task); | ||
463 | if (tsk_rt(p)->ctrl_page) { | ||
464 | free_page((unsigned long) tsk_rt(p)->ctrl_page); | ||
465 | tsk_rt(p)->ctrl_page = NULL; | ||
466 | } | ||
467 | } | ||
468 | } | ||
469 | |||
470 | void exit_litmus(struct task_struct *dead_tsk) | ||
471 | { | ||
472 | /* We also allow non-RT tasks to | ||
473 | * allocate control pages to allow | ||
474 | * measurements with non-RT tasks. | ||
475 | * So check if we need to free the page | ||
476 | * in any case. | ||
477 | */ | ||
478 | if (tsk_rt(dead_tsk)->ctrl_page) { | ||
479 | TRACE_TASK(dead_tsk, | ||
480 | "freeing ctrl_page %p\n", | ||
481 | tsk_rt(dead_tsk)->ctrl_page); | ||
482 | free_page((unsigned long) tsk_rt(dead_tsk)->ctrl_page); | ||
483 | } | ||
484 | |||
485 | /* main cleanup only for RT tasks */ | ||
486 | if (is_realtime(dead_tsk)) | ||
487 | litmus_exit_task(dead_tsk); | ||
488 | } | ||
489 | |||
490 | |||
491 | #ifdef CONFIG_MAGIC_SYSRQ | ||
492 | int sys_kill(int pid, int sig); | ||
493 | |||
494 | static void sysrq_handle_kill_rt_tasks(int key) | ||
495 | { | ||
496 | struct task_struct *t; | ||
497 | read_lock(&tasklist_lock); | ||
498 | for_each_process(t) { | ||
499 | if (is_realtime(t)) { | ||
500 | sys_kill(t->pid, SIGKILL); | ||
501 | } | ||
502 | } | ||
503 | read_unlock(&tasklist_lock); | ||
504 | } | ||
505 | |||
506 | static struct sysrq_key_op sysrq_kill_rt_tasks_op = { | ||
507 | .handler = sysrq_handle_kill_rt_tasks, | ||
508 | .help_msg = "quit-rt-tasks(X)", | ||
509 | .action_msg = "sent SIGKILL to all LITMUS^RT real-time tasks", | ||
510 | }; | ||
511 | #endif | ||
512 | |||
513 | extern struct sched_plugin linux_sched_plugin; | ||
514 | |||
515 | static int litmus_shutdown_nb(struct notifier_block *unused1, | ||
516 | unsigned long unused2, void *unused3) | ||
517 | { | ||
518 | /* Attempt to switch back to regular Linux scheduling. | ||
519 | * Forces the active plugin to clean up. | ||
520 | */ | ||
521 | if (litmus != &linux_sched_plugin) { | ||
522 | int ret = switch_sched_plugin(&linux_sched_plugin); | ||
523 | if (ret) { | ||
524 | printk("Auto-shutdown of active Litmus plugin failed.\n"); | ||
525 | } | ||
526 | } | ||
527 | return NOTIFY_DONE; | ||
528 | } | ||
529 | |||
530 | static struct notifier_block shutdown_notifier = { | ||
531 | .notifier_call = litmus_shutdown_nb, | ||
532 | }; | ||
533 | |||
534 | static int __init _init_litmus(void) | ||
535 | { | ||
536 | /* Common initializers, | ||
537 | * mode change lock is used to enforce single mode change | ||
538 | * operation. | ||
539 | */ | ||
540 | printk("Starting LITMUS^RT kernel\n"); | ||
541 | |||
542 | register_sched_plugin(&linux_sched_plugin); | ||
543 | |||
544 | bheap_node_cache = KMEM_CACHE(bheap_node, SLAB_PANIC); | ||
545 | release_heap_cache = KMEM_CACHE(release_heap, SLAB_PANIC); | ||
546 | |||
547 | #ifdef CONFIG_MAGIC_SYSRQ | ||
548 | /* offer some debugging help */ | ||
549 | if (!register_sysrq_key('x', &sysrq_kill_rt_tasks_op)) | ||
550 | printk("Registered kill rt tasks magic sysrq.\n"); | ||
551 | else | ||
552 | printk("Could not register kill rt tasks magic sysrq.\n"); | ||
553 | #endif | ||
554 | |||
555 | init_litmus_proc(); | ||
556 | |||
557 | #ifdef CONFIG_SCHED_CPU_AFFINITY | ||
558 | init_topology(); | ||
559 | #endif | ||
560 | |||
561 | register_reboot_notifier(&shutdown_notifier); | ||
562 | |||
563 | return 0; | ||
564 | } | ||
565 | |||
566 | static void _exit_litmus(void) | ||
567 | { | ||
568 | unregister_reboot_notifier(&shutdown_notifier); | ||
569 | |||
570 | exit_litmus_proc(); | ||
571 | kmem_cache_destroy(bheap_node_cache); | ||
572 | kmem_cache_destroy(release_heap_cache); | ||
573 | } | ||
574 | |||
575 | module_init(_init_litmus); | ||
576 | module_exit(_exit_litmus); | ||