/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, using version 2 of the License.             */
/*                                                                           */
/* 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.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/

#include "common.h"
#include <linux/slab.h>

struct handle_node {
	void *object;	/* object (or zero) */
	s32 link;	/* list link; s32 to store -1 */
	u16 label;	/* label of currently existing or future object */
	u16 usage_count;/* usage counter */
	u8 dead;	/* non-zero for dead object */
};

/* 'object' is initialized as zero, becomes non-zero in bind_object_to_handle(),
 * returns to zero in garbage_collector().
 * 'dead' is initialized as zero, becomes non-zero in mark_dead_object(),
 * returns to zero in garbage_collector(); at this point also 'label' increases.
 */

static inline u16 handle_to_index(u32 handle)
{
	return handle & 0xFFFF;
}

static inline u16 handle_to_label(u32 handle)
{
	return handle >> 16;
}

static inline u32 index_to_handle(struct handle_table *ht, u16 index)
{
	return index | (((u32)ht->table[index].label) << 16);
}

static inline void link_free(struct handle_table *ht, u16 index)
{
	if (likely(ht->free_tail >= 0))	/* free_head also valid */
		ht->table[ht->free_tail].link = index;
	else
		ht->free_head = index;
	ht->table[index].link = -1;
	ht->free_tail = index;
}

static inline void link_garbage(struct handle_table *ht, u16 index)
{
	ht->table[index].link = ht->garbage_head;
	ht->garbage_head = index;
	schedule_work(&ht->work);
}

static void garbage_collector(struct work_struct *work)
{
	struct handle_table *ht = container_of(work, struct handle_table, work);
	u16 index;
	struct handle_node *node;
	void *object;
	unsigned long flags;

	while (1) {
		spin_lock_irqsave(&ht->lock, flags);
		if (ht->garbage_head == -1) {
			spin_unlock_irqrestore(&ht->lock, flags);
			return;
		}
		index = ht->garbage_head;
		node = &ht->table[index];
		ht->garbage_head = node->link;
		object = node->object;
		node->object = 0;
		node->dead = 0;
		node->label++;
		link_free(ht, index);
		spin_unlock_irqrestore(&ht->lock, flags);
		/* deallocate when not in atomic */
		ht->deallocator(object);
	}
}

static void init_n_nodes(struct handle_table *ht, u16 first, u16 n)
{
	int i;

	memset(&ht->table[first], 0, sizeof(struct handle_node) * n);

	for (i = first; i < first + n - 1; i++)
		ht->table[i].link = i + 1;
	ht->table[n - 1].link = -1;

	ht->free_head = first;
	ht->free_tail = first + n - 1;
}


int init_handle_table(struct handle_table *ht, int initial_size, const char *name)
{
	int sz = initial_size * sizeof(struct handle_node);

	ht->table = (struct handle_node *)kzalloc(sz, GFP_KERNEL);
	if (!ht->table)
		return -ENOMEM;
	ht->table_size = initial_size;
	init_n_nodes(ht, 0, initial_size);

	ht->garbage_head = -1;
	ht->deallocator = (object_deallocator)kfree;
	INIT_WORK(&ht->work, garbage_collector);
	ht->lock = SPIN_LOCK_UNLOCKED;
	ht->name = name;
	return 0;
}

void uninit_handle_table(struct handle_table *ht)
{
	unsigned int i;

	flush_scheduled_work();	/* be sure any destructors finished */

	/* not locking here - too late :) */
	for (i = 0; i < ht->table_size; i++) {
		if (unlikely(ht->table[i].object)) {
			printk(KERN_WARNING "%s %08x still exists, deallocating it now\n",
					ht->name, index_to_handle(ht, i));
			ht->deallocator(ht->table[i].object);
		}
	}

	kfree(ht->table);
}


