/*
 * kernel/power/tuxonice_alloc.c
 *
 * Copyright (C) 2008-2010 Nigel Cunningham (nigel at tuxonice net)
 *
 * This file is released under the GPLv2.
 *
 */

#ifdef CONFIG_PM_DEBUG
#include <linux/module.h>
#include <linux/slab.h>
#include "tuxonice_modules.h"
#include "tuxonice_alloc.h"
#include "tuxonice_sysfs.h"
#include "tuxonice.h"

#define TOI_ALLOC_PATHS 40

static DEFINE_MUTEX(toi_alloc_mutex);

static struct toi_module_ops toi_alloc_ops;

static int toi_fail_num;

static atomic_t toi_alloc_count[TOI_ALLOC_PATHS],
		toi_free_count[TOI_ALLOC_PATHS],
		toi_test_count[TOI_ALLOC_PATHS],
		toi_fail_count[TOI_ALLOC_PATHS];
static int toi_cur_allocd[TOI_ALLOC_PATHS], toi_max_allocd[TOI_ALLOC_PATHS];
static int cur_allocd, max_allocd;

static char *toi_alloc_desc[TOI_ALLOC_PATHS] = {
	"", /* 0 */
	"get_io_info_struct",
	"extent",
	"extent (loading chain)",
	"userui channel",
	"userui arg", /* 5 */
	"attention list metadata",
	"extra pagedir memory metadata",
	"bdev metadata",
	"extra pagedir memory",
	"header_locations_read", /* 10 */
	"bio queue",
	"prepare_readahead",
	"i/o buffer",
	"writer buffer in bio_init",
	"checksum buffer", /* 15 */
	"compression buffer",
	"filewriter signature op",
	"set resume param alloc1",
	"set resume param alloc2",
	"debugging info buffer", /* 20 */
	"check can resume buffer",
	"write module config buffer",
	"read module config buffer",
	"write image header buffer",
	"read pageset1 buffer", /* 25 */
	"get_have_image_data buffer",
	"checksum page",
	"worker rw loop",
	"get nonconflicting page",
	"ps1 load addresses", /* 30 */
	"remove swap image",
	"swap image exists",
	"swap parse sig location",
	"sysfs kobj",
	"swap mark resume attempted buffer", /* 35 */
	"cluster member",
	"boot kernel data buffer",
	"setting swap signature",
	"block i/o bdev struct"
};

#define MIGHT_FAIL(FAIL_NUM, FAIL_VAL) \
	do { \
		BUG_ON(FAIL_NUM >= TOI_ALLOC_PATHS); \
		\
		if (FAIL_NUM == toi_fail_num) { \
			atomic_inc(&toi_test_count[FAIL_NUM]); \
			toi_fail_num = 0; \
			return FAIL_VAL; \
		} \
	} while (0)

static void alloc_update_stats(int fail_num, void *result, int size)
{
	if (!result) {
		atomic_inc(&toi_fail_count[fail_num]);
		return;
	}

	atomic_inc(&toi_alloc_count[fail_num]);
	if (unlikely(test_action_state(TOI_GET_MAX_MEM_ALLOCD))) {
		mutex_lock(&toi_alloc_mutex);
		toi_cur_allocd[fail_num]++;
		cur_allocd += size;
		if (unlikely(cur_allocd > max_allocd)) {
			int i;

			for (i = 0; i < TOI_ALLOC_PATHS; i++)
				toi_max_allocd[i] = toi_cur_allocd[i];
			max_allocd = cur_allocd;
		}
		mutex_unlock(&toi_alloc_mutex);
	}
}

static void free_update_stats(int fail_num, int size)
{
	BUG_ON(fail_num >= TOI_ALLOC_PATHS);
	atomic_inc(&toi_free_count[fail_num]);
	if (unlikely(atomic_read(&toi_free_count[fail_num]) >
				atomic_read(&toi_alloc_count[fail_num])))
		dump_stack();
	if (unlikely(test_action_state(TOI_GET_MAX_MEM_ALLOCD))) {
		mutex_lock(&toi_alloc_mutex);
		cur_allocd -= size;
		toi_cur_allocd[fail_num]--;
		mutex_unlock(&toi_alloc_mutex);
	}
}

void *toi_kzalloc(int fail_num, size_t size, gfp_t flags)
{
	void *result;

	if (toi_alloc_ops.enabled)
		MIGHT_FAIL(fail_num, NULL);
	result = kzalloc(size, flags);
	if (toi_alloc_ops.enabled)
		alloc_update_stats(fail_num, result, size);
	if (fail_num == toi_trace_allocs)
		dump_stack();
	return result;
}
EXPORT_SYMBOL_GPL(toi_kzalloc);

unsigned long toi_get_free_pages(int fail_num, gfp_t mask,
		unsigned int order)
{
	unsigned long result;

	if (toi_alloc_ops.enabled)
		MIGHT_FAIL(fail_num, 0);
	result = __get_free_pages(mask, order);
	if (toi_alloc_ops.enabled)
		alloc_update_stats(fail_num, (void *) result,
				PAGE_SIZE << order);
	if (fail_num == toi_trace_allocs)
		dump_stack();
	return result;
}
EXPORT_SYMBOL_GPL(toi_get_free_pages);

