// SPDX-License-Identifier: GPL-2.0
/*
 * Support for Intel Camera Imaging ISP subsystem.
 * Copyright (c) 2015, Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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 "hmm.h"

#include "ia_css_refcount.h"
#include "sh_css_defs.h"

#include "platform_support.h"

#include "assert_support.h"

#include "ia_css_debug.h"

/* TODO: enable for other memory aswell
	 now only for ia_css_ptr */
struct ia_css_refcount_entry {
	u32 count;
	ia_css_ptr data;
	s32 id;
};

struct ia_css_refcount_list {
	u32 size;
	struct ia_css_refcount_entry *items;
};

static struct ia_css_refcount_list myrefcount;

static struct ia_css_refcount_entry *refcount_find_entry(ia_css_ptr ptr,
	bool firstfree)
{
	u32 i;

	if (ptr == 0)
		return NULL;
	if (!myrefcount.items) {
		ia_css_debug_dtrace(IA_CSS_DEBUG_ERROR,
				    "%s(): Ref count not initialized!\n", __func__);
		return NULL;
	}

	for (i = 0; i < myrefcount.size; i++) {
		if ((&myrefcount.items[i])->data == 0) {
			if (firstfree) {
				/* for new entry */
				return &myrefcount.items[i];
			}
		}
		if ((&myrefcount.items[i])->data == ptr) {
			/* found entry */
			return &myrefcount.items[i];
		}
	}
	return NULL;
}

int ia_css_refcount_init(uint32_t size)
{
	int err = 0;

	if (size == 0) {
		ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
				    "%s(): Size of 0 for Ref count init!\n", __func__);
		return -EINVAL;
	}
	if (myrefcount.items) {
		ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
				    "%s(): Ref count is already initialized\n", __func__);
		return -EINVAL;
	}
	myrefcount.items =
	    kvmalloc(sizeof(struct ia_css_refcount_entry) * size, GFP_KERNEL);
	if (!myrefcount.items)
		err = -ENOMEM;
	if (!err) {
		memset(myrefcount.items, 0,
		       sizeof(struct ia_css_refcount_entry) * size);
		myrefcount.size = size;
	}
	return err;
}

void ia_css_refcount_uninit(void)
{
	struct ia_css_refcount_entry *entry;
	u32 i;

	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
			    "%s() entry\n", __func__);
	for (i = 0; i < myrefcount.size; i++) {
		/* driver verifier tool has issues with &arr[i]
		   and prefers arr + i; as these are actually equivalent
		   the line below uses + i
		*/
		entry = myrefcount.items + i;
		if (entry->data != mmgr_NULL) {
			/*	ia_css_debug_dtrace(IA_CSS_DBG_TRACE,
				"ia_css_refcount_uninit: freeing (%x)\n",
				entry->data);*/
			hmm_free(entry->data);
			entry->data = mmgr_NULL;
			entry->count = 0;
			entry->id = 0;
		}
	}
	kvfree(myrefcount.items);
	myrefcount.items = NULL;
	myrefcount.size = 0;
	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
			    "%s() leave\n", __func__);
}

ia_css_ptr ia_css_refcount_increment(s32 id, ia_css_ptr ptr)
{
	struct ia_css_refcount_entry *entry;

	if (ptr == mmgr_NULL)
		return ptr;

	entry = refcount_find_entry(ptr, false);

	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
			    "%s(%x) 0x%x\n", __func__, id, ptr);

	if (!entry) {
		entry = refcount_find_entry(ptr, true);
		assert(entry);
		if (!entry)
			return mmgr_NULL;
		entry->id = id;
	}

	if (entry->id != id) {
		ia_css_debug_dtrace(IA_CSS_DEBUG_ERROR,
				    "%s(): Ref count IDS do not match!\n", __func__);
		return mmgr_NULL;
	}

	if (entry->data == ptr)
		entry->count += 1;
	else if (entry->data == mmgr_NULL) {
		entry->data = ptr;
		entry->count = 1;
	} else
		return mmgr_NULL;

	return ptr;
}

bool ia_css_refcount_decrement(s32 id, ia_css_ptr ptr)
{
	struct ia_css_refcount_entry *entry;

	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
			    "%s(%x) 0x%x\n", __func__, id, ptr);

	if (ptr == mmgr_NULL)
		return false;

	entry = refcount_find_entry(ptr, false);

	if (entry) {
		if (entry->id != id) {
			ia_css_debug_dtrace(IA_CSS_DEBUG_ERROR,
					    "%s(): Ref count IDS do not match!\n", __func__);
			return false;
		}
		if (entry->count > 0) {
			entry->count -= 1;
			if (entry->count == 0) {
				/* ia_css_debug_dtrace(IA_CSS_DBEUG_TRACE,
				   "ia_css_refcount_decrement: freeing\n");*/
				hmm_free(ptr);
				entry->data = mmgr_NULL;
				entry->id = 0;
			}
			return true;
		}
	}

	/* SHOULD NOT HAPPEN: ptr not managed by refcount, or not
	   valid anymore */
	if (entry)
		IA_CSS_ERROR("id %x, ptr 0x%x entry %p entry->id %x entry->count %d\n",
			     id, ptr, entry, entry->id, entry->count);
	else
		IA_CSS_ERROR("entry NULL\n");
	assert(false);

	return false;
}

bool ia_css_refcount_is_single(ia_css_ptr ptr)
{
	struct ia_css_refcount_entry *entry;

	if (ptr == mmgr_NULL)
		return false;

	entry = refcount_find_entry(ptr, false);

	if (entry)
		return (entry->count == 1);

	return true;
}

void ia_css_refcount_clear(s32 id, clear_func clear_func_ptr)
{
	struct ia_css_refcount_entry *entry;
	u32 i;
	u32 count = 0;

	assert(clear_func_ptr);
	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "%s(%x)\n",
			    __func__, id);

	for (i = 0; i < myrefcount.size; i++) {
		/* driver verifier tool has issues with &arr[i]
		   and prefers arr + i; as these are actually equivalent
		   the line below uses + i
		*/
		entry = myrefcount.items + i;
		if ((entry->data != mmgr_NULL) && (entry->id == id)) {
			ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
					    "%s: %x: 0x%x\n", __func__,
					    id, entry->data);
			if (clear_func_ptr) {
				/* clear using provided function */
				clear_func_ptr(entry->data);
			} else {
				ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
						    "%s: using hmm_free: no clear_func\n", __func__);
				hmm_free(entry->data);
			}

			if (entry->count != 0) {
				IA_CSS_WARNING("Ref count for entry %x is not zero!", entry->id);
			}

			assert(entry->count == 0);

			entry->data = mmgr_NULL;
			entry->count = 0;
			entry->id = 0;
			count++;
		}
	}
	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
			    "%s(%x): cleared %d\n", __func__, id,
			    count);
}

bool ia_css_refcount_is_valid(ia_css_ptr ptr)
{
	struct ia_css_refcount_entry *entry;

	if (ptr == mmgr_NULL)
		return false;

	entry = refcount_find_entry(ptr, false);

	return entry;
}