/** @file mlan_util.h
 *
 *  @brief This file contains wrappers for linked-list,
 *  spinlock and timer defines.
 *
 *  Copyright (C) 2008-2019, Marvell International Ltd.
 *
 *  This software file (the "File") is distributed by Marvell International
 *  Ltd. under the terms of the GNU General Public License Version 2, June 1991
 *  (the "License").  You may use, redistribute and/or modify this File in
 *  accordance with the terms and conditions of the License, a copy of which
 *  is available by writing to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
 *  worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 *  THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
 *  ARE EXPRESSLY DISCLAIMED.  The License provides additional details about
 *  this warranty disclaimer.
 */

/******************************************************
Change log:
    10/28/2008: initial version
******************************************************/

#ifndef _MLAN_UTIL_H_
#define _MLAN_UTIL_H_

/** Circular doubly linked list */
typedef struct _mlan_linked_list {
    /** Pointer to previous node */
	struct _mlan_linked_list *pprev;
    /** Pointer to next node */
	struct _mlan_linked_list *pnext;
} mlan_linked_list, *pmlan_linked_list;

/** List head */
typedef struct _mlan_list_head {
    /** Pointer to previous node */
	struct _mlan_linked_list *pprev;
    /** Pointer to next node */
	struct _mlan_linked_list *pnext;
    /** Pointer to lock */
	t_void *plock;
} mlan_list_head, *pmlan_list_head;

/**
 *  @brief This function initializes a list without locking
 *
 *  @param phead		List head
 *
 *  @return			N/A
 */
static INLINE t_void
util_init_list(pmlan_linked_list phead)
{
	/* Both next and prev point to self */
	phead->pprev = phead->pnext = (pmlan_linked_list)phead;
}

/**
 *  @brief This function initializes a list
 *
 *  @param phead		List head
 *  @param lock_required	A flag for spinlock requirement
 *  @param moal_init_lock	A pointer to init lock handler
 *
 *  @return			N/A
 */
static INLINE t_void
util_init_list_head(t_void *pmoal_handle,
		    pmlan_list_head phead,
		    t_u8 lock_required,
		    mlan_status (*moal_init_lock) (t_void *handle,
						   t_void **pplock))
{
	/* Both next and prev point to self */
	util_init_list((pmlan_linked_list)phead);
	if (lock_required)
		moal_init_lock(pmoal_handle, &phead->plock);
	else
		phead->plock = 0;
}

/**
 *  @brief This function frees a list
 *
 *  @param phead		List head
 *  @param moal_free_lock	A pointer to free lock handler
 *
 *  @return			N/A
 */
static INLINE t_void
util_free_list_head(t_void *pmoal_handle,
		    pmlan_list_head phead,
		    mlan_status (*moal_free_lock) (t_void *handle,
						   t_void *plock))
{
	phead->pprev = phead->pnext = 0;
	if (phead->plock)
		moal_free_lock(pmoal_handle, phead->plock);
}

/**
 *  @brief This function peeks into a list
 *
 *  @param phead		List head
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return			List node
 */
static INLINE pmlan_linked_list
util_peek_list(t_void *pmoal_handle,
	       pmlan_list_head phead,
	       mlan_status (*moal_spin_lock) (t_void *handle, t_void *plock),
	       mlan_status (*moal_spin_unlock) (t_void *handle, t_void *plock))
{
	pmlan_linked_list pnode = 0;

	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, phead->plock);
	if (phead->pnext != (pmlan_linked_list)phead)
		pnode = phead->pnext;
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, phead->plock);
	return pnode;
}

/**
 *  @brief This function queues a node at the list tail
 *
 *  @param phead		List head
 *  @param pnode		List node to queue
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return			N/A
 */
static INLINE t_void
util_enqueue_list_tail(t_void *pmoal_handle,
		       pmlan_list_head phead,
		       pmlan_linked_list pnode,
		       mlan_status (*moal_spin_lock) (t_void *handle,
						      t_void *plock),
		       mlan_status (*moal_spin_unlock) (t_void *handle,
							t_void *plock))
{
	pmlan_linked_list pold_last;

	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, phead->plock);
	pold_last = phead->pprev;
	pnode->pprev = pold_last;
	pnode->pnext = (pmlan_linked_list)phead;

	phead->pprev = pold_last->pnext = pnode;
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, phead->plock);
}

/**
 *  @brief This function adds a node at the list head
 *
 *  @param phead		List head
 *  @param pnode		List node to add
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return			N/A
 */
static INLINE t_void
util_enqueue_list_head(t_void *pmoal_handle,
		       pmlan_list_head phead,
		       pmlan_linked_list pnode,
		       mlan_status (*moal_spin_lock) (t_void *handle,
						      t_void *plock),
		       mlan_status (*moal_spin_unlock) (t_void *handle,
							t_void *plock))
{
	pmlan_linked_list pold_first;

	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, phead->plock);
	pold_first = phead->pnext;
	pnode->pprev = (pmlan_linked_list)phead;
	pnode->pnext = pold_first;

	phead->pnext = pold_first->pprev = pnode;
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, phead->plock);
}

