/*
 * Copyright (c) 2017-2018 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_tdls_cmds_process.c
 *
 * TDLS north bound commands implementation
 */
#include <wlan_reg_services_api.h>
#include <wlan_serialization_api.h>
#include "wlan_tdls_main.h"
#include "wlan_tdls_peer.h"
#include "wlan_tdls_ct.h"
#include "wlan_tdls_mgmt.h"
#include "wlan_tdls_cmds_process.h"
#include "wlan_tdls_tgt_api.h"
#include "wlan_policy_mgr_api.h"

static uint16_t tdls_get_connected_peer(struct tdls_soc_priv_obj *soc_obj)
{
	return soc_obj->connected_peer_count;
}

/**
 * tdls_decrement_peer_count() - decrement connected TDLS peer counter
 * @soc_obj: TDLS soc object
 *
 * Used in scheduler thread context, no lock needed.
 *
 * Return: None.
 */
void tdls_decrement_peer_count(struct tdls_soc_priv_obj *soc_obj)
{
	if (soc_obj->connected_peer_count)
		soc_obj->connected_peer_count--;

	tdls_debug("Connected peer count %d", soc_obj->connected_peer_count);
}

/**
 * tdls_increment_peer_count() - increment connected TDLS peer counter
 * @soc_obj: TDLS soc object
 *
 * Used in scheduler thread context, no lock needed.
 *
 * Return: None.
 */
static void tdls_increment_peer_count(struct tdls_soc_priv_obj *soc_obj)
{
	soc_obj->connected_peer_count++;
	tdls_debug("Connected peer count %d", soc_obj->connected_peer_count);
}

/**
 * tdls_validate_current_mode() - check current TDL mode
 * @soc_obj: TDLS soc object
 *
 * Return: QDF_STATUS_SUCCESS if TDLS enabled, other for disabled
 */
static QDF_STATUS tdls_validate_current_mode(struct tdls_soc_priv_obj *soc_obj)
{
	if (soc_obj->tdls_current_mode == TDLS_SUPPORT_DISABLED ||
	    soc_obj->tdls_current_mode == TDLS_SUPPORT_SUSPENDED) {
		tdls_err("TDLS mode disabled OR not enabled, current mode %d",
			 soc_obj->tdls_current_mode);
		return QDF_STATUS_E_NOSUPPORT;
	}
	return QDF_STATUS_SUCCESS;
}

static char *tdls_get_ser_cmd_str(enum  wlan_serialization_cmd_type type)
{
	switch (type) {
	case WLAN_SER_CMD_TDLS_ADD_PEER:
		return "TDLS_ADD_PEER_CMD";
	case WLAN_SER_CMD_TDLS_DEL_PEER:
		return "TDLS_DEL_PEER_CMD";
	case WLAN_SER_CMD_TDLS_SEND_MGMT:
		return "TDLS_SEND_MGMT_CMD";
	default:
		return "UNKNOWN";
	}
}

static void
tdls_release_serialization_command(struct wlan_objmgr_vdev *vdev,
				   enum wlan_serialization_cmd_type type)
{
	struct wlan_serialization_queued_cmd_info cmd = {0};

	cmd.cmd_type = type;
	cmd.cmd_id = 0;
	cmd.vdev = vdev;

	tdls_debug("release %s", tdls_get_ser_cmd_str(type));
	/* Inform serialization for command completion */
	wlan_serialization_remove_cmd(&cmd);
}

/**
 * tdls_pe_add_peer() - send TDLS add peer request to PE
 * @req: TDL add peer request
 *
 * Return: QDF_STATUS_SUCCESS for success; other values if failed
 */
static QDF_STATUS tdls_pe_add_peer(struct tdls_add_peer_request *req)
{
	struct tdls_add_sta_req *addstareq;
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_peer *peer;
	struct tdls_soc_priv_obj *soc_obj;
	struct scheduler_msg msg = {0,};
	QDF_STATUS status;

	addstareq = qdf_mem_malloc(sizeof(*addstareq));
	if (!addstareq) {
		tdls_err("allocate failed");
		return QDF_STATUS_E_NOMEM;
	}
	vdev = req->vdev;
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	if (!soc_obj) {
		tdls_err("NULL tdls soc object");
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	addstareq->tdls_oper = TDLS_OPER_ADD;
	addstareq->transaction_id = 0;

	addstareq->session_id = wlan_vdev_get_id(vdev);
	peer = wlan_vdev_get_bsspeer(vdev);
	if (!peer) {
		tdls_err("bss peer is NULL");
		status = QDF_STATUS_E_INVAL;
		goto error;
	}
	status = wlan_objmgr_peer_try_get_ref(peer, WLAN_TDLS_NB_ID);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("can't get bss peer");
		goto error;
	}
	wlan_peer_obj_lock(peer);
	qdf_mem_copy(addstareq->bssid.bytes,
		     wlan_peer_get_macaddr(peer), QDF_MAC_ADDR_SIZE);
	wlan_peer_obj_unlock(peer);
	wlan_objmgr_peer_release_ref(peer, WLAN_TDLS_NB_ID);
	qdf_mem_copy(addstareq->peermac.bytes, req->add_peer_req.peer_addr,
		     QDF_MAC_ADDR_SIZE);

	tdls_debug("for " QDF_MAC_ADDR_STR,
		   QDF_MAC_ADDR_ARRAY(addstareq->peermac.bytes));
	msg.type = soc_obj->tdls_add_sta_req;
	msg.bodyptr = addstareq;
	status = scheduler_post_msg(QDF_MODULE_ID_PE, &msg);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("fail to post pe msg to add peer");
		goto error;
	}
	return status;
error:
	qdf_mem_free(addstareq);
	return status;
}

/**
 * tdls_pe_del_peer() - send TDLS delete peer request to PE
 * @req: TDLS delete peer request
 *
 * Return: QDF_STATUS_SUCCESS for success; other values if failed
 */
QDF_STATUS tdls_pe_del_peer(struct tdls_del_peer_request *req)
{
	struct tdls_del_sta_req *delstareq;
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_peer *peer;
	struct tdls_soc_priv_obj *soc_obj;
	struct scheduler_msg msg = {0,};
	QDF_STATUS status;

	delstareq = qdf_mem_malloc(sizeof(*delstareq));
	if (!delstareq) {
		tdls_err("allocate failed");
		return QDF_STATUS_E_NOMEM;
	}
	vdev = req->vdev;
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	if (!soc_obj) {
		tdls_err("NULL tdls soc object");
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	delstareq->transaction_id = 0;

	delstareq->session_id = wlan_vdev_get_id(vdev);
	peer = wlan_vdev_get_bsspeer(vdev);
	if (!peer) {
		tdls_err("bss peer is NULL");
		status = QDF_STATUS_E_INVAL;
		goto error;
	}
	status = wlan_objmgr_peer_try_get_ref(peer, WLAN_TDLS_NB_ID);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("can't get bss peer");
		goto error;
	}
	wlan_peer_obj_lock(peer);
	qdf_mem_copy(delstareq->bssid.bytes,
		     wlan_peer_get_macaddr(peer), QDF_MAC_ADDR_SIZE);
	wlan_peer_obj_unlock(peer);
	wlan_objmgr_peer_release_ref(peer, WLAN_TDLS_NB_ID);
	qdf_mem_copy(delstareq->peermac.bytes, req->del_peer_req.peer_addr,
		     QDF_MAC_ADDR_SIZE);

	tdls_debug("for " QDF_MAC_ADDR_STR,
		   QDF_MAC_ADDR_ARRAY(delstareq->peermac.bytes));
	msg.type = soc_obj->tdls_del_sta_req;
	msg.bodyptr = delstareq;
	status = scheduler_post_msg(QDF_MODULE_ID_PE, &msg);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("fail to post pe msg to del peer");
		goto error;
	}
	return status;
error:
	qdf_mem_free(delstareq);
	return status;
}

/**
 * tdls_pe_update_peer() - send TDLS update peer request to PE
 * @req: TDLS update peer request
 *
 * Return: QDF_STATUS_SUCCESS for success; other values if failed
 */
static QDF_STATUS tdls_pe_update_peer(struct tdls_update_peer_request *req)
{
	struct tdls_add_sta_req *addstareq;
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_peer *peer;
	struct tdls_soc_priv_obj *soc_obj;
	struct scheduler_msg msg = {0,};
	struct tdls_update_peer_params *update_peer;
	QDF_STATUS status;

	addstareq = qdf_mem_malloc(sizeof(*addstareq));
	if (!addstareq) {
		tdls_err("allocate failed");
		return QDF_STATUS_E_NOMEM;
	}
	vdev = req->vdev;
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	if (!soc_obj) {
		tdls_err("NULL tdls soc object");
		status = QDF_STATUS_E_INVAL;
		goto error;
	}
	update_peer = &req->update_peer_req;

	addstareq->tdls_oper = TDLS_OPER_UPDATE;
	addstareq->transaction_id = 0;

	addstareq->session_id = wlan_vdev_get_id(vdev);
	peer = wlan_vdev_get_bsspeer(vdev);
	if (!peer) {
		tdls_err("bss peer is NULL");
		status = QDF_STATUS_E_INVAL;
		goto error;
	}
	status = wlan_objmgr_peer_try_get_ref(peer, WLAN_TDLS_NB_ID);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("can't get bss peer");
		goto error;
	}
	wlan_peer_obj_lock(peer);
	qdf_mem_copy(addstareq->bssid.bytes,
		     wlan_peer_get_macaddr(peer), QDF_MAC_ADDR_SIZE);
	wlan_peer_obj_unlock(peer);
	wlan_objmgr_peer_release_ref(peer, WLAN_TDLS_NB_ID);
	qdf_mem_copy(addstareq->peermac.bytes, update_peer->peer_addr,
		     QDF_MAC_ADDR_SIZE);
	addstareq->capability = update_peer->capability;
	addstareq->uapsd_queues = update_peer->uapsd_queues;
	addstareq->max_sp = update_peer->max_sp;

	qdf_mem_copy(addstareq->extn_capability,
		     update_peer->extn_capability, WLAN_MAC_MAX_EXTN_CAP);
	addstareq->htcap_present = update_peer->htcap_present;
	qdf_mem_copy(&addstareq->ht_cap,
		     &update_peer->ht_cap,
		     sizeof(update_peer->ht_cap));
	addstareq->vhtcap_present = update_peer->vhtcap_present;
	qdf_mem_copy(&addstareq->vht_cap,
		     &update_peer->vht_cap,
		     sizeof(update_peer->vht_cap));
	addstareq->supported_rates_length = update_peer->supported_rates_len;
	qdf_mem_copy(&addstareq->supported_rates,
		     update_peer->supported_rates,
		     update_peer->supported_rates_len);
	tdls_debug("for " QDF_MAC_ADDR_STR,
		   QDF_MAC_ADDR_ARRAY(addstareq->peermac.bytes));

	msg.type = soc_obj->tdls_add_sta_req;
	msg.bodyptr = addstareq;
	status = scheduler_post_msg(QDF_MODULE_ID_PE, &msg);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("fail to post pe msg to update peer");
		goto error;
	}
	return status;
