/*
 * Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
 /**
 * DOC: Public APIs to perform operations on Peer object
 */
#include <wlan_objmgr_cmn.h>
#include <wlan_objmgr_global_obj.h>
#include <wlan_objmgr_psoc_obj.h>
#include <wlan_objmgr_pdev_obj.h>
#include <wlan_objmgr_vdev_obj.h>
#include <wlan_objmgr_peer_obj.h>
#include <qdf_mem.h>
#include "wlan_objmgr_global_obj_i.h"
#include "wlan_objmgr_psoc_obj_i.h"
#include "wlan_objmgr_pdev_obj_i.h"
#include "wlan_objmgr_vdev_obj_i.h"


/**
 ** APIs to Create/Delete Peer object APIs
 */
static QDF_STATUS wlan_objmgr_peer_object_status(
		struct wlan_objmgr_peer *peer)
{
	uint8_t id;
	QDF_STATUS status = QDF_STATUS_SUCCESS;

	wlan_peer_obj_lock(peer);
	/* Iterate through all components to derive the object status */
	for (id = 0; id < WLAN_UMAC_MAX_COMPONENTS; id++) {
		/* If component disabled, Ignore */
		if (peer->obj_status[id] == QDF_STATUS_COMP_DISABLED)
			continue;
		/* If component operates in Async, status is Partially created,
			break */
		else if (peer->obj_status[id] == QDF_STATUS_COMP_ASYNC) {
			if (peer->peer_comp_priv_obj[id] == NULL) {
				status = QDF_STATUS_COMP_ASYNC;
				break;
			}
		/* If component failed to allocate its object, treat it as
			failure, complete object need to be cleaned up */
		} else if ((peer->obj_status[id] == QDF_STATUS_E_NOMEM) ||
			(peer->obj_status[id] == QDF_STATUS_E_FAILURE)) {
			status = QDF_STATUS_E_FAILURE;
			break;
		}
	}
	wlan_peer_obj_unlock(peer);
	return status;
}

static QDF_STATUS wlan_objmgr_peer_obj_free(struct wlan_objmgr_peer *peer)
{
	struct wlan_objmgr_psoc *psoc;
	struct wlan_objmgr_vdev *vdev;

	vdev = wlan_peer_get_vdev(peer);
	if (vdev == NULL) {
		qdf_print("%s: VDEV is NULL\n", __func__);
		return QDF_STATUS_E_FAILURE;
	}

	/* get PSOC from VDEV, if it is NULL, return */
	psoc = wlan_vdev_get_psoc(vdev);
	if (psoc == NULL) {
		qdf_print("%s: PSOC is NULL\n", __func__);
		return QDF_STATUS_E_FAILURE;
	}

	/* Decrement ref count for BSS peer, so that BSS peer deletes last*/
	if ((wlan_peer_get_peer_type(peer) == WLAN_PEER_STA) ||
	    (wlan_peer_get_peer_type(peer) == WLAN_PEER_STA_TEMP))
		wlan_objmgr_peer_release_ref(wlan_vdev_get_bsspeer(vdev),
					     WLAN_OBJMGR_ID);

	/* Detach peer from VDEV's peer list */
	if (wlan_objmgr_vdev_peer_detach(vdev, peer) == QDF_STATUS_E_FAILURE) {
		qdf_print("%s: Peer VDEV detach failure\n", __func__);
		return QDF_STATUS_E_FAILURE;
	}
	/* Detach peer from PSOC's peer list */
	if (wlan_objmgr_psoc_peer_detach(psoc, peer) == QDF_STATUS_E_FAILURE) {
		qdf_print("%s: Peer PSOC detach failure\n", __func__);
		return QDF_STATUS_E_FAILURE;
	}
	qdf_spinlock_destroy(&peer->peer_lock);
	qdf_mem_free(peer);

	return QDF_STATUS_SUCCESS;

}