/**
 *  @brief This function removes a node from the list
 *
 *  @param phead		List head
 *  @param pnode		List node to remove
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return			N/A
 */
static INLINE t_void
util_unlink_list(t_void *pmoal_handle,
		 pmlan_list_head phead,
		 pmlan_linked_list pnode,
		 mlan_status (*moal_spin_lock) (t_void *handle, t_void *plock),
		 mlan_status (*moal_spin_unlock) (t_void *handle,
						  t_void *plock))
{
	pmlan_linked_list pmy_prev;
	pmlan_linked_list pmy_next;

	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, phead->plock);
	pmy_prev = pnode->pprev;
	pmy_next = pnode->pnext;
	pmy_next->pprev = pmy_prev;
	pmy_prev->pnext = pmy_next;

	pnode->pnext = pnode->pprev = 0;
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, phead->plock);
}

/**
 *  @brief This function dequeues a node from the list
 *
 *  @param phead		List head
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return			List node
 */
static INLINE pmlan_linked_list
util_dequeue_list(t_void *pmoal_handle,
		  pmlan_list_head phead,
		  mlan_status (*moal_spin_lock) (t_void *handle, t_void *plock),
		  mlan_status (*moal_spin_unlock) (t_void *handle,
						   t_void *plock))
{
	pmlan_linked_list pnode;

	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, phead->plock);
	pnode = phead->pnext;
	if (pnode && (pnode != (pmlan_linked_list)phead))
		util_unlink_list(pmoal_handle, phead, pnode, 0, 0);
	else
		pnode = 0;
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, phead->plock);
	return pnode;
}

/** Access controlled scalar variable */
typedef struct _mlan_scalar {
    /** Value */
	t_s32 value;
    /** Pointer to lock */
	t_void *plock;
    /** Control flags */
	t_u32 flags;
} mlan_scalar, *pmlan_scalar;

/** Flag to scalar lock acquired */
#define MLAN_SCALAR_FLAG_UNIQUE_LOCK	MBIT(16)

/** scalar conditional value list */
typedef enum _MLAN_SCALAR_CONDITIONAL {
	MLAN_SCALAR_COND_EQUAL,
	MLAN_SCALAR_COND_NOT_EQUAL,
	MLAN_SCALAR_COND_GREATER_THAN,
	MLAN_SCALAR_COND_GREATER_OR_EQUAL,
	MLAN_SCALAR_COND_LESS_THAN,
	MLAN_SCALAR_COND_LESS_OR_EQUAL
} MLAN_SCALAR_CONDITIONAL;

/**
 *  @brief This function initializes a scalar
 *
 *  @param pscalar			Pointer to scalar
 *  @param val				Initial scalar value
 *  @param plock_to_use		A new lock is created if NULL, else lock to use
 *  @param moal_init_lock	A pointer to init lock handler
 *
 *  @return					N/A
 */
static INLINE t_void
util_scalar_init(t_void *pmoal_handle,
		 pmlan_scalar pscalar,
		 t_s32 val,
		 t_void *plock_to_use,
		 mlan_status (*moal_init_lock) (t_void *handle,
						t_void **pplock))
{
	pscalar->value = val;
	pscalar->flags = 0;
	if (plock_to_use) {
		pscalar->flags &= ~MLAN_SCALAR_FLAG_UNIQUE_LOCK;
		pscalar->plock = plock_to_use;
	} else {
		pscalar->flags |= MLAN_SCALAR_FLAG_UNIQUE_LOCK;
		moal_init_lock(pmoal_handle, &pscalar->plock);
	}
}

/**
 *  @brief This function frees a scalar
 *
 *  @param pscalar			Pointer to scalar
 *  @param moal_free_lock	A pointer to free lock handler
 *
 *  @return			N/A
 */
static INLINE t_void
util_scalar_free(t_void *pmoal_handle,
		 pmlan_scalar pscalar,
		 mlan_status (*moal_free_lock) (t_void *handle, t_void *plock))
{
	if (pscalar->flags & MLAN_SCALAR_FLAG_UNIQUE_LOCK)
		moal_free_lock(pmoal_handle, pscalar->plock);
}

/**
 *  @brief This function reads value from scalar
 *
 *  @param pscalar			Pointer to scalar
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return					Stored value
 */
static INLINE t_s32
util_scalar_read(t_void *pmoal_handle,
		 pmlan_scalar pscalar,
		 mlan_status (*moal_spin_lock) (t_void *handle, t_void *plock),
		 mlan_status (*moal_spin_unlock) (t_void *handle,
						  t_void *plock))
{
	t_s32 val;

	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, pscalar->plock);
	val = pscalar->value;
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, pscalar->plock);

	return val;
}

/**
 *  @brief This function writes value to scalar
 *
 *  @param pscalar			Pointer to scalar
 *  @param val				Value to write
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return					N/A
 */
