/*
 * 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: contains core nan function definitions
 */

#include "nan_main_i.h"
#include "nan_ucfg_api.h"
#include "wlan_nan_api.h"
#include "target_if_nan.h"
#include "scheduler_api.h"
#include "wlan_serialization_api.h"
#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"

struct nan_vdev_priv_obj *nan_get_vdev_priv_obj(
				struct wlan_objmgr_vdev *vdev)
{
	struct nan_vdev_priv_obj *obj;

	if (!vdev) {
		nan_err("vdev is null");
		return NULL;
	}
	wlan_vdev_obj_lock(vdev);
	obj = wlan_objmgr_vdev_get_comp_private_obj(vdev, WLAN_UMAC_COMP_NAN);
	wlan_vdev_obj_unlock(vdev);

	return obj;
}

struct nan_psoc_priv_obj *nan_get_psoc_priv_obj(
				struct wlan_objmgr_psoc *psoc)
{
	struct nan_psoc_priv_obj *obj;

	if (!psoc) {
		nan_err("psoc is null");
		return NULL;
	}
	wlan_psoc_obj_lock(psoc);
	obj = wlan_objmgr_psoc_get_comp_private_obj(psoc, WLAN_UMAC_COMP_NAN);
	wlan_psoc_obj_unlock(psoc);

	return obj;
}

void nan_release_cmd(void *in_req, uint32_t req_type)
{
	struct wlan_objmgr_vdev *vdev = NULL;

	if (!in_req)
		return;

	switch (req_type) {
	case NDP_INITIATOR_REQ: {
		struct nan_datapath_initiator_req *req = in_req;
		vdev = req->vdev;
		qdf_mem_free(req->pmk.pmk);
		qdf_mem_free(req->ndp_info.ndp_app_info);
		qdf_mem_free(req->ndp_config.ndp_cfg);
		break;
	}
	case NDP_RESPONDER_REQ: {
		struct nan_datapath_responder_req *req = in_req;
		vdev = req->vdev;
		qdf_mem_free(req->pmk.pmk);
		qdf_mem_free(req->ndp_info.ndp_app_info);
		qdf_mem_free(req->ndp_config.ndp_cfg);
		break;
	}
	case NDP_END_REQ: {
		struct nan_datapath_end_req *req = in_req;
		vdev = req->vdev;
		qdf_mem_free(req->ndp_ids);
		break;
	}
	default:
		nan_err("invalid req type: %d", req_type);
		break;
	}

	if (vdev)
		wlan_objmgr_vdev_release_ref(vdev, WLAN_NAN_ID);
	else
		nan_err("vdev is null");

	qdf_mem_free(in_req);
}

static void nan_req_incomplete(void *req, uint32_t cmdtype)
{
	/* send msg to userspace if needed that cmd got incomplete */
}

static void nan_req_activated(void *in_req, uint32_t cmdtype)
{
	uint32_t req_type;
	struct wlan_objmgr_psoc *psoc;
	struct wlan_objmgr_vdev *vdev;
	struct wlan_lmac_if_nan_tx_ops *tx_ops;

	switch (cmdtype) {
	case WLAN_SER_CMD_NDP_INIT_REQ: {
		struct nan_datapath_initiator_req *req = in_req;
		vdev = req->vdev;
		req_type = NDP_INITIATOR_REQ;
		break;
	}
	case WLAN_SER_CMD_NDP_RESP_REQ: {
		struct nan_datapath_responder_req *req = in_req;
		vdev = req->vdev;
		req_type = NDP_RESPONDER_REQ;
		break;
	}
	case WLAN_SER_CMD_NDP_DATA_END_INIT_REQ: {
		struct nan_datapath_end_req *req = in_req;
		vdev = req->vdev;
		req_type = NDP_END_REQ;
		break;
	}
	default:
		nan_alert("in correct cmdtype: %d", cmdtype);
		return;
	}

	if (!vdev) {
		nan_alert("vdev is null");
		return;
	}

	psoc = wlan_vdev_get_psoc(vdev);
	if (!psoc) {
		nan_alert("psoc is null");
		return;
	}

	tx_ops = target_if_nan_get_tx_ops(psoc);
	if (!tx_ops) {
		nan_alert("tx_ops is null");
		return;
	}

	/* send ndp_intiator_req/responder_req/end_req to FW */
	tx_ops->nan_req_tx(in_req, req_type);
}

static QDF_STATUS nan_serialized_cb(void *cmd,
				enum wlan_serialization_cb_reason reason)
{
	void *req;
	struct wlan_serialization_command *ser_cmd = cmd;