error:
	qdf_mem_free(addstareq);
	return status;
}

static QDF_STATUS
tdls_internal_add_peer_rsp(struct tdls_add_peer_request *req,
			   QDF_STATUS status)
{
	struct tdls_soc_priv_obj *soc_obj;
	struct wlan_objmgr_vdev *vdev;
	struct tdls_osif_indication ind;
	QDF_STATUS ret;

	if (!req || !req->vdev) {
		tdls_err("req: %pK", req);
		return QDF_STATUS_E_INVAL;
	}
	vdev = req->vdev;
	ret = wlan_objmgr_vdev_try_get_ref(vdev, WLAN_TDLS_SB_ID);
	if (QDF_IS_STATUS_ERROR(ret)) {
		tdls_err("can't get vdev object");
		return ret;
	}

	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	if (soc_obj && soc_obj->tdls_event_cb) {
		ind.vdev = vdev;
		ind.status = status;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_ADD_PEER, &ind);
	}
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_SB_ID);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS
tdls_internal_update_peer_rsp(struct tdls_update_peer_request *req,
			      QDF_STATUS status)
{
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_osif_indication ind;
	struct wlan_objmgr_vdev *vdev;
	QDF_STATUS ret;

	if (!req || !req->vdev) {
		tdls_err("req: %pK", req);
		return QDF_STATUS_E_INVAL;
	}
	vdev = req->vdev;
	ret = wlan_objmgr_vdev_try_get_ref(vdev, WLAN_TDLS_SB_ID);
	if (QDF_IS_STATUS_ERROR(ret)) {
		tdls_err("can't get vdev object");
		return ret;
	}

	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	if (soc_obj && soc_obj->tdls_event_cb) {
		ind.vdev = vdev;
		ind.status = status;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_ADD_PEER, &ind);
	}
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_SB_ID);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS tdls_internal_del_peer_rsp(struct tdls_oper_request *req)
{
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_osif_indication ind;
	struct wlan_objmgr_vdev *vdev;
	QDF_STATUS status;

	if (!req || !req->vdev) {
		tdls_err("req: %pK", req);
		return QDF_STATUS_E_INVAL;
	}
	vdev = req->vdev;
	status = wlan_objmgr_vdev_try_get_ref(vdev, WLAN_TDLS_NB_ID);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("can't get vdev object");
		return status;
	}

	soc_obj = wlan_vdev_get_tdls_soc_obj(req->vdev);
	if (soc_obj && soc_obj->tdls_event_cb) {
		ind.vdev = req->vdev;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_DEL_PEER, &ind);
	}
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_NB_ID);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS tdls_activate_add_peer(struct tdls_add_peer_request *req)
{
	QDF_STATUS status;
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct tdls_peer *peer;
	uint16_t curr_tdls_peers;
	const uint8_t *mac;
	struct tdls_osif_indication ind;

	if (!req->vdev) {
		tdls_err("vdev null when add tdls peer");
		QDF_ASSERT(0);
		return QDF_STATUS_E_NULL_VALUE;
	}

	mac = req->add_peer_req.peer_addr;
	soc_obj = wlan_vdev_get_tdls_soc_obj(req->vdev);
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(req->vdev);

	if (!soc_obj || !vdev_obj) {
		tdls_err("soc_obj: %pK, vdev_obj: %pK", soc_obj, vdev_obj);
		return QDF_STATUS_E_INVAL;
	}
	status = tdls_validate_current_mode(soc_obj);
	if (QDF_IS_STATUS_ERROR(status))
		goto addrsp;

	peer = tdls_get_peer(vdev_obj, mac);
	if (!peer) {
		tdls_err("peer: " QDF_MAC_ADDR_STR " not exist. invalid",
			 QDF_MAC_ADDR_ARRAY(mac));
		status = QDF_STATUS_E_INVAL;
		goto addrsp;
	}

	/* in add station, we accept existing valid sta_id if there is */
	if ((peer->link_status > TDLS_LINK_CONNECTING) ||
	    (TDLS_STA_INDEX_CHECK((peer->sta_id)))) {
		tdls_notice("link_status %d sta_id %d add peer ignored",
			    peer->link_status, peer->sta_id);
		status = QDF_STATUS_SUCCESS;
		goto addrsp;
	}

	/* when others are on-going, we want to change link_status to idle */
	if (tdls_is_progress(vdev_obj, mac, true)) {
		tdls_notice(QDF_MAC_ADDR_STR " TDLS setuping. Req declined.",
			    QDF_MAC_ADDR_ARRAY(mac));
		status = QDF_STATUS_E_PERM;
		goto setlink;
	}

	/* first to check if we reached to maximum supported TDLS peer. */
	curr_tdls_peers = tdls_get_connected_peer(soc_obj);
	if (soc_obj->max_num_tdls_sta <= curr_tdls_peers) {
		tdls_err(QDF_MAC_ADDR_STR
			 " Request declined. Current %d, Max allowed %d.",
			 QDF_MAC_ADDR_ARRAY(mac), curr_tdls_peers,
			 soc_obj->max_num_tdls_sta);
		status = QDF_STATUS_E_PERM;
		goto setlink;
	}

	tdls_set_peer_link_status(peer,
				  TDLS_LINK_CONNECTING, TDLS_LINK_SUCCESS);

	status = tdls_pe_add_peer(req);
	if (QDF_IS_STATUS_ERROR(status))
		goto setlink;

	return QDF_STATUS_SUCCESS;

setlink:
	tdls_set_link_status(vdev_obj, mac, TDLS_LINK_IDLE,
			     TDLS_LINK_UNSPECIFIED);
addrsp:
	if (soc_obj->tdls_event_cb) {
		ind.status = status;
		ind.vdev = req->vdev;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_ADD_PEER, &ind);
	}

	return QDF_STATUS_E_PERM;
}

static QDF_STATUS
tdls_add_peer_serialize_callback(struct wlan_serialization_command *cmd,
				 enum wlan_serialization_cb_reason reason)
{
	struct tdls_add_peer_request *req;
	QDF_STATUS status = QDF_STATUS_SUCCESS;

	if (!cmd || !cmd->umac_cmd) {
		tdls_err("cmd: %pK, reason: %d", cmd, reason);
		return QDF_STATUS_E_NULL_VALUE;
	}

	req = cmd->umac_cmd;
	tdls_debug("reason: %d, req %pK", reason, req);

	switch (reason) {
	case WLAN_SER_CB_ACTIVATE_CMD:
		/* command moved to active list
		 */
		status = tdls_activate_add_peer(req);
		break;

	case WLAN_SER_CB_CANCEL_CMD:
		/* command removed from pending list.
		 * notify os interface the status
		 */
		status = tdls_internal_add_peer_rsp(req, QDF_STATUS_E_FAILURE);
		break;

	case WLAN_SER_CB_ACTIVE_CMD_TIMEOUT:
		/* active command time out. */
		status = tdls_internal_add_peer_rsp(req, QDF_STATUS_E_FAILURE);
		break;

	case WLAN_SER_CB_RELEASE_MEM_CMD:
		/* command successfully completed.
		 * release memory & vdev reference count
		 */
		wlan_objmgr_vdev_release_ref(req->vdev, WLAN_TDLS_NB_ID);
		qdf_mem_free(req);
		break;

	default:
		/* Do nothing but logging */
		QDF_ASSERT(0);
		status = QDF_STATUS_E_INVAL;
		break;
	}

	return status;
}

void tdls_reset_nss(struct tdls_soc_priv_obj *tdls_soc,
				  uint8_t action_code)
{
	if (!tdls_soc)
		return;

	if (TDLS_TEARDOWN != action_code ||
	    !tdls_soc->tdls_nss_switch_in_progress)
		return;

	if (tdls_soc->tdls_teardown_peers_cnt != 0)
		tdls_soc->tdls_teardown_peers_cnt--;
	if (tdls_soc->tdls_teardown_peers_cnt == 0) {
		if (tdls_soc->tdls_nss_transition_mode ==
		    TDLS_NSS_TRANSITION_S_1x1_to_2x2) {
			/* TDLS NSS switch is fully completed, so
			 * reset the flags.
			 */
			tdls_notice("TDLS NSS switch is fully completed");
			tdls_soc->tdls_nss_switch_in_progress = false;
			tdls_soc->tdls_nss_teardown_complete = false;
		} else {
			/* TDLS NSS switch is not yet completed, but
			 * tdls teardown is completed for all the
			 * peers.
			 */
			tdls_notice("teardown done & NSS switch in progress");
			tdls_soc->tdls_nss_teardown_complete = true;
		}
		tdls_soc->tdls_nss_transition_mode =
			TDLS_NSS_TRANSITION_S_UNKNOWN;
	}

}

/**
 * tdls_set_cap() - set TDLS capability type
 * @tdls_vdev: tdls vdev object
 * @mac: peer mac address
 * @cap: TDLS capability type
 *
 * Return: 0 if successful or negative errno otherwise
 */
int tdls_set_cap(struct tdls_vdev_priv_obj *tdls_vdev, const uint8_t *mac,
			  enum tdls_peer_capab cap)
{
	struct tdls_peer *curr_peer;

	curr_peer = tdls_get_peer(tdls_vdev, mac);
	if (curr_peer == NULL) {
		tdls_err("curr_peer is NULL");
		return -EINVAL;
	}

	curr_peer->tdls_support = cap;
	return 0;
}

