// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020 MediaTek Inc.
 */

#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq_work.h>
#include <linux/kthread.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>

/* TIMER_SOFTIRQ duration warning test */

static struct timer_list timer;
static void delayed_timer(struct timer_list *t)
{
	mdelay(600);
}

void irq_mon_test_TIMER_SOFTIRQ(void)
{
	timer_setup(&timer, delayed_timer, 0);
	mod_timer(&timer, jiffies + msecs_to_jiffies(100));
}

/* TASKLET_SOFTIRQ duration warning test */

static struct tasklet_struct irq_mon_tasklet;
static void irq_mon_tasklet_func(unsigned long data)
{
	mdelay(600);
}

void irq_mon_test_TASKLET_SOFTIRQ(void)
{
	tasklet_init(&irq_mon_tasklet, irq_mon_tasklet_func, 0);
	tasklet_schedule(&irq_mon_tasklet);
}

/* RCU_SOFTIRQ duration warning test */

struct irq_mon_rcu_t {
	int val;
};

static struct irq_mon_rcu_t __rcu *irq_mon_rcu_g;
static struct rcu_head irq_mon_rcu_head;
static void delayed_rcu_callback(struct rcu_head *r)
{
	/* RCU_SOFTIRQ handler */
	mdelay(600);
}

void irq_mon_test_RCU_SOFTIRQ(void)
{
	struct irq_mon_rcu_t *irq_mon_rcu;

	irq_mon_rcu = kmalloc(sizeof(*irq_mon_rcu), GFP_KERNEL);
	if (!irq_mon_rcu)
		return;
	irq_mon_rcu->val = 100;

	RCU_INIT_POINTER(irq_mon_rcu_g, irq_mon_rcu);
	call_rcu(&irq_mon_rcu_head, delayed_rcu_callback);
}

/* irq_work monitor test */

static struct irq_work irq_mon_irqwork;
static void irq_mon_irq_work(struct irq_work *work)
{
	mdelay(600);
}

void irq_mon_test_irq_work(void)
{
	init_irq_work(&irq_mon_irqwork, irq_mon_irq_work);
	irq_work_queue(&irq_mon_irqwork);
}

/* IRQ disable monitor test */

void irq_mon_test_irq_disable(void)
{
	unsigned long flags;

	local_irq_save(flags);
	mdelay(600);
	local_irq_restore(flags);

	/* The test will trigger KernelAPI Dump */
}

/* hrtimer monitor test */

static struct hrtimer irq_mon_hrtimer;
static enum hrtimer_restart irq_mon_hrtimer_func(struct hrtimer *unused)
{
	mdelay(600);
	return HRTIMER_NORESTART;
}

void irq_mon_test_HRTIMER(void)
{
	hrtimer_init(&irq_mon_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
	irq_mon_hrtimer.function = irq_mon_hrtimer_func;
	hrtimer_start(&irq_mon_hrtimer, ms_to_ktime(0),
		      HRTIMER_MODE_REL_PINNED);
}

/* preempt count monitor test */

static struct hrtimer irq_mon_hrtimer2;
static enum hrtimer_restart irq_mon_hrtimer_func2(struct hrtimer *unused)
{
	preempt_disable();
	return HRTIMER_NORESTART;
}

void irq_mon_test_PREEMPT_COUNT(void)
{
	hrtimer_init(&irq_mon_hrtimer2, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
	irq_mon_hrtimer2.function = irq_mon_hrtimer_func2;
	hrtimer_start(&irq_mon_hrtimer2, ms_to_ktime(0),
		      HRTIMER_MODE_REL_PINNED);
}

void irq_mon_delay(void *unused)
{
	mdelay(600);
}

void irq_mon_test_smp_func(void)
{
	smp_call_function(irq_mon_delay, 0, 0);
}

struct irq_mon_test_func {
	char name[32];
	void (*func)(void);
};

struct irq_mon_test_func irq_mon_test_list[] = {
	{"sirq_timer_dur", irq_mon_test_TIMER_SOFTIRQ},
	{"sirq_tasklet_dur", irq_mon_test_TASKLET_SOFTIRQ},
	{"sirq_rcu_dur", irq_mon_test_RCU_SOFTIRQ},
	{"irq_work_dur", irq_mon_test_irq_work},
	{"hirq_disable", irq_mon_test_irq_disable},
	{"hrtimer_dur", irq_mon_test_HRTIMER},
	{"preempt_count", irq_mon_test_PREEMPT_COUNT},
	{"smp_func_dur", irq_mon_test_smp_func},
};

static ssize_t
irq_mon_test_write(struct file *file, const char *ubuf,
		     size_t count, loff_t *ppos)
{
	int i;
	char buf[32];

	if (count >= sizeof(buf) || count == 0)
		return -EINVAL;

	if (copy_from_user(&buf, ubuf, count))
		return -EFAULT;

	buf[count] = '\0';

	for (i = 0; i < ARRAY_SIZE(irq_mon_test_list); i++) {
		if (!strncmp(buf, irq_mon_test_list[i].name,
			     strlen(irq_mon_test_list[i].name)))
			irq_mon_test_list[i].func();
	}

	return count;
}

static const struct proc_ops proc_irq_monitor_test_fops = {
	.proc_open  = simple_open,
	.proc_write = irq_mon_test_write,
};

void mt_irq_monitor_test_init(struct proc_dir_entry *dir)
{
	proc_create("irq_mon_test", 0220, dir,
		    &proc_irq_monitor_test_fops);
}
