// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/compat.h>
#include <linux/efi.h>
#include <linux/elf.h>
#include <linux/export.h>
#include <linux/sched.h>
#include <linux/sched/debug.h>
#include <linux/sched/task.h>
#include <linux/sched/task_stack.h>
#include <linux/kernel.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/nospec.h>
#include <linux/stddef.h>
#include <linux/sysctl.h>
#include <linux/unistd.h>
#include <linux/user.h>
#include <linux/delay.h>
#include <linux/reboot.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/cpu.h>
#include <linux/elfcore.h>
#include <linux/pm.h>
#include <linux/tick.h>
#include <linux/utsname.h>
#include <linux/uaccess.h>
#include <linux/random.h>
#include <linux/hw_breakpoint.h>
#include <linux/personality.h>
#include <linux/notifier.h>
#include <trace/events/power.h>
#include <linux/percpu.h>
#include <linux/thread_info.h>
#include <linux/prctl.h>
#include <trace/hooks/fpsimd.h>
#include <trace/hooks/mpam.h>
#include <linux/mmap_lock.h>

#include <asm/compat.h>
#ifndef CONFIG_RISCV
#include <asm/cpufeature.h>
#endif
#include <asm/cacheflush.h>
#include <asm/exec.h>
#include <asm/mmu_context.h>
#include <asm/processor.h>
#include <asm/stacktrace.h>
#include <asm/switch_to.h>
#ifndef CONFIG_RISCV
#include <asm/system_misc.h>
#endif

#include <linux/mm_inline.h>
#include <linux/amlogic/user_fault.h>
#if IS_ENABLED(CONFIG_AMLOGIC_SECMON)
#include <linux/amlogic/secmon.h>
#endif

#ifndef CONFIG_RISCV
#ifndef CONFIG_ARM64
#include <asm/ptrace.h>
#include <asm/vdso/cp15.h>
#include <linux/ratelimit.h>

#define USR_FAULT_DBG_RATELIMIT_INTERVAL	(5 * HZ)
#define USR_FAULT_DBG_RATELIMIT_BURST		3

#define user_fault_debug_ratelimited()					\
({									\
	static DEFINE_RATELIMIT_STATE(usr_fault_dgb_rs,			\
				      USR_FAULT_DBG_RATELIMIT_INTERVAL,	\
				      USR_FAULT_DBG_RATELIMIT_BURST);	\
	bool __show_ratelimited = false;				\
	if (__ratelimit(&usr_fault_dgb_rs))				\
		__show_ratelimited = true;				\
	__show_ratelimited;						\
})
#endif
#endif

static int show_kernel_data_valid(unsigned long reg)
{
	struct page *page;

	if (reg < (unsigned long)PAGE_OFFSET)
		return 0;
	else if (reg <= (unsigned long)high_memory)
		return 1;

	page = vmalloc_to_page((const void *)reg);
	if (page && pfn_valid(page_to_pfn(page)))
		return 1;

	return 0;
}

