/*
* Copyright (c) 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: Implements gtk offload feature API's
 */

#include "wlan_pmo_gtk.h"
#include "wlan_pmo_tgt_api.h"
#include "wlan_pmo_main.h"
#include "wlan_pmo_obj_mgmt_public_struct.h"

static QDF_STATUS pmo_core_cache_gtk_req_in_vdev_priv(
		struct wlan_objmgr_vdev *vdev,
		struct pmo_gtk_req *gtk_req)
{
	struct pmo_vdev_priv_obj *vdev_ctx;
	QDF_STATUS status;
	struct qdf_mac_addr peer_bssid;

	vdev_ctx = pmo_vdev_get_priv(vdev);

	status = pmo_get_vdev_bss_peer_mac_addr(vdev, &peer_bssid);
	if (status != QDF_STATUS_SUCCESS)
		return status;

	qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock);
	qdf_mem_copy(&vdev_ctx->vdev_gtk_req, gtk_req,
		sizeof(vdev_ctx->vdev_gtk_req));
	qdf_mem_copy(&vdev_ctx->vdev_gtk_req.bssid,
		&peer_bssid, QDF_MAC_ADDR_SIZE);
	vdev_ctx->vdev_gtk_req.flags = PMO_GTK_OFFLOAD_ENABLE;
	qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS pmo_core_flush_gtk_req_from_vdev_priv(
		struct wlan_objmgr_vdev *vdev)
{
	struct pmo_vdev_priv_obj *vdev_ctx;

	vdev_ctx = pmo_vdev_get_priv(vdev);

	qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock);
	qdf_mem_zero(&vdev_ctx->vdev_gtk_req, sizeof(vdev_ctx->vdev_gtk_req));
	vdev_ctx->vdev_gtk_req.flags = PMO_GTK_OFFLOAD_DISABLE;
	qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS pmo_core_do_enable_gtk_offload(
			struct wlan_objmgr_vdev *vdev,
			struct pmo_vdev_priv_obj *vdev_ctx,
			struct pmo_gtk_req *op_gtk_req)
{
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	uint8_t vdev_id;

	if (!pmo_core_is_vdev_supports_offload(vdev)) {
		pmo_info("vdev in invalid opmode for gtk offload %d",
			pmo_get_vdev_opmode(vdev));
		return QDF_STATUS_E_INVAL;
	}

	if (!pmo_core_is_vdev_connected(vdev))
		return QDF_STATUS_E_INVAL;

	vdev_id = pmo_vdev_get_id(vdev);

	qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock);
	qdf_mem_copy(op_gtk_req, &vdev_ctx->vdev_gtk_req,
		sizeof(*op_gtk_req));
	qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock);

	if ((op_gtk_req->flags == PMO_GTK_OFFLOAD_ENABLE) &&
	    (qdf_atomic_read(&vdev_ctx->gtk_err_enable) == 1)) {
		pmo_info("GTK Offload already enabled, Disabling vdev_id: %d",
			vdev_id);
		op_gtk_req->flags = PMO_GTK_OFFLOAD_DISABLE;
		status = pmo_tgt_send_gtk_offload_req(vdev, op_gtk_req);
		if (status != QDF_STATUS_SUCCESS) {
			pmo_err("Failed to disable GTK Offload");
			goto out;
		}
		pmo_debug("Enable GTK Offload again with updated inputs");
		op_gtk_req->flags = PMO_GTK_OFFLOAD_ENABLE;
	}

	status = pmo_tgt_send_gtk_offload_req(vdev, op_gtk_req);
out:

	return status;
}

static QDF_STATUS pmo_core_is_gtk_enabled_in_fwr(
			struct wlan_objmgr_vdev *vdev,
			struct pmo_vdev_priv_obj *vdev_ctx)
{
	QDF_STATUS status;
	struct qdf_mac_addr peer_bssid;

	if (!pmo_core_is_vdev_supports_offload(vdev)) {
		pmo_info("vdev in invalid opmode for gtk offload enable %d",
			pmo_get_vdev_opmode(vdev));
		return QDF_STATUS_E_INVAL;
	}

