/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only 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 <mach/ocmem_priv.h>
#include <linux/hardirq.h>

static unsigned notifier_threshold;

/* Protect the notifier structure below */
DEFINE_MUTEX(nc_lock);

struct ocmem_notifier {
	int owner;
	struct atomic_notifier_head nc;
	unsigned listeners;
} notifiers[OCMEM_CLIENT_MAX];

int check_notifier(int id)
{
	int ret = 0;

	if (!check_id(id))
		return 0;

	mutex_lock(&nc_lock);
	ret = notifiers[id].listeners;
	mutex_unlock(&nc_lock);
	return ret;
}

int ocmem_notifier_init(void)
{
	int id;
	/* Maximum notifiers for each subsystem */
	notifier_threshold = 1;
	mutex_lock(&nc_lock);
	for (id = 0; id < OCMEM_CLIENT_MAX; id++) {
		notifiers[id].owner = id;
		ATOMIC_INIT_NOTIFIER_HEAD(&notifiers[id].nc);
		notifiers[id].listeners = 0;
	}
	mutex_unlock(&nc_lock);
	return 0;
}

/* Broadcast a notification to listeners */
int dispatch_notification(int id, enum ocmem_notif_type notif,
				struct ocmem_buf *buf)
{
	int ret = 0;
	struct ocmem_notifier *nc_hndl = NULL;
	mutex_lock(&nc_lock);
	nc_hndl = &notifiers[id];
	if (nc_hndl->listeners == 0) {
		/* Send an error so that the scheduler can clean up */
		mutex_unlock(&nc_lock);
		return -EINVAL;
	}
	ret = atomic_notifier_call_chain(&notifiers[id].nc, notif, buf);
	mutex_unlock(&nc_lock);
	return ret;
}

struct ocmem_notifier *ocmem_notifier_register(int client_id,
						struct notifier_block *nb)
{

	int ret = 0;
	struct ocmem_notifier *nc_hndl = NULL;

	if (!check_id(client_id)) {
		pr_err("ocmem: Invalid Client id\n");
		return NULL;
	}

	if (!zone_active(client_id)) {
		pr_err("ocmem: Client %s (id: %d) not allowed to use OCMEM\n",
					get_name(client_id), client_id);
		return NULL;
	}

	if (!nb) {
		pr_err("ocmem: Invalid Notifier Block\n");
		return NULL;
	}

	mutex_lock(&nc_lock);

	nc_hndl = &notifiers[client_id];

	if (nc_hndl->listeners >= notifier_threshold) {
		pr_err("ocmem: Max notifiers already registered\n");
		mutex_unlock(&nc_lock);
		return NULL;
	}

	ret = atomic_notifier_chain_register(&nc_hndl->nc, nb);

	if (ret < 0) {
		mutex_unlock(&nc_lock);
		return NULL;
	}

	nc_hndl->listeners++;
	pr_info("ocmem: Notifier registered for %d\n", client_id);
	mutex_unlock(&nc_lock);
	return nc_hndl;
}
EXPORT_SYMBOL(ocmem_notifier_register);

int ocmem_notifier_unregister(struct ocmem_notifier *nc_hndl,
				struct notifier_block *nb)
{

	int ret = 0;

	if (!nc_hndl) {
		pr_err("ocmem: Invalid notification handle\n");
		return -EINVAL;
	}

	mutex_lock(&nc_lock);
	ret = atomic_notifier_chain_unregister(&nc_hndl->nc, nb);
	nc_hndl->listeners--;
	mutex_unlock(&nc_lock);

	return ret;
}
EXPORT_SYMBOL(ocmem_notifier_unregister);