int allocate_handle(struct handle_table *ht, u32 *handle)
{
	struct handle_node *new_table, *old_table, *node;
	u16 index;
	u32 old_size, new_size;
	unsigned long flags;

retry:
	spin_lock_irqsave(&ht->lock, flags);

	if (likely(ht->free_head >= 0)) {

		index = ht->free_head;
		node = &ht->table[index];
		ht->free_head = node->link;
		if (unlikely(ht->free_tail == index))
			ht->free_tail = -1;

		/* label has already new value, object is zero, dead flag
		   is zero */
		*handle = index_to_handle(ht, index);
		spin_unlock_irqrestore(&ht->lock, flags);
		return 0;
	}

	/* slow case: no free nodes, need to increase table */
	old_table = ht->table;
	old_size = ht->table_size;
	spin_unlock_irqrestore(&ht->lock, flags);
	if (unlikely(old_size == 0x10000))
		return -EMFILE;		/* will hardly ever happen */
	new_size = 2 * old_size;
	new_table = (struct handle_node *) kzalloc(new_size * sizeof(struct handle_node), GFP_KERNEL);
	spin_lock_irqsave(&ht->lock, flags);

	/* while lock was not held, table could be already increased! */
	if (unlikely(ht->table != old_table)) {
		spin_unlock_irqrestore(&ht->lock, flags);
		if (new_table)
			kfree(new_table);
		goto retry;
	}

	if (unlikely(!new_table)) {
		spin_unlock_irqrestore(&ht->lock, flags);
		return -ENOMEM;
	}

	memcpy(new_table, old_table, old_size * sizeof(struct handle_node));
	ht->table = new_table;
	ht->table_size = new_size;
	init_n_nodes(ht, old_size, new_size - old_size);

	spin_unlock_irqrestore(&ht->lock, flags);
	kfree(old_table);
	goto retry;
}


void bind_object_to_handle(struct handle_table *ht, u32 handle, void *object)
{
	u16 index;
	struct handle_node *node;
	unsigned long flags;

	/* this is called only for handles just returned by allocate_handle(),
	 * so no checks needed here */
	spin_lock_irqsave(&ht->lock, flags);
	index = handle_to_index(handle);
	node = &ht->table[index];
	node->object = object;
	node->usage_count = 0;
	spin_unlock_irqrestore(&ht->lock, flags);
}


void cancel_handle(struct handle_table *ht, u32 handle)
{
	u16 index;
	unsigned long flags;

	/* this is called only for handles just returned by allocate_handle(),
	 * so no checks needed here */
	spin_lock_irqsave(&ht->lock, flags);
	index = handle_to_index(handle);
	link_free(ht, index);
	spin_unlock_irqrestore(&ht->lock, flags);
}


void *get_object(struct handle_table *ht, u32 handle)
{
	struct handle_node *node;
	u16 index, label;
	void *ret = 0;
	unsigned long flags;

	spin_lock_irqsave(&ht->lock, flags);

	index = handle_to_index(handle);
	if (unlikely(index >= ht->table_size))
		goto out;
	node = &ht->table[index];

	label = handle_to_label(handle);
	if (unlikely(label != node->label))
		goto out;

	if (unlikely(node->dead))
		goto out;

	ret = node->object;
	if (unlikely(!ret))
		goto out;

	node->usage_count++;
out:
	spin_unlock_irqrestore(&ht->lock, flags);
	return ret;
}


void put_object(struct handle_table *ht, u32 handle)
{
	struct handle_node *node;
	u16 index;
	unsigned long flags;

	/* this is called only after get_object - so no checks needed */
	spin_lock_irqsave(&ht->lock, flags);
	index = handle_to_index(handle);
	node = &ht->table[index];
	node->usage_count--;
	if (likely(!node->usage_count) && unlikely(node->dead))
		link_garbage(ht, index);
	spin_unlock_irqrestore(&ht->lock, flags);
}


void mark_dead_object(struct handle_table *ht, u32 handle)
{
	struct handle_node *node;
	u16 index;
	unsigned long flags;

	/* this is called only after get_object - so no checks needed */
	spin_lock_irqsave(&ht->lock, flags);
	index = handle_to_index(handle);
	node = &ht->table[index];
	node->dead = 1;
	spin_unlock_irqrestore(&ht->lock, flags);
}


void *search_object(struct handle_table *ht, object_condition cond, void *param)
{
	struct handle_node *node;
	u16 index;
	void *ret = 0;
	unsigned long flags;

	spin_lock_irqsave(&ht->lock, flags);

	for (index = 0; index < ht->table_size; index++) {
		node = &ht->table[index];
		if (node->object && !node->dead && cond(node->object, param)) {
			ret = node->object;
			node->usage_count++;
			break;
		}
	}

	spin_unlock_irqrestore(&ht->lock, flags);
	return ret;
}


int object_alive(struct handle_table *ht, u32 handle)
{
	struct handle_node *node;
	u16 index, label;
	int ret = 0;
	unsigned long flags;

	spin_lock_irqsave(&ht->lock, flags);

	index = handle_to_index(handle);
	if (unlikely(index >= ht->table_size))
		goto out;
	node = &ht->table[index];

	label = handle_to_label(handle);
	if (unlikely(label != node->label))
		goto out;

	if (unlikely(!node->object) || unlikely(node->dead))
		goto out;

	ret = 1;
out:
	spin_unlock_irqrestore(&ht->lock, flags);
	return ret;
}