struct wlan_objmgr_peer *wlan_objmgr_peer_obj_create(
			struct wlan_objmgr_vdev *vdev,
			enum wlan_peer_type type,
			uint8_t *macaddr)
{
	struct wlan_objmgr_peer *peer;
	struct wlan_objmgr_psoc *psoc;
	wlan_objmgr_peer_create_handler handler;
	wlan_objmgr_peer_status_handler stat_handler;
	void *arg;
	QDF_STATUS obj_status;
	uint8_t id;

	if (vdev == NULL) {
		qdf_print("%s: VDEV is NULL\n", __func__);
		return NULL;
	}
	/* Get psoc, if psoc is NULL, return */
	psoc = wlan_vdev_get_psoc(vdev);
	if (psoc == NULL) {
		qdf_print("%s: PSOC is NULL\n", __func__);
		return NULL;
	}
	/* Allocate memory for peer object */
	peer = qdf_mem_malloc(sizeof(*peer));
	if (peer == NULL) {
		qdf_print("%s: Peer allocation failure\n", __func__);
		return NULL;
	}
	/* set vdev to peer */
	wlan_peer_set_vdev(peer, vdev);
	/* set peer type */
	wlan_peer_set_peer_type(peer, type);
	/* set mac address of peer */
	wlan_peer_set_macaddr(peer, macaddr);
	/* initialize peer state */
	wlan_peer_mlme_set_state(peer, WLAN_INIT_STATE);
	wlan_peer_mlme_reset_seq_num(peer);
	qdf_atomic_init(&peer->peer_objmgr.ref_cnt);
	/* Attach peer to psoc, psoc maintains the node table for the device */
	if (wlan_objmgr_psoc_peer_attach(psoc, peer) !=
					QDF_STATUS_SUCCESS) {
		qdf_print("%s: Peer PSOC attach failure\n", __func__);
		qdf_mem_free(peer);
		return NULL;
	}
	/* Attach peer to vdev peer table */
	if (wlan_objmgr_vdev_peer_attach(vdev, peer) !=
					QDF_STATUS_SUCCESS) {
		qdf_print("%s: Peer VDEV attach failure\n", __func__);
		/* if attach fails, detach from psoc table before free */
		wlan_objmgr_psoc_peer_detach(psoc, peer);
		qdf_mem_free(peer);
		return NULL;
	}
	qdf_spinlock_create(&peer->peer_lock);
	wlan_objmgr_peer_get_ref(peer, WLAN_OBJMGR_ID);
	/* Increment ref count for BSS peer, so that BSS peer deletes last*/
	if ((type == WLAN_PEER_STA) || (type == WLAN_PEER_STA_TEMP))
		wlan_objmgr_peer_get_ref(wlan_vdev_get_bsspeer(vdev),
					 WLAN_OBJMGR_ID);
	/* TODO init other parameters */
	/* Invoke registered create handlers */
	for (id = 0; id < WLAN_UMAC_MAX_COMPONENTS; id++) {
		handler = g_umac_glb_obj->peer_create_handler[id];
		arg = g_umac_glb_obj->peer_create_handler_arg[id];
		if (handler != NULL)
			peer->obj_status[id] = handler(peer, arg);
		else
			peer->obj_status[id] = QDF_STATUS_COMP_DISABLED;
	}
	/* derive the object status */
	obj_status = wlan_objmgr_peer_object_status(peer);
	/* If SUCCESS, Object is created */
	if (obj_status == QDF_STATUS_SUCCESS) {
		peer->obj_state = WLAN_OBJ_STATE_CREATED;
		for (id = 0; id < WLAN_UMAC_MAX_COMPONENTS; id++) {
			stat_handler = g_umac_glb_obj->peer_status_handler[id];
			arg = g_umac_glb_obj->peer_status_handler_arg[id];
			if (stat_handler != NULL)
				stat_handler(peer, arg,
					     QDF_STATUS_SUCCESS);
		}
	} else if (obj_status == QDF_STATUS_COMP_ASYNC) {
		/* If any component operates in different context, update it
		as partially created */
		peer->obj_state = WLAN_OBJ_STATE_PARTIALLY_CREATED;
	} else if (obj_status == QDF_STATUS_E_FAILURE) {
		/* Clean up the peer */
		qdf_print("%s: Peer component object alloc failure\n",
			  __func__);
		wlan_objmgr_peer_obj_delete(peer);
		return NULL;
	}
	return peer;
}

static QDF_STATUS wlan_objmgr_peer_obj_destroy(struct wlan_objmgr_peer *peer)
{
	uint8_t id;
	wlan_objmgr_peer_destroy_handler handler;
	QDF_STATUS obj_status;
	void *arg;

	if (peer == NULL) {
		qdf_print("%s: PEER is NULL\n", __func__);
		return QDF_STATUS_E_FAILURE;
	}

	if (peer->obj_state != WLAN_OBJ_STATE_LOGICALLY_DELETED) {
		qdf_print("%s:peer object delete is not invoked\n", __func__);
		WLAN_OBJMGR_BUG(0);
	}

	/* Invoke registered destroy handlers */
	for (id = 0; id < WLAN_UMAC_MAX_COMPONENTS; id++) {
		handler = g_umac_glb_obj->peer_destroy_handler[id];
		arg = g_umac_glb_obj->peer_destroy_handler_arg[id];
		if (handler != NULL)
			peer->obj_status[id] = handler(peer, arg);
		else
			peer->obj_status[id] = QDF_STATUS_COMP_DISABLED;
	}
	/* Derive the object status */
	obj_status = wlan_objmgr_peer_object_status(peer);
	if (obj_status == QDF_STATUS_E_FAILURE) {
		/* If it status is failure, memory will not be freed */
		QDF_BUG(0);
		return QDF_STATUS_E_FAILURE;
	}
	/* few components deletion is in progress */
	if (obj_status == QDF_STATUS_COMP_ASYNC) {
		peer->obj_state = WLAN_OBJ_STATE_PARTIALLY_DELETED;
		return QDF_STATUS_COMP_ASYNC;
	}

	/* Free the peer object */
	return wlan_objmgr_peer_obj_free(peer);
}

