/*
 * 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:    wlan_mgmt_txrx_utils_api.c
 *  This file contains mgmt txrx public API definitions for umac
 *  converged components.
 */

#include "wlan_mgmt_txrx_utils_api.h"
#include "../../core/src/wlan_mgmt_txrx_main_i.h"
#include "wlan_objmgr_psoc_obj.h"
#include "wlan_objmgr_global_obj.h"
#include "wlan_objmgr_pdev_obj.h"
#include "wlan_objmgr_vdev_obj.h"
#include "wlan_objmgr_peer_obj.h"
#include "qdf_nbuf.h"

/**
 * wlan_mgmt_txrx_psoc_obj_create_notification() - called from objmgr when psoc
 *                                                 is created
 * @psoc: psoc context
 * @arg: argument
 *
 * This function gets called from object manager when psoc is being created and
 * creates mgmt_txrx context, mgmt desc pool.
 *
 * Return: QDF_STATUS_SUCCESS - in case of success
 */
static QDF_STATUS wlan_mgmt_txrx_psoc_obj_create_notification(
			struct wlan_objmgr_psoc *psoc,
			void *arg)
{
	struct mgmt_txrx_priv_context *mgmt_txrx_ctx;
	struct mgmt_txrx_stats_t *mgmt_txrx_stats;
	QDF_STATUS status;

	if (!psoc) {
		mgmt_txrx_err("psoc context passed is NULL");
		return QDF_STATUS_E_INVAL;
	}

	mgmt_txrx_ctx = qdf_mem_malloc(sizeof(*mgmt_txrx_ctx));
	if (!mgmt_txrx_ctx) {
		mgmt_txrx_err("Failed to allocate mgmt txrx context");
		return QDF_STATUS_E_NOMEM;
	}

	mgmt_txrx_ctx->psoc = psoc;

	status = wlan_mgmt_txrx_desc_pool_init(mgmt_txrx_ctx,
					       MGMT_DESC_POOL_MAX);
	if (status != QDF_STATUS_SUCCESS) {
		mgmt_txrx_err("Failed to initialize mgmt desc. pool with status: %u",
				status);
		goto err_desc_pool_init;
	}

	mgmt_txrx_stats = qdf_mem_malloc(sizeof(*mgmt_txrx_stats));
	if (!mgmt_txrx_stats) {
		mgmt_txrx_err("Failed to allocate memory for mgmt txrx stats structure");
		status = QDF_STATUS_E_NOMEM;
		goto err_mgmt_txrx_stats;
	}
	mgmt_txrx_ctx->mgmt_txrx_stats = mgmt_txrx_stats;

	qdf_spinlock_create(&mgmt_txrx_ctx->mgmt_txrx_ctx_lock);

	if (wlan_objmgr_psoc_component_obj_attach(psoc,
				WLAN_UMAC_COMP_MGMT_TXRX,
				mgmt_txrx_ctx, QDF_STATUS_SUCCESS)
			!= QDF_STATUS_SUCCESS) {
		mgmt_txrx_err("Failed to attach mgmt txrx ctx in psoc ctx");
		status = QDF_STATUS_E_FAILURE;
		goto err_psoc_attach;
	}

	mgmt_txrx_info("Mgmt txrx creation successful, mgmt txrx ctx: %p, psoc: %p",
			mgmt_txrx_ctx, psoc);

	return QDF_STATUS_SUCCESS;

err_psoc_attach:
	qdf_spinlock_destroy(&mgmt_txrx_ctx->mgmt_txrx_ctx_lock);
	qdf_mem_free(mgmt_txrx_stats);
err_mgmt_txrx_stats:
	wlan_mgmt_txrx_desc_pool_deinit(mgmt_txrx_ctx);
err_desc_pool_init:
	qdf_mem_free(mgmt_txrx_ctx);
	return status;
}

/**
 * wlan_mgmt_txrx_psoc_obj_destroy_notification() - called from objmgr when
 *                                                  psoc is destroyed
 * @psoc: psoc context
 * @arg: argument
 *
 * This function gets called from object manager when psoc is being destroyed
 * psoc deletes mgmt_txrx context, mgmt desc pool.
 *
 * Return: QDF_STATUS_SUCCESS - in case of success
 */