	if (!ser_cmd || !ser_cmd->umac_cmd) {
		nan_alert("cmd or umac_cmd is null");
		return QDF_STATUS_E_NULL_VALUE;
	}
	req = ser_cmd->umac_cmd;

	switch (reason) {
	case WLAN_SER_CB_ACTIVATE_CMD:
		nan_req_activated(req, ser_cmd->cmd_type);
		break;
	case WLAN_SER_CB_CANCEL_CMD:
	case WLAN_SER_CB_ACTIVE_CMD_TIMEOUT:
		nan_req_incomplete(req, ser_cmd->cmd_type);
		break;
	case WLAN_SER_CB_RELEASE_MEM_CMD:
		nan_release_cmd(req, ser_cmd->cmd_type);
		break;
	default:
		/* Do nothing but logging */
		nan_alert("invalid serialized cb reason: %d", reason);
		break;
	}

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS nan_scheduled_msg_handler(struct scheduler_msg *msg)
{
	QDF_STATUS ref_status;
	enum wlan_serialization_status status = 0;
	struct wlan_serialization_command cmd = {0};

	if (!msg || !msg->bodyptr) {
		nan_alert("msg or bodyptr is null");
		return QDF_STATUS_E_NULL_VALUE;
	}
	switch (msg->type) {
	case NDP_INITIATOR_REQ: {
		struct nan_datapath_initiator_req *req = msg->bodyptr;
		cmd.cmd_type = WLAN_SER_CMD_NDP_INIT_REQ;
		cmd.vdev = req->vdev;
		break;
	}
	case NDP_RESPONDER_REQ: {
		struct nan_datapath_responder_req *req = msg->bodyptr;
		cmd.cmd_type = WLAN_SER_CMD_NDP_RESP_REQ;
		cmd.vdev = req->vdev;
		break;
	}
	case NDP_END_REQ: {
		struct nan_datapath_end_req *req = msg->bodyptr;
		cmd.cmd_type = WLAN_SER_CMD_NDP_DATA_END_INIT_REQ;
		cmd.vdev = req->vdev;
		break;
	}
	default:
		nan_err("wrong request type: %d", msg->type);
		return QDF_STATUS_E_INVAL;
	}

	/* try get ref now, if failure, then vdev may have been deleted */
	ref_status = wlan_objmgr_vdev_try_get_ref(cmd.vdev, WLAN_NAN_ID);
	if (QDF_IS_STATUS_ERROR(ref_status)) {
		nan_alert("couldn't get ref. vdev maybe deleted");
		return QDF_STATUS_E_INVAL;
	}
	/* reference will be released when ser command finishes */

	/* TBD - support more than one req of same type or avoid */
	cmd.cmd_id = 0;
	cmd.cmd_cb = nan_serialized_cb;
	cmd.umac_cmd = msg->bodyptr;
	cmd.source = WLAN_UMAC_COMP_NAN;
	cmd.is_high_priority = false;
	cmd.cmd_timeout_duration = 30000 /* 30 sec for now. TBD */;

	status = wlan_serialization_request(&cmd);
	/* following is TBD */
	if (status != WLAN_SER_CMD_ACTIVE && status != WLAN_SER_CMD_PENDING) {
		nan_err("unable to serialize command");
		wlan_objmgr_vdev_release_ref(cmd.vdev, WLAN_NAN_ID);
		return QDF_STATUS_E_INVAL;
	}
	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS nan_handle_confirm(
				struct nan_datapath_confirm_event *confirm)
{
	uint8_t vdev_id;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct wlan_objmgr_psoc *psoc;
	struct nan_psoc_priv_obj *psoc_nan_obj;

	vdev_id = wlan_vdev_get_id(confirm->vdev);
	psoc = wlan_vdev_get_psoc(confirm->vdev);
	if (!psoc) {
		nan_err("psoc is null");
		status = QDF_STATUS_E_NULL_VALUE;
		goto free_resource;
	}

	psoc_nan_obj = nan_get_psoc_priv_obj(psoc);
	if (!psoc_nan_obj) {
		nan_err("psoc_nan_obj is null");
		status = QDF_STATUS_E_NULL_VALUE;
		goto free_resource;
	}

	if (confirm->rsp_code != NAN_DATAPATH_RESPONSE_ACCEPT &&
		confirm->num_active_ndps_on_peer == 0) {
		/*
		 * This peer was created at ndp_indication but
		 * confirm failed, so it needs to be deleted
		 */
		nan_err("NDP confirm with reject and no active ndp sessions. deleting peer: "QDF_MAC_ADDRESS_STR" on vdev_id: %d",
			QDF_MAC_ADDR_ARRAY(confirm->peer_ndi_mac_addr.bytes),
			vdev_id);
		psoc_nan_obj->cb_obj.delete_peers_by_addr(vdev_id,
						confirm->peer_ndi_mac_addr);
	}
	psoc_nan_obj->cb_obj.os_if_event_handler(psoc, confirm->vdev,
						 NDP_CONFIRM, confirm);

free_resource:
	qdf_mem_free(confirm->ndp_info.ndp_app_info);

	return status;
}

static QDF_STATUS nan_handle_initiator_rsp(
				struct nan_datapath_initiator_rsp *rsp,
				struct wlan_objmgr_vdev **vdev)
{
	struct wlan_objmgr_psoc *psoc;
	struct nan_psoc_priv_obj *psoc_nan_obj;

	*vdev = rsp->vdev;
	psoc = wlan_vdev_get_psoc(rsp->vdev);
	if (!psoc) {
		nan_err("psoc is null");
		return QDF_STATUS_E_NULL_VALUE;
	}

	psoc_nan_obj = nan_get_psoc_priv_obj(psoc);
	if (!psoc_nan_obj) {
		nan_err("psoc_nan_obj is null");
		return QDF_STATUS_E_NULL_VALUE;
	}

	psoc_nan_obj->cb_obj.os_if_event_handler(psoc, rsp->vdev,
					NDP_INITIATOR_RSP, rsp);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS nan_handle_ndp_ind(
				struct nan_datapath_indication_event *ndp_ind)
{
	uint8_t vdev_id;
	struct wlan_objmgr_psoc *psoc;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct nan_psoc_priv_obj *psoc_nan_obj;

	vdev_id = wlan_vdev_get_id(ndp_ind->vdev);
	psoc = wlan_vdev_get_psoc(ndp_ind->vdev);
	if (!psoc) {
		nan_err("psoc is null");
		status = QDF_STATUS_E_NULL_VALUE;
		goto ndp_indication_failed;
	}

	psoc_nan_obj = nan_get_psoc_priv_obj(psoc);
	if (!psoc_nan_obj) {
		nan_err("psoc_nan_obj is null");
		status = QDF_STATUS_E_NULL_VALUE;
		goto ndp_indication_failed;
	}

	nan_debug("role: %d, vdev: %d, csid: %d, peer_mac_addr "
		QDF_MAC_ADDRESS_STR,
		ndp_ind->role, vdev_id, ndp_ind->ncs_sk_type,
		QDF_MAC_ADDR_ARRAY(ndp_ind->peer_mac_addr.bytes));

	if ((ndp_ind->role == NAN_DATAPATH_ROLE_INITIATOR) ||
	   ((NAN_DATAPATH_ROLE_RESPONDER == ndp_ind->role) &&
	   (NAN_DATAPATH_ACCEPT_POLICY_ALL == ndp_ind->policy))) {
		status = psoc_nan_obj->cb_obj.add_ndi_peer(vdev_id,
						ndp_ind->peer_mac_addr);
		if (QDF_IS_STATUS_ERROR(status)) {
			nan_err("Couldn't add ndi peer, ndp_role: %d",
				ndp_ind->role);
			goto ndp_indication_failed;
		}
	}
	if (NAN_DATAPATH_ROLE_RESPONDER == ndp_ind->role)
		psoc_nan_obj->cb_obj.os_if_event_handler(psoc, ndp_ind->vdev,
						NDP_INDICATION, ndp_ind);
ndp_indication_failed:
	qdf_mem_free(ndp_ind->ndp_config.ndp_cfg);
	qdf_mem_free(ndp_ind->ndp_info.ndp_app_info);
	qdf_mem_free(ndp_ind->scid.scid);

	return status;
}

static QDF_STATUS nan_handle_responder_rsp(
				struct nan_datapath_responder_rsp *rsp,
				struct wlan_objmgr_vdev **vdev)
{
	struct wlan_objmgr_psoc *psoc;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct nan_psoc_priv_obj *psoc_nan_obj;

	*vdev = rsp->vdev;
	psoc = wlan_vdev_get_psoc(rsp->vdev);
	if (!psoc) {
		nan_err("psoc is null");
		return QDF_STATUS_E_NULL_VALUE;
	}

	psoc_nan_obj = nan_get_psoc_priv_obj(psoc);
	if (!psoc_nan_obj) {
		nan_err("psoc_nan_obj is null");
		return QDF_STATUS_E_NULL_VALUE;
	}

	if (QDF_IS_STATUS_SUCCESS(rsp->status) && rsp->create_peer == true) {
		status = psoc_nan_obj->cb_obj.add_ndi_peer(
						wlan_vdev_get_id(rsp->vdev),
						rsp->peer_mac_addr);
		if (QDF_IS_STATUS_ERROR(status)) {
			nan_err("Couldn't add ndi peer");
			rsp->status = QDF_STATUS_E_FAILURE;
		}
	}
	psoc_nan_obj->cb_obj.os_if_event_handler(psoc, rsp->vdev,
						 NDP_RESPONDER_RSP, rsp);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS nan_handle_ndp_end_rsp(
			struct nan_datapath_end_rsp_event *rsp,
			struct wlan_objmgr_vdev **vdev)
{
	struct wlan_objmgr_psoc *psoc;
	struct nan_psoc_priv_obj *psoc_nan_obj;

	*vdev = rsp->vdev;
	psoc = wlan_vdev_get_psoc(rsp->vdev);
	if (!psoc) {
		nan_err("psoc is NULL");
		return QDF_STATUS_E_NULL_VALUE;
	}

	psoc_nan_obj = nan_get_psoc_priv_obj(psoc);
	if (!psoc_nan_obj) {
		nan_err("psoc_nan_obj is NULL");
		return QDF_STATUS_E_NULL_VALUE;
	}

	psoc_nan_obj->cb_obj.os_if_event_handler(psoc, rsp->vdev,
						NDP_END_RSP, rsp);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS nan_handle_end_ind(
				struct nan_datapath_end_indication_event *ind)
{
	struct wlan_objmgr_psoc *psoc;
	struct nan_psoc_priv_obj *psoc_nan_obj;

	psoc = wlan_vdev_get_psoc(ind->vdev);
	if (!psoc) {
		nan_err("psoc is NULL");
		return QDF_STATUS_E_NULL_VALUE;
	}

	psoc_nan_obj = nan_get_psoc_priv_obj(psoc);
	if (!psoc_nan_obj) {
		nan_err("psoc_nan_obj is NULL");
		return QDF_STATUS_E_NULL_VALUE;
	}

	psoc_nan_obj->cb_obj.ndp_delete_peers(ind->ndp_map, ind->num_ndp_ids);
	psoc_nan_obj->cb_obj.os_if_event_handler(psoc, ind->vdev,
						 NDP_END_IND, ind);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS nan_event_handler(struct scheduler_msg *pe_msg)
{
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct wlan_serialization_queued_cmd_info cmd;

	cmd.requestor = WLAN_UMAC_COMP_NAN;
	cmd.cmd_id = 0;
	cmd.req_type = WLAN_SER_CANCEL_NON_SCAN_CMD;
	cmd.queue_type = WLAN_SERIALIZATION_ACTIVE_QUEUE;

	if (!pe_msg->bodyptr) {
		nan_err("msg body is null");
		return QDF_STATUS_E_NULL_VALUE;
	}

	switch (pe_msg->type) {
	case NDP_CONFIRM: {
		nan_handle_confirm(pe_msg->bodyptr);
		break;
	}
	case NDP_INITIATOR_RSP: {
		nan_handle_initiator_rsp(pe_msg->bodyptr, &cmd.vdev);
		cmd.cmd_type = WLAN_SER_CMD_NDP_INIT_REQ;
		wlan_serialization_remove_cmd(&cmd);
		break;
	}
	case NDP_INDICATION: {
		nan_handle_ndp_ind(pe_msg->bodyptr);
		break;
	}
	case NDP_RESPONDER_RSP:
		nan_handle_responder_rsp(pe_msg->bodyptr, &cmd.vdev);
		cmd.cmd_type = WLAN_SER_CMD_NDP_RESP_REQ;
		wlan_serialization_remove_cmd(&cmd);
		break;
	case NDP_END_RSP:
		nan_handle_ndp_end_rsp(pe_msg->bodyptr, &cmd.vdev);
		cmd.cmd_type = WLAN_SER_CMD_NDP_DATA_END_INIT_REQ;
		wlan_serialization_remove_cmd(&cmd);
		break;
	case NDP_END_IND:
		nan_handle_end_ind(pe_msg->bodyptr);
		break;
	default:
		nan_alert("Unhandled NDP event: %d", pe_msg->type);
		status = QDF_STATUS_E_NOSUPPORT;
		break;
	}
	return status;
}
