/*
 * 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: This file contains p2p south bound interface definitions
 */

#include <wlan_objmgr_psoc_obj.h>
#include <wlan_mgmt_txrx_utils_api.h>
#include <scheduler_api.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 "wlan_p2p_tgt_api.h"
#include "wlan_p2p_public_struct.h"
#include "../../core/src/wlan_p2p_main.h"
#include "../../core/src/wlan_p2p_roc.h"
#include "../../core/src/wlan_p2p_off_chan_tx.h"

#define IEEE80211_FC0_TYPE_MASK              0x0c
#define P2P_NOISE_FLOOR_DBM_DEFAULT          (-96)

static inline struct wlan_lmac_if_p2p_tx_ops *
wlan_psoc_get_p2p_tx_ops(struct wlan_objmgr_psoc *psoc)
{
	return &(psoc->soc_cb.tx_ops.p2p);
}

QDF_STATUS tgt_p2p_register_lo_ev_handler(
	struct wlan_objmgr_psoc *psoc)
{
	struct wlan_lmac_if_p2p_tx_ops *p2p_ops;
	QDF_STATUS status = QDF_STATUS_E_FAILURE;

	p2p_ops = wlan_psoc_get_p2p_tx_ops(psoc);
	if (p2p_ops && p2p_ops->reg_lo_ev_handler) {
		status = p2p_ops->reg_lo_ev_handler(psoc, NULL);
		p2p_debug("register lo event, status:%d", status);
	}

	return status;
}

QDF_STATUS tgt_p2p_register_noa_ev_handler(
	struct wlan_objmgr_psoc *psoc)
{
	struct wlan_lmac_if_p2p_tx_ops *p2p_ops;
	QDF_STATUS status = QDF_STATUS_E_FAILURE;

	p2p_ops = wlan_psoc_get_p2p_tx_ops(psoc);
	if (p2p_ops && p2p_ops->reg_noa_ev_handler) {
		status = p2p_ops->reg_noa_ev_handler(psoc, NULL);
		p2p_debug("register noa event, status:%d", status);
	}

	return status;
}

QDF_STATUS tgt_p2p_unregister_lo_ev_handler(
	struct wlan_objmgr_psoc *psoc)
{
	struct wlan_lmac_if_p2p_tx_ops *p2p_ops;
	QDF_STATUS status = QDF_STATUS_E_FAILURE;

	p2p_ops = wlan_psoc_get_p2p_tx_ops(psoc);
	if (p2p_ops && p2p_ops->unreg_lo_ev_handler) {
		status = p2p_ops->unreg_lo_ev_handler(psoc, NULL);
		p2p_debug("unregister lo event, status:%d", status);
	}

	return status;
}

QDF_STATUS tgt_p2p_unregister_noa_ev_handler(
	struct wlan_objmgr_psoc *psoc)
{
	struct wlan_lmac_if_p2p_tx_ops *p2p_ops;
	QDF_STATUS status = QDF_STATUS_E_FAILURE;

	p2p_ops = wlan_psoc_get_p2p_tx_ops(psoc);
	if (p2p_ops && p2p_ops->unreg_noa_ev_handler) {
		status = p2p_ops->unreg_noa_ev_handler(psoc, NULL);
		p2p_debug("unregister noa event, status:%d", status);
	}

	return status;
}

void tgt_p2p_scan_event_cb(struct wlan_objmgr_vdev *vdev,
	struct scan_event *event, void *arg)
{
	p2p_scan_event_cb(vdev, event, arg);
}