static INLINE t_void
util_scalar_write(t_void *pmoal_handle,
		  pmlan_scalar pscalar,
		  t_s32 val,
		  mlan_status (*moal_spin_lock) (t_void *handle, t_void *plock),
		  mlan_status (*moal_spin_unlock) (t_void *handle,
						   t_void *plock))
{
	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, pscalar->plock);
	pscalar->value = val;
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, pscalar->plock);
}

/**
 *  @brief This function increments the value in scalar
 *
 *  @param pscalar			Pointer to scalar
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return					N/A
 */
static INLINE t_void
util_scalar_increment(t_void *pmoal_handle,
		      pmlan_scalar pscalar,
		      mlan_status (*moal_spin_lock) (t_void *handle,
						     t_void *plock),
		      mlan_status (*moal_spin_unlock) (t_void *handle,
						       t_void *plock))
{
	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, pscalar->plock);
	pscalar->value++;
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, pscalar->plock);
}

/**
 *  @brief This function decrements the value in scalar
 *
 *  @param pscalar			Pointer to scalar
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return					N/A
 */
static INLINE t_void
util_scalar_decrement(t_void *pmoal_handle,
		      pmlan_scalar pscalar,
		      mlan_status (*moal_spin_lock) (t_void *handle,
						     t_void *plock),
		      mlan_status (*moal_spin_unlock) (t_void *handle,
						       t_void *plock))
{
	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, pscalar->plock);
	pscalar->value--;
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, pscalar->plock);
}

/**
 *  @brief This function adds an offset to the value in scalar,
 *         and returns the new value
 *
 *  @param pscalar			Pointer to scalar
 *  @param offset			Offset value (can be negative)
 *  @param moal_spin_lock	A pointer to spin lock handler
 *  @param moal_spin_unlock	A pointer to spin unlock handler
 *
 *  @return					Value after offset
 */
static INLINE t_s32
util_scalar_offset(t_void *pmoal_handle,
		   pmlan_scalar pscalar,
		   t_s32 offset,
		   mlan_status (*moal_spin_lock) (t_void *handle,
						  t_void *plock),
		   mlan_status (*moal_spin_unlock) (t_void *handle,
						    t_void *plock))
{
	t_s32 newval;

	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, pscalar->plock);
	newval = (pscalar->value += offset);
	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, pscalar->plock);

	return newval;
}

/**
 *  @brief This function writes the value to the scalar
 *         if existing value compared with other value is true.
 *
 *  @param pscalar          Pointer to scalar
 *  @param condition        Condition to check
 *  @param val_compare      Value to compare against current value
 *                          ((A X B), where B = val_compare)
 *  @param val_to_set       Value to set if comparison is true
 *  @param moal_spin_lock   A pointer to spin lock handler
 *  @param moal_spin_unlock A pointer to spin unlock handler
 *
 *  @return                 Comparison result (MTRUE or MFALSE)
 */
static INLINE t_u8
util_scalar_conditional_write(t_void *pmoal_handle,
			      pmlan_scalar pscalar,
			      MLAN_SCALAR_CONDITIONAL condition,
			      t_s32 val_compare,
			      t_s32 val_to_set,
			      mlan_status (*moal_spin_lock) (t_void *handle,
							     t_void *plock),
			      mlan_status (*moal_spin_unlock) (t_void *handle,
							       t_void *plock))
{
	t_u8 update;
	if (moal_spin_lock)
		moal_spin_lock(pmoal_handle, pscalar->plock);

	switch (condition) {
	case MLAN_SCALAR_COND_EQUAL:
		update = (pscalar->value == val_compare);
		break;
	case MLAN_SCALAR_COND_NOT_EQUAL:
		update = (pscalar->value != val_compare);
		break;
	case MLAN_SCALAR_COND_GREATER_THAN:
		update = (pscalar->value > val_compare);
		break;
	case MLAN_SCALAR_COND_GREATER_OR_EQUAL:
		update = (pscalar->value >= val_compare);
		break;
	case MLAN_SCALAR_COND_LESS_THAN:
		update = (pscalar->value < val_compare);
		break;
	case MLAN_SCALAR_COND_LESS_OR_EQUAL:
		update = (pscalar->value <= val_compare);
		break;
	default:
		update = MFALSE;
		break;
	}
	if (update)
		pscalar->value = val_to_set;

	if (moal_spin_unlock)
		moal_spin_unlock(pmoal_handle, pscalar->plock);
	return (update) ? MTRUE : MFALSE;
}

/**
 *  @brief This function counts the bits of unsigned int number
 *
 *  @param num  number
 *  @return     number of bits
 */
static INLINE t_u32
bitcount(t_u32 num)
{
	t_u32 count = 0;
	static t_u32 nibblebits[] = {
		0, 1, 1, 2, 1, 2, 2, 3,
		1, 2, 2, 3, 2, 3, 3, 4
	};
	for (; num != 0; num >>= 4)
		count += nibblebits[num & 0x0f];
	return count;
}

#endif /* !_MLAN_UTIL_H_ */