static void show_data(unsigned long addr, int nbytes, const char *name)
{
	int	i, j;
	int	nlines;
	u32	*p;
	char	buf[128] = {0};
	int	len = 0;

	/*
	 * don't attempt to dump non-kernel addresses or
	 * values that are probably just small negative numbers
	 */
#ifdef CONFIG_RISCV
	if (addr < VMALLOC_START || addr > -256UL)
#else
	if (addr < PAGE_OFFSET || addr > -256UL)
#endif
		return;
	/*
	 * Treating data in general purpose register as an address
	 * and dereferencing it is quite a dangerous behaviour,
	 * especially when it belongs to secure monotor region or
	 * ioremap region(for arm64 vmalloc region is already filtered
	 * out), which can lead to external abort on non-linefetch and
	 * can not be protected by probe_kernel_address.
	 * We need more strict filtering rules
	 */

	if (!show_kernel_data_valid((unsigned long)(addr + nbytes / 2)))
		return;

#if IS_ENABLED(CONFIG_AMLOGIC_SECMON)
	/*
	 * filter out secure monitor region
	 */
	if (within_secmon_region(addr + nbytes / 2)) {
		pr_emerg("\n%s: %#lx S\n", name, addr + nbytes / 2);
		return;
	}
#endif

	printk("\n%s: %#lx:\n", name, addr);

	/*
	 * round address down to a 32 bit boundary
	 * and always dump a multiple of 32 bytes
	 */
	p = (u32 *)(addr & ~(sizeof(u32) - 1));
	nbytes += (addr & (sizeof(u32) - 1));
	nlines = (nbytes + 31) / 32;

	for (i = 0; i < nlines; i++) {
		/*
		 * just display low 16 bits of address to keep
		 * each line of the dump < 80 characters
		 */
		len = 0;
		len += snprintf(buf + len, sizeof(buf) - len, "%04lx ", (unsigned long)p & 0xffff);
		for (j = 0; j < 8; j++) {
			u32 data = 0;

			if (get_kernel_nofault(data, p))
				len += snprintf(buf + len, sizeof(buf) - len, " ********");
			else
				len += snprintf(buf + len, sizeof(buf) - len, " %08x", data);
			++p;
		}
		pr_info("%s\n", buf);
	}
}

/*
 * dump a block of user memory from around the given address
 */
static void show_user_data(unsigned long addr, int nbytes, const char *name)
{
	int	i, j;
	int	nlines;
	u32	*p;
	char	buf[128] = {0};
	int	len = 0;

	if (!access_ok((void *)addr, nbytes))
		return;

	pr_info("\n%s: %#lx:\n", name, addr);

	/*
	 * round address down to a 32 bit boundary
	 * and always dump a multiple of 32 bytes
	 */
	p = (u32 *)(addr & ~(sizeof(u32) - 1));
	nbytes += (addr & (sizeof(u32) - 1));
	nlines = (nbytes + 31) / 32;

	for (i = 0; i < nlines; i++) {
		/*
		 * just display low 16 bits of address to keep
		 * each line of the dump < 80 characters
		 */
		len = 0;
		len += snprintf(buf + len, sizeof(buf) - len,
						"%04lx ", (unsigned long)p & 0xffff);
		for (j = 0; j < 8; j++) {
			int bad;
			unsigned char data[4];

			bad = __copy_from_user_inatomic(data, (const void *)p, sizeof(u32));
			if (bad)
				len += snprintf(buf + len, sizeof(buf) - len, " ********");
			else
				len += snprintf(buf + len, sizeof(buf) - len,
								" %08x", *(u32 *)data);
			++p;
		}
		pr_info("%s\n", buf);
	}
}

#ifdef CONFIG_ARM64
static void show_pfn(unsigned long reg, char *s)
{
	struct page *page;

	if (reg < (unsigned long)PAGE_OFFSET) {
		pr_info("%s : %016lx  U\n", s, reg);
	} else if (reg <= (unsigned long)high_memory) {
		pr_info("%s : %016lx, PFN:%5lx L\n", s, reg, virt_to_pfn(reg));
	} else {
		page = vmalloc_to_page((const void *)reg);
		if (page)
			pr_info("%s : %016lx, PFN:%5lx V\n", s, reg, page_to_pfn(page));
		else
			pr_info("%s : %016lx, PFN:***** V\n", s, reg);
	}
}

static void show_regs_pfn(struct pt_regs *regs)
{
	int i;
	struct page *page;

	for (i = 0; i < 31; i++) {
		if (regs->regs[i] < (unsigned long)PAGE_OFFSET) {
			continue;
		} else if (regs->regs[i] <= (unsigned long)high_memory) {
			pr_info("R%-2d : %016llx, PFN:%5lx L\n",
				i, regs->regs[i], virt_to_pfn((void *)regs->regs[i]));
		} else {
			page = vmalloc_to_page((void *)regs->regs[i]);
			if (page)
				pr_info("R%-2d : %016llx, PFN:%5lx V\n",
					i, regs->regs[i], page_to_pfn(page));
			else
				pr_info("R%-2d : %016llx, PFN:***** V\n", i, regs->regs[i]);
		}
	}
}