QDF_STATUS wlan_objmgr_peer_obj_delete(struct wlan_objmgr_peer *peer)
{
	if (peer == NULL) {
		qdf_print("%s: PEER is NULL\n", __func__);
		return QDF_STATUS_E_FAILURE;
	}

	/**
	 * Update VDEV object state to LOGICALLY DELETED
	 * It prevents further access of this object
	 */
	wlan_peer_obj_lock(peer);
	peer->obj_state = WLAN_OBJ_STATE_LOGICALLY_DELETED;
	wlan_peer_obj_unlock(peer);
	wlan_objmgr_peer_release_ref(peer, WLAN_OBJMGR_ID);

	return QDF_STATUS_SUCCESS;
}
EXPORT_SYMBOL(wlan_objmgr_peer_obj_delete);
/**
 ** APIs to attach/detach component objects
 */
QDF_STATUS wlan_objmgr_peer_component_obj_attach(
		struct wlan_objmgr_peer *peer,
		enum wlan_umac_comp_id id,
		void *comp_priv_obj,
		QDF_STATUS status)
{
	wlan_objmgr_peer_status_handler s_hler;
	void *arg;
	uint8_t i;
	QDF_STATUS obj_status;

	/* component id is invalid */
	if (id >= WLAN_UMAC_MAX_COMPONENTS)
		return QDF_STATUS_MAXCOMP_FAIL;

	wlan_peer_obj_lock(peer);
	/* If there is a valid entry, return failure,
	valid object needs to be freed first */
	if (peer->peer_comp_priv_obj[id] != NULL) {
		wlan_peer_obj_unlock(peer);
		return QDF_STATUS_E_FAILURE;
	}
	/* Assign component object private pointer(can be NULL also), status */
	peer->peer_comp_priv_obj[id] = comp_priv_obj;
	peer->obj_status[id] = status;
	wlan_peer_obj_unlock(peer);

	if (peer->obj_state != WLAN_OBJ_STATE_PARTIALLY_CREATED)
		return QDF_STATUS_SUCCESS;

	/* If PEER object status is partially created means, this API is
	invoked with differnt context. this block should be executed for async
	components only */
	/* Derive status */
	obj_status = wlan_objmgr_peer_object_status(peer);
	/* STATUS_SUCCESS means, object is CREATED */
	if (obj_status == QDF_STATUS_SUCCESS)
		peer->obj_state = WLAN_OBJ_STATE_CREATED;
	/* update state as CREATION failed, caller has to delete the
	PEER object */
	else if (obj_status == QDF_STATUS_E_FAILURE)
		peer->obj_state = WLAN_OBJ_STATE_CREATION_FAILED;
	/* Notify components about the CREATION success/failure */
	if ((obj_status == QDF_STATUS_SUCCESS) ||
	    (obj_status == QDF_STATUS_E_FAILURE)) {
		/* nofity object status */
		for (i = 0; i < WLAN_UMAC_MAX_COMPONENTS; i++) {
			s_hler = g_umac_glb_obj->peer_status_handler[i];
			arg = g_umac_glb_obj->peer_status_handler_arg[i];
			if (s_hler != NULL)
				s_hler(peer, arg, obj_status);
		}
	}
	return QDF_STATUS_SUCCESS;
}