static int tdls_validate_setup_frames(struct tdls_soc_priv_obj *tdls_soc,
				struct tdls_validate_action_req *tdls_validate)
{
	/* supplicant still sends tdls_mgmt(SETUP_REQ)
	 * even after we return error code at
	 * 'add_station()'. Hence we have this check
	 * again in addition to add_station().	Anyway,
	 * there is no harm to double-check.
	 */
	if (TDLS_SETUP_REQUEST == tdls_validate->action_code) {
		tdls_err(QDF_MAC_ADDR_STR " TDLS Max peer already connected. action (%d) declined. Num of peers (%d), Max allowed (%d).",
			 QDF_MAC_ADDR_ARRAY(tdls_validate->peer_mac),
			 tdls_validate->action_code,
			 tdls_soc->connected_peer_count,
			 tdls_soc->max_num_tdls_sta);
		return -EINVAL;
	}
	/* maximum reached. tweak to send
	 * error code to peer and return error
	 * code to supplicant
	 */
	tdls_validate->status_code = QDF_STATUS_E_RESOURCES;
	tdls_err(QDF_MAC_ADDR_STR " TDLS Max peer already connected, send response status (%d). Num of peers (%d), Max allowed (%d).",
		 QDF_MAC_ADDR_ARRAY(tdls_validate->peer_mac),
		 tdls_validate->action_code,
		 tdls_soc->connected_peer_count,
		 tdls_soc->max_num_tdls_sta);

	tdls_validate->max_sta_failed = -EPERM;
	return 0;
}

int tdls_validate_mgmt_request(struct tdls_action_frame_request *tdls_mgmt_req)
{
	struct tdls_vdev_priv_obj *tdls_vdev;
	struct tdls_soc_priv_obj *tdls_soc;
	struct tdls_peer *curr_peer;
	struct tdls_peer *temp_peer;
	QDF_STATUS status;

	struct tdls_validate_action_req *tdls_validate =
		tdls_mgmt_req->chk_frame;

	if (!tdls_validate || !tdls_validate->vdev)
		return -EINVAL;

	if (QDF_STATUS_SUCCESS != tdls_get_vdev_objects(tdls_validate->vdev,
							&tdls_vdev,
							&tdls_soc))
		return -ENOTSUPP;

	/*
	 * STA or P2P client should be connected and authenticated before
	 *  sending any TDLS frames
	 */
	if (!tdls_is_vdev_connected(tdls_validate->vdev) ||
	    !tdls_is_vdev_authenticated(tdls_validate->vdev)) {
		tdls_err("STA is not connected or not authenticated.");
		return -EAGAIN;
	}

	/* other than teardown frame, mgmt frames are not sent if disabled */
	if (TDLS_TEARDOWN != tdls_validate->action_code) {
		if (!tdls_check_is_tdls_allowed(tdls_validate->vdev)) {
			tdls_err("TDLS not allowed, reject MGMT, action = %d",
				tdls_validate->action_code);
			return -EPERM;
		}
		/* if tdls_mode is disabled, then decline the peer's request */
		if (TDLS_SUPPORT_DISABLED == tdls_soc->tdls_current_mode ||
		    TDLS_SUPPORT_SUSPENDED == tdls_soc->tdls_current_mode) {
			tdls_notice(QDF_MAC_ADDR_STR
				" TDLS mode is disabled. action %d declined.",
				QDF_MAC_ADDR_ARRAY(tdls_validate->peer_mac),
				tdls_validate->action_code);
			return -ENOTSUPP;
		}
		if (tdls_soc->tdls_nss_switch_in_progress) {
			tdls_err("nss switch in progress, action %d declined "
				QDF_MAC_ADDR_STR,
				tdls_validate->action_code,
				QDF_MAC_ADDR_ARRAY(tdls_validate->peer_mac));
			return -EAGAIN;
		}
	}

	if (TDLS_IS_SETUP_ACTION(tdls_validate->action_code)) {
		if (NULL != tdls_is_progress(tdls_vdev,
			tdls_validate->peer_mac, true)) {
			tdls_err("setup is ongoing. action %d declined for "
				 QDF_MAC_ADDR_STR,
				 tdls_validate->action_code,
				 QDF_MAC_ADDR_ARRAY(tdls_validate->peer_mac));
			return -EPERM;
		}
	}

	/*  call hdd_wmm_is_acm_allowed() */
	if (!tdls_soc->tdls_wmm_cb(&tdls_vdev->vdev)) {
		tdls_debug("admission ctrl set to VI, send the frame with least AC (BK) for action %d",
			   tdls_validate->action_code);
		tdls_mgmt_req->use_default_ac = false;
	} else {
		tdls_mgmt_req->use_default_ac = true;
	}

	if (TDLS_SETUP_REQUEST == tdls_validate->action_code ||
	    TDLS_SETUP_RESPONSE == tdls_validate->action_code) {
		if (tdls_soc->max_num_tdls_sta <=
			tdls_soc->connected_peer_count) {
			status = tdls_validate_setup_frames(tdls_soc,
							    tdls_validate);
			if (QDF_STATUS_SUCCESS != status)
				return status;
			/* fall through to send setup resp
			 * with failure status code
			 */
		} else {
			curr_peer =
				tdls_find_peer(tdls_vdev,
					       tdls_validate->peer_mac);
			if (curr_peer) {
				if (TDLS_IS_LINK_CONNECTED(curr_peer)) {
					tdls_err(QDF_MAC_ADDR_STR " already connected action %d declined.",
						QDF_MAC_ADDR_ARRAY(
						tdls_validate->peer_mac),
						tdls_validate->action_code);

					return -EPERM;
				}
			}
		}
	}

	tdls_notice("tdls_mgmt" QDF_MAC_ADDR_STR " action %d, dialog_token %d status %d, len = %zu",
		   QDF_MAC_ADDR_ARRAY(tdls_validate->peer_mac),
		   tdls_validate->action_code, tdls_validate->dialog_token,
		   tdls_validate->status_code, tdls_validate->len);

	/*Except teardown responder will not be used so just make 0 */
	tdls_validate->responder = 0;
	if (TDLS_TEARDOWN == tdls_validate->action_code) {
		temp_peer = tdls_find_peer(tdls_vdev, tdls_validate->peer_mac);
		if (!temp_peer) {
			tdls_err(QDF_MAC_ADDR_STR " peer doesn't exist",
				     QDF_MAC_ADDR_ARRAY(
				     tdls_validate->peer_mac));
			return -EPERM;
		}

		if (TDLS_IS_LINK_CONNECTED(temp_peer))
			tdls_validate->responder = temp_peer->is_responder;
		else {
			tdls_err(QDF_MAC_ADDR_STR " peer doesn't exist or not connected %d dialog_token %d status %d, tdls_validate->len = %zu",
				 QDF_MAC_ADDR_ARRAY(tdls_validate->peer_mac),
				 temp_peer->link_status,
				 tdls_validate->dialog_token,
				 tdls_validate->status_code,
				 tdls_validate->len);
			return -EPERM;
		}
	}

	/* For explicit trigger of DIS_REQ come out of BMPS for
	 * successfully receiving DIS_RSP from peer.
	 */
	if ((TDLS_SETUP_RESPONSE == tdls_validate->action_code) ||
	    (TDLS_SETUP_CONFIRM == tdls_validate->action_code) ||
	    (TDLS_DISCOVERY_RESPONSE == tdls_validate->action_code) ||
	    (TDLS_DISCOVERY_REQUEST == tdls_validate->action_code)) {
		/* Fw will take care if PS offload is enabled. */
		if (TDLS_DISCOVERY_REQUEST != tdls_validate->action_code)
			tdls_set_cap(tdls_vdev, tdls_validate->peer_mac,
					      TDLS_CAP_SUPPORTED);
	}
	return 0;
}

QDF_STATUS tdls_process_add_peer(struct tdls_add_peer_request *req)
{
	struct wlan_serialization_command cmd = {0,};
	enum wlan_serialization_status ser_cmd_status;
	struct wlan_objmgr_vdev *vdev;
	QDF_STATUS status = QDF_STATUS_SUCCESS;

	if (!req || !req->vdev) {
		tdls_err("req: %pK", req);
		status = QDF_STATUS_E_INVAL;
		goto error;
	}
	vdev = req->vdev;
	cmd.cmd_type = WLAN_SER_CMD_TDLS_ADD_PEER;
	cmd.cmd_id = 0;
	cmd.cmd_cb = (wlan_serialization_cmd_callback)
		tdls_add_peer_serialize_callback;
	cmd.umac_cmd = req;
	cmd.source = WLAN_UMAC_COMP_TDLS;
	cmd.is_high_priority = false;
	cmd.cmd_timeout_duration = WAIT_TIME_TDLS_ADD_STA;
	cmd.vdev = vdev;

	ser_cmd_status = wlan_serialization_request(&cmd);
	tdls_debug("req: 0x%pK wlan_serialization_request status:%d", req,
		   ser_cmd_status);

	switch (ser_cmd_status) {
	case WLAN_SER_CMD_PENDING:
		/* command moved to pending list. Do nothing */
		break;
	case WLAN_SER_CMD_ACTIVE:
		/* command moved to active list. Do nothing */
		break;
	case WLAN_SER_CMD_DENIED_LIST_FULL:
	case WLAN_SER_CMD_DENIED_RULES_FAILED:
	case WLAN_SER_CMD_DENIED_UNSPECIFIED:
		/* notify os interface about internal error*/
		status = tdls_internal_add_peer_rsp(req, QDF_STATUS_E_FAILURE);
		wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_NB_ID);
		/* cmd can't be serviced.
		 * release tdls_add_peer_request memory
		 */
		qdf_mem_free(req);
		break;
	default:
		QDF_ASSERT(0);
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	return status;
error:
	status = tdls_internal_add_peer_rsp(req, QDF_STATUS_E_FAILURE);
	qdf_mem_free(req);
	return status;
}