#elif defined CONFIG_RISCV
static void show_pfn(unsigned long reg, const char *s)
{
	struct page *page;

	if (reg < (unsigned long)VMALLOC_START) {
		pr_info("%s : %016lx  U\n", s, reg);
	} else if (reg <= (unsigned long)PAGE_OFFSET) {
		page = vmalloc_to_page((const void *)reg);
		if (page)
			pr_info("%s : %016lx, PFN:%5lx V\n", s, reg, page_to_pfn(page));
		else
			pr_info("%s : %016lx, PFN:***** V\n", s, reg);
	} else if (reg <= (unsigned long)high_memory) {
		pr_info("%s : %016lx, PFN:%5lx L\n", s, reg, virt_to_pfn(reg));
	} else if (reg <= KERNEL_LINK_ADDR) {
	} else if (reg <= kernel_map.virt_addr + kernel_map.size) {
		pr_info("%s : %016lx, PFN:%5lx L\n", s, reg, virt_to_pfn(reg));
	}
}

static const char * const regs_name[] = {
	"epc", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1",
	"a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3",
	"s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4",
	"t5", "t6"
};

static void show_regs_pfn(struct pt_regs *regs)
{
	int i;
	struct page *page;
	unsigned long *reg_array = (unsigned long *)regs;

	for (i = 0; i < 32; i++) {
		if (reg_array[i] < (unsigned long)VMALLOC_START) {
			continue;
		} else if (reg_array[i] <= (unsigned long)PAGE_OFFSET) {
			page = vmalloc_to_page((void *)reg_array[i]);
			if (page)
				pr_info("%-3s : %016lx, PFN:%5lx V\n",
					regs_name[i], reg_array[i], page_to_pfn(page));
			else
				pr_info("%-3s : %016lx, PFN:***** V\n",
					regs_name[i], reg_array[i]);
		} else if (reg_array[i] <= (unsigned long)high_memory) {
			pr_info("%-3s : %016lx, PFN:%5lx L\n",
				regs_name[i], reg_array[i], virt_to_pfn((void *)reg_array[i]));
		} else if (reg_array[i] <= KERNEL_LINK_ADDR) {
		} else if (reg_array[i] <= kernel_map.virt_addr + kernel_map.size) {
			pr_info("%-3s : %016lx, PFN:%5lx L\n",
				regs_name[i], reg_array[i], virt_to_pfn((void *)reg_array[i]));
		}
	}
}
#endif /* CONFIG_ARM64 */

#ifdef CONFIG_ARM64
static void show_extra_register_data(struct pt_regs *regs, int nbytes)
{
	unsigned int i;

	show_data(regs->pc - nbytes, nbytes * 2, "PC");
	show_data(regs->regs[30] - nbytes, nbytes * 2, "LR");
	show_data(regs->sp - nbytes, nbytes * 2, "SP");
	show_data(read_sysreg(far_el1), nbytes * 2, "FAR");
	for (i = 0; i < 30; i++) {
		char name[4];

		snprintf(name, sizeof(name), "X%u", i);
		show_data(regs->regs[i] - nbytes, nbytes * 2, name);
	}
}