QDF_STATUS wlan_objmgr_peer_component_obj_detach(
		struct wlan_objmgr_peer *peer,
		enum wlan_umac_comp_id id,
		void *comp_priv_obj)
{
	QDF_STATUS obj_status;

	/* component id is invalid */
	if (id >= WLAN_UMAC_MAX_COMPONENTS)
		return QDF_STATUS_MAXCOMP_FAIL;

	wlan_peer_obj_lock(peer);
	/* If there is a invalid entry, return failure */
	if (peer->peer_comp_priv_obj[id] != comp_priv_obj) {
		peer->obj_status[id] = QDF_STATUS_E_FAILURE;
		wlan_peer_obj_unlock(peer);
		return QDF_STATUS_E_FAILURE;
	}
	/* Reset the pointer to NULL */
	peer->peer_comp_priv_obj[id] = NULL;
	peer->obj_status[id] = QDF_STATUS_SUCCESS;
	wlan_peer_obj_unlock(peer);

	/* If PEER object status is partially destroyed means, this API is
	invoked with differnt context, this block should be executed for async
	components only */
	if ((peer->obj_state == WLAN_OBJ_STATE_PARTIALLY_DELETED) ||
	    (peer->obj_state == WLAN_OBJ_STATE_COMP_DEL_PROGRESS)) {
		/* Derive object status */
		obj_status = wlan_objmgr_peer_object_status(peer);
		if (obj_status == QDF_STATUS_SUCCESS) {
			/*Update the status as Deleted, if full object
				deletion is in progress */
			if (peer->obj_state == WLAN_OBJ_STATE_PARTIALLY_DELETED)
				peer->obj_state = WLAN_OBJ_STATE_DELETED;
			/* Move to creation state, since this component
			deletion alone requested */
			if (peer->obj_state == WLAN_OBJ_STATE_COMP_DEL_PROGRESS)
				peer->obj_state = WLAN_OBJ_STATE_CREATED;
		/* Object status is failure */
		} else if (obj_status == QDF_STATUS_E_FAILURE) {
			/*Update the status as Deletion failed, if full object
				deletion is in progress */
			if (peer->obj_state == WLAN_OBJ_STATE_PARTIALLY_DELETED)
				peer->obj_state =
					WLAN_OBJ_STATE_DELETION_FAILED;
			/* Move to creation state, since this component
			deletion alone requested (do not block other
			components) */
			if (peer->obj_state == WLAN_OBJ_STATE_COMP_DEL_PROGRESS)
				peer->obj_state = WLAN_OBJ_STATE_CREATED;
		}

			/* Delete peer object */
		if ((obj_status == QDF_STATUS_SUCCESS)  &&
		    (peer->obj_state == WLAN_OBJ_STATE_DELETED)) {
			/* Free the peer object */
			return wlan_objmgr_peer_obj_free(peer);
		}
	}

	return QDF_STATUS_SUCCESS;
}


QDF_STATUS wlan_objmgr_trigger_peer_comp_priv_object_creation(
		struct wlan_objmgr_peer *peer,
		enum wlan_umac_comp_id id)
{
	wlan_objmgr_peer_create_handler handler;
	void *arg;
	QDF_STATUS obj_status = QDF_STATUS_SUCCESS;

	/* Component id is invalid */
	if (id >= WLAN_UMAC_MAX_COMPONENTS)
		return QDF_STATUS_MAXCOMP_FAIL;

	wlan_peer_obj_lock(peer);
	/* If component object is already created, delete old
		component object, then invoke creation */
	if (peer->peer_comp_priv_obj[id] != NULL) {
		wlan_peer_obj_unlock(peer);
		return QDF_STATUS_E_FAILURE;
	}
	wlan_peer_obj_unlock(peer);

	/* Invoke registered create handlers */
	handler = g_umac_glb_obj->peer_create_handler[id];
	arg = g_umac_glb_obj->peer_create_handler_arg[id];
	if (handler != NULL)
		peer->obj_status[id] = handler(peer, arg);
	else
		return QDF_STATUS_E_FAILURE;

	/* If object status is created, then only handle this object status */
	if (peer->obj_state == WLAN_OBJ_STATE_CREATED) {
		/* Derive object status */
		obj_status = wlan_objmgr_peer_object_status(peer);
		/* Move PDEV object state to Partially created state */
		if (obj_status == QDF_STATUS_COMP_ASYNC) {
			/*TODO atomic */
			peer->obj_state = WLAN_OBJ_STATE_PARTIALLY_CREATED;
		}
	}

	return obj_status;
}


QDF_STATUS wlan_objmgr_trigger_peer_comp_priv_object_deletion(
		struct wlan_objmgr_peer *peer,
		enum wlan_umac_comp_id id)
{
	wlan_objmgr_peer_destroy_handler handler;
	void *arg;
	QDF_STATUS obj_status = QDF_STATUS_SUCCESS;

	/* component id is invalid */
	if (id >= WLAN_UMAC_MAX_COMPONENTS)
		return QDF_STATUS_MAXCOMP_FAIL;