	if (!pmo_core_is_vdev_connected(vdev))
		return QDF_STATUS_E_INVAL;

	status = pmo_get_vdev_bss_peer_mac_addr(vdev,
			&peer_bssid);
	if (status != QDF_STATUS_SUCCESS)
		return QDF_STATUS_E_INVAL;

	qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock);
	if (qdf_mem_cmp(&vdev_ctx->vdev_gtk_req.bssid,
		&peer_bssid, QDF_MAC_ADDR_SIZE)) {
		qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock);
		pmo_err("cache request mac:%pM, peer mac:%pM are not same",
			vdev_ctx->vdev_gtk_req.bssid.bytes,
			peer_bssid.bytes);
		 return QDF_STATUS_E_INVAL;
	}

	if (vdev_ctx->vdev_gtk_req.flags != PMO_GTK_OFFLOAD_ENABLE) {
		qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock);
		pmo_err("gtk flag is disabled hence no gtk rsp required");
		return QDF_STATUS_E_INVAL;
	}
	qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS pmo_core_do_disable_gtk_offload(
			struct wlan_objmgr_vdev *vdev,
			struct pmo_vdev_priv_obj *vdev_ctx,
			struct pmo_gtk_req *op_gtk_req)
{
	QDF_STATUS status = QDF_STATUS_SUCCESS;

	status = pmo_core_is_gtk_enabled_in_fwr(vdev, vdev_ctx);
	if (status != QDF_STATUS_SUCCESS)
		return status;

	op_gtk_req->flags = PMO_GTK_OFFLOAD_DISABLE;
	status = pmo_tgt_send_gtk_offload_req(vdev, op_gtk_req);

	return status;
}

QDF_STATUS pmo_core_cache_gtk_offload_req(struct wlan_objmgr_vdev *vdev,
		struct pmo_gtk_req *gtk_req)
{
	QDF_STATUS status;
	enum tQDF_ADAPTER_MODE opmode;
	uint8_t vdev_id;

	PMO_ENTER();
	if (!gtk_req) {
		pmo_err("gtk_req is NULL");
		status = QDF_STATUS_E_INVAL;
		goto out;
	}

	if (!vdev) {
		pmo_err("vdev is NULL");
		status = QDF_STATUS_E_INVAL;
		goto out;
	}

	status = pmo_vdev_get_ref(vdev);
	if (status != QDF_STATUS_SUCCESS)
		goto out;

	opmode = pmo_get_vdev_opmode(vdev);
	vdev_id = pmo_vdev_get_id(vdev);
	pmo_info("vdev opmode: %d vdev_id: %d", opmode, vdev_id);
	if (!pmo_core_is_vdev_supports_offload(vdev)) {
		pmo_info("vdev in invalid opmode for caching gtk request %d",
			opmode);
		status = QDF_STATUS_E_INVAL;
		goto dec_ref;
	}

	status = pmo_core_cache_gtk_req_in_vdev_priv(vdev, gtk_req);
dec_ref:
	pmo_vdev_put_ref(vdev);
out:
	PMO_EXIT();

	return status;
}

QDF_STATUS pmo_core_flush_gtk_offload_req(struct wlan_objmgr_vdev *vdev)
{
	enum tQDF_ADAPTER_MODE opmode;
	uint8_t vdev_id;
	QDF_STATUS status;

	PMO_ENTER();
	if (!vdev) {
		pmo_err("psoc is NULL");
		status = QDF_STATUS_E_INVAL;
		goto out;
	}

	status = pmo_vdev_get_ref(vdev);
	if (status != QDF_STATUS_SUCCESS)
		goto out;

	opmode = pmo_get_vdev_opmode(vdev);
	vdev_id = pmo_vdev_get_id(vdev);
	pmo_info("vdev opmode: %d vdev_id: %d", opmode, vdev_id);
	if (!pmo_core_is_vdev_supports_offload(vdev)) {
		pmo_info("vdev in invalid opmode for flushing gtk request %d",
			opmode);
		status = QDF_STATUS_E_INVAL;
		goto dec_ref;
	}

	status = pmo_core_flush_gtk_req_from_vdev_priv(vdev);
dec_ref:
	pmo_vdev_put_ref(vdev);
out:
	PMO_EXIT();

	return status;
}