static void show_user_extra_register_data(struct pt_regs *regs, int nbytes)
{
	unsigned int i, top_reg;
	u64 sp, lr;

	if (compat_user_mode(regs)) {
		lr = regs->compat_lr;
		sp = regs->compat_sp;
		top_reg = 13;
	} else {
		lr = regs->regs[30];
		sp = regs->sp;
		top_reg = 29;
	}

	show_user_data(regs->pc - nbytes, nbytes * 2, "PC");
	show_user_data(lr - nbytes, nbytes * 2, "LR");
	show_user_data(sp - nbytes, nbytes * 2, "SP");
	show_user_data(read_sysreg(far_el1), nbytes * 2, "FAR");
	for (i = 0; i < top_reg; i++) {
		char name[4];

		snprintf(name, sizeof(name), "r%u", i);
		show_user_data(regs->regs[i] - nbytes, nbytes * 2, name);
	}
}
#elif defined CONFIG_ARM
static void show_extra_register_data(struct pt_regs *regs, int nbytes)
{
	show_data(regs->ARM_pc - nbytes, nbytes * 2, "PC");
	show_data(regs->ARM_lr - nbytes, nbytes * 2, "LR");
	show_data(regs->ARM_sp - nbytes, nbytes * 2, "SP");
	show_data(regs->ARM_ip - nbytes, nbytes * 2, "IP");
	show_data(regs->ARM_fp - nbytes, nbytes * 2, "FP");
	show_data(regs->ARM_r0 - nbytes, nbytes * 2, "R0");
	show_data(regs->ARM_r1 - nbytes, nbytes * 2, "R1");
	show_data(regs->ARM_r2 - nbytes, nbytes * 2, "R2");
	show_data(regs->ARM_r3 - nbytes, nbytes * 2, "R3");
	show_data(regs->ARM_r4 - nbytes, nbytes * 2, "R4");
	show_data(regs->ARM_r5 - nbytes, nbytes * 2, "R5");
	show_data(regs->ARM_r6 - nbytes, nbytes * 2, "R6");
	show_data(regs->ARM_r7 - nbytes, nbytes * 2, "R7");
	show_data(regs->ARM_r8 - nbytes, nbytes * 2, "R8");
	show_data(regs->ARM_r9 - nbytes, nbytes * 2, "R9");
	show_data(regs->ARM_r10 - nbytes, nbytes * 2, "R10");
}

static void show_user_extra_register_data(struct pt_regs *regs, int nbytes)
{
	show_user_data(regs->ARM_pc - nbytes, nbytes * 2, "PC");
	show_user_data(regs->ARM_lr - nbytes, nbytes * 2, "LR");
	show_user_data(regs->ARM_sp - nbytes, nbytes * 2, "SP");
	show_user_data(regs->ARM_ip - nbytes, nbytes * 2, "IP");
	show_user_data(regs->ARM_fp - nbytes, nbytes * 2, "FP");
	show_user_data(regs->ARM_r0 - nbytes, nbytes * 2, "R0");
	show_user_data(regs->ARM_r1 - nbytes, nbytes * 2, "R1");
	show_user_data(regs->ARM_r2 - nbytes, nbytes * 2, "R2");
	show_user_data(regs->ARM_r3 - nbytes, nbytes * 2, "R3");
	show_user_data(regs->ARM_r4 - nbytes, nbytes * 2, "R4");
	show_user_data(regs->ARM_r5 - nbytes, nbytes * 2, "R5");
	show_user_data(regs->ARM_r6 - nbytes, nbytes * 2, "R6");
	show_user_data(regs->ARM_r7 - nbytes, nbytes * 2, "R7");
	show_user_data(regs->ARM_r8 - nbytes, nbytes * 2, "R8");
	show_user_data(regs->ARM_r9 - nbytes, nbytes * 2, "R9");
	show_user_data(regs->ARM_r10 - nbytes, nbytes * 2, "R10");
}

void show_vmalloc_pfn(struct pt_regs *regs)
{
	int i;
	struct page *page;

	for (i = 0; i < 16; i++) {
		if (is_vmalloc_or_module_addr((void *)regs->uregs[i])) {
			page = vmalloc_to_page((void *)regs->uregs[i]);
			if (!page)
				continue;
			pr_info("R%-2d : %08lx, PFN:%5lx\n",
				i, regs->uregs[i], page_to_pfn(page));
		}
	}
}
#elif defined CONFIG_RISCV
static void show_extra_register_data(struct pt_regs *regs, int nbytes)
{
	int i;
	unsigned long *reg_array = (unsigned long *)regs;

	show_data(regs->badaddr - nbytes, nbytes * 2, "badaddr");

	for (i = 0; i < 32; i++)
		show_data(reg_array[i] - nbytes, nbytes * 2, regs_name[i]);
}

