/*
 * Kernel Hooks Interface.
 * 
 * Authors: Richard J Moore <richardj_moore@uk.ibm.com>
 *	    Vamsi Krishna S. <vamsi_krishna@in.ibm.com>
 */
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/hook.h>
#include <asm/system.h>
#include <asm/uaccess.h>

static spinlock_t hook_lock = SPIN_LOCK_UNLOCKED;

static inline void deactivate_hook(struct hook *hook)
{
	hook->hook_flags &= ~HOOK_ACTIVE;
	if(hook->hook_flags & HOOK_ASM_HOOK)
		deactivate_asm_hook(hook);
}

static inline void activate_hook(struct hook *hook)
{
	hook->hook_flags |= HOOK_ACTIVE;
	if(hook->hook_flags & HOOK_ASM_HOOK)
		activate_asm_hook(hook);
}

static void disarm_one_hook_exit(struct hook_rec *hook_rec)
{
	struct hook_rec *rec;

	hook_rec->hook_flags &= ~HOOK_ARMED;
	list_for_each_entry(rec, &hook_rec->hook_head->exit_list, exit_list) {
		if(rec->hook_flags & HOOK_ARMED) {
			return;
		}
	}
	deactivate_hook(hook_rec->hook_head);
}

int hook_exit_register(struct hook *hook, struct hook_rec *hook_rec)
{
	unsigned long flags;

	spin_lock_irqsave(&hook_lock, flags);
	if(hook->hook_flags & HOOK_EXCLUSIVE) {
		if (!list_empty(&hook->exit_list)) {
			spin_unlock_irqrestore(&hook_lock, flags);
			return -EBUSY;
		}
		hook->hook_ex_exit = hook_rec->hook_exit;
	}
	list_add(&hook_rec->exit_list, &hook->exit_list);
	
	hook_rec->hook_head = hook;
	if(is_asm_hook(hook->hook_addr))
		hook->hook_flags |= HOOK_ASM_HOOK;

	MOD_INC_USE_COUNT;
	spin_unlock_irqrestore(&hook_lock, flags);
	return 0;
}

void hook_exit_deregister(struct hook_rec *rec)
{
	unsigned long flags;

	spin_lock_irqsave(&hook_lock, flags);
	if(rec->hook_flags & HOOK_ARMED)
		disarm_one_hook_exit(rec);
	if(rec->hook_head->hook_flags & HOOK_EXCLUSIVE)
		rec->hook_head->hook_ex_exit = NULL;
	list_del(&rec->exit_list);

	MOD_DEC_USE_COUNT;
	spin_unlock_irqrestore(&hook_lock, flags);
}

void hook_exit_arm(struct hook_rec *rec)
{
	unsigned long flags;

	spin_lock_irqsave(&hook_lock, flags);
	rec->hook_flags |= HOOK_ARMED;
	if(!(rec->hook_head->hook_flags & HOOK_ACTIVE))
		activate_hook(rec->hook_head);
	spin_unlock_irqrestore(&hook_lock, flags);
}

void hook_exit_disarm(struct hook_rec *rec)
{
	unsigned long flags;

	spin_lock_irqsave(&hook_lock, flags);
	disarm_one_hook_exit(rec);
	spin_unlock_irqrestore(&hook_lock, flags);
}

static int __init hook_init_module(void)
{
	printk(KERN_INFO "Kernel Hooks Interface installed.\n");
	return 0;
}

static void __exit hook_cleanup_module(void)
{
	printk(KERN_INFO "Kernel Hooks Interface terminated.\n");
}

module_init(hook_init_module);
module_exit(hook_cleanup_module);

EXPORT_SYMBOL_GPL(hook_exit_deregister);
EXPORT_SYMBOL_GPL(hook_exit_arm);
EXPORT_SYMBOL_GPL(hook_exit_disarm);
EXPORT_SYMBOL_GPL(hook_exit_register);

MODULE_LICENSE("GPL");