static QDF_STATUS
tdls_activate_update_peer(struct tdls_update_peer_request *req)
{
	QDF_STATUS status;
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct wlan_objmgr_vdev *vdev;
	struct tdls_peer *curr_peer;
	uint16_t curr_tdls_peers;
	const uint8_t *mac;
	struct tdls_update_peer_params *update_peer;
	struct tdls_osif_indication ind;

	if (!req->vdev) {
		tdls_err("vdev object NULL when add TDLS peer");
		QDF_ASSERT(0);
		return QDF_STATUS_E_NULL_VALUE;
	}

	mac = req->update_peer_req.peer_addr;
	vdev = req->vdev;
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(vdev);
	if (!soc_obj || !vdev_obj) {
		tdls_err("soc_obj: %pK, vdev_obj: %pK", soc_obj, vdev_obj);
		return QDF_STATUS_E_INVAL;
	}

	status = tdls_validate_current_mode(soc_obj);
	if (QDF_IS_STATUS_ERROR(status))
		goto updatersp;

	curr_peer = tdls_get_peer(vdev_obj, mac);
	if (!curr_peer) {
		tdls_err(QDF_MAC_ADDR_STR " not exist. return invalid",
			 QDF_MAC_ADDR_ARRAY(mac));
		status = QDF_STATUS_E_INVAL;
		goto updatersp;
	}

	/* in change station, we accept only when sta_id is valid */
	if (curr_peer->link_status > TDLS_LINK_CONNECTING ||
	    !(TDLS_STA_INDEX_CHECK(curr_peer->sta_id))) {
		tdls_err(QDF_MAC_ADDR_STR " link %d. sta %d. update peer %s",
			 QDF_MAC_ADDR_ARRAY(mac), curr_peer->link_status,
			 curr_peer->sta_id,
			 (TDLS_STA_INDEX_CHECK(curr_peer->sta_id)) ? "ignored"
			 : "declined");
		status = (TDLS_STA_INDEX_CHECK(curr_peer->sta_id)) ?
			QDF_STATUS_SUCCESS : QDF_STATUS_E_PERM;
		goto updatersp;
	}

	/* when others are on-going, we want to change link_status to idle */
	if (tdls_is_progress(vdev_obj, mac, true)) {
		tdls_notice(QDF_MAC_ADDR_STR " TDLS setuping. Req declined.",
			    QDF_MAC_ADDR_ARRAY(mac));
		status = QDF_STATUS_E_PERM;
		goto setlink;
	}

	curr_tdls_peers = tdls_get_connected_peer(soc_obj);
	if (soc_obj->max_num_tdls_sta <= curr_tdls_peers) {
		tdls_err(QDF_MAC_ADDR_STR
			 " Request declined. Current: %d, Max allowed: %d.",
			 QDF_MAC_ADDR_ARRAY(mac), curr_tdls_peers,
			 soc_obj->max_num_tdls_sta);
		status = QDF_STATUS_E_PERM;
		goto setlink;
	}
	update_peer = &req->update_peer_req;

	if (update_peer->htcap_present)
		curr_peer->spatial_streams = update_peer->ht_cap.mcsset[1];

	tdls_set_peer_caps(vdev_obj, mac, &req->update_peer_req);
	status = tdls_pe_update_peer(req);
	if (QDF_IS_STATUS_ERROR(status))
		goto setlink;

	return QDF_STATUS_SUCCESS;

setlink:
	tdls_set_link_status(vdev_obj, mac, TDLS_LINK_IDLE,
			     TDLS_LINK_UNSPECIFIED);
updatersp:
	if (soc_obj->tdls_event_cb) {
		ind.status = status;
		ind.vdev = vdev;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_ADD_PEER, &ind);
	}

	return QDF_STATUS_E_PERM;
}

static QDF_STATUS
tdls_update_peer_serialize_callback(struct wlan_serialization_command *cmd,
				    enum wlan_serialization_cb_reason reason)
{
	struct tdls_update_peer_request *req;
	QDF_STATUS status = QDF_STATUS_SUCCESS;

	if (!cmd || !cmd->umac_cmd) {
		tdls_err("cmd: %pK, reason: %d", cmd, reason);
		return QDF_STATUS_E_NULL_VALUE;
	}

	req = cmd->umac_cmd;
	tdls_debug("reason: %d, req %pK", reason, req);

	switch (reason) {
	case WLAN_SER_CB_ACTIVATE_CMD:
		/* command moved to active list
		 */
		status = tdls_activate_update_peer(req);
		break;

	case WLAN_SER_CB_CANCEL_CMD:
		/* command removed from pending list.
		 * notify os interface the status
		 */
		status = tdls_internal_update_peer_rsp(req,
						       QDF_STATUS_E_FAILURE);
		break;

	case WLAN_SER_CB_ACTIVE_CMD_TIMEOUT:
		/* active command time out. */
		status = tdls_internal_update_peer_rsp(req,
						       QDF_STATUS_E_FAILURE);
		break;

	case WLAN_SER_CB_RELEASE_MEM_CMD:
		/* command successfully completed.
		 * release memory & release reference count
		 */
		wlan_objmgr_vdev_release_ref(req->vdev, WLAN_TDLS_NB_ID);
		qdf_mem_free(req);
		break;

	default:
		/* Do nothing but logging */
		QDF_ASSERT(0);
		status = QDF_STATUS_E_INVAL;
		break;
	}

	return status;
}

QDF_STATUS tdls_process_update_peer(struct tdls_update_peer_request *req)
{
	struct wlan_serialization_command cmd = {0,};
	enum wlan_serialization_status ser_cmd_status;
	struct wlan_objmgr_vdev *vdev;
	QDF_STATUS status = QDF_STATUS_SUCCESS;

	if (!req || !req->vdev) {
		tdls_err("req: %pK", req);
		status = QDF_STATUS_E_FAILURE;
		goto error;
	}

	vdev = req->vdev;
	cmd.cmd_type = WLAN_SER_CMD_TDLS_ADD_PEER;
	cmd.cmd_id = 0;
	cmd.cmd_cb = (wlan_serialization_cmd_callback)
		tdls_update_peer_serialize_callback;
	cmd.umac_cmd = req;
	cmd.source = WLAN_UMAC_COMP_TDLS;
	cmd.is_high_priority = false;
	cmd.cmd_timeout_duration = WAIT_TIME_TDLS_ADD_STA;
	cmd.vdev = req->vdev;

	ser_cmd_status = wlan_serialization_request(&cmd);
	tdls_debug("req: 0x%pK wlan_serialization_request status:%d", req,
		   ser_cmd_status);

	switch (ser_cmd_status) {
	case WLAN_SER_CMD_PENDING:
		/* command moved to pending list. Do nothing */
		break;
	case WLAN_SER_CMD_ACTIVE:
		/* command moved to active list. Do nothing */
		break;
	case WLAN_SER_CMD_DENIED_LIST_FULL:
	case WLAN_SER_CMD_DENIED_RULES_FAILED:
	case WLAN_SER_CMD_DENIED_UNSPECIFIED:
		/* notify os interface about internal error*/
		status = tdls_internal_update_peer_rsp(req,
						       QDF_STATUS_E_FAILURE);
		wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_NB_ID);
		/* cmd can't be serviced.
		 * release tdls_add_peer_request memory
		 */
		qdf_mem_free(req);
		break;
	default:
		QDF_ASSERT(0);
		status = QDF_STATUS_E_INVAL;
		break;
	}

	return status;
error:
	status = tdls_internal_update_peer_rsp(req, QDF_STATUS_E_FAILURE);
	qdf_mem_free(req);
	return status;
}

static QDF_STATUS tdls_activate_del_peer(struct tdls_oper_request *req)
{
	struct tdls_del_peer_request request = {0,};

	request.vdev = req->vdev;
	request.del_peer_req.peer_addr = req->peer_addr;

	return tdls_pe_del_peer(&request);
}

static QDF_STATUS
tdls_del_peer_serialize_callback(struct wlan_serialization_command *cmd,
				 enum wlan_serialization_cb_reason reason)
{
	struct tdls_oper_request *req;
	QDF_STATUS status = QDF_STATUS_SUCCESS;

	if (!cmd || !cmd->umac_cmd) {
		tdls_err("cmd: %pK, reason: %d", cmd, reason);
		return QDF_STATUS_E_NULL_VALUE;
	}

	req = cmd->umac_cmd;
	tdls_debug("reason: %d, req %pK", reason, req);

	switch (reason) {
	case WLAN_SER_CB_ACTIVATE_CMD:
		/* command moved to active list
		 */
		status = tdls_activate_del_peer(req);
		break;

	case WLAN_SER_CB_CANCEL_CMD:
		/* command removed from pending list.
		 * notify os interface the status
		 */
		status = tdls_internal_del_peer_rsp(req);
		break;

	case WLAN_SER_CB_ACTIVE_CMD_TIMEOUT:
		/* active command time out. */
		status = tdls_internal_del_peer_rsp(req);
		break;

	case WLAN_SER_CB_RELEASE_MEM_CMD:
		/* command successfully completed.
		 * release memory & vdev reference count
		 */
		wlan_objmgr_vdev_release_ref(req->vdev, WLAN_TDLS_NB_ID);
		qdf_mem_free(req);
		break;

	default:
		/* Do nothing but logging */
		QDF_ASSERT(0);
		status = QDF_STATUS_E_INVAL;
		break;
	}

	return status;
}

QDF_STATUS tdls_process_del_peer(struct tdls_oper_request *req)
{
	struct wlan_serialization_command cmd = {0,};
	enum wlan_serialization_status ser_cmd_status;
	struct wlan_objmgr_vdev *vdev;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct tdls_soc_priv_obj *soc_obj;
	uint8_t *mac;
	struct tdls_peer *peer;
	QDF_STATUS status = QDF_STATUS_SUCCESS;

	if (!req || !req->vdev) {
		tdls_err("req: %pK", req);
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	vdev = req->vdev;

	/* vdev reference cnt is acquired in ucfg_tdls_oper */
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(vdev);
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);

	if (!vdev_obj || !soc_obj) {
		tdls_err("tdls vdev_obj: %pK soc_obj: %pK", vdev_obj, soc_obj);
		status = QDF_STATUS_E_NULL_VALUE;
		goto error;
	}

	mac = req->peer_addr;
	peer = tdls_find_peer(vdev_obj, mac);
	if (!peer) {
		tdls_err(QDF_MAC_ADDR_STR
			 " not found, ignore NL80211_TDLS_ENABLE_LINK",
			 QDF_MAC_ADDR_ARRAY(mac));
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	if (soc_obj->tdls_dp_vdev_update)
		soc_obj->tdls_dp_vdev_update(&soc_obj->soc,
					peer->sta_id,
					soc_obj->tdls_update_dp_vdev_flags,
					false);

	cmd.cmd_type = WLAN_SER_CMD_TDLS_DEL_PEER;
	cmd.cmd_id = 0;
	cmd.cmd_cb = (wlan_serialization_cmd_callback)
		tdls_del_peer_serialize_callback;
	cmd.umac_cmd = req;
	cmd.source = WLAN_UMAC_COMP_TDLS;
	cmd.is_high_priority = false;
	cmd.cmd_timeout_duration = WAIT_TIME_TDLS_DEL_STA;
	cmd.vdev = vdev;

	ser_cmd_status = wlan_serialization_request(&cmd);
	tdls_debug("req: 0x%pK wlan_serialization_request status:%d", req,
		   ser_cmd_status);

	switch (ser_cmd_status) {
	case WLAN_SER_CMD_PENDING:
		/* command moved to pending list. Do nothing */
		break;
	case WLAN_SER_CMD_ACTIVE:
		/* command moved to active list. Do nothing */
		break;
	case WLAN_SER_CMD_DENIED_LIST_FULL:
	case WLAN_SER_CMD_DENIED_RULES_FAILED:
	case WLAN_SER_CMD_DENIED_UNSPECIFIED:
		/* notify os interface about internal error*/
		status = tdls_internal_del_peer_rsp(req);
		wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_NB_ID);
		/* cmd can't be serviced.
		 * release tdls_add_peer_request memory
		 */
		qdf_mem_free(req);
		break;
	default:
		QDF_ASSERT(0);
		status = QDF_STATUS_E_INVAL;
		break;
	}

	return status;
error:
	status = tdls_internal_del_peer_rsp(req);
	qdf_mem_free(req);
	return status;
}