static void show_user_extra_register_data(struct pt_regs *regs, int nbytes)
{
	int i;
	unsigned long *reg_array = (unsigned long *)regs;

	show_user_data(regs->badaddr - nbytes, nbytes * 2, "badaddr");

	for (i = 0; i < 32; i++)
		show_user_data(reg_array[i] - nbytes, nbytes * 2, regs_name[i]);
}
#endif

#if IS_MODULE(CONFIG_AMLOGIC_USER_FAULT)
__weak const char *arch_vma_name(struct vm_area_struct *vma)
{
	return NULL;
}
#endif

/* Check if the vma is being used as a stack by this task */
static int aml_vma_is_stack_for_current(struct vm_area_struct *vma)
{
	struct task_struct * __maybe_unused t = current;

	return (vma->vm_start <= KSTK_ESP(t) && vma->vm_end >= KSTK_ESP(t));
}

static struct anon_vma_name *aml_anon_vma_name(struct vm_area_struct *vma)
{
	mmap_assert_locked(vma->vm_mm);

	if (vma->vm_file)
		return NULL;

	return vma->anon_name;
}

void show_vma(struct mm_struct *mm, unsigned long addr)
{
	struct vm_area_struct *vma;
	struct file *file;
	vm_flags_t flags;
	unsigned long ino = 0;
	unsigned long long pgoff = 0;
	unsigned long start, end;
	dev_t dev = 0;
	const char *name = NULL;

	vma = find_vma(mm, addr);
	if (!vma) {
		pr_info("can't find vma for %lx\n", addr);
		return;
	}

	file = vma->vm_file;
	flags = vma->vm_flags;
	if (file) {
		struct inode *inode = file_inode(vma->vm_file);

		dev = inode->i_sb->s_dev;
		ino = inode->i_ino;
		pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT;
	}

	/* We don't show the stack guard page in /proc/maps */
	start = vma->vm_start;
	end = vma->vm_end;

	pr_info("vma for %lx:\n", addr);
	pr_info("%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu ",
		start,
		end,
		flags & VM_READ ? 'r' : '-',
		flags & VM_WRITE ? 'w' : '-',
		flags & VM_EXEC ? 'x' : '-',
		flags & VM_MAYSHARE ? 's' : 'p',
		pgoff,
		MAJOR(dev), MINOR(dev), ino);

	/*
	 * Print the dentry name for named mappings, and a
	 * special [heap] marker for the heap:
	 */
	if (file) {
		char file_name[256] = {};
		char *p = d_path(&file->f_path, file_name, 256);

		if (!IS_ERR(p)) {
			mangle_path(file_name, p, "\n");
			pr_info("%s", p);
		} else {
			pr_info(" get file path failed\n");
		}
		goto done;
	}

	name = arch_vma_name(vma);
	if (!name) {
		pid_t tid;

		if (!mm) {
			name = "[vdso]";
			goto done;
		}

		if (vma->vm_start <= mm->brk &&
		    vma->vm_end >= mm->start_brk) {
			name = "[heap]";
			goto done;
		}

		tid = aml_vma_is_stack_for_current(vma);

		if (tid != 0) {
			/*
			 * Thread stack in /proc/PID/task/TID/maps or
			 * the main process stack.
			 */
			if ((vma->vm_start <= mm->start_stack &&
			    vma->vm_end >= mm->start_stack)) {
				name = "[stack]";
			} else {
				/* Thread stack in /proc/PID/maps */
				pr_info("[stack:%d]", tid);
			}
			goto done;
		}

		if (aml_anon_vma_name(vma))
			pr_info("[anon]");
	}

done:
	if (name)
		pr_info("%s", name);
	pr_info("\n");
}

#if IS_MODULE(CONFIG_AMLOGIC_USER_FAULT) && IS_ENABLED(CONFIG_KALLSYMS_ALL)
struct mm_struct *aml_init_mm;

