219 lines
4.5 KiB
C
219 lines
4.5 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Preempt / IRQ disable delay thread to test latency tracers
|
||
|
*
|
||
|
* Copyright (C) 2018 Joel Fernandes (Google) <joel@joelfernandes.org>
|
||
|
*/
|
||
|
|
||
|
#include <linux/trace_clock.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/irq.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/kobject.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/printk.h>
|
||
|
#include <linux/string.h>
|
||
|
#include <linux/sysfs.h>
|
||
|
#include <linux/completion.h>
|
||
|
|
||
|
static ulong delay = 100;
|
||
|
static char test_mode[12] = "irq";
|
||
|
static uint burst_size = 1;
|
||
|
static int cpu_affinity = -1;
|
||
|
|
||
|
module_param_named(delay, delay, ulong, 0444);
|
||
|
module_param_string(test_mode, test_mode, 12, 0444);
|
||
|
module_param_named(burst_size, burst_size, uint, 0444);
|
||
|
module_param_named(cpu_affinity, cpu_affinity, int, 0444);
|
||
|
MODULE_PARM_DESC(delay, "Period in microseconds (100 us default)");
|
||
|
MODULE_PARM_DESC(test_mode, "Mode of the test such as preempt, irq, or alternate (default irq)");
|
||
|
MODULE_PARM_DESC(burst_size, "The size of a burst (default 1)");
|
||
|
MODULE_PARM_DESC(cpu_affinity, "Cpu num test is running on");
|
||
|
|
||
|
static struct completion done;
|
||
|
|
||
|
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
||
|
|
||
|
static void busy_wait(ulong time)
|
||
|
{
|
||
|
u64 start, end;
|
||
|
|
||
|
start = trace_clock_local();
|
||
|
|
||
|
do {
|
||
|
end = trace_clock_local();
|
||
|
if (kthread_should_stop())
|
||
|
break;
|
||
|
} while ((end - start) < (time * 1000));
|
||
|
}
|
||
|
|
||
|
static __always_inline void irqoff_test(void)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
|
||
|
local_irq_save(flags);
|
||
|
busy_wait(delay);
|
||
|
local_irq_restore(flags);
|
||
|
}
|
||
|
|
||
|
static __always_inline void preemptoff_test(void)
|
||
|
{
|
||
|
preempt_disable();
|
||
|
busy_wait(delay);
|
||
|
preempt_enable();
|
||
|
}
|
||
|
|
||
|
static void execute_preemptirqtest(int idx)
|
||
|
{
|
||
|
if (!strcmp(test_mode, "irq"))
|
||
|
irqoff_test();
|
||
|
else if (!strcmp(test_mode, "preempt"))
|
||
|
preemptoff_test();
|
||
|
else if (!strcmp(test_mode, "alternate")) {
|
||
|
if (idx % 2 == 0)
|
||
|
irqoff_test();
|
||
|
else
|
||
|
preemptoff_test();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#define DECLARE_TESTFN(POSTFIX) \
|
||
|
static void preemptirqtest_##POSTFIX(int idx) \
|
||
|
{ \
|
||
|
execute_preemptirqtest(idx); \
|
||
|
} \
|
||
|
|
||
|
/*
|
||
|
* We create 10 different functions, so that we can get 10 different
|
||
|
* backtraces.
|
||
|
*/
|
||
|
DECLARE_TESTFN(0)
|
||
|
DECLARE_TESTFN(1)
|
||
|
DECLARE_TESTFN(2)
|
||
|
DECLARE_TESTFN(3)
|
||
|
DECLARE_TESTFN(4)
|
||
|
DECLARE_TESTFN(5)
|
||
|
DECLARE_TESTFN(6)
|
||
|
DECLARE_TESTFN(7)
|
||
|
DECLARE_TESTFN(8)
|
||
|
DECLARE_TESTFN(9)
|
||
|
|
||
|
static void (*testfuncs[])(int) = {
|
||
|
preemptirqtest_0,
|
||
|
preemptirqtest_1,
|
||
|
preemptirqtest_2,
|
||
|
preemptirqtest_3,
|
||
|
preemptirqtest_4,
|
||
|
preemptirqtest_5,
|
||
|
preemptirqtest_6,
|
||
|
preemptirqtest_7,
|
||
|
preemptirqtest_8,
|
||
|
preemptirqtest_9,
|
||
|
};
|
||
|
|
||
|
#define NR_TEST_FUNCS ARRAY_SIZE(testfuncs)
|
||
|
|
||
|
static int preemptirq_delay_run(void *data)
|
||
|
{
|
||
|
int i;
|
||
|
int s = MIN(burst_size, NR_TEST_FUNCS);
|
||
|
struct cpumask cpu_mask;
|
||
|
|
||
|
if (cpu_affinity > -1) {
|
||
|
cpumask_clear(&cpu_mask);
|
||
|
cpumask_set_cpu(cpu_affinity, &cpu_mask);
|
||
|
if (set_cpus_allowed_ptr(current, &cpu_mask))
|
||
|
pr_err("cpu_affinity:%d, failed\n", cpu_affinity);
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < s; i++)
|
||
|
(testfuncs[i])(i);
|
||
|
|
||
|
complete(&done);
|
||
|
|
||
|
set_current_state(TASK_INTERRUPTIBLE);
|
||
|
while (!kthread_should_stop()) {
|
||
|
schedule();
|
||
|
set_current_state(TASK_INTERRUPTIBLE);
|
||
|
}
|
||
|
|
||
|
__set_current_state(TASK_RUNNING);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int preemptirq_run_test(void)
|
||
|
{
|
||
|
struct task_struct *task;
|
||
|
char task_name[50];
|
||
|
|
||
|
init_completion(&done);
|
||
|
|
||
|
snprintf(task_name, sizeof(task_name), "%s_test", test_mode);
|
||
|
task = kthread_run(preemptirq_delay_run, NULL, task_name);
|
||
|
if (IS_ERR(task))
|
||
|
return PTR_ERR(task);
|
||
|
if (task) {
|
||
|
wait_for_completion(&done);
|
||
|
kthread_stop(task);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static ssize_t trigger_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
ssize_t ret;
|
||
|
|
||
|
ret = preemptirq_run_test();
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static struct kobj_attribute trigger_attribute =
|
||
|
__ATTR(trigger, 0200, NULL, trigger_store);
|
||
|
|
||
|
static struct attribute *attrs[] = {
|
||
|
&trigger_attribute.attr,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static struct attribute_group attr_group = {
|
||
|
.attrs = attrs,
|
||
|
};
|
||
|
|
||
|
static struct kobject *preemptirq_delay_kobj;
|
||
|
|
||
|
static int __init preemptirq_delay_init(void)
|
||
|
{
|
||
|
int retval;
|
||
|
|
||
|
retval = preemptirq_run_test();
|
||
|
if (retval != 0)
|
||
|
return retval;
|
||
|
|
||
|
preemptirq_delay_kobj = kobject_create_and_add("preemptirq_delay_test",
|
||
|
kernel_kobj);
|
||
|
if (!preemptirq_delay_kobj)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
retval = sysfs_create_group(preemptirq_delay_kobj, &attr_group);
|
||
|
if (retval)
|
||
|
kobject_put(preemptirq_delay_kobj);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static void __exit preemptirq_delay_exit(void)
|
||
|
{
|
||
|
kobject_put(preemptirq_delay_kobj);
|
||
|
}
|
||
|
|
||
|
module_init(preemptirq_delay_init)
|
||
|
module_exit(preemptirq_delay_exit)
|
||
|
MODULE_LICENSE("GPL v2");
|