/**
 * tdls_process_add_peer_rsp() - handle response for update TDLS peer
 * @rsp: TDLS add peer response
 *
 * Return: QDF_STATUS_SUCCESS for success; other values if failed
 */
static QDF_STATUS tdls_update_peer_rsp(struct tdls_add_sta_rsp *rsp)
{
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_psoc *psoc;
	struct tdls_soc_priv_obj *soc_obj;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct tdls_osif_indication ind;

	psoc = rsp->psoc;
	if (!psoc) {
		tdls_err("psoc is NULL");
		QDF_ASSERT(0);
		return QDF_STATUS_E_FAILURE;
	}
	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, rsp->session_id,
						    WLAN_TDLS_SB_ID);
	if (!vdev) {
		tdls_err("invalid vdev: %d", rsp->session_id);
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	tdls_release_serialization_command(vdev, WLAN_SER_CMD_TDLS_ADD_PEER);
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_SB_ID);
error:
	soc_obj = wlan_psoc_get_tdls_soc_obj(psoc);
	if (soc_obj && soc_obj->tdls_event_cb) {
		ind.status = rsp->status_code;
		ind.vdev = vdev;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_ADD_PEER, &ind);
	}
	qdf_mem_free(rsp);

	return status;
}

/**
 * tdls_process_send_mgmt_rsp() - handle response for send mgmt
 * @rsp: TDLS send mgmt response
 *
 * Return: QDF_STATUS_SUCCESS for success; other values if failed
 */
QDF_STATUS tdls_process_send_mgmt_rsp(struct tdls_send_mgmt_rsp *rsp)
{
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_psoc *psoc;
	struct tdls_vdev_priv_obj *tdls_vdev;
	struct tdls_soc_priv_obj *tdls_soc = NULL;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct tdls_osif_indication ind;

	psoc = rsp->psoc;
	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, rsp->session_id,
						    WLAN_TDLS_SB_ID);
	if (!vdev) {
		tdls_err("invalid vdev");
		status =  QDF_STATUS_E_INVAL;
		qdf_mem_free(rsp);
		return status;
	}
	tdls_soc = wlan_psoc_get_tdls_soc_obj(psoc);
	tdls_vdev = wlan_vdev_get_tdls_vdev_obj(vdev);
	if (!tdls_soc || !tdls_vdev) {
		tdls_err("soc object:%pK, vdev object:%pK", tdls_soc, tdls_vdev);
		status = QDF_STATUS_E_FAILURE;
	}

	tdls_release_serialization_command(vdev, WLAN_SER_CMD_TDLS_SEND_MGMT);

	if (legacy_result_success == rsp->status_code)
		goto free_rsp;
	tdls_err("send mgmt failed. status code(=%d)", rsp->status_code);
	status = QDF_STATUS_E_FAILURE;

	if (tdls_soc && tdls_soc->tdls_event_cb) {
		ind.vdev = vdev;
		ind.status = status;
		tdls_soc->tdls_event_cb(tdls_soc->tdls_evt_cb_data,
				       TDLS_EVENT_MGMT_TX_ACK_CNF, &ind);
	}

free_rsp:
	qdf_mem_free(rsp);
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_SB_ID);
	return status;
}

/**
 * tdls_send_mgmt_tx_completion() - process tx completion
 * @tx_complete: TDLS mgmt completion info
 *
 * Return: QDF_STATUS_SUCCESS for success; other values if failed
 */
QDF_STATUS tdls_send_mgmt_tx_completion(
			struct tdls_mgmt_tx_completion_ind *tx_complete)
{
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_psoc *psoc;
	struct tdls_vdev_priv_obj *tdls_vdev;
	struct tdls_soc_priv_obj *tdls_soc = NULL;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct tdls_osif_indication ind;

	psoc = tx_complete->psoc;
	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc,
						    tx_complete->session_id,
						    WLAN_TDLS_SB_ID);

	if (!vdev) {
		tdls_err("invalid vdev");
		status =  QDF_STATUS_E_INVAL;
		goto free_tx_complete;
	}

	tdls_soc = wlan_psoc_get_tdls_soc_obj(psoc);
	tdls_vdev = wlan_vdev_get_tdls_vdev_obj(vdev);

	if (!tdls_soc || !tdls_vdev) {
		tdls_err("soc object:%pK, vdev object:%pK", tdls_soc, tdls_vdev);
		status = QDF_STATUS_E_FAILURE;
	}

	if (tdls_soc && tdls_soc->tdls_event_cb) {
		ind.vdev = vdev;
		ind.status = tx_complete->tx_complete_status;
		tdls_soc->tdls_event_cb(tdls_soc->tdls_evt_cb_data,
			       TDLS_EVENT_MGMT_TX_ACK_CNF, &ind);
	}

	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_SB_ID);
free_tx_complete:
	qdf_mem_free(tx_complete);
	return status;
}

/**
 * tdls_add_peer_rsp() - handle response for add TDLS peer
 * @rsp: TDLS add peer response
 *
 * Return: QDF_STATUS_SUCCESS for success; other values if failed
 */
static QDF_STATUS tdls_add_peer_rsp(struct tdls_add_sta_rsp *rsp)
{
	uint8_t sta_idx;
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_psoc *psoc;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct tdls_soc_priv_obj *soc_obj = NULL;
	struct tdls_conn_info *conn_rec;
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct tdls_osif_indication ind;

	psoc = rsp->psoc;
	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, rsp->session_id,
						    WLAN_TDLS_SB_ID);
	if (!vdev) {
		tdls_err("invalid vdev: %d", rsp->session_id);
		status =  QDF_STATUS_E_INVAL;
		goto error;
	}
	soc_obj = wlan_psoc_get_tdls_soc_obj(psoc);
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(vdev);
	if (!soc_obj || !vdev_obj) {
		tdls_err("soc object:%pK, vdev object:%pK", soc_obj, vdev_obj);
		status = QDF_STATUS_E_FAILURE;
		goto cmddone;
	}
	if (rsp->status_code) {
		tdls_err("add sta failed. status code(=%d)", rsp->status_code);
		status = QDF_STATUS_E_FAILURE;
	} else {
		conn_rec = soc_obj->tdls_conn_info;
		for (sta_idx = 0; sta_idx < soc_obj->max_num_tdls_sta;
		     sta_idx++) {
			if (0 == conn_rec[sta_idx].sta_id) {
				conn_rec[sta_idx].session_id = rsp->session_id;
				conn_rec[sta_idx].sta_id = rsp->sta_id;
				qdf_copy_macaddr(&conn_rec[sta_idx].peer_mac,
						 &rsp->peermac);
				tdls_warn("TDLS: STA IDX at %d is %d of mac "
					  QDF_MAC_ADDR_STR, sta_idx,
					  rsp->sta_id, QDF_MAC_ADDR_ARRAY
					  (rsp->peermac.bytes));
				break;
			}
		}

		if (sta_idx < soc_obj->max_num_tdls_sta) {
			status = tdls_set_sta_id(vdev_obj, rsp->peermac.bytes,
						 rsp->sta_id);
			if (QDF_IS_STATUS_ERROR(status)) {
				tdls_err("set staid failed");
				status = QDF_STATUS_E_FAILURE;
			}
		} else {
			status = QDF_STATUS_E_FAILURE;
		}
	}

cmddone:
	tdls_release_serialization_command(vdev, WLAN_SER_CMD_TDLS_ADD_PEER);
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_SB_ID);
error:
	if (soc_obj && soc_obj->tdls_event_cb) {
		ind.vdev = vdev;
		ind.status = rsp->status_code;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_ADD_PEER, &ind);
	}
	qdf_mem_free(rsp);

	return status;
}

QDF_STATUS tdls_process_add_peer_rsp(struct tdls_add_sta_rsp *rsp)
{
	tdls_debug("peer oper %d", rsp->tdls_oper);

	if (rsp->tdls_oper == TDLS_OPER_ADD)
		return tdls_add_peer_rsp(rsp);
	else if (rsp->tdls_oper == TDLS_OPER_UPDATE)
		return tdls_update_peer_rsp(rsp);

	return QDF_STATUS_E_INVAL;
}