struct page *toi_alloc_page(int fail_num, gfp_t mask)
{
	struct page *result;

	if (toi_alloc_ops.enabled)
		MIGHT_FAIL(fail_num, NULL);
	result = alloc_page(mask);
	if (toi_alloc_ops.enabled)
		alloc_update_stats(fail_num, (void *) result, PAGE_SIZE);
	if (fail_num == toi_trace_allocs)
		dump_stack();
	return result;
}
EXPORT_SYMBOL_GPL(toi_alloc_page);

unsigned long toi_get_zeroed_page(int fail_num, gfp_t mask)
{
	unsigned long result;

	if (toi_alloc_ops.enabled)
		MIGHT_FAIL(fail_num, 0);
	result = get_zeroed_page(mask);
	if (toi_alloc_ops.enabled)
		alloc_update_stats(fail_num, (void *) result, PAGE_SIZE);
	if (fail_num == toi_trace_allocs)
		dump_stack();
	return result;
}
EXPORT_SYMBOL_GPL(toi_get_zeroed_page);

void toi_kfree(int fail_num, const void *arg, int size)
{
	if (arg && toi_alloc_ops.enabled)
		free_update_stats(fail_num, size);

	if (fail_num == toi_trace_allocs)
		dump_stack();
	kfree(arg);
}
EXPORT_SYMBOL_GPL(toi_kfree);

void toi_free_page(int fail_num, unsigned long virt)
{
	if (virt && toi_alloc_ops.enabled)
		free_update_stats(fail_num, PAGE_SIZE);

	if (fail_num == toi_trace_allocs)
		dump_stack();
	free_page(virt);
}
EXPORT_SYMBOL_GPL(toi_free_page);

void toi__free_page(int fail_num, struct page *page)
{
	if (page && toi_alloc_ops.enabled)
		free_update_stats(fail_num, PAGE_SIZE);

	if (fail_num == toi_trace_allocs)
		dump_stack();
	__free_page(page);
}
EXPORT_SYMBOL_GPL(toi__free_page);

void toi_free_pages(int fail_num, struct page *page, int order)
{
	if (page && toi_alloc_ops.enabled)
		free_update_stats(fail_num, PAGE_SIZE << order);

	if (fail_num == toi_trace_allocs)
		dump_stack();
	__free_pages(page, order);
}

void toi_alloc_print_debug_stats(void)
{
	int i, header_done = 0;

	if (!toi_alloc_ops.enabled)
		return;

	for (i = 0; i < TOI_ALLOC_PATHS; i++)
		if (atomic_read(&toi_alloc_count[i]) !=
		    atomic_read(&toi_free_count[i])) {
			if (!header_done) {
				printk(KERN_INFO "Idx  Allocs   Frees   Tests "
					"  Fails     Max Description\n");
				header_done = 1;
			}

			printk(KERN_INFO "%3d %7d %7d %7d %7d %7d %s\n", i,
				atomic_read(&toi_alloc_count[i]),
				atomic_read(&toi_free_count[i]),
				atomic_read(&toi_test_count[i]),
				atomic_read(&toi_fail_count[i]),
				toi_max_allocd[i],
				toi_alloc_desc[i]);
		}
}
EXPORT_SYMBOL_GPL(toi_alloc_print_debug_stats);

static int toi_alloc_initialise(int starting_cycle)
{
	int i;

	if (!starting_cycle)
		return 0;

	if (toi_trace_allocs)
		dump_stack();

	for (i = 0; i < TOI_ALLOC_PATHS; i++) {
		atomic_set(&toi_alloc_count[i], 0);
		atomic_set(&toi_free_count[i], 0);
		atomic_set(&toi_test_count[i], 0);
		atomic_set(&toi_fail_count[i], 0);
		toi_cur_allocd[i] = 0;
		toi_max_allocd[i] = 0;
	};

	max_allocd = 0;
	cur_allocd = 0;
	return 0;
}

static struct toi_sysfs_data sysfs_params[] = {
	SYSFS_INT("failure_test", SYSFS_RW, &toi_fail_num, 0, 99, 0, NULL),
	SYSFS_INT("trace", SYSFS_RW, &toi_trace_allocs, 0, TOI_ALLOC_PATHS, 0,
			NULL),
	SYSFS_BIT("find_max_mem_allocated", SYSFS_RW, &toi_bkd.toi_action,
			TOI_GET_MAX_MEM_ALLOCD, 0),
	SYSFS_INT("enabled", SYSFS_RW, &toi_alloc_ops.enabled, 0, 1, 0,
			NULL)
};

static struct toi_module_ops toi_alloc_ops = {
	.type					= MISC_HIDDEN_MODULE,
	.name					= "allocation debugging",
	.directory				= "alloc",
	.module					= THIS_MODULE,
	.early					= 1,
	.initialise				= toi_alloc_initialise,

	.sysfs_data		= sysfs_params,
	.num_sysfs_entries	= sizeof(sysfs_params) /
		sizeof(struct toi_sysfs_data),
};

int toi_alloc_init(void)
{
	int result = toi_register_module(&toi_alloc_ops);
	return result;
}

void toi_alloc_exit(void)
{
	toi_unregister_module(&toi_alloc_ops);
}
#endif