	wlan_peer_obj_lock(peer);
	/* Component object was never created, invalid operation */
	if (peer->peer_comp_priv_obj[id] == NULL) {
		wlan_peer_obj_unlock(peer);
		return QDF_STATUS_E_FAILURE;
	}

	wlan_peer_obj_unlock(peer);

	/* Invoke registered destroy handlers */
	handler = g_umac_glb_obj->peer_destroy_handler[id];
	arg = g_umac_glb_obj->peer_destroy_handler_arg[id];
	if (handler != NULL)
		peer->obj_status[id] = handler(peer, arg);
	else
		return QDF_STATUS_E_FAILURE;

	/* If object status is created, then only handle this object status */
	if (peer->obj_state == WLAN_OBJ_STATE_CREATED) {
		obj_status = wlan_objmgr_peer_object_status(peer);
			/* move object state to DEL progress */
		if (obj_status == QDF_STATUS_COMP_ASYNC)
			peer->obj_state = WLAN_OBJ_STATE_COMP_DEL_PROGRESS;
	}
	return obj_status;
}

void *wlan_objmgr_peer_get_comp_private_obj(
		struct wlan_objmgr_peer *peer,
		enum wlan_umac_comp_id id)
{
	void *comp_priv_obj;

	/* This API is invoked with lock acquired, don't add any debug prints */

	/* component id is invalid */
	if (id >= WLAN_UMAC_MAX_COMPONENTS) {
		QDF_BUG(0);
		return NULL;
	}

	if (peer == NULL) {
		QDF_BUG(0);
		return NULL;
	}

	comp_priv_obj = peer->peer_comp_priv_obj[id];
	return comp_priv_obj;
}
EXPORT_SYMBOL(wlan_objmgr_peer_get_comp_private_obj);

void wlan_objmgr_peer_get_ref(struct wlan_objmgr_peer *peer,
					wlan_objmgr_ref_dbgid id)
{
	if (peer == NULL) {
		qdf_print("%s: peer obj is NULL\n", __func__);
		QDF_ASSERT(0);
		return;
	}
	/* Increment ref count */
	qdf_atomic_inc(&peer->peer_objmgr.ref_cnt);
	qdf_atomic_inc(&peer->peer_objmgr.ref_id_dbg[id]);

	return;
}
EXPORT_SYMBOL(wlan_objmgr_peer_get_ref);

QDF_STATUS wlan_objmgr_peer_try_get_ref(struct wlan_objmgr_peer *peer,
						 wlan_objmgr_ref_dbgid id)
{
	if (peer == NULL) {
		qdf_print("%s: peer obj is NULL\n", __func__);
		QDF_ASSERT(0);
		return QDF_STATUS_E_FAILURE;
	}

	wlan_peer_obj_lock(peer);
	if (peer->obj_state == WLAN_OBJ_STATE_LOGICALLY_DELETED) {
		wlan_peer_obj_unlock(peer);
		qdf_print("%s: peer obj is in Deletion Progress state\n",
			  __func__);
		return QDF_STATUS_E_RESOURCES;
	}

	wlan_objmgr_peer_get_ref(peer, id);
	wlan_peer_obj_unlock(peer);

	return QDF_STATUS_SUCCESS;
}
EXPORT_SYMBOL(wlan_objmgr_peer_try_get_ref);

void wlan_objmgr_peer_release_ref(struct wlan_objmgr_peer *peer,
						 wlan_objmgr_ref_dbgid id)
{
	if (peer == NULL) {
		qdf_print("%s: peer obj is NULL\n", __func__);
		QDF_ASSERT(0);
		return;
	}

	if (!qdf_atomic_read(&peer->peer_objmgr.ref_id_dbg[id])) {
		qdf_print("%s: peer ref cnt was not taken by %d\n",
			  __func__, id);
		wlan_objmgr_print_ref_ids(peer->peer_objmgr.ref_id_dbg);
		WLAN_OBJMGR_BUG(0);
	}

	if (!qdf_atomic_read(&peer->peer_objmgr.ref_cnt)) {
		qdf_print("%s: peer ref cnt is 0\n", __func__);
		WLAN_OBJMGR_BUG(0);
		return;
	}
	qdf_atomic_dec(&peer->peer_objmgr.ref_id_dbg[id]);

	/* Decrement ref count, free vdev, if ref acount == 0 */
	if (qdf_atomic_dec_and_test(&peer->peer_objmgr.ref_cnt))
		wlan_objmgr_peer_obj_destroy(peer);

	return;
}
EXPORT_SYMBOL(wlan_objmgr_peer_release_ref);