QDF_STATUS tdls_process_del_peer_rsp(struct tdls_del_sta_rsp *rsp)
{
	uint8_t sta_idx, id;
	QDF_STATUS status = QDF_STATUS_E_FAILURE;
	struct wlan_objmgr_vdev *vdev;
	struct wlan_objmgr_psoc *psoc;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct tdls_soc_priv_obj *soc_obj = NULL;
	struct tdls_conn_info *conn_rec;
	struct tdls_peer *curr_peer = NULL;
	const uint8_t *macaddr;
	struct tdls_osif_indication ind;

	tdls_debug("del peer rsp: vdev %d  peer " QDF_MAC_ADDR_STR,
		   rsp->session_id, QDF_MAC_ADDR_ARRAY(rsp->peermac.bytes));
	psoc = rsp->psoc;
	vdev = wlan_objmgr_get_vdev_by_id_from_psoc(psoc, rsp->session_id,
						    WLAN_TDLS_SB_ID);
	if (!vdev) {
		tdls_err("invalid vdev: %d", rsp->session_id);
		status = QDF_STATUS_E_INVAL;
		goto error;
	}
	soc_obj = wlan_psoc_get_tdls_soc_obj(psoc);
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(vdev);
	if (!soc_obj || !vdev_obj) {
		tdls_err("soc object:%pK, vdev object:%pK", soc_obj, vdev_obj);
		status = QDF_STATUS_E_FAILURE;
		goto cmddone;
	}

	conn_rec = soc_obj->tdls_conn_info;
	for (sta_idx = 0; sta_idx < soc_obj->max_num_tdls_sta; sta_idx++) {
		if (conn_rec[sta_idx].session_id != rsp->session_id ||
		    conn_rec[sta_idx].sta_id != rsp->sta_id)
			continue;

		macaddr = rsp->peermac.bytes;
		tdls_warn("TDLS: del STA IDX = %x", rsp->sta_id);
		curr_peer = tdls_find_peer(vdev_obj, macaddr);
		if (curr_peer) {
			tdls_debug(QDF_MAC_ADDR_STR " status is %d",
				   QDF_MAC_ADDR_ARRAY(macaddr),
				   curr_peer->link_status);

			id = wlan_vdev_get_id(vdev);

			if (TDLS_IS_LINK_CONNECTED(curr_peer)) {
				soc_obj->tdls_dereg_peer(
					soc_obj->tdls_peer_context,
					id, curr_peer->sta_id);
				tdls_decrement_peer_count(soc_obj);
			} else if (TDLS_LINK_CONNECTING ==
				   curr_peer->link_status) {
				soc_obj->tdls_dereg_peer(
					soc_obj->tdls_peer_context,
					id, curr_peer->sta_id);
			}
		}
		tdls_reset_peer(vdev_obj, macaddr);
		conn_rec[sta_idx].sta_id = 0;
		conn_rec[sta_idx].session_id = 0xff;
		qdf_mem_zero(&conn_rec[sta_idx].peer_mac,
			     QDF_MAC_ADDR_SIZE);

		status = QDF_STATUS_SUCCESS;
		break;
	}
	macaddr = rsp->peermac.bytes;
	if (!curr_peer) {
		curr_peer = tdls_find_peer(vdev_obj, macaddr);

		if (curr_peer)
			tdls_set_peer_link_status(curr_peer, TDLS_LINK_IDLE,
						  (curr_peer->link_status ==
						   TDLS_LINK_TEARING) ?
						  TDLS_LINK_UNSPECIFIED :
						  TDLS_LINK_DROPPED_BY_REMOTE);
	}

cmddone:
	tdls_release_serialization_command(vdev, WLAN_SER_CMD_TDLS_DEL_PEER);
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_SB_ID);
error:

	if (soc_obj && soc_obj->tdls_event_cb) {
		ind.vdev = vdev;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_DEL_PEER, &ind);
	}
	qdf_mem_free(rsp);

	return status;
}

static QDF_STATUS
tdls_wma_update_peer_state(struct tdls_soc_priv_obj *soc_obj,
			   struct tdls_peer_update_state *peer_state)
{
	struct scheduler_msg msg = {0,};
	QDF_STATUS status;

	tdls_debug("update TDLS peer " QDF_MAC_ADDR_STR " vdev %d, state %d",
		   QDF_MAC_ADDR_ARRAY(peer_state->peer_macaddr),
		   peer_state->vdev_id, peer_state->peer_state);
	msg.type = soc_obj->tdls_update_peer_state;
	msg.reserved = 0;
	msg.bodyptr = peer_state;

	status = scheduler_post_msg(QDF_MODULE_ID_WMA, &msg);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("scheduler_post_msg failed");
		status = QDF_STATUS_E_FAILURE;
	}

	return status;
}

static QDF_STATUS
tdls_update_uapsd(struct wlan_objmgr_psoc *psoc, struct wlan_objmgr_vdev *vdev,
		  uint8_t sta_id, uint32_t srvc_int, uint32_t sus_int,
		  uint8_t dir, uint8_t psb, uint32_t delay_interval)
{
	uint8_t i;
	static const uint8_t ac[AC_PRIORITY_NUM] = {UAPSD_AC_VO, UAPSD_AC_VI,
						    UAPSD_AC_BK, UAPSD_AC_BE};
	static const uint8_t tid[AC_PRIORITY_NUM] = {7, 5, 2, 3};
	uint32_t vdev_id;

	struct sta_uapsd_params tdls_uapsd_params;
	struct sta_uapsd_trig_params tdls_trig_params;
	struct wlan_objmgr_peer *bsspeer;
	uint8_t macaddr[QDF_MAC_ADDR_SIZE];
	QDF_STATUS status;

	if (!psb) {
		tdls_debug("No need to configure auto trigger:psb is 0");
		return QDF_STATUS_SUCCESS;
	}
	vdev_id = wlan_vdev_get_id(vdev);
	bsspeer = wlan_vdev_get_bsspeer(vdev);
	if (!bsspeer) {
		tdls_err("bss peer is NULL");
		return QDF_STATUS_E_FAILURE;
	}
	wlan_vdev_obj_lock(vdev);
	qdf_mem_copy(macaddr,
		     wlan_peer_get_macaddr(bsspeer), QDF_MAC_ADDR_SIZE);
	wlan_vdev_obj_unlock(vdev);

	tdls_debug("TDLS uapsd id %d, srvc %d, sus %d, dir %d psb %d delay %d",
		   sta_id, srvc_int, sus_int, dir, psb, delay_interval);
	for (i = 0; i < AC_PRIORITY_NUM; i++) {
		tdls_uapsd_params.wmm_ac = ac[i];
		tdls_uapsd_params.user_priority = tid[i];
		tdls_uapsd_params.service_interval = srvc_int;
		tdls_uapsd_params.delay_interval = delay_interval;
		tdls_uapsd_params.suspend_interval = sus_int;

		tdls_trig_params.vdevid = vdev_id;
		tdls_trig_params.num_ac = 1;
		tdls_trig_params.auto_triggerparam = &tdls_uapsd_params;

		qdf_mem_copy(tdls_trig_params.peer_addr,
			     macaddr, QDF_MAC_ADDR_SIZE);
		status = tgt_tdls_set_uapsd(psoc, &tdls_trig_params);
		if (QDF_IS_STATUS_ERROR(status)) {
			tdls_err("Failed to set uapsd for vdev %d, status %d",
				 vdev_id, status);
		}
	}

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS tdls_process_enable_link(struct tdls_oper_request *req)
{
	struct tdls_peer *peer;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct tdls_soc_priv_obj *soc_obj;
	struct wlan_objmgr_vdev *vdev;
	uint8_t *mac;
	struct tdls_peer_update_state *peer_update_param;
	QDF_STATUS status;
	uint32_t feature;
	uint8_t id;

	vdev = req->vdev;
	if (!vdev) {
		tdls_err("NULL vdev object");
		qdf_mem_free(req);
		return QDF_STATUS_E_NULL_VALUE;
	}

	/* vdev reference cnt is acquired in ucfg_tdls_oper */
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(vdev);
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);

	if (!vdev_obj || !soc_obj) {
		tdls_err("tdls vdev_obj: %pK soc_obj: %pK", vdev_obj, soc_obj);
		status = QDF_STATUS_E_NULL_VALUE;
		goto error;
	}

	mac = req->peer_addr;
	peer = tdls_find_peer(vdev_obj, mac);
	if (!peer) {
		tdls_err(QDF_MAC_ADDR_STR
			 " not found, ignore NL80211_TDLS_ENABLE_LINK",
			 QDF_MAC_ADDR_ARRAY(mac));
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	tdls_debug("enable link for peer " QDF_MAC_ADDR_STR " link state %d",
		   QDF_MAC_ADDR_ARRAY(mac), peer->link_status);
	if (!TDLS_STA_INDEX_CHECK(peer->sta_id)) {
		tdls_err("invalid sta idx %u for " QDF_MAC_ADDR_STR,
			 peer->sta_id, QDF_MAC_ADDR_ARRAY(mac));
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	peer->tdls_support = TDLS_CAP_SUPPORTED;
	if (TDLS_LINK_CONNECTED != peer->link_status)
		tdls_set_peer_link_status(peer, TDLS_LINK_CONNECTED,
					  TDLS_LINK_SUCCESS);

	id = wlan_vdev_get_id(vdev);
	status = soc_obj->tdls_reg_peer(soc_obj->tdls_peer_context,
					id, mac, peer->sta_id,
					peer->qos);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("TDLS register peer fail, status %d", status);
		goto error;
	}

	peer_update_param = qdf_mem_malloc(sizeof(*peer_update_param));
	if (!peer_update_param) {
		tdls_err("memory allocation failed");
		status = QDF_STATUS_E_NOMEM;
		goto error;
	}

	tdls_extract_peer_state_param(peer_update_param, peer);

	status = tdls_wma_update_peer_state(soc_obj, peer_update_param);
	if (QDF_IS_STATUS_ERROR(status)) {
		qdf_mem_free(peer_update_param);
		status = QDF_STATUS_E_PERM;
		goto error;
	}

	tdls_increment_peer_count(soc_obj);
	feature = soc_obj->tdls_configs.tdls_feature_flags;

	if (soc_obj->tdls_dp_vdev_update)
		soc_obj->tdls_dp_vdev_update(&soc_obj->soc,
					peer->sta_id,
					soc_obj->tdls_update_dp_vdev_flags,
					((peer->link_status ==
					TDLS_LINK_CONNECTED) ? true : false));

	tdls_debug("TDLS buffer sta: %d, uapsd_mask %d",
		   TDLS_IS_BUFFER_STA_ENABLED(feature),
		   soc_obj->tdls_configs.tdls_uapsd_mask);

	if (TDLS_IS_BUFFER_STA_ENABLED(feature) ||
	    soc_obj->tdls_configs.tdls_uapsd_mask)
		tdls_update_uapsd(soc_obj->soc,
				  vdev, peer->sta_id, 0, 0, BI_DIR, 1,
				  soc_obj->tdls_configs.delayed_trig_framint);
error:
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_NB_ID);
	qdf_mem_free(req);

	return status;
}

/**
 * tdls_config_force_peer() - configure an externally controllable TDLS peer
 * @req: TDLS operation request
 *
 * This is not the tdls_process_cmd function. No need to acquire the reference
 * count, release reference count  and free the request, the caller handle it
 * correctly.
 *
 * Return: QDF_STATUS_SUCCESS if success; other values if failed
 */
static QDF_STATUS tdls_config_force_peer(
	struct tdls_oper_config_force_peer_request *req)
{
	struct tdls_peer *peer;
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct wlan_objmgr_pdev *pdev;
	struct wlan_objmgr_vdev *vdev;
	const uint8_t *macaddr;
	uint32_t feature;
	QDF_STATUS status;
	struct tdls_peer_update_state *peer_update_param;

	macaddr = req->peer_addr;
	tdls_debug("NL80211_TDLS_SETUP for " QDF_MAC_ADDR_STR,
		   QDF_MAC_ADDR_ARRAY(macaddr));