unsigned long (*aml_syms_lookup)(const char *name);

/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp_lookup_name = {
	.symbol_name	= "kallsyms_lookup_name",
};
#endif

static long __nocfi get_user_pfn(struct mm_struct *mm, unsigned long addr)
{
	long pfn = -1;
	pgd_t *pgd;

#if IS_MODULE(CONFIG_AMLOGIC_USER_FAULT) && IS_ENABLED(CONFIG_KALLSYMS_ALL)
	if (!mm || addr >= VMALLOC_START)
		mm = aml_init_mm;
	if (!mm)
		return pfn;
#else
	if (!mm || addr >= VMALLOC_START)
		mm = &init_mm;
#endif

	pgd = pgd_offset(mm, addr);

	do {
		p4d_t *p4d;
		pud_t *pud;
		pmd_t *pmd;
		pte_t *pte;

		if (pgd_none(*pgd) || pgd_bad(*pgd))
			break;

		p4d = p4d_offset(pgd, addr);
		if (p4d_none(*p4d) || p4d_bad(*p4d))
			break;

		pud = pud_offset(p4d, addr);
		if (pud_none(*pud) || pud_bad(*pud))
			break;

		pmd = pmd_offset(pud, addr);
		if (pmd_none(*pmd) || pmd_bad(*pmd))
			break;

		pte = pte_offset_map(pmd, addr);
		pfn = pte_pfn(*pte);
		pte_unmap(pte);
	} while (0);

	return pfn;
}

#ifdef CONFIG_ARM64
void show_all_pfn(struct task_struct *task, struct pt_regs *regs)
{
	int i;
	long pfn1, far;
	char s1[10];
	int top;
	char buf[128] = {0};
	int len = 0;

	if (compat_user_mode(regs))
		top = 15;
	else
		top = 31;

	len += snprintf(buf + len, sizeof(buf) - len, "reg              value       pfn  ");
	len += snprintf(buf + len, sizeof(buf) - len, "reg              value       pfn");
	pr_info("%s\n", buf);

	len = 0;
	memset(buf, 0, sizeof(buf));

	for (i = 0; i < top; i++) {
		pfn1 = get_user_pfn(task->mm, regs->regs[i]);
		if (pfn1 >= 0)
			sprintf(s1, "%8lx", pfn1);
		else
			sprintf(s1, "--------");
		if (i % 2 == 1) {
			len += snprintf(buf + len, sizeof(buf) - len,
					"r%-2d:  %016llx  %s", i, regs->regs[i], s1);
			pr_info("%s\n", buf);
			len = 0;
		} else {
			len += snprintf(buf + len, sizeof(buf) - len,
					"r%-2d:  %016llx  %s  ", i, regs->regs[i], s1);
		}
	}

	if (len > 0)
		pr_info("%s\n", buf);

	pfn1 = get_user_pfn(task->mm, regs->pc);
	if (pfn1 >= 0)
		sprintf(s1, "%8lx", pfn1);
	else
		sprintf(s1, "--------");
	pr_info("pc :  %016llx  %s\n", regs->pc, s1);
	pfn1 = get_user_pfn(task->mm, regs->sp);
	if (pfn1 >= 0)
		sprintf(s1, "%8lx", pfn1);
	else
		sprintf(s1, "--------");
	pr_info("sp :  %016llx  %s\n", regs->sp, s1);

	far = read_sysreg(far_el1);
	pfn1 = get_user_pfn(task->mm, far);
	if (pfn1 >= 0)
		sprintf(s1, "%8lx", pfn1);
	else
		sprintf(s1, "--------");
	pr_info("unused :  %016lx  %s\n", far, s1);
#if IS_BUILTIN(CONFIG_AMLOGIC_USER_FAULT)
	pr_info("offset :  %016lx\n", kaslr_offset());
#endif
}
#elif defined CONFIG_ARM
static unsigned char *regidx_to_name[] = {
	"r0 ", "r1 ", "r2 ", "r3 ",
	"r4 ", "r5 ", "r6 ", "r7 ",
	"r8 ", "r9 ", "r10",
	"fp ", "ip ", "sp ", "lr ",
	"pc "
};