static QDF_STATUS wlan_mgmt_txrx_psoc_obj_destroy_notification(
			struct wlan_objmgr_psoc *psoc,
			void *arg)
{
	struct mgmt_txrx_priv_context *mgmt_txrx_ctx;

	if (!psoc) {
		mgmt_txrx_err("psoc context passed is NULL");
		return QDF_STATUS_E_INVAL;
	}

	mgmt_txrx_ctx = wlan_objmgr_psoc_get_comp_private_obj(
			psoc, WLAN_UMAC_COMP_MGMT_TXRX);
	if (!mgmt_txrx_ctx) {
		mgmt_txrx_err("mgmt txrx context is already NULL");
		return QDF_STATUS_E_FAILURE;
	}

	mgmt_txrx_info("deleting mgmt txrx psoc obj, mgmt txrx ctx: %p, psoc: %p",
			mgmt_txrx_ctx, psoc);
	if (wlan_objmgr_psoc_component_obj_detach(psoc,
				WLAN_UMAC_COMP_MGMT_TXRX, mgmt_txrx_ctx)
			!= QDF_STATUS_SUCCESS) {
		mgmt_txrx_err("Failed to detach mgmt txrx ctx in psoc ctx");
		return QDF_STATUS_E_FAILURE;
	}

	wlan_mgmt_txrx_desc_pool_deinit(mgmt_txrx_ctx);
	qdf_mem_free(mgmt_txrx_ctx->mgmt_txrx_stats);
	qdf_spinlock_destroy(&mgmt_txrx_ctx->mgmt_txrx_ctx_lock);
	qdf_mem_free(mgmt_txrx_ctx);

	mgmt_txrx_info("mgmt txrx deletion successful, psoc: %p", psoc);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS wlan_mgmt_txrx_init(void)
{
	QDF_STATUS status = QDF_STATUS_SUCCESS;

	status = wlan_objmgr_register_psoc_create_handler(
				WLAN_UMAC_COMP_MGMT_TXRX,
				wlan_mgmt_txrx_psoc_obj_create_notification,
				NULL);
	if (status != QDF_STATUS_SUCCESS) {
		mgmt_txrx_err("Failed to register mgmt txrx obj create handler");
		goto err_psoc_create;
	}

	status = wlan_objmgr_register_psoc_destroy_handler(
				WLAN_UMAC_COMP_MGMT_TXRX,
				wlan_mgmt_txrx_psoc_obj_destroy_notification,
				NULL);
	if (status != QDF_STATUS_SUCCESS) {
		mgmt_txrx_err("Failed to register mgmt txrx obj destroy handler");
		goto err_psoc_delete;
	}

	mgmt_txrx_info("Successfully registered create and destroy handlers with objmgr");
	return QDF_STATUS_SUCCESS;

err_psoc_delete:
	wlan_objmgr_unregister_psoc_create_handler(WLAN_UMAC_COMP_MGMT_TXRX,
			wlan_mgmt_txrx_psoc_obj_create_notification, NULL);
err_psoc_create:
	return status;
}

QDF_STATUS wlan_mgmt_txrx_deinit(void)
{
	if (wlan_objmgr_unregister_psoc_create_handler(WLAN_UMAC_COMP_MGMT_TXRX,
				wlan_mgmt_txrx_psoc_obj_create_notification,
				NULL)
			!= QDF_STATUS_SUCCESS) {
		return QDF_STATUS_E_FAILURE;
	}

	if (wlan_objmgr_unregister_psoc_destroy_handler(
				WLAN_UMAC_COMP_MGMT_TXRX,
				wlan_mgmt_txrx_psoc_obj_destroy_notification,
				NULL)
			!= QDF_STATUS_SUCCESS) {
		return QDF_STATUS_E_FAILURE;
	}

	mgmt_txrx_info("Successfully unregistered create and destroy handlers with objmgr");
	return QDF_STATUS_SUCCESS;
}

QDF_STATUS wlan_mgmt_txrx_mgmt_frame_tx(struct wlan_objmgr_peer *peer,
					void *context,
					qdf_nbuf_t buf,
					mgmt_tx_download_comp_cb tx_comp_cb,
					mgmt_ota_comp_cb tx_ota_comp_cb,
					enum wlan_umac_comp_id comp_id,
					void *mgmt_tx_params)
{
	struct mgmt_txrx_desc_elem_t *desc;
	struct wlan_objmgr_psoc *psoc;
	struct mgmt_txrx_priv_context *txrx_ctx;
	struct wlan_objmgr_vdev *vdev;

	if (!peer) {
		mgmt_txrx_err("peer passed is NULL");
		return QDF_STATUS_E_NULL_VALUE;
	}

	wlan_objmgr_peer_get_ref(peer, WLAN_MGMT_SB_ID);

	vdev = wlan_peer_get_vdev(peer);
	if (!vdev) {
		mgmt_txrx_err("vdev unavailable for peer %p", peer);
		wlan_objmgr_peer_release_ref(peer, WLAN_MGMT_SB_ID);
		return QDF_STATUS_E_NULL_VALUE;
	}

	psoc = wlan_vdev_get_psoc(vdev);
	if (!psoc) {
		mgmt_txrx_err("psoc unavailable for peer %p vdev %p",
				peer, vdev);
		wlan_objmgr_peer_release_ref(peer, WLAN_MGMT_SB_ID);
		return QDF_STATUS_E_NULL_VALUE;
	}

	txrx_ctx = (struct mgmt_txrx_priv_context *)
			wlan_objmgr_psoc_get_comp_private_obj(psoc,
				WLAN_UMAC_COMP_MGMT_TXRX);
	if (!txrx_ctx) {
		mgmt_txrx_err("No txrx context for peer %p psoc %p",
				peer, psoc);
		wlan_objmgr_peer_release_ref(peer, WLAN_MGMT_SB_ID);
		return QDF_STATUS_E_NULL_VALUE;
	}

	desc = wlan_mgmt_txrx_desc_get(txrx_ctx);
	if (!desc) {
		wlan_objmgr_peer_release_ref(peer, WLAN_MGMT_SB_ID);
		return QDF_STATUS_E_RESOURCES;
	}

	desc->nbuf = buf;
	desc->tx_ota_cmpl_cb = tx_ota_comp_cb;
	desc->tx_dwnld_cmpl_cb = tx_comp_cb;
	desc->peer = peer;
	desc->vdev_id = wlan_vdev_get_id(vdev);
	desc->context = context;

	if (!psoc->soc_cb.tx_ops.mgmt_txrx_tx_ops.mgmt_tx_send) {
		mgmt_txrx_err("mgmt txrx tx op to send mgmt frame is NULL for psoc: %p",
				psoc);
		wlan_objmgr_peer_release_ref(peer, WLAN_MGMT_SB_ID);
		wlan_mgmt_txrx_desc_put(txrx_ctx, desc->desc_id);
		return QDF_STATUS_E_FAILURE;
	}

	if (psoc->soc_cb.tx_ops.mgmt_txrx_tx_ops.mgmt_tx_send(
			vdev, buf, desc->desc_id, mgmt_tx_params)) {
		mgmt_txrx_err("Mgmt send fail for peer %p psoc %p",
				peer, psoc);
		wlan_objmgr_peer_release_ref(peer, WLAN_MGMT_SB_ID);
		wlan_mgmt_txrx_desc_put(txrx_ctx, desc->desc_id);
		return QDF_STATUS_E_FAILURE;
	}
	return QDF_STATUS_SUCCESS;
}

QDF_STATUS wlan_mgmt_txrx_beacon_frame_tx(struct wlan_objmgr_peer *peer,
					  qdf_nbuf_t buf,
					  enum wlan_umac_comp_id comp_id)
{
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_psoc *psoc;

	vdev = wlan_peer_get_vdev(peer);
	if (!vdev) {
		mgmt_txrx_err("vdev unavailable for peer %p", peer);
		return QDF_STATUS_E_NULL_VALUE;
	}

	psoc = wlan_vdev_get_psoc(vdev);
	if (!psoc) {
		mgmt_txrx_err("psoc unavailable for peer %p", peer);
		return QDF_STATUS_E_NULL_VALUE;
	}

	if (!psoc->soc_cb.tx_ops.mgmt_txrx_tx_ops.beacon_send) {
		mgmt_txrx_err("mgmt txrx tx op to send beacon frame is NULL for psoc: %p",
				psoc);
		return QDF_STATUS_E_FAILURE;
	}

	if (psoc->soc_cb.tx_ops.mgmt_txrx_tx_ops.beacon_send(vdev, buf)) {
		mgmt_txrx_err("Beacon send fail for peer %p psoc %p",
				peer, psoc);
		return QDF_STATUS_E_FAILURE;
	}
	return QDF_STATUS_SUCCESS;
}

/**
 * wlan_mgmt_txrx_create_rx_handler() - creates rx handler node for umac comp.
 * @mgmt_txrx_ctx: mgmt txrx context
 * @mgmt_rx_cb: mgmt rx callback to be registered
 * @comp_id: umac component id
 * @frm_type: mgmt. frame for which cb to be registered.
 *
 * This function creates rx handler node for frame type and
 * umac component passed in the func.
 *
 * Return: QDF_STATUS_SUCCESS - in case of success
 */
static QDF_STATUS wlan_mgmt_txrx_create_rx_handler(
				struct mgmt_txrx_priv_context *mgmt_txrx_ctx,
				mgmt_frame_rx_callback mgmt_rx_cb,
				enum wlan_umac_comp_id comp_id,
				enum mgmt_frame_type frm_type)
{
	struct mgmt_rx_handler *rx_handler;

	rx_handler = qdf_mem_malloc(sizeof(*rx_handler));
	if (!rx_handler) {
		mgmt_txrx_err("Couldn't allocate memory for rx handler");
		return QDF_STATUS_E_NOMEM;
	}

	rx_handler->comp_id = comp_id;
	rx_handler->rx_cb = mgmt_rx_cb;

	qdf_spin_lock_bh(&mgmt_txrx_ctx->mgmt_txrx_ctx_lock);
	rx_handler->next = mgmt_txrx_ctx->mgmt_rx_comp_cb[frm_type];
	mgmt_txrx_ctx->mgmt_rx_comp_cb[frm_type] = rx_handler;
	qdf_spin_unlock_bh(&mgmt_txrx_ctx->mgmt_txrx_ctx_lock);

	mgmt_txrx_info("Callback registered for comp_id: %d, frm_type: %d",
			comp_id, frm_type);
	return QDF_STATUS_SUCCESS;
}

/**
 * wlan_mgmt_txrx_delete_rx_handler() - deletes rx handler node for umac comp.
 * @mgmt_txrx_ctx: mgmt txrx context
 * @mgmt_rx_cb: mgmt rx callback to be deregistered
 * @comp_id: umac component id
 * @frm_type: mgmt. frame for which cb to be registered.
 *
 * This function deletes rx handler node for frame type and
 * umac component passed in the func.
 *
 * Return: QDF_STATUS_SUCCESS - in case of success
 */
static QDF_STATUS wlan_mgmt_txrx_delete_rx_handler(
				struct mgmt_txrx_priv_context *mgmt_txrx_ctx,
				mgmt_frame_rx_callback mgmt_rx_cb,
				enum wlan_umac_comp_id comp_id,
				enum mgmt_frame_type frm_type)
{
	struct mgmt_rx_handler *rx_handler, *rx_handler_prev = NULL;
	bool delete = false;

	qdf_spin_lock_bh(&mgmt_txrx_ctx->mgmt_txrx_ctx_lock);
	rx_handler = mgmt_txrx_ctx->mgmt_rx_comp_cb[frm_type];
	while (rx_handler) {
		if (rx_handler->comp_id == comp_id &&
				rx_handler->rx_cb == mgmt_rx_cb) {
			if (rx_handler_prev)
				rx_handler_prev->next =
					rx_handler->next;
			else
				mgmt_txrx_ctx->mgmt_rx_comp_cb[frm_type] =
					rx_handler->next;

			qdf_mem_free(rx_handler);
			delete = true;
			break;
		}

		rx_handler_prev = rx_handler;
		rx_handler = rx_handler->next;
	}
	qdf_spin_unlock_bh(&mgmt_txrx_ctx->mgmt_txrx_ctx_lock);

	if (!delete) {
		mgmt_txrx_err("No callback registered for comp_id: %d, frm_type: %d",
				comp_id, frm_type);
		return QDF_STATUS_E_FAILURE;
	}

	mgmt_txrx_info("Callback deregistered for comp_id: %d, frm_type: %d",
			comp_id, frm_type);
	return QDF_STATUS_SUCCESS;
}

QDF_STATUS wlan_mgmt_txrx_register_rx_cb(
			struct wlan_objmgr_psoc *psoc,
			enum wlan_umac_comp_id comp_id,
			struct mgmt_txrx_mgmt_frame_cb_info *frm_cb_info,
			uint8_t num_entries)
{
	struct mgmt_txrx_priv_context *mgmt_txrx_ctx;
	QDF_STATUS status;
	uint8_t i, j;

	if (!psoc) {
		mgmt_txrx_err("psoc context is NULL");
		return QDF_STATUS_E_INVAL;
	}

	if (comp_id >= WLAN_UMAC_MAX_COMPONENTS) {
		mgmt_txrx_err("Invalid component id %d passed", comp_id);
		return QDF_STATUS_E_INVAL;
	}

	if (!num_entries || num_entries >= MGMT_MAX_FRAME_TYPE) {
		mgmt_txrx_err("Invalid value for num_entries: %d passed",
				num_entries);
		return QDF_STATUS_E_INVAL;
	}

	if (!frm_cb_info) {
		mgmt_txrx_err("frame cb info pointer is NULL");
		return QDF_STATUS_E_INVAL;
	}

	mgmt_txrx_ctx = (struct mgmt_txrx_priv_context *)
			wlan_objmgr_psoc_get_comp_private_obj(psoc,
				WLAN_UMAC_COMP_MGMT_TXRX);
	if (!mgmt_txrx_ctx) {
		mgmt_txrx_err("mgmt txrx context is NULL");
		return QDF_STATUS_E_FAILURE;
	}

	for (i = 0; i < num_entries; i++) {
		status = wlan_mgmt_txrx_create_rx_handler(mgmt_txrx_ctx,
				frm_cb_info[i].mgmt_rx_cb, comp_id,
				frm_cb_info[i].frm_type);
		if (status != QDF_STATUS_SUCCESS) {
			for (j = 0; j < i; j++) {
				wlan_mgmt_txrx_delete_rx_handler(
					mgmt_txrx_ctx,
					frm_cb_info[j].mgmt_rx_cb,
					comp_id, frm_cb_info[j].frm_type);
			}
			return status;
		}
	}

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS wlan_mgmt_txrx_deregister_rx_cb(
			struct wlan_objmgr_psoc *psoc,
			enum wlan_umac_comp_id comp_id,
			struct mgmt_txrx_mgmt_frame_cb_info *frm_cb_info,
			uint8_t num_entries)
{
	struct mgmt_txrx_priv_context *mgmt_txrx_ctx;
	uint8_t i;

	if (!psoc) {
		mgmt_txrx_err("psoc context is NULL");
		return QDF_STATUS_E_INVAL;
	}

	if (comp_id >= WLAN_UMAC_MAX_COMPONENTS) {
		mgmt_txrx_err("Invalid component id %d passed", comp_id);
		return QDF_STATUS_E_INVAL;
	}

	if (!num_entries || num_entries >= MGMT_MAX_FRAME_TYPE) {
		mgmt_txrx_err("Invalid value for num_entries: %d passed",
				num_entries);
		return QDF_STATUS_E_INVAL;
	}

	if (!frm_cb_info) {
		mgmt_txrx_err("frame cb info pointer is NULL");
		return QDF_STATUS_E_INVAL;
	}

	mgmt_txrx_ctx = (struct mgmt_txrx_priv_context *)
			wlan_objmgr_psoc_get_comp_private_obj(psoc,
				WLAN_UMAC_COMP_MGMT_TXRX);
	if (!mgmt_txrx_ctx) {
		mgmt_txrx_err("mgmt txrx context is NULL");
		return QDF_STATUS_E_FAILURE;
	}

	for (i = 0; i < num_entries; i++) {
		wlan_mgmt_txrx_delete_rx_handler(mgmt_txrx_ctx,
				frm_cb_info[i].mgmt_rx_cb, comp_id,
				frm_cb_info[i].frm_type);
	}

	return QDF_STATUS_SUCCESS;
}