	vdev = req->vdev;
	pdev = wlan_vdev_get_pdev(vdev);
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(vdev);
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	if (!pdev || !vdev_obj || !soc_obj) {
		tdls_err("pdev: %pK, vdev_obj: %pK, soc_obj: %pK",
			 pdev, vdev_obj, soc_obj);
		return QDF_STATUS_E_INVAL;
	}

	feature = soc_obj->tdls_configs.tdls_feature_flags;
	if (!TDLS_IS_EXTERNAL_CONTROL_ENABLED(feature) ||
	    !TDLS_IS_IMPLICIT_TRIG_ENABLED(feature)) {
		tdls_err("TDLS ext ctrl or Imp Trig not enabled, %x", feature);
		return QDF_STATUS_E_NOSUPPORT;
	}

	peer_update_param = qdf_mem_malloc(sizeof(*peer_update_param));
	if (!peer_update_param) {
		tdls_err("memory allocation failed");
		return QDF_STATUS_E_NOMEM;
	}

	peer = tdls_get_peer(vdev_obj, macaddr);
	if (!peer) {
		tdls_err("peer " QDF_MAC_ADDR_STR " does not exist",
			 QDF_MAC_ADDR_ARRAY(macaddr));
		status = QDF_STATUS_E_NULL_VALUE;
		goto error;
	}
	status = tdls_set_force_peer(vdev_obj, macaddr, true);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("set force peer failed");
		goto error;
	}

	/* Update the peer mac to firmware, so firmware could update the
	 * connection table
	 */
	peer_update_param->vdev_id = wlan_vdev_get_id(vdev);
	qdf_mem_copy(peer_update_param->peer_macaddr,
		     macaddr, QDF_MAC_ADDR_SIZE);
	peer_update_param->peer_state = TDLS_PEER_ADD_MAC_ADDR;

	status = tdls_wma_update_peer_state(soc_obj, peer_update_param);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("update peer state failed");
		goto error;
	}

	soc_obj->tdls_external_peer_count++;

	/* Validate if off channel is DFS channel */
	if (wlan_reg_is_dfs_ch(pdev, req->chan)) {
		tdls_err("Resetting TDLS off-channel from %d to %d",
			 req->chan, WLAN_TDLS_PREFERRED_OFF_CHANNEL_NUM_DEF);
		req->chan = WLAN_TDLS_PREFERRED_OFF_CHANNEL_NUM_DEF;
	}
	tdls_set_extctrl_param(peer, req->chan, req->max_latency, req->op_class,
			       req->min_bandwidth);

	tdls_set_callback(peer, req->callback);

	tdls_set_ct_mode(soc_obj->soc);
	if (soc_obj->enable_tdls_connection_tracker)
		tdls_implicit_enable(vdev_obj);

	return status;
error:
	qdf_mem_free(peer_update_param);
	return status;
}

/**
 * tdls_process_setup_peer() - process configure an externally
 *                                    controllable TDLS peer
 * @req: TDLS operation request
 *
 * Return: QDF_STATUS_SUCCESS if success; other values if failed
 */
QDF_STATUS tdls_process_setup_peer(struct tdls_oper_request *req)
{
	struct tdls_oper_config_force_peer_request peer_req;
	struct tdls_soc_priv_obj *soc_obj;
	struct wlan_objmgr_vdev *vdev;
	QDF_STATUS status;

	tdls_debug("Configure external TDLS peer " QDF_MAC_ADDR_STR,
		   QDF_MAC_ADDR_ARRAY(req->peer_addr));

	/* reference cnt is acquired in ucfg_tdls_oper */
	vdev = req->vdev;
	if (!vdev) {
		tdls_err("NULL vdev object");
		status = QDF_STATUS_E_NULL_VALUE;
		goto freereq;
	}

	qdf_mem_zero(&peer_req, sizeof(peer_req));
	peer_req.vdev = vdev;
	qdf_mem_copy(peer_req.peer_addr, req->peer_addr, QDF_MAC_ADDR_SIZE);

	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	if (!soc_obj) {
		tdls_err("NULL soc object");
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	peer_req.chan = soc_obj->tdls_configs.tdls_pre_off_chan_num;

	status = tdls_config_force_peer(&peer_req);
error:
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_NB_ID);
freereq:
	qdf_mem_free(req);

	return status;
}

QDF_STATUS tdls_process_remove_force_peer(struct tdls_oper_request *req)
{
	struct tdls_peer *peer;
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct wlan_objmgr_vdev *vdev;
	const uint8_t *macaddr;
	uint32_t feature;
	QDF_STATUS status;
	struct tdls_peer_update_state *peer_update_param;
	struct tdls_osif_indication ind;

	macaddr = req->peer_addr;
	tdls_debug("NL80211_TDLS_TEARDOWN for " QDF_MAC_ADDR_STR,
		   QDF_MAC_ADDR_ARRAY(macaddr));

	vdev = req->vdev;
	if (!vdev) {
		tdls_err("NULL vdev object");
		qdf_mem_free(req);
		return QDF_STATUS_E_NULL_VALUE;
	}

	/* reference cnt is acquired in ucfg_tdls_oper */
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(req->vdev);
	soc_obj = wlan_vdev_get_tdls_soc_obj(req->vdev);
	if (!soc_obj || !vdev_obj) {
		tdls_err("soc_obj: %pK, vdev_obj: %pK", soc_obj, vdev_obj);
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	feature = soc_obj->tdls_configs.tdls_feature_flags;
	if (!TDLS_IS_EXTERNAL_CONTROL_ENABLED(feature) ||
	    !TDLS_IS_IMPLICIT_TRIG_ENABLED(feature)) {
		tdls_err("TDLS ext ctrl or Imp Trig not enabled, %x", feature);
		status = QDF_STATUS_E_NOSUPPORT;
		goto error;
	}

	peer = tdls_find_peer(vdev_obj, macaddr);
	if (!peer) {
		tdls_err("peer matching " QDF_MAC_ADDR_STR " not found",
			 QDF_MAC_ADDR_ARRAY(macaddr));
		status = QDF_STATUS_E_NULL_VALUE;
		goto error;
	}

	tdls_set_peer_link_status(peer, TDLS_LINK_TEARING,
				  TDLS_LINK_UNSPECIFIED);

	if (soc_obj->tdls_dp_vdev_update)
		soc_obj->tdls_dp_vdev_update(&soc_obj->soc,
				peer->sta_id,
				soc_obj->tdls_update_dp_vdev_flags,
				false);

	if (soc_obj->tdls_event_cb) {
		qdf_mem_copy(ind.peer_mac, macaddr, QDF_MAC_ADDR_SIZE);
		ind.vdev = vdev;
		ind.reason = TDLS_TEARDOWN_PEER_UNSPEC_REASON;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_TEARDOWN_REQ, &ind);
	}

	status = tdls_set_force_peer(vdev_obj, macaddr, false);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("set force peer failed");
		status = QDF_STATUS_E_INVAL;
		goto error;
	}

	if (soc_obj->tdls_external_peer_count)
		soc_obj->tdls_external_peer_count--;

	tdls_set_callback(peer, NULL);
	peer_update_param = qdf_mem_malloc(sizeof(*peer_update_param));
	if (!peer_update_param) {
		tdls_err("memory allocation failed");
		status = QDF_STATUS_E_NOMEM;
		goto error;
	}

	peer_update_param->vdev_id = wlan_vdev_get_id(vdev);
	qdf_mem_copy(peer_update_param->peer_macaddr,
		     macaddr, QDF_MAC_ADDR_SIZE);
	peer_update_param->peer_state = TDLS_PEER_REMOVE_MAC_ADDR;
	status = tdls_wma_update_peer_state(soc_obj, peer_update_param);
	if (QDF_IS_STATUS_ERROR(status)) {
		qdf_mem_free(peer_update_param);
		goto error;
	}
	tdls_set_ct_mode(soc_obj->soc);
	if (!soc_obj->enable_tdls_connection_tracker)
		tdls_implicit_disable(vdev_obj);

error:
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_NB_ID);
	qdf_mem_free(req);

	return status;
}

static const char *tdls_evt_to_str(enum tdls_event_msg_type type)
{
	switch (type) {
	case TDLS_SHOULD_DISCOVER:
		return "SHOULD_DISCOVER";
	case TDLS_SHOULD_TEARDOWN:
		return "SHOULD_TEARDOWN";
	case TDLS_PEER_DISCONNECTED:
		return "SHOULD_PEER_DISCONNECTED";
	case TDLS_CONNECTION_TRACKER_NOTIFY:
		return "CONNECTION_TRACKER_NOTIFICATION";
	default:
		return "INVALID_TYPE";
	}
}

QDF_STATUS tdls_process_should_discover(struct wlan_objmgr_vdev *vdev,
					struct tdls_event_info *evt)
{
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct tdls_peer *curr_peer;
	uint32_t feature;
	uint16_t type;

	/*TODO ignore this if any concurrency detected*/
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(vdev);
	type = evt->message_type;

	tdls_debug("TDLS %s: " QDF_MAC_ADDR_STR "reason %d",
		   tdls_evt_to_str(type),
		   QDF_MAC_ADDR_ARRAY(evt->peermac.bytes),
		   evt->peer_reason);
	if (!soc_obj || !vdev_obj) {
		tdls_err("soc_obj: %pK, vdev_obj: %pK, ignore %s",
			 soc_obj, vdev_obj, tdls_evt_to_str(type));
		return QDF_STATUS_E_NULL_VALUE;
	}
	if (soc_obj->tdls_nss_switch_in_progress) {
		tdls_err("TDLS antenna switching, ignore %s",
			 tdls_evt_to_str(type));
		return QDF_STATUS_SUCCESS;
	}

	curr_peer = tdls_get_peer(vdev_obj, evt->peermac.bytes);
	if (!curr_peer) {
		tdls_notice("curr_peer is null");
		return QDF_STATUS_E_NULL_VALUE;
	}

	if (TDLS_LINK_CONNECTED == curr_peer->link_status) {
		tdls_err("TDLS link status is connected, ignore");
		return QDF_STATUS_SUCCESS;
	}

	feature = soc_obj->tdls_configs.tdls_feature_flags;
	if (TDLS_IS_EXTERNAL_CONTROL_ENABLED(feature) &&
	    !curr_peer->is_forced_peer) {
		tdls_debug("curr_peer is not forced, ignore %s",
			   tdls_evt_to_str(type));
		return QDF_STATUS_SUCCESS;
	}