void show_all_pfn(struct task_struct *task, struct pt_regs *regs)
{
	int i;
	long pfn1;
	char s1[10];
	int top;
	char buf[128] = {0};
	int len = 0;

	top = 16;
	len += snprintf(buf + len, sizeof(buf) - len, "reg     value       pfn   ");
	len += snprintf(buf + len, sizeof(buf) - len, "reg     value       pfn");
	pr_info("%s\n", buf);

	len = 0;
	memset(buf, 0, sizeof(buf));

	for (i = 0; i < top; i++) {
		pfn1 = get_user_pfn(task->mm, regs->uregs[i]);
		if (pfn1 >= 0)
			sprintf(s1, "%8lx", pfn1);
		else
			sprintf(s1, "--------");
		if (i % 2 == 0) {
			len += snprintf(buf + len, sizeof(buf) - len, "%s:  %08lx  %s  ",
				       regidx_to_name[i], regs->uregs[i], s1);
		} else {
			len += snprintf(buf + len, sizeof(buf) - len, "%s:  %08lx  %s",
				       regidx_to_name[i], regs->uregs[i], s1);

			pr_info("%s\n", buf);
			len = 0;
		}
	}

	if (len > 0)
		pr_info("%s\n", buf);
}

void show_debug_ratelimited(struct pt_regs *regs, unsigned int reg_en)
{
	if (user_fault_debug_ratelimited()) {
		show_all_pfn(current, regs);

		if (reg_en)
			show_regs(regs);
	}
}
#elif defined CONFIG_RISCV
void show_all_pfn(struct task_struct *task, struct pt_regs *regs)
{
	int i;
	long pfn1;
	char s1[10];
	int top = 32;
	unsigned long *reg_array = (unsigned long *)regs;

	pr_info("reg              value       pfn  ");
	pr_cont("reg              value       pfn\n");
	for (i = 0; i < top; i++) {
		pfn1 = get_user_pfn(task->mm, reg_array[i]);
		if (pfn1 >= 0)
			sprintf(s1, "%8lx", pfn1);
		else
			sprintf(s1, "--------");
		if (i % 2 == 1)
			pr_cont("%-3s:  %016lx  %s\n", regs_name[i], reg_array[i], s1);
		else
			pr_info("%-3s:  %016lx  %s  ", regs_name[i], reg_array[i], s1);
	}
	pr_cont("\n");
	pfn1 = get_user_pfn(task->mm, regs->epc);
	if (pfn1 >= 0)
		sprintf(s1, "%8lx", pfn1);
	else
		sprintf(s1, "--------");
	pr_info("pc :  %016lx  %s\n", regs->epc, s1);
	pfn1 = get_user_pfn(task->mm, regs->sp);
	if (pfn1 >= 0)
		sprintf(s1, "%8lx", pfn1);
	else
		sprintf(s1, "--------");
	pr_info("sp :  %016lx  %s\n", regs->sp, s1);

	pfn1 = get_user_pfn(task->mm, regs->badaddr);
	if (pfn1 >= 0)
		sprintf(s1, "%8lx", pfn1);
	else
		sprintf(s1, "--------");
	pr_info("unused :  %016lx  %s\n", regs->badaddr, s1);
//	pr_info("offset :  %016lx\n", kaslr_offset());
}

#endif

static int (*dmc_cb)(char *);
void set_dump_dmc_func(void *f)
{
	dmc_cb =  (void *)f;
}
EXPORT_SYMBOL(set_dump_dmc_func);

void _dump_dmc_reg(void)
{
	char buf[1024] = {0};

	if (!dmc_cb)
		return;
	dmc_cb(buf);
	pr_crit("%s\n", buf);
}

