/*
 * Copyright (c) 2005-2010 Wind River Systems, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 */
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <asm/uaccess.h>


#define LOCAL_MODULE_VERSION	"1.0"
#define MODULE_NAME		"test_panic"
#define DEAD_ADDRESS		0xdeadbeef

static struct proc_dir_entry *panic_dir;
static struct proc_dir_entry *panic_file;
static struct proc_dir_entry *bad_kfree_file;
static struct proc_dir_entry *bad_access_file;
static struct proc_dir_entry *deep_panic_file;
static struct proc_dir_entry *deep_bug_file;
static struct proc_dir_entry *bug_file;
static struct proc_dir_entry *hang_file;
static struct proc_dir_entry *disableirq_file;

static int no_crash_here;

static int bug_write(struct file *file, const char *buffer,
                     unsigned long count, void *data)
{
	printk(KERN_INFO "Starting BUG\n");
	if (!no_crash_here)
		BUG();
	return count;
}

static int hang_write(struct file *file, const char *buffer,
                     unsigned long count, void *data)
{
	printk(KERN_INFO "going to sleep for 11 seconds with irq disabled\n");
	local_irq_disable();
	local_irq_disable();
	/* Sleep and locking experiments */
	/*	write_lock_irq(&tasklist_lock); */
	printk("Disable local irq for 11 seconds\n");
	mdelay(11000);
	/*	write_unlock_irq(&tasklist_lock); */
	local_irq_enable();
	mdelay(1000);
	cpu_relax();
	return count;
}

int test_panic_recover = 0;
int deep_bug_test_panic = 0;

noinline void deep01(void) {
	printk(KERN_CRIT "Deep crash\n");
	if (deep_bug_test_panic)
		panic("Hard panic in deep trace");
	else
		BUG();
}
noinline void deep02(void) {
	deep01();
}
noinline void deep03(void) {
	deep02();
}
noinline void deep04(void) {
	deep03();
}
noinline void deep05(void) {
	deep04();
}
noinline void deep06(void) {
	deep05();
}
noinline void deep07(void) {
	deep06();
}
noinline void deep08(void) {
	deep07();
}
noinline void deep09(void) {
	deep08();
}
noinline void deep10(void) {
	deep09();
}
noinline void deep11(void) {
	deep10();
}
noinline void deep12(void) {
	deep11();
}
noinline void deep13(void) {
	deep12();
}
noinline void deep14(void) {
	deep13();
}
noinline void deep15(void) {
	deep14();
}
noinline void deep16(void) {
	deep15();
}
noinline void deep17(void) {
	deep16();
}
noinline void deep18(void) {
	deep17();
}
noinline void deep19(void) {
	deep18();
}
noinline void deep20(void) {
	deep19();
}
noinline void deep21(void) {
	deep20();
}
noinline void deep22(void) {
	deep21();
}
noinline void deep23(void) {
	deep22();
}
noinline void deep24(void) {
	deep23();
}
noinline void deep25(void) {
	deep24();
}


static int deep_panic_write(struct file *file, const char *buffer,
                     unsigned long count, void *data)
{
	deep_bug_test_panic = 1;
	deep25();
	return 0;
}

static int deep_bug_write(struct file *file, const char *buffer,
                     unsigned long count, void *data)
{
	deep_bug_test_panic = 0;
	deep25();
	return 0;
}

static int disableirq_write(struct file *file, const char *buffer,
                     unsigned long count, void *data)
{
	printk(KERN_INFO "irq disable\n");
	local_irq_disable();
	while (1);
	local_irq_enable();
	return 0;
}

/* called when someone writes to the proc file.  trigger a panic here */
static int bad_kfree_write(struct file *file, const char *buffer,
                           unsigned long count, void *data)
{
	printk(KERN_INFO "Starting bad kfree\n");
	kfree((void *)1);
	return count;
}

static int test_panic_counter;
static int *tp_address_ref = &test_panic_counter;

static void create_bad_access(void)
{
	trace_printk("create_bad_access() does bad things!\n");
	tp_address_ref = (int *)DEAD_ADDRESS;
}

static int bad_access_write(struct file *file, const char *buffer,
                            unsigned long count, void *data)
{
	create_bad_access();
	printk(KERN_INFO "Starting bad memory write\n");
	*tp_address_ref = 1;
	return count;
}

static int panic_write(struct file *file, const char *buffer,
                       unsigned long count, void *data)
{
	trace_printk("Starting panic\n");
	printk(KERN_INFO "Starting panic\n");
	panic("test_panic running!\n");
	return count;
}