QDF_STATUS tgt_p2p_mgmt_download_comp_cb(void *context,
	qdf_nbuf_t buf, bool free)
{
	p2p_debug("conext:%p, buf:%p, free:%d", context,
		qdf_nbuf_data(buf), free);

	qdf_nbuf_free(buf);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS tgt_p2p_mgmt_ota_comp_cb(void *context, qdf_nbuf_t buf,
	uint32_t status, void *tx_compl_params)
{
	struct p2p_tx_cnf *tx_cnf;
	struct p2p_tx_conf_event *tx_conf_event;
	struct p2p_soc_priv_obj *p2p_soc_obj;
	struct tx_action_context *tx_ctx;
	struct scheduler_msg msg = {0};

	p2p_debug("context:%p, buf:%p, status:%d, tx complete params:%p",
		context, buf, status, tx_compl_params);

	if (!context) {
		p2p_err("invalid context");
		return QDF_STATUS_E_INVAL;
	}
	tx_ctx = (struct tx_action_context *)context;
	p2p_soc_obj = tx_ctx->p2p_soc_obj;

	tx_conf_event = qdf_mem_malloc(sizeof(*tx_conf_event));
	if (!tx_conf_event) {
		p2p_err("Failed to allocate tx cnf event");
		return QDF_STATUS_E_NOMEM;
	}

	tx_cnf = qdf_mem_malloc(sizeof(*tx_cnf));
	if (!tx_cnf) {
		p2p_err("Failed to allocate tx cnf");
		return QDF_STATUS_E_NOMEM;
	}

	qdf_nbuf_free(buf);
	tx_cnf->vdev_id = tx_ctx->vdev_id;
	tx_cnf->action_cookie = (uintptr_t)tx_ctx;
	tx_cnf->buf = tx_ctx->buf;
	tx_cnf->buf_len = tx_ctx->buf_len;
	tx_cnf->status = status;
	tx_conf_event->p2p_soc_obj = p2p_soc_obj;
	tx_conf_event->tx_cnf = tx_cnf;
	tx_conf_event->tx_ctx = tx_ctx;
	msg.type = P2P_EVENT_MGMT_TX_ACK_CNF;
	msg.bodyptr = tx_conf_event;
	msg.callback = p2p_process_evt;
	scheduler_post_msg(QDF_MODULE_ID_TARGET_IF, &msg);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS tgt_p2p_mgmt_frame_rx_cb(struct wlan_objmgr_psoc *psoc,
	struct wlan_objmgr_peer *peer, qdf_nbuf_t buf,
	struct mgmt_rx_event_params *mgmt_rx_params,
	enum mgmt_frame_type frm_type)
{
	struct p2p_rx_mgmt_frame *rx_mgmt;
	struct p2p_rx_mgmt_event *rx_mgmt_event;
	struct p2p_soc_priv_obj *p2p_soc_obj;
	struct scheduler_msg msg = {0};
	struct wlan_frame_hdr *wh;
	struct wlan_objmgr_vdev *vdev;
	struct p2p_roc_context *roc_ctx;
	uint32_t vdev_id;
	uint8_t *pdata;

	p2p_debug("psoc:%p, peer:%p, type:%d", psoc, peer, frm_type);

	if (!mgmt_rx_params) {
		p2p_err("mgmt rx params is NULL");
		qdf_nbuf_free(buf);
		return QDF_STATUS_E_INVAL;
	}

	p2p_soc_obj = wlan_objmgr_psoc_get_comp_private_obj(psoc,
			WLAN_UMAC_COMP_P2P);
	if (!p2p_soc_obj) {
		p2p_err("p2p ctx is NULL, drop this frame");
		return QDF_STATUS_E_FAILURE;
	}

	if (!peer) {
		roc_ctx = p2p_find_current_roc_ctx(p2p_soc_obj);
		if (!roc_ctx) {
			p2p_err("current roc ctx is null, can't get vdev id");
			return QDF_STATUS_E_FAILURE;
		} else {
			vdev_id = roc_ctx->vdev_id;
		}
	} else {
		wlan_peer_obj_lock(peer);
		vdev = wlan_peer_get_vdev(peer);
		wlan_peer_obj_unlock(peer);
		if (!vdev) {
			p2p_err("vdev is NULL in peer, drop this frame");
			return QDF_STATUS_E_FAILURE;
		}
		wlan_vdev_obj_lock(vdev);
		vdev_id = wlan_vdev_get_id(vdev);
		wlan_vdev_obj_unlock(vdev);
	}

	rx_mgmt_event = qdf_mem_malloc(sizeof(*rx_mgmt_event));
	if (!rx_mgmt_event) {
		p2p_err("Failed to allocate rx mgmt event");
		return QDF_STATUS_E_NOMEM;
	}

	rx_mgmt = qdf_mem_malloc(sizeof(*rx_mgmt) +
			mgmt_rx_params->buf_len);
	if (!rx_mgmt) {
		p2p_err("Failed to allocate rx mgmt frame");
		return QDF_STATUS_E_NOMEM;
	}

	wh = (struct wlan_frame_hdr *)qdf_nbuf_data(buf);
	pdata = (uint8_t *)qdf_nbuf_data(buf);
	rx_mgmt->frame_len = mgmt_rx_params->buf_len;
	rx_mgmt->rx_chan = mgmt_rx_params->channel;
	rx_mgmt->vdev_id = vdev_id;
	rx_mgmt->frm_type = frm_type;
	rx_mgmt->rx_rssi = mgmt_rx_params->snr +
				P2P_NOISE_FLOOR_DBM_DEFAULT;
	rx_mgmt_event->rx_mgmt = rx_mgmt;
	rx_mgmt_event->p2p_soc_obj = p2p_soc_obj;
	qdf_mem_copy(rx_mgmt->buf, pdata, mgmt_rx_params->buf_len);
	msg.type = P2P_EVENT_RX_MGMT;
	msg.bodyptr = rx_mgmt_event;
	msg.callback = p2p_process_evt;
	scheduler_post_msg(QDF_MODULE_ID_TARGET_IF, &msg);

	qdf_nbuf_free(buf);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS  tgt_p2p_noa_event_cb(struct wlan_objmgr_psoc *psoc,
		struct p2p_noa_info *event_info)
{
	struct p2p_noa_event *noa_event;
	struct scheduler_msg msg = {0};
	struct p2p_soc_priv_obj *p2p_soc_obj;

	p2p_debug("soc:%p, event_info:%p", psoc, event_info);

	if (!psoc) {
		p2p_err("psoc context passed is NULL");
		if (event_info)
			qdf_mem_free(event_info);
		return QDF_STATUS_E_INVAL;
	}

	p2p_soc_obj = wlan_objmgr_psoc_get_comp_private_obj(psoc,
			WLAN_UMAC_COMP_P2P);
	if (!p2p_soc_obj) {
		p2p_err("p2p soc object is NULL");
		if (event_info)
			qdf_mem_free(event_info);
		return QDF_STATUS_E_INVAL;
	}

	if (!event_info) {
		p2p_err("invalid noa event information");
		return QDF_STATUS_E_INVAL;
	}

	noa_event = qdf_mem_malloc(sizeof(*noa_event));
	if (!noa_event) {
		p2p_err("Failed to allocate p2p noa event");
		qdf_mem_free(event_info);
		return QDF_STATUS_E_NOMEM;
	}

	noa_event->p2p_soc_obj = p2p_soc_obj;
	noa_event->noa_info = event_info;
	msg.type = P2P_EVENT_NOA;
	msg.bodyptr = noa_event;
	msg.callback = p2p_process_evt;
	scheduler_post_msg(QDF_MODULE_ID_TARGET_IF, &msg);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS tgt_p2p_lo_event_cb(struct wlan_objmgr_psoc *psoc,
		struct p2p_lo_event *event_info)
{
	struct p2p_lo_stop_event *lo_stop_event;
	struct scheduler_msg msg = {0};
	struct p2p_soc_priv_obj *p2p_soc_obj;

	p2p_debug("soc:%p, event_info:%p", psoc, event_info);

	if (!psoc) {
		p2p_err("psoc context passed is NULL");
		if (event_info)
			qdf_mem_free(event_info);
		return QDF_STATUS_E_INVAL;
	}

	p2p_soc_obj = wlan_objmgr_psoc_get_comp_private_obj(psoc,
			WLAN_UMAC_COMP_P2P);
	if (!p2p_soc_obj) {
		p2p_err("p2p soc object is NULL");
		if (event_info)
			qdf_mem_free(event_info);
		return QDF_STATUS_E_INVAL;
	}

	if (!event_info) {
		p2p_err("invalid lo stop event information");
		return QDF_STATUS_E_INVAL;
	}

	lo_stop_event = qdf_mem_malloc(sizeof(*lo_stop_event));
	if (!lo_stop_event) {
		p2p_err("Failed to allocate p2p lo stop event");
		qdf_mem_free(event_info);
		return QDF_STATUS_E_NOMEM;
	}

	lo_stop_event->p2p_soc_obj = p2p_soc_obj;
	lo_stop_event->lo_event = event_info;
	msg.type = P2P_EVENT_LO_STOPPED;
	msg.bodyptr = lo_stop_event;
	msg.callback = p2p_process_evt;
	scheduler_post_msg(QDF_MODULE_ID_TARGET_IF, &msg);

	return QDF_STATUS_SUCCESS;
}
