linux/kernel/watch_preempt.c

252 lines
5.4 KiB
C

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irqnr.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/timex.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <asm/uaccess.h>
#if defined(CONFIG_E90S) || defined(CONFIG_E2K)
# include <asm-l/pic.h>
# include <asm-l/io_pic.h>
#endif
int do_watch_preempt_disable = 0;
EXPORT_SYMBOL(do_watch_preempt_disable);
DEFINE_PER_CPU(u64, max_prmtdsbled);
DEFINE_PER_CPU(u64, tm_prmtdsbled);
DEFINE_PER_CPU(u32, nowatch_set);
static u32 nowatch_mask = NOPWATCH_DEAD | NOPWATCH_LOCTIM | NEVER_PWATCH;
static u64 max_tm;
static int num_dumps;
#ifdef CONFIG_E90
extern u64 get_usec_from_start(void);
#endif
void save_tm_prmtdsbl(int val)
{
if (val == preempt_count()) {
#ifdef CONFIG_E90
__this_cpu_write(tm_prmtdsbled, get_usec_from_start() << 1);
#else
__this_cpu_write(tm_prmtdsbled, get_cycles());
#endif
}
}
EXPORT_SYMBOL(save_tm_prmtdsbl);
void chck_tm_prmtdsbl(int val)
{
u64 stm = __this_cpu_read(tm_prmtdsbled);
#ifdef CONFIG_MCST
if (preempt_count() == val) {
current->my_last_ipi_prmt_enable =
(unsigned long)__builtin_return_address(0);
}
#endif
if (stm == 0) {
return;
}
if (preempt_count() == val) {
if (__this_cpu_read(nowatch_set) & nowatch_mask) {
/* Not intersting case for us */
__this_cpu_write(nowatch_set, 0);
__this_cpu_write(tm_prmtdsbled, 0);
return;
}
#ifdef CONFIG_E90
u64 delta = (get_usec_from_start() << 1) - stm;
if (delta > 2000000) {
/* we can get wrong value . skip */
__this_cpu_write(tm_prmtdsbled, 0);
return;
}
#else
u64 delta = get_cycles() - stm;
#endif
__this_cpu_write(tm_prmtdsbled, 0);
if (delta > __this_cpu_read(max_prmtdsbled)) {
__this_cpu_write(max_prmtdsbled, delta);
}
if (max_tm && delta > max_tm && num_dumps > 0) {
num_dumps--;
if (num_dumps <= 0) {
max_tm = 0;
}
WARN(1, "Preempt disable time too long on CPU%d: %llu\n",
smp_processor_id(), delta);
}
}
}
EXPORT_SYMBOL(chck_tm_prmtdsbl);
/* show max prrempt disable time for each CPU */
static void *wp_seq_start(struct seq_file *f, loff_t *pos)
{
return (*pos <= num_possible_cpus()) ? pos : NULL;
}
static void *wp_seq_next(struct seq_file *f, void *v, loff_t *pos)
{
(*pos)++;
if (*pos >= NR_CPUS)
return NULL;
return pos;
}
static void wp_seq_stop(struct seq_file *f, void *v)
{
/* Nothing to do */
}
static ssize_t wp_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
char c;
int cpu;
char mb[32];
char *me;
unsigned long l;
if (count == 0) {
return 0;
}
if (get_user(c, buf)) {
return -EFAULT;
}
if (c == '0') {
do_watch_preempt_disable = 0;
return count;
}
if (c == '1') {
if (do_watch_preempt_disable) {
do_watch_preempt_disable = 0;
udelay(100);
}
for_each_possible_cpu(cpu) {
per_cpu(max_prmtdsbled, cpu) = 0;
per_cpu(tm_prmtdsbled, cpu) = 0;
}
do_watch_preempt_disable = 1;
return count;
}
if (c == 't') {
l = (count > 32) ? 32 : (count - 1);
if (copy_from_user(mb, buf + 1, l)) {
return -EFAULT;
}
max_tm = usecs_2cycles(simple_strtoul(mb, &me, 10));
if (*me == 'n') {
num_dumps = simple_strtoul(me + 1, NULL, 10);
}
if (num_dumps == 0) {
num_dumps = 1;
}
return count;
}
if (c == 'n') {
l = (count > 32) ? 32 : (count - 1);
if (copy_from_user(mb, buf + 1, l)) {
return -EFAULT;
}
num_dumps = simple_strtoul(mb, NULL, 10);
return count;
}
if (c == 'N') {
l = (count > 32) ? 32 : (count - 1);
if (copy_from_user(mb, buf + 1, l)) {
return -EFAULT;
}
if (!strncmp(mb, "loctim", 6)) {
nowatch_mask |= NOPWATCH_LOCTIM;
} else if (!strncmp(mb, "tsbgrow", 7)) {
nowatch_mask |= NOPWATCH_TSBGROW;
} else if (!strncmp(mb, "sched", 5)) {
nowatch_mask |= NOPWATCH_SCHED;
} else if (!strncmp(mb, "exitmm", 6)) {
nowatch_mask |= NOPWATCH_EXITMM;
} else {
return -EFAULT;
}
return count;
}
if (c == 'Y') {
l = (count > 32) ? 32 : (count - 1);
if (copy_from_user(mb, buf + 1, l)) {
return -EFAULT;
}
if (!strncmp(mb, "loctim", 6)) {
nowatch_mask &= ~NOPWATCH_LOCTIM;
} else if (!strncmp(mb, "tsbgrow", 7)) {
nowatch_mask &= ~NOPWATCH_TSBGROW;
} else if (!strncmp(mb, "sched", 5)) {
nowatch_mask &= ~NOPWATCH_SCHED;
} else if (!strncmp(mb, "exitmm", 6)) {
nowatch_mask &= ~NOPWATCH_EXITMM;
} else {
return -EFAULT;
}
return count;
}
#if defined(CONFIG_E90S) || defined(CONFIG_E2K)
if (c == 'A') {
print_IO_PICs();
print_local_pics(true);
return count;
}
#endif
return -EINVAL;
}
int show_wp(struct seq_file *p, void *v)
{
int i = *(loff_t *) v;
if (!cpu_online(i)) {
return 0;
}
seq_printf(p, "CPU%d\t%llu usecs\n", i,
cycles_2usec(per_cpu(max_prmtdsbled, i)));
return 0;
}
static const struct seq_operations wp_seq_ops = {
.start = wp_seq_start,
.next = wp_seq_next,
.stop = wp_seq_stop,
.show = show_wp,
};
static int wp_open(struct inode *inode, struct file *filp)
{
return seq_open(filp, &wp_seq_ops);
}
static const struct file_operations proc_wp_operations = {
.open = wp_open,
.read = seq_read,
.write = wp_write,
.llseek = seq_lseek,
.release = seq_release,
};
static int __init proc_wp_init(void)
{
proc_create("watch-preempt", 0, NULL, &proc_wp_operations);
return 0;
}
module_init(proc_wp_init);