void show_user_fault_info(struct pt_regs *regs, u64 lr, u64 sp)
{
#ifdef CONFIG_ARM64
	if (user_mode(regs)) {
		show_vma(current->mm, instruction_pointer(regs));
		show_vma(current->mm, lr);
		show_vma(current->mm, read_sysreg(far_el1));
	}
	show_pfn(instruction_pointer(regs), "PC");
	show_pfn(sp, "SP");
	show_pfn(read_sysreg(far_el1), "FAR");
	show_regs_pfn(regs);
#elif defined CONFIG_ARM
	if (user_mode(regs)) {
		show_vma(current->mm, instruction_pointer(regs));
		show_vma(current->mm, regs->ARM_lr);
	}
#elif defined CONFIG_RISCV
	if (user_mode(regs)) {
		show_vma(current->mm, instruction_pointer(regs));
		show_vma(current->mm, lr);
		show_vma(current->mm, regs->badaddr);
	}

	show_pfn(regs->badaddr, "badaddr");
	show_regs_pfn(regs);
#endif
}

void show_extra_reg_data(struct pt_regs *regs)
{
	if (!user_mode(regs))
		show_extra_register_data(regs, 128);
	else
		show_user_extra_register_data(regs, 128);
	printk("\n");
}

#if IS_MODULE(CONFIG_AMLOGIC_USER_FAULT) && IS_ENABLED(CONFIG_KALLSYMS_ALL)
static struct kprobe kp_show_regs = {
	.symbol_name	= "__show_regs",
};

static struct kprobe kp_bad_el0_sync = {
	.symbol_name	= "bad_el0_sync",
};

static void __kprobes show_regs_handler_post(struct kprobe *p,
				struct pt_regs *param_regs, unsigned long flags)
{
	struct pt_regs *regs = (struct pt_regs *)param_regs->regs[0];
	u64 lr, sp;

	if (compat_user_mode(regs)) {
		lr = regs->compat_lr;
		sp = regs->compat_sp;
	} else {
		lr = regs->regs[30];
		sp = regs->sp;
	}

	show_user_fault_info(regs, lr, sp);
	show_extra_reg_data(regs);
}

static void __kprobes bad_el0_sync_handler_post(struct kprobe *p,
				struct pt_regs *param_regs, unsigned long flags)
{
	struct pt_regs *regs = (struct pt_regs *)param_regs->regs[0];

	show_all_pfn(current, regs);
}

static int __nocfi user_fault_register_kprobe(void *data)
{
	int ret;

	ret = register_kprobe(&kp_lookup_name);
	if (ret < 0) {
		pr_err("register_kprobe failed, returned %d\n", ret);
		return -1;
	}
	pr_debug("kprobe lookup offset at %px\n", kp_lookup_name.addr);

	aml_syms_lookup = (unsigned long (*)(const char *name))kp_lookup_name.addr;

	aml_init_mm = (struct mm_struct *)aml_syms_lookup("init_mm");
	pr_debug("aml_init_mm: %px\n", aml_init_mm);

	kp_show_regs.post_handler = show_regs_handler_post;
	ret = register_kprobe(&kp_show_regs);
	if (ret < 0) {
		pr_err("register_kprobe %s failed, returned %d\n",
			kp_show_regs.symbol_name, ret);
		return -1;
	}

	kp_bad_el0_sync.post_handler = bad_el0_sync_handler_post;
	ret = register_kprobe(&kp_bad_el0_sync);
	if (ret < 0) {
		pr_err("register_kprobe %s failed, returned %d\n",
			kp_bad_el0_sync.symbol_name, ret);
		return -1;
	}

	return 0;
}

static int __init user_fault_module_init(void)
{
	kthread_run(user_fault_register_kprobe, NULL, "AML_USER_FAULT");

	return 0;
}

static void __exit user_fault_module_exit(void)
{
	unregister_kprobe(&kp_lookup_name);
	unregister_kprobe(&kp_show_regs);
	unregister_kprobe(&kp_bad_el0_sync);
}

module_init(user_fault_module_init);
module_exit(user_fault_module_exit);
MODULE_LICENSE("GPL v2");
#endif