	tdls_debug("initiate TDLS setup on %s, ext: %d, force: %d, reason: %d",
		   tdls_evt_to_str(type),
		   TDLS_IS_EXTERNAL_CONTROL_ENABLED(feature),
		   curr_peer->is_forced_peer, evt->peer_reason);
	vdev_obj->curr_candidate = curr_peer;
	tdls_implicit_send_discovery_request(vdev_obj);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS tdls_process_should_teardown(struct wlan_objmgr_vdev *vdev,
					struct tdls_event_info *evt)
{
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct tdls_peer *curr_peer;
	uint32_t reason;
	uint16_t type;

	type = evt->message_type;
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(vdev);

	tdls_debug("TDLS %s: " QDF_MAC_ADDR_STR "reason %d",
		   tdls_evt_to_str(type),
		   QDF_MAC_ADDR_ARRAY(evt->peermac.bytes), evt->peer_reason);

	if (!soc_obj || !vdev_obj) {
		tdls_err("soc_obj: %pK, vdev_obj: %pK, ignore %s",
			 soc_obj, vdev_obj, tdls_evt_to_str(type));
		return QDF_STATUS_E_NULL_VALUE;
	}

	curr_peer = tdls_find_peer(vdev_obj, evt->peermac.bytes);
	if (!curr_peer) {
		tdls_notice("curr_peer is null");
		return QDF_STATUS_E_NULL_VALUE;
	}

	reason = evt->peer_reason;
	if (TDLS_LINK_CONNECTED == curr_peer->link_status) {
		tdls_err("%s reason: %d for" QDF_MAC_ADDR_STR,
			 tdls_evt_to_str(type), evt->peer_reason,
			 QDF_MAC_ADDR_ARRAY(evt->peermac.bytes));
		if (reason == TDLS_TEARDOWN_RSSI ||
		    reason == TDLS_DISCONNECTED_PEER_DELETE ||
		    reason == TDLS_TEARDOWN_PTR_TIMEOUT ||
		    reason == TDLS_TEARDOWN_NO_RSP)
			reason = TDLS_TEARDOWN_PEER_UNREACHABLE;
		else
			reason = TDLS_TEARDOWN_PEER_UNSPEC_REASON;

		tdls_indicate_teardown(vdev_obj, curr_peer, reason);
	} else {
		tdls_err("TDLS link is not connected, ignore %s",
			 tdls_evt_to_str(type));
	}

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS tdls_process_connection_tracker_notify(struct wlan_objmgr_vdev *vdev,
						  struct tdls_event_info *evt)
{
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_vdev_priv_obj *vdev_obj;
	uint16_t type;

	type = evt->message_type;
	soc_obj = wlan_vdev_get_tdls_soc_obj(vdev);
	vdev_obj = wlan_vdev_get_tdls_vdev_obj(vdev);

	if (!soc_obj || !vdev_obj) {
		tdls_err("soc_obj: %pK, vdev_obj: %pK, ignore %s",
			 soc_obj, vdev_obj, tdls_evt_to_str(type));
		return QDF_STATUS_E_NULL_VALUE;
	}

	/*TODO connection tracker update*/
	return QDF_STATUS_SUCCESS;
}

/**
 * tdls_process_set_responder() - Set/clear TDLS peer's responder role
 * @set_req: set responder request
 *
 * Return: 0 for success or -EINVAL otherwise
 */
static
int tdls_process_set_responder(struct tdls_set_responder_req *set_req)
{
	struct tdls_peer *curr_peer;
	struct tdls_vdev_priv_obj *tdls_vdev;

	tdls_vdev = wlan_vdev_get_tdls_vdev_obj(set_req->vdev);
	if (!tdls_vdev) {
		tdls_err("tdls vdev obj is NULL");
		return -EINVAL;
	}
	curr_peer = tdls_get_peer(tdls_vdev, set_req->peer_mac);
	if (curr_peer == NULL) {
		tdls_err("curr_peer is NULL");
		return -EINVAL;
	}

	curr_peer->is_responder = set_req->responder;
	return 0;
}


/**
 * tdls_set_responder() - Set/clear TDLS peer's responder role
 * @set_req: set responder request
 *
 * Return: 0 for success or -EINVAL otherwise
 */
int tdls_set_responder(struct tdls_set_responder_req *set_req)
{
	QDF_STATUS status;

	if (!set_req || !set_req->vdev) {
		tdls_err("Invalid input params %pK", set_req);
		return -EINVAL;
	}

	status = wlan_objmgr_vdev_try_get_ref(set_req->vdev, WLAN_TDLS_NB_ID);
	if (QDF_STATUS_SUCCESS != status) {
		tdls_err("vdev object is deleted");
		return -EINVAL;
	}

	status = tdls_process_set_responder(set_req);

	wlan_objmgr_vdev_release_ref(set_req->vdev, WLAN_TDLS_NB_ID);
	qdf_mem_free(set_req);
	return status;
}

static int tdls_teardown_links(struct tdls_soc_priv_obj *soc_obj, uint32_t mode)
{
	uint8_t staidx;
	struct tdls_peer *curr_peer;
	struct tdls_conn_info *conn_rec;
	int ret = 0;

	conn_rec = soc_obj->tdls_conn_info;
	for (staidx = 0; staidx < soc_obj->max_num_tdls_sta; staidx++) {
		if (conn_rec[staidx].sta_id == 0)
			continue;

		curr_peer = tdls_find_all_peer(soc_obj,
					       conn_rec[staidx].peer_mac.bytes);
		if (!curr_peer)
			continue;

		/* if supported only 1x1, skip it */
		if (curr_peer->spatial_streams == HW_MODE_SS_1x1)
			continue;

		tdls_debug("Indicate TDLS teardown (staId %d)",
			   curr_peer->sta_id);
		tdls_indicate_teardown(curr_peer->vdev_priv, curr_peer,
				       TDLS_TEARDOWN_PEER_UNSPEC_REASON);

		soc_obj->tdls_teardown_peers_cnt++;
	}

	if (soc_obj->tdls_teardown_peers_cnt >= 1) {
		soc_obj->tdls_nss_switch_in_progress = true;
		tdls_debug("TDLS peers to be torn down = %d",
			   soc_obj->tdls_teardown_peers_cnt);

		/* set the antenna switch transition mode */
		if (mode == HW_MODE_SS_1x1) {
			soc_obj->tdls_nss_transition_mode =
				TDLS_NSS_TRANSITION_S_2x2_to_1x1;
			ret = -EAGAIN;
		} else {
			soc_obj->tdls_nss_transition_mode =
				TDLS_NSS_TRANSITION_S_1x1_to_2x2;
			ret = 0;
		}
		tdls_debug("TDLS teardown for antenna switch operation starts");
	}

	return ret;
}

QDF_STATUS tdls_process_antenna_switch(struct tdls_antenna_switch_request *req)
{
	QDF_STATUS status;
	struct tdls_soc_priv_obj *soc_obj;
	struct tdls_vdev_priv_obj *vdev_obj;
	struct wlan_objmgr_vdev *vdev = NULL;
	uint32_t vdev_nss;
	int ant_switch_state = 0;
	uint32_t vdev_id;
	enum QDF_OPMODE opmode;
	uint8_t channel;
	struct tdls_osif_indication ind;

	if (!req) {
		tdls_err("null req");
		return QDF_STATUS_E_INVAL;
	}

	vdev = req->vdev;
	if (!vdev) {
		tdls_err("null vdev");
		qdf_mem_free(req);
		return QDF_STATUS_E_INVAL;
	}

	status = tdls_get_vdev_objects(vdev, &vdev_obj, &soc_obj);
	if (QDF_IS_STATUS_ERROR(status)) {
		tdls_err("can't get vdev_obj & soc_obj");
		goto get_obj_err;
	}

	if (soc_obj->connected_peer_count == 0)
		goto ant_sw_done;

	if (soc_obj->tdls_nss_switch_in_progress) {
		if (!soc_obj->tdls_nss_teardown_complete) {
			tdls_err("TDLS antenna switch is in progress");
			goto ant_sw_in_progress;
		} else {
			goto ant_sw_done;
		}
	}

	vdev_id = wlan_vdev_get_id(vdev);
	opmode = wlan_vdev_mlme_get_opmode(vdev);
	channel = policy_mgr_get_channel(soc_obj->soc,
			policy_mgr_convert_device_mode_to_qdf_type(opmode),
			&vdev_id);

	/* Check supported nss for TDLS, if is 1x1, no need to teardown links */
	if (WLAN_REG_IS_24GHZ_CH(channel))
		vdev_nss = soc_obj->tdls_configs.tdls_vdev_nss_2g;
	else
		vdev_nss = soc_obj->tdls_configs.tdls_vdev_nss_5g;

	if (vdev_nss == HW_MODE_SS_1x1) {
		tdls_debug("Supported NSS is 1x1, no need to teardown TDLS links");
		goto ant_sw_done;
	}

	if (tdls_teardown_links(soc_obj, req->mode) == 0)
		goto ant_sw_done;

ant_sw_in_progress:
	ant_switch_state = -EAGAIN;
ant_sw_done:
	if (soc_obj->tdls_event_cb) {
		ind.vdev = vdev;
		ind.status = ant_switch_state;
		soc_obj->tdls_event_cb(soc_obj->tdls_evt_cb_data,
				       TDLS_EVENT_ANTENNA_SWITCH, &ind);
	}

	if (soc_obj->tdls_nss_switch_in_progress &&
	    soc_obj->tdls_nss_teardown_complete) {
		soc_obj->tdls_nss_switch_in_progress = false;
		soc_obj->tdls_nss_teardown_complete = false;
	}
	tdls_debug("tdls_nss_switch_in_progress: %d tdls_nss_teardown_complete: %d",
		   soc_obj->tdls_nss_switch_in_progress,
		   soc_obj->tdls_nss_teardown_complete);

get_obj_err:
	wlan_objmgr_vdev_release_ref(vdev, WLAN_TDLS_NB_ID);
	qdf_mem_free(req);

	return status;
}

QDF_STATUS tdls_antenna_switch_flush_callback(struct scheduler_msg *msg)
{
	struct tdls_antenna_switch_request *req;

	if (!msg || !msg->bodyptr) {
		tdls_err("msg: 0x%pK", msg);
		return QDF_STATUS_E_NULL_VALUE;
	}
	req = msg->bodyptr;
	wlan_objmgr_vdev_release_ref(req->vdev, WLAN_TDLS_NB_ID);
	qdf_mem_free(req);

	return QDF_STATUS_SUCCESS;
}