static int __init test_panic_init(void)
{
	int rc = -ENOMEM;

	trace_printk("Running test_panic_init()");
	/* create directory */
	panic_dir = proc_mkdir(MODULE_NAME, NULL);
	if(!panic_dir) 
		return rc;

	trace_printk("test_panic: Building panic\n");
	panic_file = create_proc_entry("panic", 0600, panic_dir);
	if(!panic_file)
		goto fail1;

	panic_file->data = NULL;
	panic_file->read_proc = NULL;
	panic_file->write_proc = panic_write;
	
	trace_printk("test_panic: Building bad_kfree\n");
	bad_kfree_file = create_proc_entry("bad_kfree", 0600, panic_dir);
	if(!bad_kfree_file)
		goto fail2;

	bad_kfree_file->data = NULL;
	bad_kfree_file->read_proc = NULL;
	bad_kfree_file->write_proc = bad_kfree_write;

	trace_printk("test_panic: Building bug\n");
	bug_file = create_proc_entry("bug", 0600, panic_dir);
	if(!bug_file)
		goto fail3;

	bug_file->data = NULL;
	bug_file->read_proc = NULL;
	bug_file->write_proc = bug_write;

	trace_printk("test_panic: Building hang\n");
	hang_file = create_proc_entry("hang", 0600, panic_dir);
	if(!hang_file)
		goto fail4;

	hang_file->data = NULL;
	hang_file->read_proc = NULL;
	hang_file->write_proc = hang_write;

	trace_printk("test_panic: Building disableirq\n");
	disableirq_file = create_proc_entry("disableirq", 0600, panic_dir);
	if(!disableirq_file)
		goto fail5;

	disableirq_file->data = NULL;
	disableirq_file->read_proc = NULL;
	disableirq_file->write_proc = disableirq_write;

	trace_printk("test_panic: Building bad_access\n");
	bad_access_file = create_proc_entry("bad_access", 0600, panic_dir);
	if(!bad_access_file)
		goto fail6;
	bad_access_file->data = NULL;
	bad_access_file->read_proc = NULL;
	bad_access_file->write_proc = bad_access_write;

	trace_printk("test_panic: Building deep_panic\n");
	deep_panic_file = create_proc_entry("deep_panic", 0600, panic_dir);
	if(!bad_access_file)
		goto fail7;

	deep_panic_file->data = NULL;
	deep_panic_file->read_proc = NULL;
	deep_panic_file->write_proc = deep_panic_write;

	trace_printk("test_panic: Building deep_bug\n");
	deep_bug_file = create_proc_entry("deep_bug", 0600, panic_dir);
	if(!bad_access_file)
		goto fail8;

	deep_bug_file->data = NULL;
	deep_bug_file->read_proc = NULL;
	deep_bug_file->write_proc = deep_bug_write;

	trace_printk(KERN_INFO "%s %s initialised\n", MODULE_NAME,
	       LOCAL_MODULE_VERSION);
	printk(KERN_INFO "%s %s initialised\n", MODULE_NAME, 
	       LOCAL_MODULE_VERSION);
	return 0;
fail8:
		remove_proc_entry("deep_panic", panic_dir);
fail7:
		remove_proc_entry("bad_access", panic_dir);
fail6:
		remove_proc_entry("disableirq", panic_dir);
fail5:
		remove_proc_entry("hang", panic_dir);
fail4:
		remove_proc_entry("bug", panic_dir);
fail3:
		remove_proc_entry("bad_kfree", panic_dir);
fail2:
		remove_proc_entry("panic", panic_dir);
fail1:
		remove_proc_entry(MODULE_NAME, NULL);
	return rc;
}

static void __exit test_panic_exit(void)
{
	remove_proc_entry("panic", panic_dir);
	remove_proc_entry("bad_kfree", panic_dir);
	remove_proc_entry("bug", panic_dir);
	remove_proc_entry("hang", panic_dir);
	remove_proc_entry("disableirq", panic_dir);
	remove_proc_entry("bad_access", panic_dir);
	remove_proc_entry("deep_panic", panic_dir);
	remove_proc_entry("deep_bug", panic_dir);
	remove_proc_entry(MODULE_NAME, NULL);

	printk(KERN_INFO "%s %s removed\n", MODULE_NAME, LOCAL_MODULE_VERSION);
}

module_init(test_panic_init);
module_exit(test_panic_exit);

MODULE_AUTHOR("WindRiver");
MODULE_DESCRIPTION("Force a kernel panic");
MODULE_LICENSE("GPL");