QDF_STATUS pmo_core_enable_gtk_offload_in_fwr(struct wlan_objmgr_vdev *vdev)
{
	QDF_STATUS status;
	struct pmo_vdev_priv_obj *vdev_ctx;
	struct pmo_gtk_req *op_gtk_req = NULL;

	PMO_ENTER();
	if (!vdev) {
		pmo_err("vdev is NULL");
		status = QDF_STATUS_E_INVAL;
		goto out;
	}

	status = pmo_vdev_get_ref(vdev);
	if (status != QDF_STATUS_SUCCESS)
		goto out;

	vdev_ctx = pmo_vdev_get_priv(vdev);

	op_gtk_req = qdf_mem_malloc(sizeof(*op_gtk_req));
	if (!op_gtk_req) {
		pmo_err("op_gtk_req is NULL");
		status = QDF_STATUS_E_INVAL;
		goto dec_ref;
	}
	status = pmo_core_do_enable_gtk_offload(vdev, vdev_ctx, op_gtk_req);
dec_ref:
	pmo_vdev_put_ref(vdev);
out:
	if (op_gtk_req)
		qdf_mem_free(op_gtk_req);
	PMO_EXIT();

	return status;
}

QDF_STATUS pmo_core_disable_gtk_offload_in_fwr(struct wlan_objmgr_vdev *vdev)
{
	QDF_STATUS status;
	struct pmo_vdev_priv_obj *vdev_ctx;
	struct pmo_gtk_req *op_gtk_req = NULL;

	PMO_ENTER();
	if (!vdev) {
		pmo_err("vdev is NULL");
		status = QDF_STATUS_E_INVAL;
		goto out;
	}

	status = pmo_vdev_get_ref(vdev);
	if (status != QDF_STATUS_SUCCESS)
		goto out;

	vdev_ctx = pmo_vdev_get_priv(vdev);

	op_gtk_req = qdf_mem_malloc(sizeof(*op_gtk_req));
	if (!op_gtk_req) {
		pmo_err("op_gtk_req is NULL");
		status = QDF_STATUS_E_NOMEM;
		goto dec_ref;
	}

	status = pmo_core_do_disable_gtk_offload(vdev, vdev_ctx, op_gtk_req);
dec_ref:
	pmo_vdev_put_ref(vdev);
out:
	if (op_gtk_req)
		qdf_mem_free(op_gtk_req);
	PMO_EXIT();

	return status;
}

QDF_STATUS pmo_core_get_gtk_rsp(struct wlan_objmgr_vdev *vdev,
			struct pmo_gtk_rsp_req *gtk_rsp_req)
{
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct pmo_vdev_priv_obj *vdev_ctx;

	PMO_ENTER();
	if (!gtk_rsp_req || !vdev) {
		pmo_err("%s is null", !vdev ? "vdev":"gtk_rsp_req");
		status = QDF_STATUS_E_INVAL;
		goto out;
	}

	status = pmo_vdev_get_ref(vdev);
	if (status != QDF_STATUS_SUCCESS)
		goto out;

	vdev_ctx = pmo_vdev_get_priv(vdev);

	status = pmo_core_is_gtk_enabled_in_fwr(vdev, vdev_ctx);
	if (status != QDF_STATUS_SUCCESS)
		goto dec_ref;

	/* cache gtk rsp request */
	qdf_spin_lock_bh(&vdev_ctx->pmo_vdev_lock);
	qdf_mem_copy(&vdev_ctx->vdev_gtk_rsp_req, gtk_rsp_req,
		sizeof(vdev_ctx->vdev_gtk_rsp_req));
	qdf_spin_unlock_bh(&vdev_ctx->pmo_vdev_lock);
	/* send cmd to fwr */
	status = pmo_tgt_get_gtk_rsp(vdev);
dec_ref:
	pmo_vdev_put_ref(vdev);
out:
	PMO_EXIT();

	return status;
}

