/*
 * Copyright (c) 2012-2017 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
 *
 *
 * 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.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */

/**
 * DOC: wlan_hdd_tdls.c
 *
 * WLAN Host Device Driver implementation for TDLS
 */

#include <wlan_hdd_includes.h>
#include <ani_global.h>
#include <wlan_hdd_hostapd.h>
#include <wlan_hdd_trace.h>
#include <net/cfg80211.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/list.h>
#include <linux/etherdevice.h>
#include <net/ieee80211_radiotap.h>
#include "wlan_hdd_tdls.h"
#include "wlan_hdd_cfg80211.h"
#include "wlan_hdd_assoc.h"
#include "sme_api.h"
#include "cds_sched.h"
#include "wma_types.h"
#include "wlan_policy_mgr_api.h"
#include <qca_vendor.h>
#include "wlan_hdd_ipa.h"

static int32_t wlan_hdd_tdls_peer_reset_discovery_processed(tdlsCtx_t *
							    pHddTdlsCtx);
static void wlan_hdd_tdls_timers_destroy(tdlsCtx_t *pHddTdlsCtx);
static void wlan_hdd_tdls_ct_handler(void *user_data);

/**
 * enum qca_wlan_vendor_tdls_trigger_mode_hdd_map: Maps the user space TDLS
 *	trigger mode in the host driver.
 * @WLAN_HDD_VENDOR_TDLS_TRIGGER_MODE_EXPLICIT: TDLS Connection and
 *	disconnection handled by user space.
 * @WLAN_HDD_VENDOR_TDLS_TRIGGER_MODE_IMPLICIT: TDLS connection and
 *	disconnection controlled by host driver based on data traffic.
 * @WLAN_HDD_VENDOR_TDLS_TRIGGER_MODE_EXTERNAL: TDLS connection and
 *	disconnection jointly controlled by user space and host driver.
 */
enum qca_wlan_vendor_tdls_trigger_mode_hdd_map {
	WLAN_HDD_VENDOR_TDLS_TRIGGER_MODE_EXPLICIT =
		QCA_WLAN_VENDOR_TDLS_TRIGGER_MODE_EXPLICIT,
	WLAN_HDD_VENDOR_TDLS_TRIGGER_MODE_IMPLICIT =
		QCA_WLAN_VENDOR_TDLS_TRIGGER_MODE_IMPLICIT,
	WLAN_HDD_VENDOR_TDLS_TRIGGER_MODE_EXTERNAL =
		((QCA_WLAN_VENDOR_TDLS_TRIGGER_MODE_EXPLICIT |
		  QCA_WLAN_VENDOR_TDLS_TRIGGER_MODE_IMPLICIT) << 1),
};

/*
 * wlan_hdd_tdls_determine_channel_opclass() - determine channel and opclass
 * @hddctx: pointer to hdd context
 * @adapter: pointer to adapter
 * @curr_peer: pointer to current tdls peer
 * @channel: pointer to channel
 * @opclass: pointer to opclass
 *
 * Function determines the channel and operating class
 *
 * Return: None
 */
static void wlan_hdd_tdls_determine_channel_opclass(hdd_context_t *hddctx,
			hdd_adapter_t *adapter, hddTdlsPeer_t *curr_peer,
			uint32_t *channel, uint32_t *opclass)
{
	hdd_station_ctx_t *hdd_sta_ctx;

	/*
	 * If tdls offchannel is not enabled then we provide base channel
	 * and in that case pass opclass as 0 since opclass is mainly needed
	 * for offchannel cases.
	 */
	if (!(hddctx->config->fEnableTDLSOffChannel) ||
		(hddctx->tdls_fw_off_chan_mode != ENABLE_CHANSWITCH)) {
		hdd_sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(adapter);
		*channel = hdd_sta_ctx->conn_info.operationChannel;
		*opclass = 0;
	} else {
		*channel = curr_peer->pref_off_chan_num;
		*opclass = curr_peer->op_class_for_pref_off_chan;
	}
	hdd_info("channel:%d opclass:%d", *channel, *opclass);
}

#ifdef FEATURE_WLAN_DIAG_SUPPORT
/**
 * hdd_send_wlan_tdls_teardown_event()- send TDLS teardown event
 * @reason: reason for tear down.
 * @peer_mac: peer mac
 *
 * This Function sends TDLS teardown diag event
 *
 * Return: void.
 */
void hdd_send_wlan_tdls_teardown_event(uint32_t reason,
					uint8_t *peer_mac)
{
	WLAN_HOST_DIAG_EVENT_DEF(tdls_tear_down,
		struct host_event_tdls_teardown);
	qdf_mem_zero(&tdls_tear_down,
			sizeof(tdls_tear_down));

	tdls_tear_down.reason = reason;
	qdf_mem_copy(tdls_tear_down.peer_mac, peer_mac, MAC_ADDR_LEN);
	WLAN_HOST_DIAG_EVENT_REPORT(&tdls_tear_down,
		EVENT_WLAN_TDLS_TEARDOWN);
}

/**
 * hdd_wlan_tdls_enable_link_event()- send TDLS enable link event
 * @peer_mac: peer mac
 * @is_off_chan_supported: Does peer supports off chan
 * @is_off_chan_configured: If off channel is configured
 * @is_off_chan_established: If off chan is established
 *
 * This Function send TDLS enable link diag event
 *
 * Return: void.
 */

void hdd_wlan_tdls_enable_link_event(const uint8_t *peer_mac,
				uint8_t is_off_chan_supported,
				uint8_t is_off_chan_configured,
				uint8_t is_off_chan_established)
{
	WLAN_HOST_DIAG_EVENT_DEF(tdls_event,
		struct host_event_tdls_enable_link);

	qdf_mem_copy(tdls_event.peer_mac,
			peer_mac, MAC_ADDR_LEN);

	tdls_event.is_off_chan_supported =
			is_off_chan_supported;
	tdls_event.is_off_chan_configured =
			is_off_chan_configured;
	tdls_event.is_off_chan_established =
			is_off_chan_established;

	WLAN_HOST_DIAG_EVENT_REPORT(&tdls_event,
		EVENT_WLAN_TDLS_ENABLE_LINK);
}

/**
 * hdd_wlan_block_scan_by_tdls_event()- send event
 * if scan is blocked by tdls
 *
 * This Function send send diag event if scan is
 * blocked by tdls
 *
 * Return: void.
 */
void hdd_wlan_block_scan_by_tdls_event(void)
{
	WLAN_HOST_DIAG_EVENT_DEF(tdls_scan_block_status,
		struct host_event_tdls_scan_rejected);

	tdls_scan_block_status.status = true;
	WLAN_HOST_DIAG_EVENT_REPORT(&tdls_scan_block_status,
		EVENT_TDLS_SCAN_BLOCK);
}

#endif

/**
 * wlan_hdd_tdls_hash_key() - calculate tdls hash key given mac address
 * @mac: mac address
 *
 * Return: hash key
 */
static u8 wlan_hdd_tdls_hash_key(const u8 *mac)
{
	int i;
	u8 key = 0;

	for (i = 0; i < 6; i++)
		key ^= mac[i];

	return key;
}

/**
 * wlan_hdd_tdls_disable_offchan_and_teardown_links - Disable offchannel
 * and teardown TDLS links
 * @hddCtx : pointer to hdd context
 *
 * Return: None
 */
void wlan_hdd_tdls_disable_offchan_and_teardown_links(hdd_context_t *hddctx)
{
	u16 connected_tdls_peers = 0;
	u8 staidx;
	hddTdlsPeer_t *curr_peer = NULL;
	hdd_adapter_t *adapter = NULL;

	if (hddctx->tdls_umac_comp_active)
		return;

	if (eTDLS_SUPPORT_NOT_ENABLED == hddctx->tdls_mode) {
		hdd_notice("TDLS mode is disabled OR not enabled in FW");
		return;
	}

	adapter = hdd_get_adapter(hddctx, QDF_STA_MODE);

	if (adapter == NULL) {
		hdd_debug("Station Adapter Not Found");
		return;
	}

	connected_tdls_peers = wlan_hdd_tdls_connected_peers(adapter);

	if (!connected_tdls_peers) {
		hdd_debug("No TDLS connected peers to delete");
		return;
	}

	/* TDLS is not supported in case of concurrency.
	 * Disable TDLS Offchannel in FW to avoid more
	 * than two concurrent channels and generate TDLS
	 * teardown indication to supplicant.
	 * Below function Finds the first connected peer and
	 * disables TDLS offchannel for that peer.
	 * FW enables TDLS offchannel only when there is
	 * one TDLS peer. When there are more than one TDLS peer,
	 * there will not be TDLS offchannel in FW.
	 * So to avoid sending multiple request to FW, for now,
	 * just invoke offchannel mode functions only once
	 */
	hdd_set_tdls_offchannel(hddctx, hddctx->config->fTDLSPrefOffChanNum);
	hdd_set_tdls_secoffchanneloffset(hddctx,
			TDLS_SEC_OFFCHAN_OFFSET_40PLUS);
	hdd_set_tdls_offchannelmode(adapter, DISABLE_CHANSWITCH);

	/* Send Msg to PE for deleting all the TDLS peers */
	sme_delete_all_tdls_peers(hddctx->hHal, adapter->sessionId);

	for (staidx = 0; staidx < hddctx->max_num_tdls_sta;
							staidx++) {
		if (!hddctx->tdlsConnInfo[staidx].staId)
			continue;

		mutex_lock(&hddctx->tdls_lock);
		curr_peer = wlan_hdd_tdls_find_all_peer(hddctx,
				hddctx->tdlsConnInfo[staidx].peerMac.bytes);
		if (!curr_peer) {
			mutex_unlock(&hddctx->tdls_lock);
			continue;
		}

		hdd_notice("indicate TDLS teardown (staId %d)",
			   curr_peer->staId);

		/* Indicate teardown to supplicant */
		wlan_hdd_tdls_indicate_teardown(
					curr_peer->pHddTdlsCtx->pAdapter,
					curr_peer,
					eSIR_MAC_TDLS_TEARDOWN_UNSPEC_REASON);

		/*
		 * Del Sta happened already as part of sme_delete_all_tdls_peers
		 * Hence clear hdd data structure.
		 */
		wlan_hdd_tdls_reset_peer(adapter, curr_peer->peerMac);
		hdd_send_wlan_tdls_teardown_event(eTDLS_TEARDOWN_CONCURRENCY,
			curr_peer->peerMac);
		mutex_unlock(&hddctx->tdls_lock);

		hdd_roam_deregister_tdlssta(adapter,
			hddctx->tdlsConnInfo[staidx].staId);
		wlan_hdd_tdls_decrement_peer_count(adapter);
		hddctx->tdlsConnInfo[staidx].staId =
						OL_TXRX_INVALID_TDLS_PEER_ID;
		hddctx->tdlsConnInfo[staidx].sessionId = 255;

		qdf_mem_zero(&hddctx->tdlsConnInfo[staidx].peerMac,
			     sizeof(struct qdf_mac_addr));
	}
}

/**
 * hdd_update_tdls_ct_and_teardown_links - Update TDLS connection tracker and
 * teardown links.
 * @hdd_ctx : pointer to hdd context
 *
 * Return: None
 */
void hdd_update_tdls_ct_and_teardown_links(hdd_context_t *hdd_ctx)
{
	/* set tdls connection tracker state */
	cds_set_tdls_ct_mode(hdd_ctx);
	wlan_hdd_tdls_disable_offchan_and_teardown_links(hdd_ctx);
}

/**
 * wlan_hdd_tdls_discovery_sent_cnt() - get value of discovery counter sent
 * @pHddCtx: HDD context
 *
 * Return: the value of the transmitted TDLS discovery counter
 */
static uint32_t wlan_hdd_tdls_discovery_sent_cnt(hdd_context_t *pHddCtx)
{
	hdd_adapter_list_node_t *pAdapterNode = NULL, *pNext = NULL;
	hdd_adapter_t *pAdapter = NULL;
	tdlsCtx_t *pHddTdlsCtx = NULL;
	QDF_STATUS status = 0;
	uint32_t count = 0;

	status = hdd_get_front_adapter(pHddCtx, &pAdapterNode);
	while (NULL != pAdapterNode && QDF_STATUS_SUCCESS == status) {
		pAdapter = pAdapterNode->pAdapter;

		pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
		if (NULL != pHddTdlsCtx)
			count = count + pHddTdlsCtx->discovery_sent_cnt;

		status = hdd_get_next_adapter(pHddCtx, pAdapterNode, &pNext);
		pAdapterNode = pNext;
	}
	return count;
}

/**
 * wlan_hdd_tdls_check_power_save_prohibited() - set/clear proper TDLS power
 *                                               save probihited bit
 * @pAdapter: HDD adapter handle
 *
 * Ensure TDLS power save probihited bit is set/cleared properly
 *
 * Return: None
 */
static void wlan_hdd_tdls_check_power_save_prohibited(hdd_adapter_t *pAdapter)
{
	tdlsCtx_t *pHddTdlsCtx;
	hdd_context_t *pHddCtx;

	if ((NULL == pAdapter) ||
	    (WLAN_HDD_ADAPTER_MAGIC != pAdapter->magic)) {
		hdd_err("invalid pAdapter: %p", pAdapter);
		return;
	}

	pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
	pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if ((NULL == pHddTdlsCtx) || (NULL == pHddCtx)) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("pHddCtx or pHddTdlsCtx points to NULL"));
		return;
	}

	if ((0 == pHddCtx->connected_peer_count) &&
	    (0 == wlan_hdd_tdls_discovery_sent_cnt(pHddCtx))) {
		sme_set_tdls_power_save_prohibited(WLAN_HDD_GET_HAL_CTX
							(pHddTdlsCtx->pAdapter),
						   pAdapter->sessionId, 0);
		return;
	}
	sme_set_tdls_power_save_prohibited(WLAN_HDD_GET_HAL_CTX
						   (pHddTdlsCtx->pAdapter),
					   pAdapter->sessionId, 1);
}

/**
 * wlan_hdd_tdls_free_scan_request() - free tdls scan request
 * @tdls_scan_ctx: tdls scan context
 *
 * Return: None
 */
static void wlan_hdd_tdls_free_scan_request(tdls_scan_context_t *tdls_scan_ctx)
{
	if (NULL == tdls_scan_ctx) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("tdls_scan_ctx is NULL"));
		return;
	}

	tdls_scan_ctx->attempt = 0;
	tdls_scan_ctx->reject = 0;
	tdls_scan_ctx->magic = 0;
	tdls_scan_ctx->scan_request = NULL;
}

/**
 * wlan_hdd_tdls_discovery_timeout_peer_cb() - tdls discovery timeout callback
 * @userData: tdls context
 *
 * Return: None
 */
static void wlan_hdd_tdls_discovery_timeout_peer_cb(void *userData)
{
	int i;
	struct list_head *head;
	hddTdlsPeer_t *tmp;
	struct list_head *pos, *q;
	tdlsCtx_t *pHddTdlsCtx;
	hdd_context_t *pHddCtx;
	v_CONTEXT_t cds_context;

	ENTER();

	cds_context = cds_get_global_context();
	if (NULL == cds_context) {
		hdd_err("cds_context points to NULL");
		return;
	}

	pHddCtx = cds_get_context(QDF_MODULE_ID_HDD);
	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return;

	mutex_lock(&pHddCtx->tdls_lock);
	pHddTdlsCtx = (tdlsCtx_t *) userData;

	if ((NULL == pHddTdlsCtx) || (NULL == pHddTdlsCtx->pAdapter)) {
		mutex_unlock(&pHddCtx->tdls_lock);
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("pHddTdlsCtx or pAdapter points to NULL"));
		return;
	}

	if (WLAN_HDD_ADAPTER_MAGIC != pHddTdlsCtx->pAdapter->magic) {
		mutex_unlock(&pHddCtx->tdls_lock);
		hdd_err("pAdapter has invalid magic");
		return;
	}

	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &pHddTdlsCtx->peer_list[i];
		list_for_each_safe(pos, q, head) {
			tmp = list_entry(pos, hddTdlsPeer_t, node);
			if (eTDLS_LINK_DISCOVERING == tmp->link_status) {
				hdd_notice(MAC_ADDRESS_STR " to idle state",
					   MAC_ADDR_ARRAY(tmp->peerMac));
				wlan_hdd_tdls_set_peer_link_status(tmp,
						eTDLS_LINK_IDLE,
						eTDLS_LINK_NOT_SUPPORTED);
			}
		}
	}

	pHddTdlsCtx->discovery_sent_cnt = 0;
	wlan_hdd_tdls_check_power_save_prohibited(pHddTdlsCtx->pAdapter);

	mutex_unlock(&pHddCtx->tdls_lock);
	EXIT();
}

/**
 * wlan_hdd_tdls_free_list() - free TDLS peer list
 * @pHddTdlsCtx: TDLS context
 *
 * Return: None
 */
static void wlan_hdd_tdls_free_list(tdlsCtx_t *pHddTdlsCtx)
{
	int i;
	struct list_head *head;
	hddTdlsPeer_t *tmp;
	struct list_head *pos, *q;

	if (NULL == pHddTdlsCtx) {
		hdd_notice("pHddTdlsCtx is NULL");
		return;
	}
	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &pHddTdlsCtx->peer_list[i];
		list_for_each_safe(pos, q, head) {
			tmp = list_entry(pos, hddTdlsPeer_t, node);
			list_del(pos);
			qdf_mem_free(tmp);
			tmp = NULL;
		}
	}
}

/**
 * wlan_hdd_tdls_schedule_scan() - schedule scan for tdls
 * @work: work_struct used to find tdls scan context
 *
 * Return: None
 */
static void wlan_hdd_tdls_schedule_scan(struct work_struct *work)
{
	tdls_scan_context_t *scan_ctx =
		container_of(work, tdls_scan_context_t, tdls_scan_work.work);

	if (NULL == scan_ctx) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("scan_ctx is NULL"));
		return;
	}

	if (unlikely(TDLS_CTX_MAGIC != scan_ctx->magic))
		return;

	scan_ctx->attempt++;

	wlan_hdd_cfg80211_tdls_scan(scan_ctx->wiphy,
			       scan_ctx->scan_request, scan_ctx->source);
}

/**
 * dump_tdls_state_param_setting() - print tdls state & parameters to send to fw
 * @info: tdls setting to be sent to fw
 *
 * Return: void
 */
static void dump_tdls_state_param_setting(tdlsInfo_t *info)
{
	if (!info)
		return;

	hdd_debug("Setting tdls state and param in fw: vdev_id: %d, tdls_state: %d, notification_interval_ms: %d, tx_discovery_threshold: %d, tx_teardown_threshold: %d, rssi_teardown_threshold: %d, rssi_delta: %d, tdls_options: 0x%x, peer_traffic_ind_window: %d, peer_traffic_response_timeout: %d, puapsd_mask: 0x%x, puapsd_inactivity_time: %d, puapsd_rx_frame_threshold: %d, teardown_notification_ms: %d, tdls_peer_kickout_threshold: %d",
		   info->vdev_id,
		   info->tdls_state,
		   info->notification_interval_ms,
		   info->tx_discovery_threshold,
		   info->tx_teardown_threshold,
		   info->rssi_teardown_threshold,
		   info->rssi_delta,
		   info->tdls_options,
		   info->peer_traffic_ind_window,
		   info->peer_traffic_response_timeout,
		   info->puapsd_mask,
		   info->puapsd_inactivity_time,
		   info->puapsd_rx_frame_threshold,
		   info->teardown_notification_ms,
		   info->tdls_peer_kickout_threshold);

}


/**
 * wlan_hdd_tdls_monitor_timers_stop() - stop all monitoring timers
 * @hdd_tdls_ctx: TDLS context
 *
 * Return: none
 */
static void wlan_hdd_tdls_monitor_timers_stop(tdlsCtx_t *hdd_tdls_ctx)
{
	qdf_mc_timer_stop(&hdd_tdls_ctx->peerDiscoveryTimeoutTimer);
}

/**
 * wlan_hdd_tdls_peer_idle_timers_stop() - stop peer idle timers
 * @hdd_tdls_ctx: TDLS context
 *
 * Loop through the idle peer list and stop their timers
 *
 * Return: None
 */
static void wlan_hdd_tdls_peer_idle_timers_stop(tdlsCtx_t *hdd_tdls_ctx)
{
	int i;
	struct list_head *head;
	struct list_head *pos;
	hddTdlsPeer_t *curr_peer;

	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &hdd_tdls_ctx->peer_list[i];
		list_for_each(pos, head) {
			curr_peer = list_entry(pos, hddTdlsPeer_t, node);
			if (curr_peer->is_peer_idle_timer_initialised)
				qdf_mc_timer_stop(&curr_peer->peer_idle_timer);
		}
	}
}

/**
 * wlan_hdd_tdls_ct_timers_stop() - stop tdls connection tracker timers
 * @hdd_tdls_ctx: TDLS context
 *
 * Return: None
 */
static void wlan_hdd_tdls_ct_timers_stop(tdlsCtx_t *hdd_tdls_ctx)
{
	qdf_mc_timer_stop(&hdd_tdls_ctx->peer_update_timer);
	wlan_hdd_tdls_peer_idle_timers_stop(hdd_tdls_ctx);
}

/**
 * wlan_hdd_tdls_timers_stop() - stop all the tdls timers running
 * @hdd_tdls_ctx: TDLS context
 *
 * Return: none
 */
void wlan_hdd_tdls_timers_stop(tdlsCtx_t *hdd_tdls_ctx)
{
	wlan_hdd_tdls_monitor_timers_stop(hdd_tdls_ctx);
	wlan_hdd_tdls_ct_timers_stop(hdd_tdls_ctx);
}

/**
 * wlan_hdd_tdls_del_non_forced_peers() - delete non forced tdls peers
 * @hdd_tdls_ctx: TDLS context
 *
 * Return: none
 */
static void wlan_hdd_tdls_del_non_forced_peers(tdlsCtx_t *hdd_tdls_ctx)
{
	struct list_head *head, *pos, *q;
	hddTdlsPeer_t *peer = NULL;
	int i;

	/* remove entries from peer list only if peer is not forced */
	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &hdd_tdls_ctx->peer_list[i];
		list_for_each_safe(pos, q, head) {
			peer = list_entry(pos, hddTdlsPeer_t, node);
			if (false == peer->isForcedPeer) {
				list_del(pos);
				qdf_mem_free(peer);
			} else {
				peer->link_status = eTDLS_LINK_IDLE;
				peer->reason = eTDLS_LINK_UNSPECIFIED;
				peer->staId = OL_TXRX_INVALID_TDLS_PEER_ID;
				peer->discovery_attempt = 0;
			}
		}
	}
}

/**
 * hdd_tdls_context_init() - Init TDLS context
 * @hdd_ctx:	HDD context
 * @ssr:	SSR case
 *
 * Initialize TDLS global context.
 *
 * Return: None
 */
void hdd_tdls_context_init(hdd_context_t *hdd_ctx, bool ssr)
{
	uint8_t sta_idx;

	if (!ssr) {
		mutex_init(&hdd_ctx->tdls_lock);
		qdf_spinlock_create(&hdd_ctx->tdls_ct_spinlock);
	}

	/* initialize TDLS global context */
	hdd_ctx->connected_peer_count = 0;
	hdd_ctx->tdls_nss_switch_in_progress = false;
	hdd_ctx->tdls_teardown_peers_cnt = 0;
	hdd_ctx->tdls_scan_ctxt.magic = 0;
	hdd_ctx->tdls_scan_ctxt.attempt = 0;
	hdd_ctx->tdls_scan_ctxt.reject = 0;
	hdd_ctx->tdls_scan_ctxt.source = 0;
	hdd_ctx->tdls_scan_ctxt.scan_request = NULL;
	hdd_ctx->set_state_info.set_state_cnt = 0;
	hdd_ctx->set_state_info.vdev_id = 0;
	hdd_ctx->tdls_nss_teardown_complete = false;
	hdd_ctx->tdls_nss_transition_mode = TDLS_NSS_TRANSITION_UNKNOWN;

	if (false == hdd_ctx->config->fEnableTDLSImplicitTrigger) {
		hdd_ctx->tdls_mode = eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY;
		hdd_notice("TDLS Implicit trigger not enabled!");
	} else if (true == hdd_ctx->config->fTDLSExternalControl) {
		hdd_ctx->tdls_mode = eTDLS_SUPPORT_EXTERNAL_CONTROL;
	} else {
		hdd_ctx->tdls_mode = eTDLS_SUPPORT_ENABLED;
	}

	hdd_ctx->tdls_mode_last = hdd_ctx->tdls_mode;

	if (hdd_ctx->config->fEnableTDLSSleepSta ||
	    hdd_ctx->config->fEnableTDLSBufferSta ||
	    hdd_ctx->config->fEnableTDLSOffChannel)
		hdd_ctx->max_num_tdls_sta = HDD_MAX_NUM_TDLS_STA_P_UAPSD_OFFCHAN;
	else
		hdd_ctx->max_num_tdls_sta = HDD_MAX_NUM_TDLS_STA;

	hdd_notice("max_num_tdls_sta: %d", hdd_ctx->max_num_tdls_sta);

	for (sta_idx = 0; sta_idx < hdd_ctx->max_num_tdls_sta; sta_idx++) {
		hdd_ctx->tdlsConnInfo[sta_idx].staId = 0;
		hdd_ctx->tdlsConnInfo[sta_idx].sessionId = 255;
		qdf_mem_zero(&hdd_ctx->tdlsConnInfo[sta_idx].peerMac,
			     QDF_MAC_ADDR_SIZE);
	}

	/* Don't reset TDLS external peer count for SSR case */
	if (!ssr)
		hdd_ctx->tdls_external_peer_count = 0;

	/* This flag will set  be true, only when device operates in
	 * standalone STA mode
	 */
	hdd_ctx->enable_tdls_connection_tracker = false;
	hdd_info("hdd_ctx->enable_tdls_connection_tracker: 0");
}

/**
 * hdd_tdls_context_destroy() - Destroy TDLS context
 * @hdd_ctx:	HDD context
 *
 * Destroy TDLS global context.
 *
 * Return: None
 */
void hdd_tdls_context_destroy(hdd_context_t *hdd_ctx)
{
	hdd_ctx->tdls_external_peer_count = 0;
	hdd_ctx->enable_tdls_connection_tracker = false;
	hdd_info("hdd_ctx->enable_tdls_connection_tracker: 0");
	mutex_destroy(&hdd_ctx->tdls_lock);
	qdf_spinlock_destroy(&hdd_ctx->tdls_ct_spinlock);
}

/**
 * wlan_hdd_tdls_init() - tdls initializaiton
 * @pAdapter: hdd adapter
 *
 * Return: 0 for success or negative errno otherwise
 */
int wlan_hdd_tdls_init(hdd_adapter_t *pAdapter)
{
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	tdlsCtx_t *pHddTdlsCtx;
	int i;

	if (NULL == pHddCtx)
		return -EINVAL;

	ENTER();

	if (pHddCtx->tdls_umac_comp_active)
		return 0;

	mutex_lock(&pHddCtx->tdls_lock);

	if (false == pHddCtx->config->fEnableTDLSSupport) {
		pHddCtx->tdls_mode = eTDLS_SUPPORT_NOT_ENABLED;
		pAdapter->sessionCtx.station.pHddTdlsCtx = NULL;
		mutex_unlock(&pHddCtx->tdls_lock);
		hdd_err("TDLS not enabled (%d) or FW doesn't support",
			pHddCtx->config->fEnableTDLSSupport);
		return 0;
	}
	/* TDLS is supported only in STA / P2P Client modes,
	 * hence the check for TDLS support in a specific Device mode.
	 * Do not return a failure rather do not continue further
	 * with the initialization as tdls_init would be called
	 * during the open adapter for a p2p interface at which point
	 * the device mode would be a P2P_DEVICE. The point here is to
	 * continue initialization for STA / P2P Client modes.
	 * TDLS exit also check for the device mode for clean up hence
	 * there is no issue even if success is returned.
	 */
	if (0 == WLAN_HDD_IS_TDLS_SUPPORTED_ADAPTER(pAdapter)) {
		mutex_unlock(&pHddCtx->tdls_lock);
		/* Check whether connection tracker can be enabled in
		 * the system.
		 */
		if (pAdapter->device_mode == QDF_P2P_DEVICE_MODE)
			cds_set_tdls_ct_mode(pHddCtx);
		return 0;
	}
	/* Check for the valid pHddTdlsCtx. If valid do not further
	 * allocate the memory, rather continue with the initialization.
	 * If tdls_initialization would get reinvoked  without tdls_exit
	 * getting invoked (SSR) there is no point to further proceed
	 * with the memory allocations.
	 */
	if (NULL == pAdapter->sessionCtx.station.pHddTdlsCtx) {
		pHddTdlsCtx = qdf_mem_malloc(sizeof(tdlsCtx_t));

		if (NULL == pHddTdlsCtx) {
			pAdapter->sessionCtx.station.pHddTdlsCtx = NULL;
			mutex_unlock(&pHddCtx->tdls_lock);
			hdd_err("malloc failed!");
			return -ENOMEM;
		}

		/* Initialize connection tracker timer */
		qdf_mc_timer_init(&pHddTdlsCtx->peer_update_timer,
				  QDF_TIMER_TYPE_SW,
				  wlan_hdd_tdls_ct_handler,
				  pAdapter);
		qdf_mc_timer_init(&pHddTdlsCtx->peerDiscoveryTimeoutTimer,
				  QDF_TIMER_TYPE_SW,
				  wlan_hdd_tdls_discovery_timeout_peer_cb,
				  pHddTdlsCtx);

		pAdapter->sessionCtx.station.pHddTdlsCtx = pHddTdlsCtx;
		for (i = 0; i < TDLS_PEER_LIST_SIZE; i++)
			INIT_LIST_HEAD(&pHddTdlsCtx->peer_list[i]);
	} else {
		pHddTdlsCtx = pAdapter->sessionCtx.station.pHddTdlsCtx;

		wlan_hdd_tdls_timers_stop(pHddTdlsCtx);

		wlan_hdd_tdls_del_non_forced_peers(pHddTdlsCtx);

		hdd_tdls_context_init(pHddCtx, true);
	}

	sme_set_tdls_power_save_prohibited(WLAN_HDD_GET_HAL_CTX(pAdapter),
					   pAdapter->sessionId, 0);

	pHddTdlsCtx->pAdapter = pAdapter;

	pHddTdlsCtx->curr_candidate = NULL;
	pHddTdlsCtx->magic = 0;
	pHddCtx->valid_mac_entries = 0;
	pHddTdlsCtx->last_flush_ts = 0;

	pHddTdlsCtx->threshold_config.tx_period_t =
		pHddCtx->config->fTDLSTxStatsPeriod;
	pHddTdlsCtx->threshold_config.tx_packet_n =
		pHddCtx->config->fTDLSTxPacketThreshold;
	pHddTdlsCtx->threshold_config.discovery_tries_n =
		pHddCtx->config->fTDLSMaxDiscoveryAttempt;
	pHddTdlsCtx->threshold_config.idle_timeout_t =
		pHddCtx->config->tdls_idle_timeout;
	pHddTdlsCtx->threshold_config.idle_packet_n =
		pHddCtx->config->fTDLSIdlePacketThreshold;
	pHddTdlsCtx->threshold_config.rssi_trigger_threshold =
		pHddCtx->config->fTDLSRSSITriggerThreshold;
	pHddTdlsCtx->threshold_config.rssi_teardown_threshold =
		pHddCtx->config->fTDLSRSSITeardownThreshold;
	pHddTdlsCtx->threshold_config.rssi_delta =
		pHddCtx->config->fTDLSRSSIDelta;

	INIT_DELAYED_WORK(&pHddCtx->tdls_scan_ctxt.tdls_scan_work,
			  wlan_hdd_tdls_schedule_scan);

	mutex_unlock(&pHddCtx->tdls_lock);

	if (pHddCtx->config->fEnableTDLSOffChannel)
		pHddCtx->tdls_fw_off_chan_mode = ENABLE_CHANSWITCH;

	EXIT();
	return 0;
}

/**
 * wlan_hdd_tdls_exit() - TDLS de-initialization
 * @pAdapter: HDD adapter
 *
 * Return: None
 */
void wlan_hdd_tdls_exit(hdd_adapter_t *pAdapter)
{
	tdlsCtx_t *pHddTdlsCtx;
	hdd_context_t *pHddCtx;

	ENTER();
	pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	if (!pHddCtx) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_WARN,
			  FL("pHddCtx is NULL"));
		return;
	}

	if (!test_bit(TDLS_INIT_DONE, &pAdapter->event_flags)) {
		hdd_info("TDLS init was not done, exit");
		return;
	}

	cds_flush_delayed_work(&pHddCtx->tdls_scan_ctxt.tdls_scan_work);

	mutex_lock(&pHddCtx->tdls_lock);

	pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
	if (NULL == pHddTdlsCtx) {
		/*
		 * TDLS context can be null and might have been freed up during
		 * cleanup for STA adapter
		 */
		mutex_unlock(&pHddCtx->tdls_lock);

		hdd_info("pHddTdlsCtx is NULL, adapter device mode: %s(%d)",
			 hdd_device_mode_to_string(pAdapter->device_mode),
			 pAdapter->device_mode);
		goto done;
	}

	/*
	 * must stop timer here before freeing peer list, because
	 * peerIdleTimer is part of peer list structure.
	 */
	wlan_hdd_tdls_timers_destroy(pHddTdlsCtx);
	wlan_hdd_tdls_free_list(pHddTdlsCtx);

	wlan_hdd_tdls_free_scan_request(&pHddCtx->tdls_scan_ctxt);

	pHddTdlsCtx->magic = 0;
	pHddTdlsCtx->pAdapter = NULL;
	pAdapter->sessionCtx.station.pHddTdlsCtx = NULL;

	mutex_unlock(&pHddCtx->tdls_lock);

	qdf_mem_free(pHddTdlsCtx);
done:
	EXIT();
	clear_bit(TDLS_INIT_DONE, &pAdapter->event_flags);
}

/**
 * wlan_hdd_tdls_peer_idle_timers_destroy() - destroy peer idle timers
 * @hdd_tdls_ctx: TDLS context
 *
 * Loop through the idle peer list and destroy their timers
 *
 * Return: None
 */
static void wlan_hdd_tdls_peer_idle_timers_destroy(tdlsCtx_t *hdd_tdls_ctx)
{
	int i;
	struct list_head *head;
	struct list_head *pos;
	hddTdlsPeer_t *curr_peer;

	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &hdd_tdls_ctx->peer_list[i];
		list_for_each(pos, head) {
			curr_peer = list_entry(pos, hddTdlsPeer_t, node);
			if (curr_peer != NULL &&
			    curr_peer->is_peer_idle_timer_initialised) {
				hdd_info(MAC_ADDRESS_STR ": destroy idle timer",
					 MAC_ADDR_ARRAY(curr_peer->peerMac));
				qdf_mc_timer_stop(&curr_peer->peer_idle_timer);
				qdf_mc_timer_destroy(&curr_peer->peer_idle_timer);
			}
		}
	}
}

/**
 * wlan_hdd_tdls_ct_timers_destroy() - destroy tdls connection tracker timers
 * @hdd_tdls_ctx: TDLS context
 *
 * Return: None
 */
static void wlan_hdd_tdls_ct_timers_destroy(tdlsCtx_t *hdd_tdls_ctx)
{
	qdf_mc_timer_stop(&hdd_tdls_ctx->peer_update_timer);
	qdf_mc_timer_destroy(&hdd_tdls_ctx->peer_update_timer);
	wlan_hdd_tdls_peer_idle_timers_destroy(hdd_tdls_ctx);
}

/**
 * wlan_hdd_tdls_monitor_timers_destroy() - destroy all tdls monitoring timers
 * @pHddTdlsCtx: TDLS context
 *
 * Return: Void
 */
static void wlan_hdd_tdls_monitor_timers_destroy(tdlsCtx_t *pHddTdlsCtx)
{
	qdf_mc_timer_stop(&pHddTdlsCtx->peerDiscoveryTimeoutTimer);
	qdf_mc_timer_destroy(&pHddTdlsCtx->peerDiscoveryTimeoutTimer);
}

/**
 * wlan_hdd_tdls_timers_destroy() - Destroy all the tdls timers running
 * @pHddTdlsCtx: TDLS Context
 *
 * Return: Void
 */
static void wlan_hdd_tdls_timers_destroy(tdlsCtx_t *pHddTdlsCtx)
{
	wlan_hdd_tdls_monitor_timers_destroy(pHddTdlsCtx);
	wlan_hdd_tdls_ct_timers_destroy(pHddTdlsCtx);
}

/**
 * wlan_hdd_tdls_get_peer() - find or add an peer given mac address
 * @pAdapter: HDD adapter
 * @mac: MAC address used to find or create peer
 *
 * Search peer given an MAC address and create one if not found.
 *
 * Return: Pointer to peer if mac address exist or peer creation
 *         succeeds; NULL if peer creation fails
 */
hddTdlsPeer_t *wlan_hdd_tdls_get_peer(hdd_adapter_t *pAdapter, const u8 *mac)
{
	struct list_head *head;
	hddTdlsPeer_t *peer;
	u8 key;
	tdlsCtx_t *pHddTdlsCtx;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return NULL;

	/* if already there, just update */
	peer = wlan_hdd_tdls_find_peer(pAdapter, mac);
	if (peer != NULL)
		return peer;

	/* not found, allocate and add the list */
	peer = qdf_mem_malloc(sizeof(hddTdlsPeer_t));
	if (NULL == peer) {
		hdd_err("peer malloc failed!");
		return NULL;
	}

	peer->staId = 0xff;
	pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);

	if (NULL == pHddTdlsCtx) {
		qdf_mem_free(peer);
		hdd_notice("pHddTdlsCtx is NULL");
		return NULL;
	}

	key = wlan_hdd_tdls_hash_key(mac);
	head = &pHddTdlsCtx->peer_list[key];

	qdf_mem_copy(peer->peerMac, mac, sizeof(peer->peerMac));
	peer->pHddTdlsCtx = pHddTdlsCtx;
	peer->pref_off_chan_num = pHddCtx->config->fTDLSPrefOffChanNum;
	peer->op_class_for_pref_off_chan =
		wlan_hdd_find_opclass(pHddCtx->hHal, peer->pref_off_chan_num,
				pHddCtx->config->fTDLSPrefOffChanBandwidth);

	list_add_tail(&peer->node, head);

	return peer;
}

/**
 * wlan_hdd_tdls_set_cap() - set TDLS capability type
 * @pAdapter: HDD adapter
 * @mac: peer mac address
 * @cap: TDLS capability type
 *
 * Return: 0 if successful or negative errno otherwise
 */
int wlan_hdd_tdls_set_cap(hdd_adapter_t *pAdapter, const uint8_t *mac,
			  enum tdls_cap_type cap)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *hdd_ctx;
	int status = 0;

	hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);
	if (0 != (wlan_hdd_validate_context(hdd_ctx))) {
		status = -EINVAL;
		goto ret_status;
	}

	mutex_lock(&hdd_ctx->tdls_lock);
	curr_peer = wlan_hdd_tdls_get_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		hdd_err("curr_peer is NULL");
		status = -EINVAL;
		goto rel_lock;
	}

	curr_peer->tdls_support = cap;
rel_lock:
	mutex_unlock(&hdd_ctx->tdls_lock);
ret_status:
	return status;
}

/**
 * wlan_hdd_tdls_set_peer_link_status() - set TDLS peer link status
 * @curr_peer: peer
 * @status: status
 * @reason: reason
 *
 * Return: Void
 */
void wlan_hdd_tdls_set_peer_link_status(hddTdlsPeer_t *curr_peer,
					enum tdls_link_status status,
					enum tdls_link_reason reason)
{
	uint32_t state = 0;
	int32_t res = 0;
	hdd_context_t *pHddCtx;

	if (curr_peer == NULL) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("curr_peer is NULL"));
		return;
	}

	if (curr_peer->pHddTdlsCtx == NULL) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("curr_peer->pHddTdlsCtx is NULL"));
		return;
	}
	pHddCtx = WLAN_HDD_GET_CTX(curr_peer->pHddTdlsCtx->pAdapter);
	if ((wlan_hdd_validate_context(pHddCtx)))
		return;
	hdd_warn("tdls set peer " MAC_ADDRESS_STR " link status to %u",
		 MAC_ADDR_ARRAY(curr_peer->peerMac), status);

	curr_peer->link_status = status;

	/* If TDLS link status is already passed the discovery state
	 * then clear discovery attempt count
	 */
	if (status >= eTDLS_LINK_DISCOVERED)
		curr_peer->discovery_attempt = 0;

	if (curr_peer->isForcedPeer && curr_peer->state_change_notification) {
		uint32_t opclass;
		uint32_t channel;
		hdd_adapter_t *adapter = curr_peer->pHddTdlsCtx->pAdapter;

		curr_peer->reason = reason;

		hdd_info("Peer is forced and the reason:%d", reason);
		wlan_hdd_tdls_determine_channel_opclass(pHddCtx, adapter,
					curr_peer, &channel, &opclass);

		wlan_hdd_tdls_get_wifi_hal_state(curr_peer, &state, &res);
		(*curr_peer->state_change_notification)(curr_peer->peerMac,
							opclass, channel,
							state, res, adapter);
	}
}

/**
 * wlan_hdd_tdls_set_link_status() - set TDLS peer link status
 * @pAdapter: HDD adapter
 * @mac: mac address of TDLS peer
 * @linkStatus: status
 * @reason: reason
 *
 * Return: Void
 */
void wlan_hdd_tdls_set_link_status(hdd_adapter_t *pAdapter,
				   const uint8_t *mac,
				   enum tdls_link_status linkStatus,
				   enum tdls_link_reason reason)
{
	uint32_t state = 0;
	int32_t res = 0;
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if (wlan_hdd_validate_context(pHddCtx))
		return;

	curr_peer = wlan_hdd_tdls_find_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("curr_peer is NULL"));
		return;
	}

	curr_peer->link_status = linkStatus;

	/* If TDLS link status is already passed the discovery state
	 * then clear discovery attempt count
	 */
	if (linkStatus >= eTDLS_LINK_DISCOVERED)
		curr_peer->discovery_attempt = 0;

	if (curr_peer->isForcedPeer && curr_peer->state_change_notification) {
		uint32_t opclass;
		uint32_t channel;
		hdd_adapter_t *adapter = curr_peer->pHddTdlsCtx->pAdapter;

		curr_peer->reason = reason;

		wlan_hdd_tdls_determine_channel_opclass(pHddCtx, adapter,
					curr_peer, &channel, &opclass);

		wlan_hdd_tdls_get_wifi_hal_state(curr_peer, &state, &res);
		(curr_peer->state_change_notification)(mac, opclass, channel,
						       state, res, adapter);
	}
}

/**
 * wlan_hdd_tdls_recv_discovery_resp() - handling of tdls discovery response
 * @pAdapter: HDD adapter
 * @mac: mac address of peer from which the response was received
 *
 * Return: 0 for success or negative errno otherwise
 */
int wlan_hdd_tdls_recv_discovery_resp(hdd_adapter_t *pAdapter,
				      const uint8_t *mac)
{
	hddTdlsPeer_t *curr_peer;
	tdlsCtx_t *pHddTdlsCtx;
	hdd_context_t *pHddCtx;
	int status = 0;

	ENTER();

	pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if (0 != (wlan_hdd_validate_context(pHddCtx))) {
		status = -EINVAL;
		goto ret_status;
	}

	mutex_lock(&pHddCtx->tdls_lock);

	pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
	if (NULL == pHddTdlsCtx) {
		hdd_err("pHddTdlsCtx is NULL");
		status = -EINVAL;
		goto rel_lock;
	}

	curr_peer = wlan_hdd_tdls_get_peer(pAdapter, mac);
	if (NULL == curr_peer) {
		hdd_err("curr_peer is NULL");
		status = -EINVAL;
		goto rel_lock;
	}

	if (pHddTdlsCtx->discovery_sent_cnt)
		pHddTdlsCtx->discovery_sent_cnt--;

	wlan_hdd_tdls_check_power_save_prohibited(pAdapter);

	if (0 == pHddTdlsCtx->discovery_sent_cnt)
		qdf_mc_timer_stop(&pHddTdlsCtx->peerDiscoveryTimeoutTimer);

	hdd_notice("Discovery(%u) Response from " MAC_ADDRESS_STR
		   " link_status %d", pHddTdlsCtx->discovery_sent_cnt,
		   MAC_ADDR_ARRAY(curr_peer->peerMac), curr_peer->link_status);

	if (eTDLS_LINK_DISCOVERING == curr_peer->link_status) {
		/* Since we are here, it means Throughput threshold is
		 * already met. Make sure RSSI threshold is also met
		 * before setting up TDLS link.
		 */
		if ((int32_t) curr_peer->rssi >
		    (int32_t) pHddTdlsCtx->threshold_config.
		    rssi_trigger_threshold) {
			wlan_hdd_tdls_set_peer_link_status(curr_peer,
							   eTDLS_LINK_DISCOVERED,
							   eTDLS_LINK_SUCCESS);
			hdd_notice("Rssi Threshold met: " MAC_ADDRESS_STR
				   " rssi = %d threshold= %d",
				   MAC_ADDR_ARRAY(curr_peer->peerMac),
				   curr_peer->rssi,
				   pHddTdlsCtx->threshold_config.rssi_trigger_threshold);

			cfg80211_tdls_oper_request(pAdapter->dev,
						   curr_peer->peerMac,
						   NL80211_TDLS_SETUP, false,
						   GFP_KERNEL);
		} else {
			hdd_notice("Rssi Threshold not met: " MAC_ADDRESS_STR
				   " rssi = %d threshold = %d ",
				   MAC_ADDR_ARRAY(curr_peer->peerMac),
				   curr_peer->rssi,
				   pHddTdlsCtx->threshold_config.rssi_trigger_threshold);

			wlan_hdd_tdls_set_peer_link_status(curr_peer,
						eTDLS_LINK_IDLE,
						eTDLS_LINK_UNSPECIFIED);

			/* if RSSI threshold is not met then allow
			 * further discovery attempts by decrementing
			 * count for the last attempt
			 */
			if (curr_peer->discovery_attempt)
				curr_peer->discovery_attempt--;
		}
	}

	curr_peer->tdls_support = eTDLS_CAP_SUPPORTED;
rel_lock:
	mutex_unlock(&pHddCtx->tdls_lock);
ret_status:
	EXIT();
	return status;
}

/**
 * wlan_hdd_tdls_set_peer_caps() - set TDLS peer capability
 * @pAdapter: HDD adapter
 * @mac: MAC address of the TDLS peer
 * @StaParams: CSR Station Parameter
 * @isBufSta: is peer buffer station
 * @isOffChannelSupported: Is off channel supported
 * @is_qos_wmm_sta: Is QoS-WMM supported
 *
 * Return: 0 for success or negative errno otherwise
 */
int wlan_hdd_tdls_set_peer_caps(hdd_adapter_t *pAdapter,
				const uint8_t *mac,
				tCsrStaParams *StaParams,
				bool isBufSta, bool isOffChannelSupported,
				bool is_qos_wmm_sta)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);
	int status = 0;

	if (0 != (wlan_hdd_validate_context(hdd_ctx))) {
		status = -EINVAL;
		goto ret_status;
	}

	mutex_lock(&hdd_ctx->tdls_lock);
	curr_peer = wlan_hdd_tdls_get_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		hdd_err("curr_peer is NULL");
		status = -EINVAL;
		goto rel_lock;
	}

	curr_peer->uapsdQueues = StaParams->uapsd_queues;
	curr_peer->maxSp = StaParams->max_sp;
	curr_peer->isBufSta = isBufSta;
	curr_peer->isOffChannelSupported = isOffChannelSupported;

	qdf_mem_copy(curr_peer->supported_channels,
		     StaParams->supported_channels,
		     StaParams->supported_channels_len);

	curr_peer->supported_channels_len = StaParams->supported_channels_len;

	qdf_mem_copy(curr_peer->supported_oper_classes,
		     StaParams->supported_oper_classes,
		     StaParams->supported_oper_classes_len);

	curr_peer->supported_oper_classes_len =
		StaParams->supported_oper_classes_len;
	curr_peer->qos = is_qos_wmm_sta;
rel_lock:
	mutex_unlock(&hdd_ctx->tdls_lock);
ret_status:
	return status;
}

/**
 * wlan_hdd_tdls_get_link_establish_params() - get TDLS link establish
 *                                             parameter
 * @pAdapter: HDD adapter
 * @mac: mac address
 * @tdlsLinkEstablishParams: output parameter to store the result
 *
 * Return: 0 for success or negative errno otherwise
 */
int wlan_hdd_tdls_get_link_establish_params(hdd_adapter_t *pAdapter,
					    const u8 *mac,
					    tCsrTdlsLinkEstablishParams *
					    tdlsLinkEstablishParams)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);
	int status = 0;

	if (0 != (wlan_hdd_validate_context(hdd_ctx))) {
		status = -EINVAL;
		goto ret_status;
	}

	mutex_lock(&hdd_ctx->tdls_lock);
	curr_peer = wlan_hdd_tdls_get_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		hdd_err("curr_peer is NULL");
		status = -EINVAL;
		goto rel_lock;
	}

	tdlsLinkEstablishParams->isResponder = curr_peer->is_responder;
	tdlsLinkEstablishParams->uapsdQueues = curr_peer->uapsdQueues;
	tdlsLinkEstablishParams->maxSp = curr_peer->maxSp;
	tdlsLinkEstablishParams->isBufSta = curr_peer->isBufSta;
	tdlsLinkEstablishParams->isOffChannelSupported =
		curr_peer->isOffChannelSupported;

	qdf_mem_copy(tdlsLinkEstablishParams->supportedChannels,
		     curr_peer->supported_channels,
		     curr_peer->supported_channels_len);

	tdlsLinkEstablishParams->supportedChannelsLen =
		curr_peer->supported_channels_len;

	qdf_mem_copy(tdlsLinkEstablishParams->supportedOperClasses,
		     curr_peer->supported_oper_classes,
		     curr_peer->supported_oper_classes_len);

	tdlsLinkEstablishParams->supportedOperClassesLen =
		curr_peer->supported_oper_classes_len;
	tdlsLinkEstablishParams->qos = curr_peer->qos;
rel_lock:
	mutex_unlock(&hdd_ctx->tdls_lock);
ret_status:
	return status;
}

/**
 * wlan_hdd_tdls_set_rssi() - Set TDLS RSSI on peer given by mac
 * @pAdapter: HDD adapter
 * @mac: MAC address of Peer
 * @rxRssi: rssi value
 *
 * Set RSSI on TDSL peer
 *
 * Return: 0 for success or -EINVAL otherwise
 */
int wlan_hdd_tdls_set_rssi(hdd_adapter_t *pAdapter, const uint8_t *mac,
			   int8_t rxRssi)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);

	if (0 != (wlan_hdd_validate_context(hdd_ctx)))
		return -EINVAL;

	mutex_lock(&hdd_ctx->tdls_lock);
	curr_peer = wlan_hdd_tdls_find_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		mutex_unlock(&hdd_ctx->tdls_lock);
		hdd_err("curr_peer is NULL");
		return -EINVAL;
	}

	curr_peer->rssi = rxRssi;
	mutex_unlock(&hdd_ctx->tdls_lock);

	return 0;
}

/**
 * wlan_hdd_tdls_set_responder() - Set/clear TDLS peer's responder role
 * @pAdapter: HDD adapter
 * @mac: MAC address of Peer
 * @responder: flag that indicates if the TDLS peer should be responder or not
 *
 * Return: 0 for success or -EINVAL otherwise
 */
int wlan_hdd_tdls_set_responder(hdd_adapter_t *pAdapter, const uint8_t *mac,
				uint8_t responder)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);
	int status = 0;

	if (0 != (wlan_hdd_validate_context(hdd_ctx))) {
		status = -EINVAL;
		goto ret_status;
	}
	mutex_lock(&hdd_ctx->tdls_lock);
	curr_peer = wlan_hdd_tdls_get_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		hdd_err("curr_peer is NULL");
		status = -EINVAL;
		goto rel_lock;
	}

	curr_peer->is_responder = responder;

rel_lock:
	mutex_unlock(&hdd_ctx->tdls_lock);
ret_status:
	return status;
}

/**
 * wlan_hdd_tdls_set_signature() - Set TDLS peer's signature
 * @pAdapter: HDD adapter
 * @mac: MAC address of TDLS Peer
 * @uSignature: signature value
 *
 * Return: 0 for success or -EINVAL otherwise
 */
int wlan_hdd_tdls_set_signature(hdd_adapter_t *pAdapter, const uint8_t *mac,
				uint8_t uSignature)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);
	int status = 0;

	if (0 != (wlan_hdd_validate_context(hdd_ctx))) {
		status = -EINVAL;
		goto ret_status;
	}

	mutex_lock(&hdd_ctx->tdls_lock);
	curr_peer = wlan_hdd_tdls_get_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		hdd_err("curr_peer is NULL");
		status = -EINVAL;
		goto rel_lock;
	}

	curr_peer->signature = uSignature;
rel_lock:
	mutex_unlock(&hdd_ctx->tdls_lock);
ret_status:
	return status;
}

/**
 * wlan_hdd_tdls_extract_sa() - Extract source address from socket buffer
 * @skb: socket buffer
 * @mac: output mac address buffer to store the source address
 *
 * Return: Void
 */
void wlan_hdd_tdls_extract_sa(struct sk_buff *skb, uint8_t *mac)
{
	memcpy(mac, skb->data + 6, 6);
}

/**
 * wlan_hdd_tdls_check_config() - validate tdls configuration parameters
 * @config: tdls configuration parameter structure
 *
 * Return: 0 if all parameters are valid; -EINVAL otherwise
 */
static int wlan_hdd_tdls_check_config(tdls_config_params_t *config)
{
	if (config->tdls > 2) {
		hdd_err("Invalid 1st argument %d. <0...2>",
			config->tdls);
		return -EINVAL;
	}
	if (config->tx_period_t < CFG_TDLS_TX_STATS_PERIOD_MIN ||
	    config->tx_period_t > CFG_TDLS_TX_STATS_PERIOD_MAX) {
		hdd_err("Invalid 2nd argument %d. <%d...%ld>",
			config->tx_period_t, CFG_TDLS_TX_STATS_PERIOD_MIN,
			CFG_TDLS_TX_STATS_PERIOD_MAX);
		return -EINVAL;
	}
	if (config->tx_packet_n < CFG_TDLS_TX_PACKET_THRESHOLD_MIN ||
	    config->tx_packet_n > CFG_TDLS_TX_PACKET_THRESHOLD_MAX) {
		hdd_err("Invalid 3rd argument %d. <%d...%ld>",
			config->tx_packet_n, CFG_TDLS_TX_PACKET_THRESHOLD_MIN,
			CFG_TDLS_TX_PACKET_THRESHOLD_MAX);
		return -EINVAL;
	}
	if (config->discovery_tries_n < CFG_TDLS_MAX_DISCOVERY_ATTEMPT_MIN ||
	    config->discovery_tries_n > CFG_TDLS_MAX_DISCOVERY_ATTEMPT_MAX) {
		hdd_err("Invalid 5th argument %d. <%d...%d>",
			config->discovery_tries_n,
			CFG_TDLS_MAX_DISCOVERY_ATTEMPT_MIN,
			CFG_TDLS_MAX_DISCOVERY_ATTEMPT_MAX);
		return -EINVAL;
	}
	if (config->idle_packet_n < CFG_TDLS_IDLE_PACKET_THRESHOLD_MIN ||
	    config->idle_packet_n > CFG_TDLS_IDLE_PACKET_THRESHOLD_MAX) {
		hdd_err("Invalid 7th argument %d. <%d...%d>",
			config->idle_packet_n,
			CFG_TDLS_IDLE_PACKET_THRESHOLD_MIN,
			CFG_TDLS_IDLE_PACKET_THRESHOLD_MAX);
		return -EINVAL;
	}
	if (config->rssi_trigger_threshold < CFG_TDLS_RSSI_TRIGGER_THRESHOLD_MIN
	    || config->rssi_trigger_threshold >
	    CFG_TDLS_RSSI_TRIGGER_THRESHOLD_MAX) {
		hdd_err("Invalid 9th argument %d. <%d...%d>",
			config->rssi_trigger_threshold,
			CFG_TDLS_RSSI_TRIGGER_THRESHOLD_MIN,
			CFG_TDLS_RSSI_TRIGGER_THRESHOLD_MAX);
		return -EINVAL;
	}
	if (config->rssi_teardown_threshold <
	    CFG_TDLS_RSSI_TEARDOWN_THRESHOLD_MIN
	    || config->rssi_teardown_threshold >
	    CFG_TDLS_RSSI_TEARDOWN_THRESHOLD_MAX) {
		hdd_err("Invalid 10th argument %d. <%d...%d>",
			config->rssi_teardown_threshold,
			CFG_TDLS_RSSI_TEARDOWN_THRESHOLD_MIN,
			CFG_TDLS_RSSI_TEARDOWN_THRESHOLD_MAX);
		return -EINVAL;
	}
	if (config->rssi_delta < CFG_TDLS_RSSI_DELTA_MIN
	    || config->rssi_delta > CFG_TDLS_RSSI_DELTA_MAX) {
		hdd_err("Invalid 11th argument %d. <%d...%d>",
			config->rssi_delta,
			CFG_TDLS_RSSI_DELTA_MIN,
			CFG_TDLS_RSSI_DELTA_MAX);
		return -EINVAL;
	}
	return 0;
}

/**
 * wlan_tdd_tdls_reset_tx_rx() - reset tx/rx counters for all tdls peers
 * @pHddTdlsCtx: TDLS context
 *
 * Caller has to take the TDLS lock before calling this function
 *
 * Return: Void
 */
static void wlan_tdd_tdls_reset_tx_rx(tdlsCtx_t *pHddTdlsCtx)
{
	int i;
	struct list_head *head;
	hddTdlsPeer_t *tmp;
	struct list_head *pos, *q;

	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &pHddTdlsCtx->peer_list[i];
		list_for_each_safe(pos, q, head) {
			tmp = list_entry(pos, hddTdlsPeer_t, node);
			tmp->tx_pkt = 0;
			tmp->rx_pkt = 0;
		}
	}
}

/**
 * wlan_hdd_tdls_implicit_disable() - disable implicit tdls triggering
 * @pHddTdlsCtx: TDLS context
 *
 * Return: Void
 */
static void wlan_hdd_tdls_implicit_disable(tdlsCtx_t *pHddTdlsCtx)
{
	hdd_info("Disable Implicit TDLS");
	wlan_hdd_tdls_timers_stop(pHddTdlsCtx);
}

/**
 * wlan_hdd_tdls_implicit_enable() - enable implicit tdls triggering
 * @pHddTdlsCtx: TDLS context
 *
 * Return: Void
 */
static void wlan_hdd_tdls_implicit_enable(tdlsCtx_t *pHddTdlsCtx)
{
	hdd_info("Enable Implicit TDLS");
	wlan_hdd_tdls_peer_reset_discovery_processed(pHddTdlsCtx);
	pHddTdlsCtx->discovery_sent_cnt = 0;
	wlan_tdd_tdls_reset_tx_rx(pHddTdlsCtx);
	wlan_hdd_tdls_check_power_save_prohibited(pHddTdlsCtx->pAdapter);

	/* Restart the connection tracker timer */
	wlan_hdd_tdls_timer_restart(pHddTdlsCtx->pAdapter,
				    &pHddTdlsCtx->peer_update_timer,
				    pHddTdlsCtx->threshold_config.tx_period_t);

}

/**
 * wlan_hdd_tdls_set_mode() - set TDLS mode
 * @pHddCtx: HDD context
 * @tdls_mode: TDLS mode
 * @bUpdateLast: Switch on if to set pHddCtx->tdls_mode_last to tdls_mode.
 *               If 1, set pHddCtx->tdls_mode_last to tdls_mode, otherwise
 *               set pHddCtx->tdls_mode_last to pHddCtx->tdls_mode
 * @source: TDLS disable source enum values
 *
 * Return: Void
 */
static void wlan_hdd_tdls_set_mode(hdd_context_t *pHddCtx,
				   enum tdls_support_mode tdls_mode,
				   bool bUpdateLast,
				   enum tdls_disable_source source)
{
	hdd_adapter_list_node_t *pAdapterNode = NULL, *pNext = NULL;
	QDF_STATUS status;
	hdd_adapter_t *pAdapter;
	tdlsCtx_t *pHddTdlsCtx;

	ENTER();

	hdd_debug("mode %d", (int)tdls_mode);

	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return;

	mutex_lock(&pHddCtx->tdls_lock);

	if (bUpdateLast)
		pHddCtx->tdls_mode_last = tdls_mode;

	if (pHddCtx->tdls_mode == tdls_mode) {
		mutex_unlock(&pHddCtx->tdls_lock);
		hdd_debug("already in mode %d", (int)tdls_mode);

		switch (tdls_mode) {
		/* TDLS is already enabled hence clear source mask, return */
		case eTDLS_SUPPORT_ENABLED:
		case eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY:
		case eTDLS_SUPPORT_EXTERNAL_CONTROL:
			clear_bit((unsigned long)source,
				  &pHddCtx->tdls_source_bitmap);
			hdd_debug("clear source mask:%d", source);
			return;
		/* TDLS is already disabled hence set source mask, return */
		case eTDLS_SUPPORT_DISABLED:
			set_bit((unsigned long)source,
				&pHddCtx->tdls_source_bitmap);
			hdd_debug("set source mask:%d", source);
			return;
		default:
			return;
		}
	}

	status = hdd_get_front_adapter(pHddCtx, &pAdapterNode);

	while (NULL != pAdapterNode && QDF_STATUS_SUCCESS == status) {
		pAdapter = pAdapterNode->pAdapter;
		pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
		if (NULL != pHddTdlsCtx) {
			if (eTDLS_SUPPORT_ENABLED == tdls_mode ||
			    eTDLS_SUPPORT_EXTERNAL_CONTROL == tdls_mode) {
				clear_bit((unsigned long)source,
					  &pHddCtx->tdls_source_bitmap);

				/*
				 * Check if any TDLS source bit is set and if
				 * bitmap is not zero then we should not
				 * enable TDLS
				 */
				if (pHddCtx->tdls_source_bitmap) {
					mutex_unlock(&pHddCtx->tdls_lock);
					hdd_debug("Don't enable TDLS, source"
						"bitmap: %lu",
						pHddCtx->tdls_source_bitmap);
					return;
				}
				wlan_hdd_tdls_implicit_enable(pHddTdlsCtx);
				/* tdls implicit mode is enabled, so
				 * enable the connection tracker
				 */
				pHddCtx->enable_tdls_connection_tracker =
					true;
			} else if (eTDLS_SUPPORT_DISABLED == tdls_mode) {
				set_bit((unsigned long)source,
					&pHddCtx->tdls_source_bitmap);
				wlan_hdd_tdls_implicit_disable(pHddTdlsCtx);
				/* If tdls implicit mode is disabled, then
				 * stop the connection tracker.
				 */
				pHddCtx->enable_tdls_connection_tracker =
					false;
			} else if (eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY ==
				   tdls_mode) {
				clear_bit((unsigned long)source,
					  &pHddCtx->tdls_source_bitmap);
				wlan_hdd_tdls_implicit_disable(pHddTdlsCtx);
				/* If tdls implicit mode is disabled, then
				 * stop the connection tracker.
				 */
				pHddCtx->enable_tdls_connection_tracker =
					false;

				/*
				 * Check if any TDLS source bit is set and if
				 * bitmap is not zero then we should not
				 * enable TDLS
				 */
				if (pHddCtx->tdls_source_bitmap) {
					mutex_unlock(&pHddCtx->tdls_lock);
					return;
				}
			}
		}
		status = hdd_get_next_adapter(pHddCtx, pAdapterNode, &pNext);
		pAdapterNode = pNext;
	}
	if (!bUpdateLast)
		pHddCtx->tdls_mode_last = pHddCtx->tdls_mode;

	pHddCtx->tdls_mode = tdls_mode;

	mutex_unlock(&pHddCtx->tdls_lock);
	EXIT();
}

/**
 * wlan_hdd_tdls_set_params() - set TDLS parameters
 * @dev: net device
 * @config: TDLS configuration parameters
 *
 * Return: 0 if success or negative errno otherwise
 */
int wlan_hdd_tdls_set_params(struct net_device *dev,
			     tdls_config_params_t *config)
{
	hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	tdlsCtx_t *pHddTdlsCtx;
	enum tdls_support_mode req_tdls_mode;
	tdlsInfo_t *tdlsParams;
	QDF_STATUS qdf_ret_status = QDF_STATUS_E_FAILURE;

	if (wlan_hdd_tdls_check_config(config) != 0)
		return -EINVAL;

	/* config->tdls is mapped to 0->1, 1->2, 2->3 */
	req_tdls_mode = config->tdls + 1;
	if (pHddCtx->tdls_mode == req_tdls_mode) {
		hdd_err("Already in mode %d", config->tdls);
		return -EINVAL;
	}

	mutex_lock(&pHddCtx->tdls_lock);

	pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
	if (NULL == pHddTdlsCtx) {
		mutex_unlock(&pHddCtx->tdls_lock);
		hdd_err("pHddTdlsCtx is NULL");
			return -EINVAL;
	}

	/* Copy the configuration only when given tdls mode
	 * is implicit trigger enable
	 */
	if (eTDLS_SUPPORT_ENABLED == req_tdls_mode ||
	    eTDLS_SUPPORT_EXTERNAL_CONTROL == req_tdls_mode) {
		memcpy(&pHddTdlsCtx->threshold_config, config,
		       sizeof(tdls_config_params_t));
	}

	mutex_unlock(&pHddCtx->tdls_lock);

	hdd_err("iw set tdls params: %d %d %d %d %d %d %d",
		config->tdls,
		config->tx_period_t,
		config->tx_packet_n,
		config->discovery_tries_n,
		config->idle_packet_n,
		config->rssi_trigger_threshold,
		config->rssi_teardown_threshold);

	wlan_hdd_tdls_set_mode(pHddCtx, req_tdls_mode, true,
			       HDD_SET_TDLS_MODE_SOURCE_USER);

	tdlsParams = qdf_mem_malloc(sizeof(tdlsInfo_t));
	if (NULL == tdlsParams) {
		hdd_err("qdf_mem_malloc failed for tdlsParams");
		return -ENOMEM;
	}

	tdlsParams->vdev_id = pAdapter->sessionId;
	tdlsParams->tdls_state = config->tdls;
	tdlsParams->notification_interval_ms = config->tx_period_t;
	tdlsParams->tx_discovery_threshold = config->tx_packet_n;
	tdlsParams->tx_teardown_threshold = config->idle_packet_n;
	tdlsParams->rssi_teardown_threshold = config->rssi_teardown_threshold;
	tdlsParams->rssi_delta = config->rssi_delta;
	tdlsParams->tdls_options = 0;
	if (pHddCtx->config->fEnableTDLSOffChannel)
		tdlsParams->tdls_options |= ENA_TDLS_OFFCHAN;
	if (pHddCtx->config->fEnableTDLSBufferSta)
		tdlsParams->tdls_options |= ENA_TDLS_BUFFER_STA;
	if (pHddCtx->config->fEnableTDLSSleepSta)
		tdlsParams->tdls_options |= ENA_TDLS_SLEEP_STA;
	tdlsParams->peer_traffic_ind_window =
		pHddCtx->config->fTDLSPuapsdPTIWindow;
	tdlsParams->peer_traffic_response_timeout =
		pHddCtx->config->fTDLSPuapsdPTRTimeout;
	tdlsParams->puapsd_mask = pHddCtx->config->fTDLSUapsdMask;
	tdlsParams->puapsd_inactivity_time =
		pHddCtx->config->fTDLSPuapsdInactivityTimer;
	tdlsParams->puapsd_rx_frame_threshold =
		pHddCtx->config->fTDLSRxFrameThreshold;
	tdlsParams->teardown_notification_ms =
		pHddCtx->config->tdls_idle_timeout;
	tdlsParams->tdls_peer_kickout_threshold =
		pHddCtx->config->tdls_peer_kickout_threshold;

	dump_tdls_state_param_setting(tdlsParams);

	qdf_ret_status = sme_update_fw_tdls_state(pHddCtx->hHal,
						  tdlsParams, true);
	if (QDF_STATUS_SUCCESS != qdf_ret_status) {
		qdf_mem_free(tdlsParams);
		return -EINVAL;
	}

	return 0;
}

/**
 * wlan_hdd_tdls_get_adapter() - check system state and return hdd adapter
 * @hdd_ctx: hdd context
 *
 * If TDLS possible, return the corresponding hdd adapter
 * to enable TDLS in the system.
 *
 * Return: hdd adapter pointer or NULL.
 */
static hdd_adapter_t *wlan_hdd_tdls_get_adapter(hdd_context_t *hdd_ctx)
{
	if (policy_mgr_get_connection_count(hdd_ctx->hdd_psoc) > 1)
		return NULL;
	if (policy_mgr_mode_specific_connection_count(hdd_ctx->hdd_psoc,
		QDF_STA_MODE, NULL) == 1)
		return hdd_get_adapter(hdd_ctx,
				       QDF_STA_MODE);
	if (policy_mgr_mode_specific_connection_count(hdd_ctx->hdd_psoc,
		QDF_P2P_CLIENT_MODE, NULL) == 1)
		return hdd_get_adapter(hdd_ctx,
				       QDF_P2P_CLIENT_MODE);
	return NULL;
}

/**
 * wlan_hdd_update_tdls_info - update tdls status info
 * @adapter: ptr to device adapter.
 * @tdls_prohibited: indicates whether tdls is prohibited.
 * @tdls_chan_swit_prohibited: indicates whether tdls channel switch
 *                             is prohibited.
 *
 * Normally an AP does not influence TDLS connection between STAs
 * associated to it. But AP may set bits for TDLS Prohibited or
 * TDLS Channel Switch Prohibited in Extended Capability IE in
 * Assoc/Re-assoc response to STA. So after STA is connected to
 * an AP, call this function to update TDLS status as per those
 * bits set in Ext Cap IE in received Assoc/Re-assoc response
 * from AP.
 *
 * Return: None.
 */
void wlan_hdd_update_tdls_info(hdd_adapter_t *adapter, bool tdls_prohibited,
				bool tdls_chan_swit_prohibited)
{
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter);
	tdlsCtx_t *hdd_tdls_ctx;
	tdlsInfo_t *tdls_param;
	QDF_STATUS qdf_ret_status = QDF_STATUS_E_FAILURE;

	/* If TDLS support is disabled then no need to update target */
	if (false == hdd_ctx->config->fEnableTDLSSupport) {
		hdd_err("TDLS not enabled");
		goto done;
	}

	hdd_info("tdls_prohibited: %d, tdls_chan_swit_prohibited: %d",
		 tdls_prohibited, tdls_chan_swit_prohibited);

	mutex_lock(&hdd_ctx->tdls_lock);

	hdd_tdls_ctx = WLAN_HDD_GET_TDLS_CTX_PTR(adapter);
	if (!hdd_tdls_ctx) {
		mutex_unlock(&hdd_ctx->tdls_lock);
		/* may be TDLS is not applicable for this adapter */
		hdd_err("HDD TDLS context is null");
		goto done;
	}

	if (hdd_ctx->set_state_info.set_state_cnt == 0 &&
	    tdls_prohibited) {
		mutex_unlock(&hdd_ctx->tdls_lock);
		goto done;
	}

	/* If AP or caller indicated TDLS Prohibited then disable tdls mode */
	if (tdls_prohibited) {
		hdd_ctx->tdls_mode = eTDLS_SUPPORT_NOT_ENABLED;
	} else {
		if (false == hdd_ctx->config->fEnableTDLSImplicitTrigger)
			hdd_ctx->tdls_mode = eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY;
		else if (true == hdd_ctx->config->fTDLSExternalControl)
			hdd_ctx->tdls_mode = eTDLS_SUPPORT_EXTERNAL_CONTROL;
		else
			hdd_ctx->tdls_mode = eTDLS_SUPPORT_ENABLED;
	}
	tdls_param = qdf_mem_malloc(sizeof(*tdls_param));
	if (!tdls_param) {
		mutex_unlock(&hdd_ctx->tdls_lock);
		hdd_err("memory allocation failed for tdlsParams");
		goto done;
	}

	tdls_param->notification_interval_ms =
		hdd_tdls_ctx->threshold_config.tx_period_t;
	tdls_param->tx_discovery_threshold =
		hdd_tdls_ctx->threshold_config.tx_packet_n;
	tdls_param->tx_teardown_threshold =
		hdd_tdls_ctx->threshold_config.idle_packet_n;
	tdls_param->rssi_teardown_threshold =
		hdd_tdls_ctx->threshold_config.rssi_teardown_threshold;
	tdls_param->rssi_delta = hdd_tdls_ctx->threshold_config.rssi_delta;

	mutex_unlock(&hdd_ctx->tdls_lock);

	/* If any concurrency detected, teardown all TDLS links and disable
	 * the tdls support
	 */
	hdd_debug("Concurrency check in TDLS! set state cnt %d tdls_prohibited %d",
		hdd_ctx->set_state_info.set_state_cnt, tdls_prohibited);

	if (hdd_ctx->set_state_info.set_state_cnt == 1 &&
	    !tdls_prohibited) {
		hdd_warn("Concurrency not allowed in TDLS! set state cnt %d",
			hdd_ctx->set_state_info.set_state_cnt);
		wlan_hdd_tdls_disable_offchan_and_teardown_links(hdd_ctx);
		tdls_prohibited = true;
		hdd_ctx->tdls_mode = eTDLS_SUPPORT_NOT_ENABLED;
		tdls_param->vdev_id = hdd_ctx->set_state_info.vdev_id;
	} else {
		tdls_param->vdev_id = adapter->sessionId;
	}

	tdls_param->tdls_state = hdd_ctx->tdls_mode;
	tdls_param->tdls_options = 0;

	/* Do not enable TDLS offchannel,
	 * if AP prohibited TDLS channel switch
	 */
	if ((hdd_ctx->config->fEnableTDLSOffChannel) &&
	    (!tdls_chan_swit_prohibited)) {
		tdls_param->tdls_options |= ENA_TDLS_OFFCHAN;
	}

	if (hdd_ctx->config->fEnableTDLSBufferSta)
		tdls_param->tdls_options |= ENA_TDLS_BUFFER_STA;

	if (hdd_ctx->config->fEnableTDLSSleepSta)
		tdls_param->tdls_options |= ENA_TDLS_SLEEP_STA;

	tdls_param->peer_traffic_ind_window =
		hdd_ctx->config->fTDLSPuapsdPTIWindow;
	tdls_param->peer_traffic_response_timeout =
		hdd_ctx->config->fTDLSPuapsdPTRTimeout;
	tdls_param->puapsd_mask =
		hdd_ctx->config->fTDLSUapsdMask;
	tdls_param->puapsd_inactivity_time =
		hdd_ctx->config->fTDLSPuapsdInactivityTimer;
	tdls_param->puapsd_rx_frame_threshold =
		hdd_ctx->config->fTDLSRxFrameThreshold;
	tdls_param->teardown_notification_ms =
		hdd_ctx->config->tdls_idle_timeout;
	tdls_param->tdls_peer_kickout_threshold =
		hdd_ctx->config->tdls_peer_kickout_threshold;

	dump_tdls_state_param_setting(tdls_param);

	qdf_ret_status = sme_update_fw_tdls_state(hdd_ctx->hHal,
					       tdls_param,
					       true);
	if (QDF_STATUS_SUCCESS != qdf_ret_status) {
		qdf_mem_free(tdls_param);
		goto done;
	}

	mutex_lock(&hdd_ctx->tdls_lock);

	if (!tdls_prohibited) {
		hdd_ctx->set_state_info.set_state_cnt++;
		hdd_ctx->set_state_info.vdev_id = adapter->sessionId;
	} else {
		hdd_ctx->set_state_info.set_state_cnt--;
	}

	hdd_info("TDLS Set state cnt %d",
		hdd_ctx->set_state_info.set_state_cnt);

	mutex_unlock(&hdd_ctx->tdls_lock);
done:
	cds_set_tdls_ct_mode(hdd_ctx);
}

/**
 * wlan_hdd_tdls_notify_connect() - Update tdls state for every
 * connect event.
 * @adapter: hdd adapter
 * @csr_roam_info: csr information
 *
 * After every connect event in the system, check whether TDLS
 * can be enabled in the system. If TDLS can be enabled, update the
 * TDLS state as needed.
 *
 * Return: None
 */
void wlan_hdd_tdls_notify_connect(hdd_adapter_t *adapter,
				  tCsrRoamInfo *csr_roam_info)
{
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter);

	hdd_info("Check and update TDLS state");

	if (!hdd_ctx)
		return;

	if (hdd_ctx->tdls_umac_comp_active)
		return;

	/* Association event */
	if (adapter->device_mode == QDF_STA_MODE ||
	     adapter->device_mode == QDF_P2P_CLIENT_MODE) {
		wlan_hdd_update_tdls_info(adapter,
					  csr_roam_info->tdls_prohibited,
					  csr_roam_info->tdls_chan_swit_prohibited);
	}
}

/**
 * wlan_hdd_tdls_notify_disconnect() - Update tdls state for every
 * disconnect event.
 * @adapter: hdd adapter
 * @lfr_roam: roaming case
 *
 * After every disconnect event in the system, check whether TDLS
 * can be disabled/enabled in the system and update the
 * TDLS state as needed.
 *
 * Return: None
 */
void wlan_hdd_tdls_notify_disconnect(hdd_adapter_t *adapter, bool lfr_roam)
{
	hdd_adapter_t *temp_adapter;
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter);

	hdd_info("Check and update TDLS state");

	if (!hdd_ctx)
		return;

	if (hdd_ctx->tdls_umac_comp_active)
		return;


	/* Disassociation event */
	if (adapter->device_mode == QDF_STA_MODE ||
	     adapter->device_mode == QDF_P2P_CLIENT_MODE) {
		wlan_hdd_update_tdls_info(adapter, true, true);
	}

	/* Check TDLS could be enabled in the system
	 * after this disassoc event.
	 */
	if (!lfr_roam) {
		temp_adapter = wlan_hdd_tdls_get_adapter(
					hdd_ctx);
		if (NULL != temp_adapter)
			wlan_hdd_update_tdls_info(temp_adapter,
						  false,
						  false);
	}
}

/**
 * wlan_hdd_tdls_set_sta_id() - set station ID on a tdls peer
 * @pAdapter: HDD adapter
 * @mac: MAC address of a tdls peer
 * @staId: station ID
 *
 * Return: 0 if success; negative errno otherwise
 */
int wlan_hdd_tdls_set_sta_id(hdd_adapter_t *pAdapter, const uint8_t *mac,
			     uint8_t staId)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);
	int status = 0;

	if (0 != (wlan_hdd_validate_context(hdd_ctx))) {
		status = -EINVAL;
		goto ret_status;
	}

	mutex_lock(&hdd_ctx->tdls_lock);
	curr_peer = wlan_hdd_tdls_get_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		hdd_err("curr_peer is NULL");
		status = -EINVAL;
		goto rel_lock;
	}

	curr_peer->staId = staId;
rel_lock:
	mutex_unlock(&hdd_ctx->tdls_lock);
ret_status:
	return status;
}

/**
 * wlan_hdd_tdls_set_extctrl_param() - set external control parameter on a peer
 * @pAdapter: HDD adapter
 * @mac: MAC address of the peer
 * @chan: Channel
 * @max_latency: Maximum latency
 * @op_class: Operation class
 * @min_bandwidth: Minimal bandwidth
 *
 * Return: 0 for success; negative errno otherwise
 */
int wlan_hdd_tdls_set_extctrl_param(hdd_adapter_t *pAdapter, const uint8_t *mac,
				    uint32_t chan, uint32_t max_latency,
				    uint32_t op_class, uint32_t min_bandwidth)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if (!pHddCtx)
		return -EINVAL;

	mutex_lock(&pHddCtx->tdls_lock);
	curr_peer = wlan_hdd_tdls_find_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		mutex_unlock(&pHddCtx->tdls_lock);
		return -EINVAL;
	}
	curr_peer->op_class_for_pref_off_chan = (uint8_t) op_class;
	curr_peer->pref_off_chan_num = (uint8_t) chan;

	mutex_unlock(&pHddCtx->tdls_lock);
	return 0;
}

/**
 * wlan_hdd_tdls_update_peer_mac() - Update the peer mac information to firmware
 * @adapter: hdd adapter to interface
 * @mac: Mac address of the peer to be added
 * @peerState: Current state of the peer
 *
 * This function updates TDLS peer state to firmware. Firmware will update
 * connection table based on new peer state.
 *
 * Return:success (0) or failure (errno value)
 */
int wlan_hdd_tdls_update_peer_mac(hdd_adapter_t *adapter, const uint8_t *mac,
				  uint32_t peer_state)
{
	tSmeTdlsPeerStateParams sme_tdls_peer_state_params = {0};
	QDF_STATUS status = QDF_STATUS_E_FAILURE;
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter);

	sme_tdls_peer_state_params.vdevId = adapter->sessionId;
	qdf_mem_copy(&sme_tdls_peer_state_params.peerMacAddr, mac,
		     sizeof(sme_tdls_peer_state_params.peerMacAddr));
	sme_tdls_peer_state_params.peerState = peer_state;
	status = sme_update_tdls_peer_state(hdd_ctx->hHal,
					    &sme_tdls_peer_state_params);
	if (QDF_STATUS_SUCCESS != status) {
		hdd_err("sme_UpdateTdlsPeerState failed for "MAC_ADDRESS_STR,
			MAC_ADDR_ARRAY(mac));
		return -EPERM;
	}
	return 0;
}

/**
 * wlan_hdd_tdls_set_force_peer() - set/clear isForcedPeer flag on a peer
 * @pAdapter: HDD adapter
 * @mac: MAC address of the tdls peer
 * @forcePeer: value used to set isForcedPeer flag on the peer
 *
 * Return: 0 for success; negative errno otherwise
 */
int wlan_hdd_tdls_set_force_peer(hdd_adapter_t *pAdapter, const uint8_t *mac,
				 bool forcePeer)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if (!pHddCtx)
		return -EINVAL;

	mutex_lock(&pHddCtx->tdls_lock);
	curr_peer = wlan_hdd_tdls_find_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		mutex_unlock(&pHddCtx->tdls_lock);
		return -EINVAL;
	}

	curr_peer->isForcedPeer = forcePeer;
	mutex_unlock(&pHddCtx->tdls_lock);
	return 0;
}

/**
 * wlan_hdd_tdls_find_peer() - find TDLS peer given its MAC address
 * @pAdapter: HDD adapter
 * @mac: MAC address of peer
 *
 * Return: If peerMac is found, then it returns pointer to hddTdlsPeer_t;
 *         otherwise, it returns NULL
 */
hddTdlsPeer_t *wlan_hdd_tdls_find_peer(hdd_adapter_t *pAdapter,
				       const uint8_t *mac)
{
	uint8_t key;
	struct list_head *pos;
	struct list_head *head;
	hddTdlsPeer_t *curr_peer;
	tdlsCtx_t *pHddTdlsCtx;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);


	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return NULL;

	pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
	if (NULL == pHddTdlsCtx)
		return NULL;

	key = wlan_hdd_tdls_hash_key(mac);

	head = &pHddTdlsCtx->peer_list[key];

	list_for_each(pos, head) {
		curr_peer = list_entry(pos, hddTdlsPeer_t, node);
		if (!memcmp(mac, curr_peer->peerMac, 6)) {
			hdd_debug("findTdlsPeer: found staId %d",
				  curr_peer->staId);
			return curr_peer;
		}
	}

	return NULL;
}

/**
 * wlan_hdd_tdls_find_all_peer() - find all peers matching the input MAC
 * @pHddCtx: HDD context
 * @mac: MAC address
 *
 * Return: TDLS peer if a matching is detected; NULL otherwise
 */
hddTdlsPeer_t *wlan_hdd_tdls_find_all_peer(hdd_context_t *pHddCtx,
					   const u8 *mac)
{
	hdd_adapter_list_node_t *pAdapterNode = NULL, *pNext = NULL;
	hdd_adapter_t *pAdapter = NULL;
	tdlsCtx_t *pHddTdlsCtx = NULL;
	hddTdlsPeer_t *curr_peer = NULL;
	QDF_STATUS status = 0;

	status = hdd_get_front_adapter(pHddCtx, &pAdapterNode);
	while (NULL != pAdapterNode && QDF_STATUS_SUCCESS == status) {
		pAdapter = pAdapterNode->pAdapter;

		pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
		if (NULL != pHddTdlsCtx) {
			curr_peer =
				wlan_hdd_tdls_find_peer(pAdapter, mac);
			if (curr_peer)
				return curr_peer;
		}
		status = hdd_get_next_adapter(pHddCtx, pAdapterNode, &pNext);
		pAdapterNode = pNext;
	}
	return curr_peer;
}

/**
 * wlan_hdd_tdls_reset_peer() - reset TDLS peer identified by MAC address
 * @pAdapter: HDD adapter
 * @mac: MAC address of the peer
 *
 * Return: 0 for success; negative errno otherwise
 */
int wlan_hdd_tdls_reset_peer(hdd_adapter_t *pAdapter, const uint8_t *mac)
{
	hdd_context_t *pHddCtx;
	hddTdlsPeer_t *curr_peer;
	int status = 0;

	pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if (0 != (wlan_hdd_validate_context(pHddCtx))) {
		status = -EINVAL;
		goto ret_status;
	}

	curr_peer = wlan_hdd_tdls_get_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		hdd_err("curr_peer is NULL");
		status = -EINVAL;
		goto ret_status;
	}

	/*
	 * Reset preferred offchannel and opclass for offchannel as
	 * per INI configuration only if peer is not forced one. For
	 * forced peer, offchannel and opclass is set in HAL API at the
	 * time of enabling TDLS for that specific peer and so do not overwrite
	 * those set by user space.
	 */
	if (false == curr_peer->isForcedPeer) {
		curr_peer->pref_off_chan_num =
			pHddCtx->config->fTDLSPrefOffChanNum;
		curr_peer->op_class_for_pref_off_chan =
		    wlan_hdd_find_opclass(WLAN_HDD_GET_HAL_CTX(pAdapter),
				curr_peer->pref_off_chan_num,
				pHddCtx->config->fTDLSPrefOffChanBandwidth);
	}

	wlan_hdd_tdls_set_peer_link_status(curr_peer,
					   eTDLS_LINK_IDLE,
					   eTDLS_LINK_UNSPECIFIED);
	curr_peer->staId = OL_TXRX_INVALID_TDLS_PEER_ID;
ret_status:
	return status;
}

/**
 * wlan_hdd_tdls_peer_reset_discovery_processed() - reset discovery status
 * @pHddTdlsCtx: TDLS context
 *
 * This function resets discovery processing bit for all TDLS peers
 *
 * Caller has to take the lock before calling this function
 *
 * Return: 0
 */
static int32_t wlan_hdd_tdls_peer_reset_discovery_processed(tdlsCtx_t *
							    pHddTdlsCtx)
{
	int i;
	struct list_head *head;
	hddTdlsPeer_t *tmp;
	struct list_head *pos, *q;

	pHddTdlsCtx->discovery_peer_cnt = 0;

	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &pHddTdlsCtx->peer_list[i];
		list_for_each_safe(pos, q, head) {
			tmp = list_entry(pos, hddTdlsPeer_t, node);
			tmp->discovery_processed = 0;
		}
	}

	return 0;
}

/**
 * wlan_hdd_tdls_connected_peers() - Find the number of connected TDLS peers
 * @pAdapter: HDD adapter
 *
 * Return: The number of connected TDLS peers or 0 if error is detected
 */
uint16_t wlan_hdd_tdls_connected_peers(hdd_adapter_t *pAdapter)
{
	hdd_context_t *pHddCtx;

	if ((NULL == pAdapter) ||
	    (WLAN_HDD_ADAPTER_MAGIC != pAdapter->magic)) {
		hdd_err("invalid pAdapter: %p", pAdapter);
		return 0;
	}
	pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if (wlan_hdd_validate_context(pHddCtx))
		return 0;

	return pHddCtx->connected_peer_count;
}

/**
 * wlan_hdd_tdls_get_all_peers() - dump all TDLS peer info into output string
 * @pAdapter: HDD adapter
 * @buf: output string buffer to hold the peer info
 * @buflen: the size of output string buffer
 *
 * Return: The size (in bytes) of the valid peer info in the output buffer
 */
int wlan_hdd_tdls_get_all_peers(hdd_adapter_t *pAdapter, char *buf, int buflen)
{
	int i;
	int len, init_len;
	struct list_head *head;
	struct list_head *pos;
	hddTdlsPeer_t *curr_peer;
	tdlsCtx_t *pHddTdlsCtx;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	hdd_station_ctx_t *hdd_sta_ctx;

	ENTER();

	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return 0;

	if ((QDF_STA_MODE != pAdapter->device_mode)
	    && (QDF_P2P_CLIENT_MODE != pAdapter->device_mode)) {
		len = scnprintf(buf, buflen,
				"\nNo TDLS support for this adapter\n");
		return len;
	}

	hdd_sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(pAdapter);
	if (eConnectionState_Associated != hdd_sta_ctx->conn_info.connState) {
		len = scnprintf(buf, buflen, "\nSTA is not associated\n");
		return len;
	}

	init_len = buflen;
	len = scnprintf(buf, buflen, "\n%-18s%-3s%-4s%-3s%-5s\n",
			"MAC", "Id", "cap", "up", "RSSI");
	buf += len;
	buflen -= len;
	/*                           1234567890123456789012345678901234567 */
	len = scnprintf(buf, buflen, "---------------------------------\n");
	buf += len;
	buflen -= len;

	mutex_lock(&pHddCtx->tdls_lock);

	pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
	if (NULL == pHddTdlsCtx) {
		mutex_unlock(&pHddCtx->tdls_lock);
		len = scnprintf(buf, buflen, "TDLS not enabled\n");
		return len;
	}
	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &pHddTdlsCtx->peer_list[i];

		list_for_each(pos, head) {
			curr_peer = list_entry(pos, hddTdlsPeer_t, node);

			if (buflen < 32 + 1)
				break;
			len = scnprintf(buf, buflen,
					MAC_ADDRESS_STR "%3d%4s%3s%5d\n",
					MAC_ADDR_ARRAY(curr_peer->peerMac),
					curr_peer->staId,
					(curr_peer->tdls_support ==
					 eTDLS_CAP_SUPPORTED) ? "Y" : "N",
					TDLS_IS_CONNECTED(curr_peer) ? "Y" :
					"N", curr_peer->rssi);
			buf += len;
			buflen -= len;
		}
	}
	mutex_unlock(&pHddCtx->tdls_lock);
	EXIT();
	return init_len - buflen;
}

/**
 * wlan_hdd_tdls_connection_callback() - callback after tdls connection
 * @pAdapter: HDD adapter
 *
 * Return: Void
 */
void wlan_hdd_tdls_connection_callback(hdd_adapter_t *pAdapter)
{
	tdlsCtx_t *pHddTdlsCtx;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	uint32_t tx_period_t;

	if (NULL == pHddCtx) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("pHddCtx points to NULL"));
		return;
	}

	mutex_lock(&pHddCtx->tdls_lock);

	pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
	if (NULL == pHddTdlsCtx) {
		mutex_unlock(&pHddCtx->tdls_lock);
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("pHddTdlsCtx points to NULL"));
		return;
	}

	tx_period_t = pHddTdlsCtx->threshold_config.tx_period_t;

	hdd_debug("update %d", pHddTdlsCtx->threshold_config.tx_period_t);

	if (eTDLS_SUPPORT_ENABLED == pHddCtx->tdls_mode ||
	    eTDLS_SUPPORT_EXTERNAL_CONTROL == pHddCtx->tdls_mode) {
		wlan_hdd_tdls_peer_reset_discovery_processed(pHddTdlsCtx);
		pHddTdlsCtx->discovery_sent_cnt = 0;
		wlan_hdd_tdls_check_power_save_prohibited(pHddTdlsCtx->pAdapter);
		/* Start the connection tracker timer */
		wlan_hdd_tdls_timer_restart(pHddTdlsCtx->pAdapter,
					    &pHddTdlsCtx->peer_update_timer,
					    tx_period_t);
	}

	mutex_unlock(&pHddCtx->tdls_lock);

}

/**
 * wlan_hdd_tdls_disconnection_callback() - callback after tdls disconnection
 * @pAdapter: HDD adapter
 *
 * Return: Void
 */
void wlan_hdd_tdls_disconnection_callback(hdd_adapter_t *pAdapter)
{
	tdlsCtx_t *pHddTdlsCtx;
	hdd_context_t *pHddCtx;

	ENTER();

	pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if (!pHddCtx)
		return;

	mutex_lock(&pHddCtx->tdls_lock);

	pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
	if (NULL == pHddTdlsCtx) {
		mutex_unlock(&pHddCtx->tdls_lock);
		hdd_notice("pHddTdlsCtx is NULL");
		return;
	}
	pHddTdlsCtx->discovery_sent_cnt = 0;
	wlan_hdd_tdls_check_power_save_prohibited(pHddTdlsCtx->pAdapter);

	wlan_hdd_tdls_timers_stop(pHddTdlsCtx);

	pHddTdlsCtx->curr_candidate = NULL;

	mutex_unlock(&pHddCtx->tdls_lock);
}

/**
 * wlan_hdd_tdls_mgmt_completion_callback() - callback for TDLS management
 *                                            TX completion
 * @pAdapter: HDD adapter
 * @statusCode: management TX completion status
 *
 * Return: Void
 */
void wlan_hdd_tdls_mgmt_completion_callback(hdd_adapter_t *pAdapter,
					    uint32_t statusCode)
{
	pAdapter->mgmtTxCompletionStatus = statusCode;
	hdd_notice("Mgmt TX Completion %d", statusCode);
	complete(&pAdapter->tdls_mgmt_comp);
}

/**
 * wlan_hdd_tdls_increment_peer_count() - increment connected TDLS peer counter
 * @pAdapter: HDD adapter
 *
 * Return: Void
 */
void wlan_hdd_tdls_increment_peer_count(hdd_adapter_t *pAdapter)
{
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	ENTER();

	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return;

	mutex_lock(&pHddCtx->tdls_lock);

	pHddCtx->connected_peer_count++;
	wlan_hdd_tdls_check_power_save_prohibited(pAdapter);

	hdd_notice("Connected peer count %d",
		   pHddCtx->connected_peer_count);

	mutex_unlock(&pHddCtx->tdls_lock);
	EXIT();
}

/**
 * wlan_hdd_tdls_decrement_peer_count() - decrement connected TDLS peer counter
 * @pAdapter: HDD adapter
 *
 * Return: Void
 */
void wlan_hdd_tdls_decrement_peer_count(hdd_adapter_t *pAdapter)
{
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	uint16_t connected_peer_count;

	ENTER();

	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return;

	mutex_lock(&pHddCtx->tdls_lock);

	if (pHddCtx->connected_peer_count)
		pHddCtx->connected_peer_count--;
	wlan_hdd_tdls_check_power_save_prohibited(pAdapter);

	connected_peer_count = pHddCtx->connected_peer_count;
	mutex_unlock(&pHddCtx->tdls_lock);

	hdd_notice("Connected peer count %d", connected_peer_count);

	EXIT();
}


/**
 * wlan_hdd_tdls_find_progress_peer() - find peer if TDLS is ongoing
 * @pAdapter: HDD adapter
 * @mac: If NULL check for all the peer list, otherwise, skip this mac when
 *       skip_self is true
 * @skip_self: If true, skip this mac. otherwise, check all the peer list. if
 *             mac is NULL, this argument is ignored, and check for all the peer
 *             list.
 *
 * Return: Pointer to hddTdlsPeer_t if TDLS is ongoing. Otherwise return NULL.
 */
static hddTdlsPeer_t *wlan_hdd_tdls_find_progress_peer(hdd_adapter_t *pAdapter,
						       const u8 *mac,
						       u8 skip_self)
{
	int i;
	struct list_head *head;
	hddTdlsPeer_t *curr_peer;
	struct list_head *pos;
	tdlsCtx_t *pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);

	if (NULL == pHddTdlsCtx) {
		hdd_notice("pHddTdlsCtx is NULL");
		return NULL;
	}

	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &pHddTdlsCtx->peer_list[i];
		list_for_each(pos, head) {
			curr_peer = list_entry(pos, hddTdlsPeer_t, node);
			if (skip_self && mac
			    && !memcmp(mac, curr_peer->peerMac, 6)) {
				continue;
			} else {
				if (eTDLS_LINK_CONNECTING ==
				    curr_peer->link_status) {
					hdd_notice(MAC_ADDRESS_STR
						   " eTDLS_LINK_CONNECTING",
						   MAC_ADDR_ARRAY(curr_peer->peerMac));
					return curr_peer;
				}
			}
		}
	}
	return NULL;
}

/**
 * wlan_hdd_tdls_is_progress() - find the peer with ongoing TDLS progress
 * @pHddCtx: HDD context
 * @mac: mac address of the peer
 * @skip_self: if 1, skip checking self. If 0, search includes self
 *
 * Return: TDLS peer if found; NULL otherwise
 */
hddTdlsPeer_t *wlan_hdd_tdls_is_progress(hdd_context_t *pHddCtx,
					 const uint8_t *mac,
					 uint8_t skip_self)
{
	hdd_adapter_list_node_t *pAdapterNode = NULL, *pNext = NULL;
	hdd_adapter_t *pAdapter = NULL;
	tdlsCtx_t *pHddTdlsCtx = NULL;
	hddTdlsPeer_t *curr_peer = NULL;
	QDF_STATUS status = 0;

	status = hdd_get_front_adapter(pHddCtx, &pAdapterNode);
	while (NULL != pAdapterNode && QDF_STATUS_SUCCESS == status) {
		pAdapter = pAdapterNode->pAdapter;

		pHddTdlsCtx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
		if (NULL != pHddTdlsCtx) {
			curr_peer =
				wlan_hdd_tdls_find_progress_peer(pAdapter, mac,
								 skip_self);
			if (curr_peer)
				return curr_peer;
		}
		status = hdd_get_next_adapter(pHddCtx, pAdapterNode, &pNext);
		pAdapterNode = pNext;
	}

	return NULL;
}

/**
 * wlan_hdd_tdls_copy_scan_context() - Copy TDLS scan context
 * @pHddCtx: HDD context
 * @wiphy: wiphy pointer
 * @dev: net device
 * request: source scan context
 *
 * Copy the source scan context into the HDD context's TDLS scan context
 *
 * Return: 0 for success; negative errno otherwise
 */
int wlan_hdd_tdls_copy_scan_context(hdd_context_t *pHddCtx,
				    struct wiphy *wiphy,
				    struct cfg80211_scan_request *request)
{
	tdls_scan_context_t *scan_ctx;

	ENTER();

	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return -EINVAL;

	scan_ctx = &pHddCtx->tdls_scan_ctxt;

	scan_ctx->wiphy = wiphy;

	scan_ctx->scan_request = request;
	EXIT();
	return 0;
}

/**
 * wlan_hdd_tdls_scan_init_work() - schedule tdls scan work
 * @pHddCtx: HDD context
 * @wiphy: wiphy pointer
 * @dev: net device
 * @request: scan request
 * @delay: delay value to pass to the work scheduling
 * @source: scan request source(NL/Vendor scan)
 *
 * Return: Void
 */
static void wlan_hdd_tdls_scan_init_work(hdd_context_t *pHddCtx,
					 struct wiphy *wiphy,
					 struct cfg80211_scan_request *request,
					 unsigned long delay, uint8_t source)
{
	if (TDLS_CTX_MAGIC != pHddCtx->tdls_scan_ctxt.magic) {
		wlan_hdd_tdls_copy_scan_context(pHddCtx, wiphy, request);
		pHddCtx->tdls_scan_ctxt.attempt = 0;
		pHddCtx->tdls_scan_ctxt.magic = TDLS_CTX_MAGIC;
		pHddCtx->tdls_scan_ctxt.source = source;
	}
	schedule_delayed_work(&pHddCtx->tdls_scan_ctxt.tdls_scan_work, delay);
}

/**
 * wlan_hdd_tdls_scan_callback() - callback for TDLS scan operation
 * @pAdapter: HDD adapter
 * @wiphy: wiphy
 * @dev: net device
 * @request: scan request
 *
 * Return: negative = caller should stop and return error code immediately
 *         0 = caller should stop and return success immediately
 *         1 = caller can continue to scan
 */
int wlan_hdd_tdls_scan_callback(hdd_adapter_t *pAdapter, struct wiphy *wiphy,
				struct cfg80211_scan_request *request,
				uint8_t source)
{
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	u16 connectedTdlsPeers;
	hddTdlsPeer_t *curr_peer;
	unsigned long delay;
	int ret;

	ENTER();

	ret = wlan_hdd_validate_context(pHddCtx);
	if (ret)
		return ret;

	if (pHddCtx->tdls_umac_comp_active)
		return 1;

	/* if tdls is not enabled, then continue scan */
	if (eTDLS_SUPPORT_NOT_ENABLED == pHddCtx->tdls_mode)
		return 1;

	mutex_lock(&pHddCtx->tdls_lock);
	curr_peer = wlan_hdd_tdls_is_progress(pHddCtx, NULL, 0);
	if (NULL != curr_peer) {
		if (pHddCtx->tdls_scan_ctxt.reject++ >= TDLS_MAX_SCAN_REJECT) {
			pHddCtx->tdls_scan_ctxt.reject = 0;
			hdd_notice(MAC_ADDRESS_STR
				   ". scan rejected %d. force it to idle",
				   MAC_ADDR_ARRAY(curr_peer->peerMac),
				   pHddCtx->tdls_scan_ctxt.reject);

			wlan_hdd_tdls_set_peer_link_status(curr_peer,
						eTDLS_LINK_IDLE,
						eTDLS_LINK_UNSPECIFIED);
			mutex_unlock(&pHddCtx->tdls_lock);
			return 1;
		}
		mutex_unlock(&pHddCtx->tdls_lock);
		hdd_warn("tdls in progress. scan rejected %d",
			 pHddCtx->tdls_scan_ctxt.reject);
		return -EBUSY;
	}
	mutex_unlock(&pHddCtx->tdls_lock);

	/* tdls teardown is ongoing */
	if (eTDLS_SUPPORT_DISABLED == pHddCtx->tdls_mode) {
		connectedTdlsPeers = wlan_hdd_tdls_connected_peers(pAdapter);
		if (connectedTdlsPeers
		    && (pHddCtx->tdls_scan_ctxt.attempt <
			TDLS_MAX_SCAN_SCHEDULE)) {
			delay =
				(unsigned long)(TDLS_DELAY_SCAN_PER_CONNECTION *
						connectedTdlsPeers);
			hdd_debug("tdls disabled, but still connected_peers %d attempt %d. schedule scan %lu msec",
				    connectedTdlsPeers,
				    pHddCtx->tdls_scan_ctxt.attempt, delay);

			wlan_hdd_tdls_scan_init_work(pHddCtx, wiphy,
						     request,
						     msecs_to_jiffies(delay),
						     source);
			/* scan should not continue */
			return 0;
		}
		/* no connected peer or max retry reached, scan continue */
		hdd_debug("tdls disabled. connected_peers %d attempt %d. scan allowed",
			   connectedTdlsPeers,
			   pHddCtx->tdls_scan_ctxt.attempt);
		return 1;
	}
	/* while tdls is up, first time scan */
	else if (eTDLS_SUPPORT_ENABLED == pHddCtx->tdls_mode ||
		 eTDLS_SUPPORT_EXTERNAL_CONTROL == pHddCtx->tdls_mode ||
		 eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY == pHddCtx->tdls_mode) {
		/* disable implicit trigger logic & tdls operatoin */
		wlan_hdd_tdls_set_mode(pHddCtx, eTDLS_SUPPORT_DISABLED, false,
				       HDD_SET_TDLS_MODE_SOURCE_SCAN);
		/* indicate the teardown all connected to peer */
		connectedTdlsPeers = wlan_hdd_tdls_connected_peers(pAdapter);
		if (connectedTdlsPeers) {
			uint8_t staIdx;
			uint8_t num = 0;
			uint8_t i;
			bool allPeersBufStas = 1;
			hddTdlsPeer_t *curr_peer;
			hddTdlsPeer_t *connectedPeerList[HDD_MAX_NUM_TDLS_STA];

			/* If TDLSScan is enabled then allow scan and
			 * maintain tdls link regardless if peer is buffer
			 * sta capable or not and if device is sleep sta
			 * capable or not. If peer is not buffer sta
			 * capable, then Tx would stop when device
			 * initiates scan and there will be loss of Rx
			 * packets since peer would not know when device
			 * moves away from the tdls channel.
			 */
			if (1 == pHddCtx->config->enable_tdls_scan) {
				hdd_debug("TDLSScan enabled, keep tdls link and allow scan, connectedTdlsPeers: %d",
					   connectedTdlsPeers);
				return 1;
			}

			mutex_lock(&pHddCtx->tdls_lock);
			for (staIdx = 0; staIdx < pHddCtx->max_num_tdls_sta;
			     staIdx++) {
				if (pHddCtx->tdlsConnInfo[staIdx].staId) {
					curr_peer =
						wlan_hdd_tdls_find_all_peer(pHddCtx,
									    pHddCtx->
									    tdlsConnInfo
									    [staIdx].
									    peerMac.
									    bytes);
					if (curr_peer) {
						connectedPeerList[num++] =
							curr_peer;
						if (!(curr_peer->isBufSta))
							allPeersBufStas = 0;
					}
				}
			}

			if ((TDLS_MAX_CONNECTED_PEERS_TO_ALLOW_SCAN ==
			     connectedTdlsPeers) &&
			    (pHddCtx->config->fEnableTDLSSleepSta) &&
			    (allPeersBufStas)) {
				mutex_unlock(&pHddCtx->tdls_lock);
				/* All connected peers bufStas and we can be
				 * sleepSta so allow scan
				 */
				hdd_debug("All peers (num %d) bufSTAs, we can be sleep sta, so allow scan, tdls mode changed to %d",
					   connectedTdlsPeers,
					   pHddCtx->tdls_mode);
				return 1;
			}

			for (i = 0; i < num; i++) {
				hdd_notice("indicate TDLS teadown (staId %d)",
					   connectedPeerList[i]->staId);
				wlan_hdd_tdls_indicate_teardown
					(connectedPeerList[i]->pHddTdlsCtx->
					 pAdapter, connectedPeerList[i],
					 eSIR_MAC_TDLS_TEARDOWN_UNSPEC_REASON);
				hdd_send_wlan_tdls_teardown_event
					(eTDLS_TEARDOWN_SCAN,
					 connectedPeerList[i]->peerMac);
			}
			mutex_unlock(&pHddCtx->tdls_lock);

			/* schedule scan */
			delay =
				(unsigned long)(TDLS_DELAY_SCAN_PER_CONNECTION *
						connectedTdlsPeers);

			hdd_debug("tdls enabled (mode %d), connected_peers %d. schedule scan %lu msec",
				   pHddCtx->tdls_mode,
				   wlan_hdd_tdls_connected_peers(pAdapter), delay);

			wlan_hdd_tdls_scan_init_work(pHddCtx, wiphy,
						     request,
						     msecs_to_jiffies(delay),
						     source);
			/* scan should not continue */
			return 0;
		}
		/* no connected peer, scan continue */
		hdd_debug("tdls_mode %d, and no tdls connection. scan allowed",
			   pHddCtx->tdls_mode);
	}
	EXIT();
	return 1;
}

/**
 * wlan_hdd_tdls_scan_done_callback() - callback for tdls scan done event
 * @pAdapter: HDD adapter
 *
 * Return: Void
 */
void wlan_hdd_tdls_scan_done_callback(hdd_adapter_t *pAdapter)
{
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	ENTER();

	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return;

	if (pHddCtx->tdls_umac_comp_active)
		return;

	if (eTDLS_SUPPORT_NOT_ENABLED == pHddCtx->tdls_mode) {
		hdd_info("TDLS mode is disabled OR not enabled");
		return;
	}

	/* free allocated memory at scan time */
	wlan_hdd_tdls_free_scan_request(&pHddCtx->tdls_scan_ctxt);

	/* if tdls was enabled before scan, re-enable tdls mode */
	if (eTDLS_SUPPORT_ENABLED == pHddCtx->tdls_mode_last ||
	    eTDLS_SUPPORT_EXTERNAL_CONTROL == pHddCtx->tdls_mode_last ||
	    eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY == pHddCtx->tdls_mode_last) {
		hdd_debug("revert tdls mode: %d", pHddCtx->tdls_mode_last);

		wlan_hdd_tdls_set_mode(pHddCtx, pHddCtx->tdls_mode_last, false,
				       HDD_SET_TDLS_MODE_SOURCE_SCAN);
	}
	EXIT();
}

/**
 * wlan_hdd_tdls_timer_restart() - restart TDLS timer
 * @pAdapter: HDD adapter
 * @timer: timer to restart
 * @expirationTime: new expiration time to set for the timer
 *
 * Return: Void
 */
void wlan_hdd_tdls_timer_restart(hdd_adapter_t *pAdapter,
				 qdf_mc_timer_t *timer,
				 uint32_t expirationTime)
{
	hdd_station_ctx_t *pHddStaCtx;

	if (NULL == pAdapter || WLAN_HDD_ADAPTER_MAGIC != pAdapter->magic) {
		hdd_err("invalid pAdapter: %p", pAdapter);
		return;
	}

	pHddStaCtx = WLAN_HDD_GET_STATION_CTX_PTR(pAdapter);

	/* Check whether driver load unload is in progress */
	if (cds_is_load_or_unload_in_progress()) {
		hdd_err("Driver load/unload is in progress.");
		return;
	}

	if (hdd_conn_is_connected(pHddStaCtx)) {
		qdf_mc_timer_stop(timer);
		qdf_mc_timer_start(timer, expirationTime);
	}
}

/**
 * wlan_hdd_tdls_indicate_teardown() - indicate tdls teardown
 * @pAdapter: HDD adapter
 * @curr_peer: peer tdls teardown happened
 * @reason: teardown reason
 *
 * Return: Void
 */
void wlan_hdd_tdls_indicate_teardown(hdd_adapter_t *pAdapter,
				     hddTdlsPeer_t *curr_peer, uint16_t reason)
{
	if ((NULL == pAdapter || WLAN_HDD_ADAPTER_MAGIC != pAdapter->magic) ||
	    (NULL == curr_peer)) {
		hdd_err("parameters passed are invalid");
		if (!curr_peer)
			hdd_err("curr_peer is NULL");
		return;
	}

	if (eTDLS_LINK_CONNECTED != curr_peer->link_status)
		return;

	wlan_hdd_tdls_set_peer_link_status(curr_peer,
					   eTDLS_LINK_TEARING,
					   eTDLS_LINK_UNSPECIFIED);
	hdd_info("Teardown reason %d", reason);
	cfg80211_tdls_oper_request(pAdapter->dev,
				   curr_peer->peerMac,
				   NL80211_TDLS_TEARDOWN, reason, GFP_KERNEL);
}

/**
 * wlan_hdd_set_callback() - set state change callback on current TDLS peer
 * @curr_peer: current TDLS peer
 * @callback: state change callback
 *
 * Return: 0 if success; negative errno otherwise
 */
int wlan_hdd_set_callback(hddTdlsPeer_t *curr_peer,
			  cfg80211_exttdls_callback callback)
{
	hdd_context_t *pHddCtx;
	hdd_adapter_t *pAdapter;

	if (!curr_peer)
		return -EINVAL;

	pAdapter = curr_peer->pHddTdlsCtx->pAdapter;
	pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	if (NULL == pHddCtx)
		return -EINVAL;

	curr_peer->state_change_notification = callback;
	return 0;
}

/**
 * wlan_hdd_tdls_get_wifi_hal_state() - get tdls wifi hal state on current peer
 * @curr_peer: current TDLS peer
 * @state: output parameter to store the tdls wifi hal state
 * @reason: output parameter to store the reason of the current peer
 *
 * Return: Void
 */
void wlan_hdd_tdls_get_wifi_hal_state(hddTdlsPeer_t *curr_peer,
				      uint32_t *state, int32_t *reason)
{
	hdd_context_t *hddctx;
	hdd_adapter_t *adapter;

	if (!curr_peer) {
		hdd_err("curr_peer is NULL");
		return;
	}

	adapter = curr_peer->pHddTdlsCtx->pAdapter;
	hddctx = WLAN_HDD_GET_CTX(adapter);

	if (0 != (wlan_hdd_validate_context(hddctx)))
		return;

	*reason = curr_peer->reason;

	switch (curr_peer->link_status) {
	case eTDLS_LINK_IDLE:
	case eTDLS_LINK_DISCOVERED:
		*state = QCA_WIFI_HAL_TDLS_ENABLED;
		break;
	case eTDLS_LINK_DISCOVERING:
	case eTDLS_LINK_CONNECTING:
		*state = QCA_WIFI_HAL_TDLS_ENABLED;
		break;
	case eTDLS_LINK_CONNECTED:
		if ((hddctx->config->fEnableTDLSOffChannel) &&
			(hddctx->tdls_fw_off_chan_mode == ENABLE_CHANSWITCH))
			*state = QCA_WIFI_HAL_TDLS_ESTABLISHED_OFF_CHANNEL;
		else
			*state = QCA_WIFI_HAL_TDLS_ESTABLISHED;
		break;
	case eTDLS_LINK_TEARING:
		*state = QCA_WIFI_HAL_TDLS_DROPPED;
		break;
	}
}

/**
 * wlan_hdd_tdls_get_status() - get tdls status on current tdls peer
 * @pAdapter: HDD adapter
 * @mac: MAC address of current TDLS peer
 * @state: output parameter to store the tdls wifi hal state
 * @reason: output parameter to store the reason of the current peer
 *
 * Return: 0 if success; negative errno otherwise
 */
int wlan_hdd_tdls_get_status(hdd_adapter_t *pAdapter,
			     const uint8_t *mac, uint32_t *opclass,
			     uint32_t *channel, uint32_t *state,
			     int32_t *reason)
{
	hddTdlsPeer_t *curr_peer;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

	if (0 != (wlan_hdd_validate_context(pHddCtx)))
		return -EINVAL;

	mutex_lock(&pHddCtx->tdls_lock);
	curr_peer = wlan_hdd_tdls_find_peer(pAdapter, mac);
	if (curr_peer == NULL) {
		mutex_unlock(&pHddCtx->tdls_lock);
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  FL("curr_peer is NULL"));
		*state = QCA_WIFI_HAL_TDLS_DISABLED;
		*reason = eTDLS_LINK_UNSPECIFIED;
		return -EINVAL;
	}
	if (pHddCtx->config->fTDLSExternalControl &&
		(false == curr_peer->isForcedPeer)) {
		hdd_err("curr_peer is not Forced");
		*state = QCA_WIFI_HAL_TDLS_DISABLED;
		*reason = eTDLS_LINK_UNSPECIFIED;
	} else {
		wlan_hdd_tdls_determine_channel_opclass(pHddCtx, pAdapter,
					curr_peer, channel, opclass);
		wlan_hdd_tdls_get_wifi_hal_state(curr_peer, state, reason);
	}
	mutex_unlock(&pHddCtx->tdls_lock);

	return 0;
}

#ifdef FEATURE_WLAN_TDLS
static const struct nla_policy
	wlan_hdd_tdls_config_enable_policy[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX +
					   1] = {
	[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR] = {
		.type = NLA_UNSPEC,
		.len = QDF_MAC_ADDR_SIZE},
	[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_CHANNEL] = {.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_GLOBAL_OPERATING_CLASS] = {.type =
								NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX_LATENCY_MS] = {.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MIN_BANDWIDTH_KBPS] = {.type =
								NLA_U32},
};
static const struct nla_policy
	wlan_hdd_tdls_config_disable_policy[QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAX +
					    1] = {
	[QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAC_ADDR] = {
		.type = NLA_UNSPEC,
		.len = QDF_MAC_ADDR_SIZE},
};
static const struct nla_policy
	wlan_hdd_tdls_config_state_change_policy[QCA_WLAN_VENDOR_ATTR_TDLS_STATE_MAX
						 + 1] = {
	[QCA_WLAN_VENDOR_ATTR_TDLS_STATE_MAC_ADDR] = {
		.type = NLA_UNSPEC,
		.len = QDF_MAC_ADDR_SIZE},
	[QCA_WLAN_VENDOR_ATTR_TDLS_NEW_STATE] = {.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_TDLS_STATE_REASON] = {.type = NLA_S32},
	[QCA_WLAN_VENDOR_ATTR_TDLS_STATE_CHANNEL] = {.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_TDLS_STATE_GLOBAL_OPERATING_CLASS] = {.type =
								NLA_U32},
};
static const struct nla_policy
	wlan_hdd_tdls_config_get_status_policy
[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAX + 1] = {
	[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR] = {
		.type = NLA_UNSPEC,
		.len = QDF_MAC_ADDR_SIZE},
	[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_STATE] = {.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_REASON] = {.type = NLA_S32},
	[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_CHANNEL] = {.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_GLOBAL_OPERATING_CLASS] = {
							.type = NLA_U32},
};

static const struct nla_policy
	wlan_hdd_tdls_mode_configuration_policy
	[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_MAX + 1] = {
		[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TRIGGER_MODE] = {
						.type = NLA_U32},
		[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TX_STATS_PERIOD] = {
						.type = NLA_U32},
		[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TX_THRESHOLD] = {
						.type = NLA_U32},
		[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_DISCOVERY_PERIOD] = {
						.type = NLA_U32},
		[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_MAX_DISCOVERY_ATTEMPT] = {
						.type = NLA_U32},
		[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_IDLE_TIMEOUT] = {
						.type = NLA_U32},
		[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_IDLE_PACKET_THRESHOLD] = {
						.type = NLA_U32},
		[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_SETUP_RSSI_THRESHOLD] = {
						.type = NLA_S32},
		[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TEARDOWN_RSSI_THRESHOLD] = {
						.type = NLA_S32},
};

/**
 * __wlan_hdd_cfg80211_exttdls_get_status() - handle get status cfg80211 command
 * @wiphy: wiphy
 * @wdev: wireless dev
 * @data: netlink buffer with the mac address of the peer to get the status for
 * @data_len: length of data in bytes
 */
static int
__wlan_hdd_cfg80211_exttdls_get_status(struct wiphy *wiphy,
					 struct wireless_dev *wdev,
					 const void *data,
					 int data_len)
{
	uint8_t peer[ETH_ALEN] = { 0 };
	struct net_device *dev = wdev->netdev;
	hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
	hdd_context_t *pHddCtx = wiphy_priv(wiphy);
	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAX + 1];
	QDF_STATUS ret;
	uint32_t state;
	int32_t reason;
	uint32_t global_operating_class = 0;
	uint32_t channel = 0;
	struct sk_buff *skb = NULL;

	ENTER_DEV(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	ret = wlan_hdd_validate_context(pHddCtx);
	if (0 != ret)
		return -EINVAL;

	if (pHddCtx->config->fTDLSExternalControl == false)
		return -ENOTSUPP;

	if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAX,
		      data, data_len, wlan_hdd_tdls_config_get_status_policy)) {
		hdd_err("Invalid attribute");
		return -EINVAL;
	}
	if (!tb[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR]) {
		hdd_err("attr mac addr failed");
		return -EINVAL;
	}
	memcpy(peer,
	       nla_data(tb[QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_MAC_ADDR]),
	       sizeof(peer));
	hdd_notice(MAC_ADDRESS_STR, MAC_ADDR_ARRAY(peer));
	ret = wlan_hdd_tdls_get_status(pAdapter, peer, &global_operating_class,
				&channel, &state, &reason);
	if (0 != ret) {
		hdd_err("get status Failed");
		return -EINVAL;
	}
	skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy,
						  4 * sizeof(int32_t) +
						  NLMSG_HDRLEN);
	if (!skb) {
		hdd_err("cfg80211_vendor_cmd_alloc_reply_skb failed");
		return -EINVAL;
	}
	hdd_notice("Reason %d Status %d class %d channel %d peer " MAC_ADDRESS_STR,
		   reason, state, global_operating_class, channel,
		   MAC_ADDR_ARRAY(peer));
	if (nla_put_u32(skb,
			QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_STATE,
			state) ||
		nla_put_s32(skb,
			QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_REASON,
			reason) ||
		nla_put_u32(skb,
			QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_GLOBAL_OPERATING_CLASS,
			global_operating_class) ||
		nla_put_u32(skb,
			QCA_WLAN_VENDOR_ATTR_TDLS_GET_STATUS_CHANNEL,
			channel)) {
		hdd_err("nla put fail");
		goto nla_put_failure;
	}
	ret = cfg80211_vendor_cmd_reply(skb);
	EXIT();
	return ret;
nla_put_failure:
	kfree_skb(skb);
	return -EINVAL;
}

/**
 * __wlan_hdd_cfg80211_configure_tdls_mode() - configure the tdls mode
 * @wiphy: wiphy
 * @wdev: wireless dev
 * @data: netlink buffer
 * @data_len: length of data in bytes
 *
 * Return 0 for success and error code for failure
 */
static int
__wlan_hdd_cfg80211_configure_tdls_mode(struct wiphy *wiphy,
					 struct wireless_dev *wdev,
					 const void *data,
					 int data_len)
{
	struct net_device *dev = wdev->netdev;
	hdd_context_t *hdd_ctx = wiphy_priv(wiphy);
	hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_MAX + 1];
	int ret;
	enum tdls_support_mode tdls_mode;
	uint32_t trigger_mode;
	tdlsCtx_t *hdd_tdls_ctx;

	ENTER_DEV(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	ret = wlan_hdd_validate_context(hdd_ctx);
	if (0 != ret)
		return -EINVAL;

	if (NULL == adapter)
		return -EINVAL;

	if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_MAX,
		      data, data_len,
		      wlan_hdd_tdls_mode_configuration_policy)) {
		hdd_err("Invalid attribute");
		return -EINVAL;
	}

	if (!tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TRIGGER_MODE]) {
		hdd_err("attr tdls trigger mode failed");
		return -EINVAL;
	}
	trigger_mode = nla_get_u32(tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TRIGGER_MODE]);
	hdd_notice("TDLS trigger mode %d", trigger_mode);

	if (hdd_ctx->tdls_umac_comp_active) {
		ret = wlan_cfg80211_tdls_configure_mode(adapter->hdd_vdev,
						trigger_mode);
		return ret;
	}

	switch (trigger_mode) {
	case WLAN_HDD_VENDOR_TDLS_TRIGGER_MODE_EXPLICIT:
		tdls_mode = eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY;
		break;
	case WLAN_HDD_VENDOR_TDLS_TRIGGER_MODE_EXTERNAL:
		tdls_mode = eTDLS_SUPPORT_EXTERNAL_CONTROL;
		break;
	case WLAN_HDD_VENDOR_TDLS_TRIGGER_MODE_IMPLICIT:
		tdls_mode = eTDLS_SUPPORT_ENABLED;
		break;
	default:
		hdd_err("Invalid TDLS trigger mode");
		return -EINVAL;
	}
	wlan_hdd_tdls_set_mode(hdd_ctx, tdls_mode, false,
			HDD_SET_TDLS_MODE_SOURCE_USER);

	mutex_lock(&hdd_ctx->tdls_lock);

	hdd_tdls_ctx = adapter->sessionCtx.station.pHddTdlsCtx;
	if (NULL == hdd_tdls_ctx) {
		mutex_unlock(&hdd_ctx->tdls_lock);
		return -EINVAL;
	}

	if (tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TX_STATS_PERIOD]) {
		hdd_tdls_ctx->threshold_config.tx_period_t = nla_get_u32(
			tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TX_STATS_PERIOD]);
		hdd_info("attr tdls tx stats period %d",
			hdd_tdls_ctx->threshold_config.tx_period_t);
	}

	if (tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TX_THRESHOLD]) {
		hdd_tdls_ctx->threshold_config.tx_packet_n = nla_get_u32(
			tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TX_THRESHOLD]);
		hdd_info("attr tdls tx packet period %d",
			hdd_tdls_ctx->threshold_config.tx_packet_n);
	}

	if (tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_MAX_DISCOVERY_ATTEMPT]) {
		hdd_tdls_ctx->threshold_config.discovery_tries_n = nla_get_u32(
			tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_MAX_DISCOVERY_ATTEMPT]);
		hdd_info("attr tdls max discovery attempt %d",
			hdd_tdls_ctx->threshold_config.discovery_tries_n);
	}

	if (tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_IDLE_TIMEOUT]) {
		hdd_tdls_ctx->threshold_config.idle_timeout_t = nla_get_u32(
			tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_IDLE_TIMEOUT]);
		hdd_info("attr tdls idle time out period %d",
			hdd_tdls_ctx->threshold_config.idle_timeout_t);
	}

	if (tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_IDLE_PACKET_THRESHOLD]) {
		hdd_tdls_ctx->threshold_config.idle_packet_n = nla_get_u32(
			tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_IDLE_PACKET_THRESHOLD]);
		hdd_info("attr tdls idle pkt threshold %d",
			hdd_tdls_ctx->threshold_config.idle_packet_n);
	}

	if (tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_SETUP_RSSI_THRESHOLD]) {
		hdd_tdls_ctx->threshold_config.rssi_trigger_threshold = nla_get_s32(
			tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_SETUP_RSSI_THRESHOLD]);
		hdd_info("attr tdls rssi trigger threshold %d",
			hdd_tdls_ctx->threshold_config.rssi_trigger_threshold);
	}

	if (tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TEARDOWN_RSSI_THRESHOLD]) {
		hdd_tdls_ctx->threshold_config.rssi_teardown_threshold = nla_get_s32(
			tb[QCA_WLAN_VENDOR_ATTR_TDLS_CONFIG_TEARDOWN_RSSI_THRESHOLD]);
		hdd_info("attr tdls tx stats period %d",
			hdd_tdls_ctx->threshold_config.rssi_teardown_threshold);
	}

	mutex_unlock(&hdd_ctx->tdls_lock);

	EXIT();
	return ret;
}

/**
 * wlan_hdd_cfg80211_configure_tdls_mode() - configure tdls mode
 * @wiphy:   pointer to wireless wiphy structure.
 * @wdev:    pointer to wireless_dev structure.
 * @data:    Pointer to the data to be passed via vendor interface
 * @data_len:Length of the data to be passed
 *
 * Return:   Return the Success or Failure code.
 */
int wlan_hdd_cfg80211_configure_tdls_mode(struct wiphy *wiphy,
					struct wireless_dev *wdev,
					const void *data,
					int data_len)
{
	int ret;

	cds_ssr_protect(__func__);
	ret = __wlan_hdd_cfg80211_configure_tdls_mode(wiphy, wdev, data,
							data_len);
	cds_ssr_unprotect(__func__);

	return ret;
}

/**
 * wlan_hdd_cfg80211_exttdls_get_status() - get ext tdls status
 * @wiphy:   pointer to wireless wiphy structure.
 * @wdev:    pointer to wireless_dev structure.
 * @data:    Pointer to the data to be passed via vendor interface
 * @data_len:Length of the data to be passed
 *
 * Return:   Return the Success or Failure code.
 */
int wlan_hdd_cfg80211_exttdls_get_status(struct wiphy *wiphy,
					struct wireless_dev *wdev,
					const void *data,
					int data_len)
{
	int ret = 0;

	cds_ssr_protect(__func__);
	ret = __wlan_hdd_cfg80211_exttdls_get_status(wiphy, wdev, data,
							data_len);
	cds_ssr_unprotect(__func__);

	return ret;
}

/**
 * wlan_hdd_cfg80211_exttdls_callback() - notify cfg80211 state change
 * @mac: MAC address of the peer with state change
 * @state: New state
 * @reason: Reason to enter new state
 * @ctx: HDD adapter
 *
 * Return: 0 for success; negative errno otherwise
 */
static int wlan_hdd_cfg80211_exttdls_callback(const uint8_t *mac,
					      uint32_t global_operating_class,
					      uint32_t channel,
					      uint32_t state,
					      int32_t reason, void *ctx)
{
	hdd_adapter_t *pAdapter = (hdd_adapter_t *) ctx;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	struct sk_buff *skb = NULL;

	ENTER();

	if (wlan_hdd_validate_context(pHddCtx))
		return -EINVAL;
	if (pHddCtx->config->fTDLSExternalControl == false)
		return -ENOTSUPP;

	skb = cfg80211_vendor_event_alloc(pHddCtx->wiphy,
					  NULL,
					  EXTTDLS_EVENT_BUF_SIZE + NLMSG_HDRLEN,
					  QCA_NL80211_VENDOR_SUBCMD_TDLS_STATE_CHANGE_INDEX,
					  GFP_KERNEL);
	if (!skb) {
		hdd_err("cfg80211_vendor_event_alloc failed");
		return -EINVAL;
	}
	hdd_notice("Reason: %d Status: %d Class: %d Channel: %d tdls peer " MAC_ADDRESS_STR,
		   reason, state, global_operating_class, channel,
		   MAC_ADDR_ARRAY(mac));
	if (nla_put(skb, QCA_WLAN_VENDOR_ATTR_TDLS_STATE_MAC_ADDR,
		QDF_MAC_ADDR_SIZE, mac) ||
		nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_TDLS_NEW_STATE,
			state) ||
		nla_put_s32(skb, QCA_WLAN_VENDOR_ATTR_TDLS_STATE_REASON,
			reason) ||
		nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_TDLS_STATE_CHANNEL,
			channel) ||
		nla_put_u32(skb,
			QCA_WLAN_VENDOR_ATTR_TDLS_STATE_GLOBAL_OPERATING_CLASS,
			global_operating_class)) {
		hdd_err("nla put fail");
		goto nla_put_failure;
	}
	cfg80211_vendor_event(skb, GFP_KERNEL);
	EXIT();
	return 0;
nla_put_failure:
	kfree_skb(skb);
	return -EINVAL;
}

/**
 * __wlan_hdd_cfg80211_exttdls_enable() - enable an externally controllable
 *                                      TDLS peer and set parameters
 * wiphy: wiphy
 * @wdev: wireless dev pointer
 * @data: netlink buffer with peer MAC address and configuration parameters
 * @data_len: size of data in bytes
 *
 * This function sets channel, operation class, maximum latency and minimal
 * bandwidth parameters on a TDLS peer that's externally controllable.
 *
 * Return: 0 for success; negative errno otherwise
 */
static int
__wlan_hdd_cfg80211_exttdls_enable(struct wiphy *wiphy,
				     struct wireless_dev *wdev,
				     const void *data,
				     int data_len)
{
	uint8_t peer[ETH_ALEN] = { 0 };
	struct net_device *dev = wdev->netdev;
	hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
	hdd_context_t *pHddCtx = wiphy_priv(wiphy);
	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX + 1];
	QDF_STATUS status;
	tdls_req_params_t pReqMsg = { 0 };
	int ret;

	ENTER_DEV(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	status = wlan_hdd_validate_context(pHddCtx);
	if (0 != status)
		return -EINVAL;
	if (pHddCtx->config->fTDLSExternalControl == false) {
		hdd_err("TDLS External Control is not enabled");
		return -ENOTSUPP;
	}
	if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX,
		      data, data_len, wlan_hdd_tdls_config_enable_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}
	if (!tb[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR]) {
		hdd_err("attr mac addr failed");
		return -EINVAL;
	}
	memcpy(peer, nla_data(tb[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAC_ADDR]),
	       sizeof(peer));
	hdd_notice(MAC_ADDRESS_STR, MAC_ADDR_ARRAY(peer));
	if (!tb[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_CHANNEL]) {
		hdd_err("attr channel failed");
		return -EINVAL;
	}
	pReqMsg.channel =
		nla_get_s32(tb[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_CHANNEL]);
	hdd_notice("Channel Num (%d)", pReqMsg.channel);
	if (!tb[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_GLOBAL_OPERATING_CLASS]) {
		hdd_err("attr operating class failed");
		return -EINVAL;
	}
	pReqMsg.global_operating_class =
		nla_get_s32(tb
			    [QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_GLOBAL_OPERATING_CLASS]);
	hdd_notice("Operating class (%d)",
		   pReqMsg.global_operating_class);
	if (!tb[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX_LATENCY_MS]) {
		hdd_err("attr latency failed");
		return -EINVAL;
	}
	pReqMsg.max_latency_ms =
		nla_get_s32(tb[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MAX_LATENCY_MS]);

	hdd_notice("Latency (%d)", pReqMsg.max_latency_ms);

	if (!tb[QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MIN_BANDWIDTH_KBPS]) {
		hdd_err("attr bandwidth failed");
		return -EINVAL;
	}
	pReqMsg.min_bandwidth_kbps =
		nla_get_s32(tb
			    [QCA_WLAN_VENDOR_ATTR_TDLS_ENABLE_MIN_BANDWIDTH_KBPS]);

	hdd_notice("Bandwidth (%d)", pReqMsg.min_bandwidth_kbps);

	ret = wlan_hdd_tdls_extctrl_config_peer(
					pAdapter,
					peer,
					wlan_hdd_cfg80211_exttdls_callback,
					pReqMsg.channel,
					pReqMsg.max_latency_ms,
					pReqMsg.
					global_operating_class,
					pReqMsg.min_bandwidth_kbps);
	EXIT();
	return ret;
}

/**
 * wlan_hdd_cfg80211_exttdls_enable() - enable ext tdls
 * @wiphy:   pointer to wireless wiphy structure.
 * @wdev:    pointer to wireless_dev structure.
 * @data:    Pointer to the data to be passed via vendor interface
 * @data_len:Length of the data to be passed
 *
 * Return:   Return the Success or Failure code.
 */
int wlan_hdd_cfg80211_exttdls_enable(struct wiphy *wiphy,
					struct wireless_dev *wdev,
					const void *data,
					int data_len)
{
	int ret = 0;

	cds_ssr_protect(__func__);
	ret = __wlan_hdd_cfg80211_exttdls_enable(wiphy, wdev, data, data_len);
	cds_ssr_unprotect(__func__);

	return ret;
}

/**
 * __wlan_hdd_cfg80211_exttdls_disable() - disable an externally controllable
 *                                       TDLS peer
 * wiphy: wiphy
 * @wdev: wireless dev pointer
 * @data: netlink buffer with peer MAC address
 * @data_len: size of data in bytes
 *
 * This function disables an externally controllable TDLS peer
 *
 * Return: 0 for success; negative errno otherwise
 */
static int __wlan_hdd_cfg80211_exttdls_disable(struct wiphy *wiphy,
				      struct wireless_dev *wdev,
				      const void *data,
				      int data_len)
{
	u8 peer[ETH_ALEN] = {0};
	struct net_device *dev = wdev->netdev;
	hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
	hdd_context_t *pHddCtx = wiphy_priv(wiphy);
	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAX + 1];
	QDF_STATUS status;

	ENTER_DEV(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	status = wlan_hdd_validate_context(pHddCtx);
	if (0 != status)
		return -EINVAL;
	if (pHddCtx->config->fTDLSExternalControl == false)
		return -ENOTSUPP;

	if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAX,
		      data, data_len, wlan_hdd_tdls_config_disable_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}
	if (!tb[QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAC_ADDR]) {
		hdd_err("attr mac addr failed");
		return -EINVAL;
	}
	memcpy(peer, nla_data(tb[QCA_WLAN_VENDOR_ATTR_TDLS_DISABLE_MAC_ADDR]),
	       sizeof(peer));
	hdd_notice(MAC_ADDRESS_STR, MAC_ADDR_ARRAY(peer));
	status = wlan_hdd_tdls_extctrl_deconfig_peer(pAdapter, peer);
	EXIT();
	return status;
}

/**
 * wlan_hdd_cfg80211_exttdls_disable() - disable ext tdls
 * @wiphy:   pointer to wireless wiphy structure.
 * @wdev:    pointer to wireless_dev structure.
 * @data:    Pointer to the data to be passed via vendor interface
 * @data_len:Length of the data to be passed
 *
 * Return:   Return the Success or Failure code.
 */
int wlan_hdd_cfg80211_exttdls_disable(struct wiphy *wiphy,
					struct wireless_dev *wdev,
					const void *data,
					int data_len)
{
	int ret = 0;

	cds_ssr_protect(__func__);
	ret = __wlan_hdd_cfg80211_exttdls_disable(wiphy, wdev, data, data_len);
	cds_ssr_unprotect(__func__);

	return ret;
}

/**
 * wlan_hdd_tdls_add_station() - add or change a TDLS peer station
 * @wiphy: wiphy
 * @dev: net device
 * @mac: MAC address of the TDLS peer
 * @update: if non-0, modify the peer with StaParams; if 0, add new peer
 * @StaParams: station parameters for the TDLS to change
 *
 * Return: 0 if success; negative errno otherwise
 */
int wlan_hdd_tdls_add_station(struct wiphy *wiphy,
			      struct net_device *dev, const uint8_t *mac,
			      bool update, tCsrStaParams *StaParams)
{
	hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
	hdd_context_t *pHddCtx = wiphy_priv(wiphy);
	QDF_STATUS status;
	hddTdlsPeer_t *pTdlsPeer;
	enum tdls_link_status link_status;
	uint16_t numCurrTdlsPeers;
	unsigned long rc;
	int ret;
	int rate_idx;

	ENTER();

	ret = wlan_hdd_validate_context(pHddCtx);
	if (ret)
		return ret;

	if ((eTDLS_SUPPORT_NOT_ENABLED == pHddCtx->tdls_mode) ||
	    (eTDLS_SUPPORT_DISABLED == pHddCtx->tdls_mode)) {
		hdd_notice("TDLS mode is disabled OR not enabled in FW " MAC_ADDRESS_STR "Request declined.",
			   MAC_ADDR_ARRAY(mac));
		return -ENOTSUPP;
	}

	mutex_lock(&pHddCtx->tdls_lock);
	pTdlsPeer = wlan_hdd_tdls_get_peer(pAdapter, mac);

	if (NULL == pTdlsPeer) {
		mutex_unlock(&pHddCtx->tdls_lock);
		hdd_err(MAC_ADDRESS_STR " update %d not exist. return invalid",
			MAC_ADDR_ARRAY(mac), update);
		ret = -EINVAL;
		goto ret_status;
	}

	link_status = pTdlsPeer->link_status;
	/* in add station, we accept existing valid staId if there is */
	if ((0 == update) &&
	    ((pTdlsPeer->link_status >= eTDLS_LINK_CONNECTING) ||
	     (TDLS_STA_INDEX_VALID(pTdlsPeer->staId)))) {
		hdd_notice(MAC_ADDRESS_STR " link_status %d. staId %d. add station ignored.",
			   MAC_ADDR_ARRAY(mac), pTdlsPeer->link_status,
			   pTdlsPeer->staId);
		ret = 0;
		goto rel_lock;
	}
	/* in change station, we accept only when staId is valid */
	if ((1 == update) &&
	    ((pTdlsPeer->link_status > eTDLS_LINK_CONNECTING) ||
	     (!TDLS_STA_INDEX_VALID(pTdlsPeer->staId)))) {
		hdd_err(MAC_ADDRESS_STR " link status %d. staId %d. change station %s.",
			MAC_ADDR_ARRAY(mac), pTdlsPeer->link_status,
			pTdlsPeer->staId,
			(TDLS_STA_INDEX_VALID(pTdlsPeer->staId)) ? "ignored" :
			"declined");
		ret = (TDLS_STA_INDEX_VALID(pTdlsPeer->staId)) ? 0 : -EPERM;
		goto rel_lock;
	}

	/* when others are on-going, we want to change link_status to idle */
	if (NULL != wlan_hdd_tdls_is_progress(pHddCtx, mac, true)) {
		mutex_unlock(&pHddCtx->tdls_lock);
		hdd_notice(MAC_ADDRESS_STR " TDLS setup is ongoing. Request declined.",
			   MAC_ADDR_ARRAY(mac));
		ret = -EPERM;
		goto error;
	}

	/* first to check if we reached to maximum supported TDLS peer.
	 * TODO: for now, return -EPERM looks working fine,
	 * but need to check if any other errno fit into this category.
	 */
	numCurrTdlsPeers = wlan_hdd_tdls_connected_peers(pAdapter);
	if (pHddCtx->max_num_tdls_sta <= numCurrTdlsPeers) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: " MAC_ADDRESS_STR
			  " TDLS Max peer already connected. Request declined."
			  " Num of peers (%d), Max allowed (%d).",
			  __func__, MAC_ADDR_ARRAY(mac), numCurrTdlsPeers,
			  pHddCtx->max_num_tdls_sta);
		mutex_unlock(&pHddCtx->tdls_lock);
		ret = -EPERM;
		goto error;
	} else {
		hddTdlsPeer_t *pTdlsPeer;

		pTdlsPeer = wlan_hdd_tdls_find_peer(pAdapter, mac);
		if (pTdlsPeer) {
			link_status = pTdlsPeer->link_status;
			if (TDLS_IS_CONNECTED(pTdlsPeer)) {
				mutex_unlock(&pHddCtx->tdls_lock);
				QDF_TRACE(QDF_MODULE_ID_HDD,
					  QDF_TRACE_LEVEL_ERROR,
					  "%s: " MAC_ADDRESS_STR
					  " already connected. "
					  "Request declined.",
					  __func__, MAC_ADDR_ARRAY(mac));
				ret = -EPERM;
				goto ret_status;
			}
		}
	}
	if (0 == update)
		wlan_hdd_tdls_set_link_status(pAdapter,
					      mac,
					      eTDLS_LINK_CONNECTING,
					      eTDLS_LINK_SUCCESS);

	/* debug code */
	if (NULL != StaParams) {
		hdd_notice("TDLS Peer Parameters.");
		if (StaParams->htcap_present) {
			hdd_notice("ht_capa->cap_info: %0x",
				   StaParams->HTCap.capInfo);
			hdd_notice("ht_capa->extended_capabilities: %0x",
				   StaParams->HTCap.extendedHtCapInfo);
		}
		hdd_notice("params->capability: %0x", StaParams->capability);
		hdd_notice("params->ext_capab_len: %0x",
			   StaParams->extn_capability[0]);
		if (StaParams->vhtcap_present) {
			hdd_notice("rxMcsMap %x rxHighest %x txMcsMap %x txHighest %x",
				   StaParams->VHTCap.suppMcs.rxMcsMap,
				   StaParams->VHTCap.suppMcs.rxHighest,
				   StaParams->VHTCap.suppMcs.txMcsMap,
				   StaParams->VHTCap.suppMcs.txHighest);
		}
		hdd_notice("Supported rates:");
		for (rate_idx = 0;
		     rate_idx < sizeof(StaParams->supported_rates);
		     rate_idx++)
			hdd_notice("rate_idx [%d]: supported_rates %x ",
				   rate_idx,
				   StaParams->supported_rates[rate_idx]);
	} /* end debug code */
	else if ((1 == update) && (NULL == StaParams)) {
		mutex_unlock(&pHddCtx->tdls_lock);
		hdd_err("update is true, but staParams is NULL. Error!");
		ret = -EPERM;
		goto ret_status;
	}

	INIT_COMPLETION(pAdapter->tdls_add_station_comp);

	/* Update the number of stream for each peer */
	if ((NULL != StaParams) && (StaParams->htcap_present)) {
		hddTdlsPeer_t *tdls_peer;

		tdls_peer = wlan_hdd_tdls_find_peer(pAdapter, mac);
		if (NULL != tdls_peer)
			tdls_peer->spatial_streams =
			StaParams->HTCap.suppMcsSet[1];
	}
	mutex_unlock(&pHddCtx->tdls_lock);

	if (!update) {
		status = sme_add_tdls_peer_sta(WLAN_HDD_GET_HAL_CTX(pAdapter),
					       pAdapter->sessionId, mac);
	} else {
		status = sme_change_tdls_peer_sta(WLAN_HDD_GET_HAL_CTX(pAdapter),
						  pAdapter->sessionId, mac,
						  StaParams);
	}

	rc = wait_for_completion_timeout(&pAdapter->tdls_add_station_comp,
					 msecs_to_jiffies
						 (WAIT_TIME_TDLS_ADD_STA));

	if (!rc) {
		hdd_err("timeout waiting for tdls add station indication %ld  peer link status %u",
			rc, link_status);
		ret = -EPERM;
		goto error;
	}

	if (QDF_STATUS_SUCCESS != pAdapter->tdlsAddStaStatus) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: Add Station is unsuccessful", __func__);
		ret = -EPERM;
		goto error;
	}

	goto ret_status;

error:
	wlan_hdd_tdls_set_link_status(pAdapter,
				      mac,
				      eTDLS_LINK_IDLE, eTDLS_LINK_UNSPECIFIED);
	goto ret_status;
rel_lock:
	mutex_unlock(&pHddCtx->tdls_lock);
ret_status:
	return ret;
}

#if TDLS_MGMT_VERSION2
/**
 * __wlan_hdd_cfg80211_tdls_mgmt() - handle management actions on a given peer
 * @wiphy: wiphy
 * @dev: net device
 * @peer: MAC address of the TDLS peer
 * @action_code: action code
 * @dialog_token: dialog token
 * @status_code: status code
 * @peer_capability: peer capability
 * @buf: additional IE to include
 * @len: length of buf in bytes
 *
 * Return: 0 if success; negative errno otherwise
 */
static int __wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
				struct net_device *dev, u8 *peer,
				u8 action_code, u8 dialog_token,
				u16 status_code, u32 peer_capability,
				const u8 *buf, size_t len)
#else
/**
 * __wlan_hdd_cfg80211_tdls_mgmt() - handle management actions on a given peer
 * @wiphy: wiphy
 * @dev: net device
 * @peer: MAC address of the TDLS peer
 * @action_code: action code
 * @dialog_token: dialog token
 * @status_code: status code
 * @buf: additional IE to include
 * @len: length of buf in bytes
 *
 * Return: 0 if success; negative errno otherwise
 */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0))
static int __wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
				struct net_device *dev, const uint8_t *peer,
				uint8_t action_code, uint8_t dialog_token,
				uint16_t status_code, uint32_t peer_capability,
				bool initiator, const uint8_t *buf,
				size_t len)
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0))
static int __wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
				struct net_device *dev, const uint8_t *peer,
				uint8_t action_code, uint8_t dialog_token,
				uint16_t status_code, uint32_t peer_capability,
				const uint8_t *buf, size_t len)
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0))
static int __wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
				struct net_device *dev, uint8_t *peer,
				uint8_t action_code, uint8_t dialog_token,
				uint16_t status_code, uint32_t peer_capability,
				const uint8_t *buf, size_t len)
#else
static int __wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
				struct net_device *dev, uint8_t *peer,
				uint8_t action_code, uint8_t dialog_token,
				uint16_t status_code, const uint8_t *buf,
				size_t len)
#endif
#endif
{

	hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
	hdd_context_t *pHddCtx = wiphy_priv(wiphy);
	hdd_station_ctx_t *hdd_sta_ctx;
	tdlsCtx_t *hdd_tdls_ctx;
	u8 peerMac[QDF_MAC_ADDR_SIZE];
	QDF_STATUS status;
	int max_sta_failed = 0;
	int responder;
	unsigned long rc;
	uint16_t numCurrTdlsPeers;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0))
#if !(TDLS_MGMT_VERSION2)
	u32 peer_capability;

	peer_capability = 0;
#endif
#endif

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EINVAL;
	}

	if (wlan_hdd_validate_session_id(pAdapter->sessionId)) {
		hdd_err("invalid session id: %d", pAdapter->sessionId);
		return -EINVAL;
	}

	MTRACE(qdf_trace(QDF_MODULE_ID_HDD,
			 TRACE_CODE_HDD_CFG80211_TDLS_MGMT,
			 pAdapter->sessionId, action_code));

	if (wlan_hdd_validate_context(pHddCtx))
		return -EINVAL;

	if (eTDLS_SUPPORT_NOT_ENABLED == pHddCtx->tdls_mode) {
		hdd_notice("TDLS mode is disabled OR not enabled in FW." MAC_ADDRESS_STR " action %d declined.",
			   MAC_ADDR_ARRAY(peer), action_code);
		return -ENOTSUPP;
	}

	hdd_sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(pAdapter);

	if (pHddCtx->tdls_umac_comp_active)
		return wlan_cfg80211_tdls_mgmt(pHddCtx->hdd_pdev, dev,
					       peer,
					       action_code, dialog_token,
					       status_code, peer_capability,
					       buf, len);

	/*
	 * STA or P2P client should be connected and authenticated before
	 *  sending any TDLS frames
	 */
	if ((eConnectionState_Associated !=
	     hdd_sta_ctx->conn_info.connState) ||
	     (false == hdd_sta_ctx->conn_info.uIsAuthenticated)) {
		hdd_err("STA is not connected or not authenticated. connState %u, uIsAuthenticated %u",
			hdd_sta_ctx->conn_info.connState,
			hdd_sta_ctx->conn_info.uIsAuthenticated);
		return -EAGAIN;
	}

	/* other than teardown frame, mgmt frames are not sent if disabled */
	if (SIR_MAC_TDLS_TEARDOWN != action_code) {
		if (!cds_check_is_tdls_allowed(pAdapter->device_mode)) {
			hdd_err("TDLS not allowed, reject TDLS MGMT, action_code=%d",
				action_code);
			return -EPERM;
		}
		/* if tdls_mode is disabled, then decline the peer's request */
		if (eTDLS_SUPPORT_DISABLED == pHddCtx->tdls_mode) {
			hdd_notice(MAC_ADDRESS_STR " TDLS mode is disabled. action %d declined.",
				   MAC_ADDR_ARRAY(peer), action_code);
			return -ENOTSUPP;
		}
		if (pHddCtx->tdls_nss_switch_in_progress) {
			hdd_err("TDLS antenna switch in progress, action %d declined for "
				MAC_ADDRESS_STR, action_code, MAC_ADDR_ARRAY(peer));
			return -EAGAIN;
		}
	}

	if (WLAN_IS_TDLS_SETUP_ACTION(action_code)) {
		mutex_lock(&pHddCtx->tdls_lock);
		if (NULL != wlan_hdd_tdls_is_progress(pHddCtx, peer, true)) {
			mutex_unlock(&pHddCtx->tdls_lock);
			QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
				  "%s: " MAC_ADDRESS_STR
				  " TDLS setup is ongoing. action %d declined.",
				  __func__, MAC_ADDR_ARRAY(peer), action_code);
			return -EPERM;
		}
		mutex_unlock(&pHddCtx->tdls_lock);
	}
	/*
	 * Discard TDLS Discovery request and setup confirm if violates
	 * ACM rules
	 */
	if ((SIR_MAC_TDLS_DIS_REQ == action_code ||
		SIR_MAC_TDLS_SETUP_CNF == action_code) &&
		(hdd_wmm_is_active(pAdapter)) &&
		!(pAdapter->hddWmmStatus.wmmAcStatus[OL_TX_WMM_AC_VI].wmmAcAccessAllowed)) {
			QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_WARN,
			"%s: Admission control is set to VI, action %d is not allowed.",
			__func__, action_code);
			return -EPERM;
	}

	if (SIR_MAC_TDLS_SETUP_REQ == action_code ||
	    SIR_MAC_TDLS_SETUP_RSP == action_code) {
		numCurrTdlsPeers = wlan_hdd_tdls_connected_peers(pAdapter);
		if (pHddCtx->max_num_tdls_sta <= numCurrTdlsPeers) {
			/* 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 (SIR_MAC_TDLS_SETUP_REQ == action_code) {
				QDF_TRACE(QDF_MODULE_ID_HDD,
					  QDF_TRACE_LEVEL_ERROR,
					  "%s: " MAC_ADDRESS_STR
					  " TDLS Max peer already connected. action (%d) declined. Num of peers (%d), Max allowed (%d).",
					  __func__, MAC_ADDR_ARRAY(peer),
					  action_code, numCurrTdlsPeers,
					  pHddCtx->max_num_tdls_sta);
				return -EINVAL;
			}
			/* maximum reached. tweak to send
			 * error code to peer and return error
			 * code to supplicant
			 */
			status_code = eSIR_MAC_UNSPEC_FAILURE_STATUS;
			QDF_TRACE(QDF_MODULE_ID_HDD,
				  QDF_TRACE_LEVEL_ERROR,
				  "%s: " MAC_ADDRESS_STR
				  " TDLS Max peer already connected, send response status (%d). Num of peers (%d), Max allowed (%d).",
				  __func__, MAC_ADDR_ARRAY(peer),
				  status_code, numCurrTdlsPeers,
				  pHddCtx->max_num_tdls_sta);
			max_sta_failed = -EPERM;
			/* fall through to send setup resp
			 * with failure status code
			 */
		} else {
			hddTdlsPeer_t *pTdlsPeer;

			mutex_lock(&pHddCtx->tdls_lock);
			pTdlsPeer =
				wlan_hdd_tdls_find_peer(pAdapter, peer);
			if (pTdlsPeer) {
				if (TDLS_IS_CONNECTED(pTdlsPeer)) {
					mutex_unlock(&pHddCtx->tdls_lock);
					QDF_TRACE(QDF_MODULE_ID_HDD,
						  QDF_TRACE_LEVEL_ERROR,
						  "%s:" MAC_ADDRESS_STR
						  " already connected."
						  " action %d declined.",
						  __func__,
						  MAC_ADDR_ARRAY(peer),
						  action_code);
					return -EPERM;
				}
			}
			mutex_unlock(&pHddCtx->tdls_lock);
		}
	}
	qdf_mem_copy(peerMac, peer, 6);

	hdd_notice("tdls_mgmt" MAC_ADDRESS_STR " action %d, dialog_token %d status %d, len = %zu",
		   MAC_ADDR_ARRAY(peer), action_code, dialog_token,
		   status_code, len);

	/*Except teardown responder will not be used so just make 0 */
	responder = 0;
	if (SIR_MAC_TDLS_TEARDOWN == action_code) {
		hddTdlsPeer_t *pTdlsPeer;

		mutex_lock(&pHddCtx->tdls_lock);
		pTdlsPeer = wlan_hdd_tdls_find_peer(pAdapter, peerMac);
		if (!pTdlsPeer) {
			mutex_unlock(&pHddCtx->tdls_lock);
			QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
				  "%s: " MAC_ADDRESS_STR " peer doesn't exist",
				  __func__, MAC_ADDR_ARRAY(peer));
			return -EPERM;
		}

		if (TDLS_IS_CONNECTED(pTdlsPeer))
			responder = pTdlsPeer->is_responder;
		else {
			QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
				  "%s: " MAC_ADDRESS_STR
				  " peer doesn't exist or not connected %d "
				  "dialog_token %d status %d, len = %zu",
				  __func__, MAC_ADDR_ARRAY(peer),
				  pTdlsPeer->link_status,
				  dialog_token, status_code, len);
			mutex_unlock(&pHddCtx->tdls_lock);
			return -EPERM;
		}
		mutex_unlock(&pHddCtx->tdls_lock);
	}

	/* For explicit trigger of DIS_REQ come out of BMPS for
	 * successfully receiving DIS_RSP from peer.
	 */
	if ((SIR_MAC_TDLS_SETUP_RSP == action_code) ||
	    (SIR_MAC_TDLS_SETUP_CNF == action_code) ||
	    (SIR_MAC_TDLS_DIS_RSP == action_code) ||
	    (SIR_MAC_TDLS_DIS_REQ == action_code)) {
		/* Fw will take care if PS offload is enabled. */
		if (SIR_MAC_TDLS_DIS_REQ != action_code)
			wlan_hdd_tdls_set_cap(pAdapter, peerMac,
					      eTDLS_CAP_SUPPORTED);
	}

	/* make sure doesn't call send_mgmt() while it is pending */
	if (TDLS_CTX_MAGIC == pAdapter->mgmtTxCompletionStatus) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: " MAC_ADDRESS_STR
			  " action %d couldn't sent, as one is pending. return EBUSY",
			  __func__, MAC_ADDR_ARRAY(peer), action_code);
		return -EBUSY;
	}

	pAdapter->mgmtTxCompletionStatus = TDLS_CTX_MAGIC;
	INIT_COMPLETION(pAdapter->tdls_mgmt_comp);

	status = sme_send_tdls_mgmt_frame(WLAN_HDD_GET_HAL_CTX(pAdapter),
					  pAdapter->sessionId, peerMac,
					  action_code, dialog_token,
					  status_code, peer_capability,
					  (uint8_t *) buf, len,
					  !responder);

	if (QDF_STATUS_SUCCESS != status) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: sme_send_tdls_mgmt_frame failed!", __func__);
		pAdapter->mgmtTxCompletionStatus = false;
		return -EINVAL;
	}

	if (SIR_MAC_TDLS_TEARDOWN == action_code &&
	    pHddCtx->tdls_nss_switch_in_progress) {
		mutex_lock(&pHddCtx->tdls_lock);
		if (pHddCtx->tdls_teardown_peers_cnt != 0)
			pHddCtx->tdls_teardown_peers_cnt--;
		if (pHddCtx->tdls_teardown_peers_cnt == 0) {
			if (pHddCtx->tdls_nss_transition_mode ==
			    TDLS_NSS_TRANSITION_1x1_to_2x2) {
				/* TDLS NSS switch is fully completed, so
				 * reset the flags.
				 */
				hdd_info("TDLS NSS switch is fully completed");
				pHddCtx->tdls_nss_switch_in_progress = false;
				pHddCtx->tdls_nss_teardown_complete = false;
			} else {
				/* TDLS NSS switch is not yet completed, but
				 * tdls teardown is completed for all the
				 * peers.
				 */
				hdd_info("TDLS teardown is completed and NSS switch still in progress");
				pHddCtx->tdls_nss_teardown_complete = true;
			}
		}
		mutex_unlock(&pHddCtx->tdls_lock);
	}
	hdd_info("Wait for tdls_mgmt_comp. Timeout %u ms",
		WAIT_TIME_TDLS_MGMT);

	rc = wait_for_completion_timeout(&pAdapter->tdls_mgmt_comp,
					 msecs_to_jiffies(WAIT_TIME_TDLS_MGMT));

	if ((0 == rc) || (true != pAdapter->mgmtTxCompletionStatus)) {
		hdd_err("%s rc %ld mgmtTxCompletionStatus %u",
			!rc ? "Mgmt Tx Completion timed out" : "Mgmt Tx Completion failed",
			rc, pAdapter->mgmtTxCompletionStatus);

		if (cds_is_driver_recovering() ||
		    cds_is_driver_in_bad_state()) {
			hdd_err("Recovery in Progress. State: 0x%x Ignore!!!",
				cds_get_driver_state());
			return -EAGAIN;
		}

		if (cds_is_driver_unloading()) {
			hdd_err("Unload in progress. State: 0x%x Ignore!!!",
				cds_get_driver_state());
			return -EAGAIN;
		}

		mutex_lock(&pHddCtx->tdls_lock);
		hdd_tdls_ctx = WLAN_HDD_GET_TDLS_CTX_PTR(pAdapter);
		if (hdd_tdls_ctx) {
			if (rc <= 0 &&
			    (((qdf_get_monotonic_boottime() -
			       hdd_tdls_ctx->last_flush_ts) >
			      TDLS_ENABLE_CDS_FLUSH_INTERVAL)
			     || !(hdd_tdls_ctx->last_flush_ts))) {
				hdd_tdls_ctx->last_flush_ts =
					qdf_get_monotonic_boottime();
				mutex_unlock(&pHddCtx->tdls_lock);
				cds_flush_logs(WLAN_LOG_TYPE_FATAL,
					       WLAN_LOG_INDICATOR_HOST_DRIVER,
					       WLAN_LOG_REASON_HDD_TIME_OUT,
					       true, false);
			} else
				mutex_unlock(&pHddCtx->tdls_lock);
		} else
			mutex_unlock(&pHddCtx->tdls_lock);

		pAdapter->mgmtTxCompletionStatus = false;
		return -EINVAL;
	}
	hdd_info("Mgmt Tx Completion status %ld TxCompletion %u",
		rc, pAdapter->mgmtTxCompletionStatus);

	if (max_sta_failed)
		return max_sta_failed;

	if (SIR_MAC_TDLS_SETUP_RSP == action_code)
		return wlan_hdd_tdls_set_responder(pAdapter, peerMac, false);
	else if (SIR_MAC_TDLS_SETUP_CNF == action_code)
		return wlan_hdd_tdls_set_responder(pAdapter, peerMac, true);

	return 0;
}

/**
 * wlan_hdd_cfg80211_tdls_mgmt() - cfg80211 tdls mgmt handler function
 * @wiphy: Pointer to wiphy structure.
 * @dev: Pointer to net_device structure.
 * @peer: peer address
 * @action_code: action code
 * @dialog_token: dialog token
 * @status_code: status code
 * @peer_capability: peer capability
 * @buf: buffer
 * @len: Length of @buf
 *
 * This is the cfg80211 tdls mgmt handler function which invokes
 * the internal function @__wlan_hdd_cfg80211_tdls_mgmt with
 * SSR protection.
 *
 * Return: 0 for success, error number on failure.
 */
#if TDLS_MGMT_VERSION2
int wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
					struct net_device *dev,
					u8 *peer, u8 action_code,
					u8 dialog_token,
					u16 status_code, u32 peer_capability,
					const u8 *buf, size_t len)
#else /* TDLS_MGMT_VERSION2 */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)) || defined(WITH_BACKPORTS)
int wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
					struct net_device *dev,
					const u8 *peer, u8 action_code,
					u8 dialog_token, u16 status_code,
					u32 peer_capability, bool initiator,
					const u8 *buf, size_t len)
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0))
int wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
					struct net_device *dev,
					const u8 *peer, u8 action_code,
					u8 dialog_token, u16 status_code,
					u32 peer_capability, const u8 *buf,
					size_t len)
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0))
int wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
					struct net_device *dev,
					u8 *peer, u8 action_code,
					u8 dialog_token,
					u16 status_code, u32 peer_capability,
					const u8 *buf, size_t len)
#else
int wlan_hdd_cfg80211_tdls_mgmt(struct wiphy *wiphy,
					struct net_device *dev,
					u8 *peer, u8 action_code,
					u8 dialog_token,
					u16 status_code, const u8 *buf,
					size_t len)
#endif
#endif
{
	int ret;

	cds_ssr_protect(__func__);
#if TDLS_MGMT_VERSION2
	ret = __wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer, action_code,
						dialog_token, status_code,
						peer_capability, buf, len);
#else /* TDLS_MGMT_VERSION2 */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)) || defined(WITH_BACKPORTS)
	ret = __wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer, action_code,
					dialog_token, status_code,
					peer_capability, initiator,
					buf, len);
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0))
	ret = __wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer, action_code,
					dialog_token, status_code,
					peer_capability, buf, len);
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0))
	ret = __wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer, action_code,
					dialog_token, status_code,
					peer_capability, buf, len);
#else
	ret = __wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer, action_code,
					dialog_token, status_code, buf, len);
#endif
#endif

	cds_ssr_unprotect(__func__);

	return ret;
}

/**
 * wlan_hdd_tdls_extctrl_config_peer() - configure an externally controllable
 *                                       TDLS peer
 * @pAdapter: HDD adapter
 * @peer: MAC address of the TDLS peer
 * @callback: Callback to set on the peer
 * @chan: Channel
 * @max_latency: Maximum latency
 * @op_class: Operation class
 * @min_bandwidth: Minimal bandwidth
 *
 * Return: 0 on success; negative otherwise
 */
int wlan_hdd_tdls_extctrl_config_peer(hdd_adapter_t *pAdapter,
				      const uint8_t *peer,
				      cfg80211_exttdls_callback callback,
				      u32 chan,
				      u32 max_latency,
				      u32 op_class, u32 min_bandwidth)
{
	hddTdlsPeer_t *pTdlsPeer;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	int status = 0;

	QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_INFO,
		  "%s : NL80211_TDLS_SETUP for " MAC_ADDRESS_STR,
		  __func__, MAC_ADDR_ARRAY(peer));
	if ((false == pHddCtx->config->fTDLSExternalControl) ||
	    (false == pHddCtx->config->fEnableTDLSImplicitTrigger)) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_INFO,
			  "%s TDLS External control or Implicit Trigger not enabled ",
			  __func__);
		status = -ENOTSUPP;
		goto ret_status;
	}

	mutex_lock(&pHddCtx->tdls_lock);
	pTdlsPeer = wlan_hdd_tdls_get_peer(pAdapter, peer);
	if (pTdlsPeer == NULL) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: peer " MAC_ADDRESS_STR " does not exist",
			  __func__, MAC_ADDR_ARRAY(peer));
		status = -EINVAL;
		goto rel_lock;
	}
	mutex_unlock(&pHddCtx->tdls_lock);

	if (0 != wlan_hdd_tdls_set_force_peer(pAdapter, peer, true)) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s TDLS Add Force Peer Failed", __func__);
		status = -EINVAL;
		goto ret_status;
	}
	/* Update the peer mac to firmware, so firmware
	 * could update the connection table
	 */
	if (0 != wlan_hdd_tdls_update_peer_mac(pAdapter, peer,
	    eSME_TDLS_PEER_ADD_MAC_ADDR)) {
		hdd_err("TDLS Peer mac update Failed " MAC_ADDRESS_STR,
			MAC_ADDR_ARRAY(peer));
		status = -EINVAL;
		goto ret_status;
	}

	pHddCtx->tdls_external_peer_count++;

	/* validate if off channel is DFS channel */
	if (wlan_reg_is_dfs_ch(pHddCtx->hdd_pdev, chan)) {
		hdd_err("Resetting TDLS off-channel from %d to %d",
		       chan, CFG_TDLS_PREFERRED_OFF_CHANNEL_NUM_DEFAULT);
		chan = CFG_TDLS_PREFERRED_OFF_CHANNEL_NUM_DEFAULT;
	}

	if (0 != wlan_hdd_tdls_set_extctrl_param(pAdapter, peer,
						 chan, max_latency,
						 op_class, min_bandwidth)) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s TDLS Set Peer's External Ctrl Parameter Failed",
			  __func__);
		status = -EINVAL;
		goto ret_status;
	}

	mutex_lock(&pHddCtx->tdls_lock);
	pTdlsPeer = wlan_hdd_tdls_get_peer(pAdapter, peer);
	if (pTdlsPeer == NULL) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: peer " MAC_ADDRESS_STR " does not exist",
			  __func__, MAC_ADDR_ARRAY(peer));
		status = -EINVAL;
		goto rel_lock;
	}
	if (0 != wlan_hdd_set_callback(pTdlsPeer, callback)) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s TDLS set callback Failed", __func__);
		status = -EINVAL;
		goto rel_lock;
	}

	mutex_unlock(&pHddCtx->tdls_lock);

	/* set tdls connection tracker state */
	cds_set_tdls_ct_mode(pHddCtx);

	return status;
rel_lock:
	mutex_unlock(&pHddCtx->tdls_lock);
ret_status:
	return status;
}

/**
 * wlan_hdd_tdls_extctrl_deconfig_peer() - de-configure an externally
 *                                         controllable TDLS peer
 * @pAdapter: HDD adapter
 * @peer: MAC address of the tdls peer
 *
 * Return: 0 if success; negative errno otherwisw
 */
int wlan_hdd_tdls_extctrl_deconfig_peer(hdd_adapter_t *pAdapter,
					const uint8_t *peer)
{
	hddTdlsPeer_t *pTdlsPeer;
	hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
	int status = 0;

	QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_INFO,
		  "%s : NL80211_TDLS_TEARDOWN for " MAC_ADDRESS_STR,
		  __func__, MAC_ADDR_ARRAY(peer));
	if ((false == pHddCtx->config->fTDLSExternalControl) ||
	    (false == pHddCtx->config->fEnableTDLSImplicitTrigger)) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_INFO,
			  "%s TDLS External control or Implicit Trigger not enabled ",
			  __func__);
		status = -ENOTSUPP;
		goto ret_status;
	}

	mutex_lock(&pHddCtx->tdls_lock);
	pTdlsPeer = wlan_hdd_tdls_find_peer(pAdapter, peer);
	if (NULL == pTdlsPeer) {
		hdd_notice("peer matching" MAC_ADDRESS_STR "not found",
			   MAC_ADDR_ARRAY(peer));
		status = -EINVAL;
		goto rel_lock;
	}

	wlan_hdd_tdls_indicate_teardown(pAdapter, pTdlsPeer,
					eSIR_MAC_TDLS_TEARDOWN_UNSPEC_REASON);
	hdd_send_wlan_tdls_teardown_event(eTDLS_TEARDOWN_EXT_CTRL,
					  pTdlsPeer->peerMac);
	mutex_unlock(&pHddCtx->tdls_lock);

	if (0 != wlan_hdd_tdls_set_force_peer(pAdapter, peer, false)) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s Failed", __func__);
		status = -EINVAL;
		goto ret_status;
	}

	if (pHddCtx->tdls_external_peer_count)
		pHddCtx->tdls_external_peer_count--;

	mutex_lock(&pHddCtx->tdls_lock);
	pTdlsPeer = wlan_hdd_tdls_find_peer(pAdapter, peer);
	if (NULL == pTdlsPeer) {
		hdd_notice("peer matching" MAC_ADDRESS_STR "not found",
			   MAC_ADDR_ARRAY(peer));
		status = -EINVAL;
		goto rel_lock;
	}
	if (0 != wlan_hdd_set_callback(pTdlsPeer, NULL)) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s TDLS set callback Failed", __func__);
		status = -EINVAL;
		goto rel_lock;
	}

	mutex_unlock(&pHddCtx->tdls_lock);

	/* Update the peer mac to firmware, so firmware
	 * could update the connection table
	 */
	if (0 != wlan_hdd_tdls_update_peer_mac(pAdapter, peer,
	    eSME_TDLS_PEER_REMOVE_MAC_ADDR)) {
		hdd_err("TDLS Peer mac update Failed " MAC_ADDRESS_STR,
			MAC_ADDR_ARRAY(peer));
		status = -EINVAL;
		goto ret_status;
	}

	/* set tdls connection tracker state */
	cds_set_tdls_ct_mode(pHddCtx);
	goto ret_status;

rel_lock:
	mutex_unlock(&pHddCtx->tdls_lock);
ret_status:
	return status;
}

#if defined(CONVERGED_TDLS_ENABLE)
/**
 * __wlan_hdd_cfg80211_tdls_oper() - helper function to handle cfg80211 operation
 *                                   on an TDLS peer
 * @wiphy: wiphy
 * @dev: net device
 * @peer: MAC address of the TDLS peer
 * @oper: cfg80211 TDLS operation
 *
 * Return: 0 on success; negative errno otherwise
 */
static int __wlan_hdd_cfg80211_tdls_oper(struct wiphy *wiphy,
					 struct net_device *dev,
					 const uint8_t *peer,
					 enum nl80211_tdls_operation oper)
{
	hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
	hdd_context_t *pHddCtx = wiphy_priv(wiphy);
	int status;

	ENTER();

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EINVAL;
	}

	if (wlan_hdd_validate_session_id(pAdapter->sessionId)) {
		hdd_err("invalid session id: %d", pAdapter->sessionId);
		return -EINVAL;
	}

	MTRACE(qdf_trace(QDF_MODULE_ID_HDD,
			 TRACE_CODE_HDD_CFG80211_TDLS_OPER,
			 pAdapter->sessionId, oper));
	if (NULL == peer) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: Invalid arguments", __func__);
		return -EINVAL;
	}

	status = wlan_hdd_validate_context(pHddCtx);

	if (0 != status)
		return status;

	status = wlan_cfg80211_tdls_oper(pHddCtx->hdd_pdev, dev, peer, oper);

	EXIT();
	return status;
}
#else
/**
 * __wlan_hdd_cfg80211_tdls_oper() - helper function to handle cfg80211
 *                                   operation on an TDLS peer
 * @wiphy: wiphy
 * @dev: net device
 * @peer: MAC address of the TDLS peer
 * @oper: cfg80211 TDLS operation
 *
 * Return: 0 on success; negative errno otherwise
 */
static int __wlan_hdd_cfg80211_tdls_oper(struct wiphy *wiphy,
					 struct net_device *dev,
					 const uint8_t *peer,
					 enum nl80211_tdls_operation oper)
{
	hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
	hdd_context_t *pHddCtx = wiphy_priv(wiphy);
	int status;
	tSmeTdlsPeerStateParams smeTdlsPeerStateParams;
	QDF_STATUS qdf_ret_status = QDF_STATUS_E_FAILURE;
	hddTdlsPeer_t *pTdlsPeer;
	enum tdls_link_status peer_status = eTDLS_LINK_IDLE;
	uint16_t peer_staid;
	uint8_t peer_offchannelsupp;

	ENTER();

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EINVAL;
	}

	if (wlan_hdd_validate_session_id(pAdapter->sessionId)) {
		hdd_err("invalid session id: %d", pAdapter->sessionId);
		return -EINVAL;
	}

	MTRACE(qdf_trace(QDF_MODULE_ID_HDD,
			 TRACE_CODE_HDD_CFG80211_TDLS_OPER,
			 pAdapter->sessionId, oper));
	if (NULL == peer) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: Invalid arguments", __func__);
		return -EINVAL;
	}

	status = wlan_hdd_validate_context(pHddCtx);

	if (0 != status)
		return status;

	/* QCA 2.0 Discrete ANDs feature capability in HDD config with that
	 * received from target, so HDD config gives combined intersected result
	 */
	if (false == pHddCtx->config->fEnableTDLSSupport) {
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "TDLS Disabled in INI OR not enabled in FW. "
			  "Cannot process TDLS commands");
		return -ENOTSUPP;
	}

	switch (oper) {
	case NL80211_TDLS_ENABLE_LINK:
	{
		QDF_STATUS status;
		unsigned long rc;
		tCsrTdlsLinkEstablishParams tdlsLinkEstablishParams = { {0}, 0,
						0, 0, 0, 0, 0, {0}, 0, {0} };

		mutex_lock(&pHddCtx->tdls_lock);
		pTdlsPeer =
			wlan_hdd_tdls_find_peer(pAdapter, peer);

		if (NULL == pTdlsPeer) {
			mutex_unlock(&pHddCtx->tdls_lock);
			QDF_TRACE(QDF_MODULE_ID_HDD,
				  QDF_TRACE_LEVEL_ERROR,
				  "%s: peer matching " MAC_ADDRESS_STR
				  " not found, ignore NL80211_TDLS_ENABLE_LINK",
				  __func__, MAC_ADDR_ARRAY(peer));
			return -EINVAL;
		}

		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: NL80211_TDLS_ENABLE_LINK for peer "
			  MAC_ADDRESS_STR " link_status: %d",
			  __func__, MAC_ADDR_ARRAY(peer),
			  pTdlsPeer->link_status);

		if (!TDLS_STA_INDEX_VALID(pTdlsPeer->staId)) {
			QDF_TRACE(QDF_MODULE_ID_HDD,
				  QDF_TRACE_LEVEL_ERROR,
				  "%s: invalid sta index %u for "
				  MAC_ADDRESS_STR
				  " TDLS_ENABLE_LINK failed", __func__,
				  pTdlsPeer->staId,
				  MAC_ADDR_ARRAY(peer));
			mutex_unlock(&pHddCtx->tdls_lock);
			return -EINVAL;
		}
		peer_status = pTdlsPeer->link_status;
		peer_offchannelsupp = pTdlsPeer->isOffChannelSupported;
		mutex_unlock(&pHddCtx->tdls_lock);

		wlan_hdd_tdls_set_cap(pAdapter, peer, eTDLS_CAP_SUPPORTED);

		qdf_mem_set(&tdlsLinkEstablishParams,
			sizeof(tdlsLinkEstablishParams), 0);

		if (eTDLS_LINK_CONNECTED != peer_status) {
			if (IS_ADVANCE_TDLS_ENABLE) {

				hdd_info("Advance TDLS is enabled");
				if (0 !=
				    wlan_hdd_tdls_get_link_establish_params
					    (pAdapter, peer,
					    &tdlsLinkEstablishParams)) {
					return -EINVAL;
				}
				INIT_COMPLETION(pAdapter->
						tdls_link_establish_req_comp);

				sme_send_tdls_link_establish_params
					(WLAN_HDD_GET_HAL_CTX(pAdapter),
					pAdapter->sessionId, peer,
					&tdlsLinkEstablishParams);
				/* Send TDLS peer UAPSD capabilities to the
				 * firmware and register with the TL on after
				 * the response for this operation is received
				 */
				rc = wait_for_completion_timeout
					     (&pAdapter->
					     tdls_link_establish_req_comp,
					     msecs_to_jiffies
						     (WAIT_TIME_TDLS_LINK_ESTABLISH_REQ));
				if (!rc) {
					QDF_TRACE(QDF_MODULE_ID_HDD,
						  QDF_TRACE_LEVEL_ERROR,
						  "%s: Link Establish Request timed out",
						  __func__);
					return -EINVAL;
				}
			}

			mutex_lock(&pHddCtx->tdls_lock);
			pTdlsPeer =
				wlan_hdd_tdls_find_peer(pAdapter, peer);

			if (NULL == pTdlsPeer) {
				mutex_unlock(&pHddCtx->tdls_lock);
				QDF_TRACE(QDF_MODULE_ID_HDD,
					  QDF_TRACE_LEVEL_ERROR,
					  "%s: peer matching " MAC_ADDRESS_STR
					  " (oper %d) peer got freed in other"
					  "context. ignored",
					  __func__, MAC_ADDR_ARRAY(peer),
					  (int)oper);
				return -EINVAL;
			}

			wlan_hdd_tdls_set_peer_link_status(pTdlsPeer,
							   eTDLS_LINK_CONNECTED,
							   eTDLS_LINK_SUCCESS);
			peer_staid = pTdlsPeer->staId;

			hdd_notice("%s: tdlsLinkEstablishParams of peer "
				MAC_ADDRESS_STR "uapsdQueues: %d"
				" qos: %d maxSp: %d isBufSta: %d"
				" isOffChannelSupported: %d"
				" isResponder: %d peerstaId: %d",
				__func__,
				MAC_ADDR_ARRAY(
					tdlsLinkEstablishParams.peerMac),
				tdlsLinkEstablishParams.uapsdQueues,
				tdlsLinkEstablishParams.qos,
				tdlsLinkEstablishParams.maxSp,
				tdlsLinkEstablishParams.isBufSta,
				tdlsLinkEstablishParams.isOffChannelSupported,
				tdlsLinkEstablishParams.isResponder,
				pTdlsPeer->staId);
			/* start TDLS client registration with TL */
			status =
				hdd_roam_register_tdlssta(
					pAdapter, peer,
					pTdlsPeer->staId,
					pTdlsPeer->signature,
					pTdlsPeer->qos);
			if (QDF_STATUS_SUCCESS == status) {
				uint8_t i;

				qdf_mem_zero(&smeTdlsPeerStateParams,
					     sizeof
					     (tSmeTdlsPeerStateParams));

				smeTdlsPeerStateParams.vdevId =
					pAdapter->sessionId;
				qdf_mem_copy(&smeTdlsPeerStateParams.
					     peerMacAddr,
					     &pTdlsPeer->peerMac,
					     sizeof(tSirMacAddr));
				smeTdlsPeerStateParams.peerState =
					eSME_TDLS_PEER_STATE_CONNECTED;
				smeTdlsPeerStateParams.peerCap.
				isPeerResponder =
					pTdlsPeer->is_responder;
				smeTdlsPeerStateParams.peerCap.
				peerUapsdQueue =
					pTdlsPeer->uapsdQueues;
				smeTdlsPeerStateParams.peerCap.
				peerMaxSp = pTdlsPeer->maxSp;
				smeTdlsPeerStateParams.peerCap.
				peerBuffStaSupport =
					pTdlsPeer->isBufSta;
				smeTdlsPeerStateParams.peerCap.
				peerOffChanSupport =
					pTdlsPeer->isOffChannelSupported;
				smeTdlsPeerStateParams.peerCap.
				peerCurrOperClass = 0;
				smeTdlsPeerStateParams.peerCap.
				selfCurrOperClass = 0;
				smeTdlsPeerStateParams.peerCap.
				peerChanLen =
					pTdlsPeer->supported_channels_len;
				smeTdlsPeerStateParams.peerCap.
				prefOffChanNum =
					pTdlsPeer->pref_off_chan_num;
				smeTdlsPeerStateParams.peerCap.
				prefOffChanBandwidth =
					pHddCtx->config->
					fTDLSPrefOffChanBandwidth;
				smeTdlsPeerStateParams.peerCap.
					opClassForPrefOffChan =
						pTdlsPeer->
						op_class_for_pref_off_chan;

				if (wlan_reg_is_dfs_ch(pHddCtx->hdd_pdev,
						smeTdlsPeerStateParams.
						peerCap.prefOffChanNum)) {
					hdd_err("Resetting TDLS off-channel from %d to %d",
					       smeTdlsPeerStateParams.peerCap.
						prefOffChanNum,
					       CFG_TDLS_PREFERRED_OFF_CHANNEL_NUM_DEFAULT);
					smeTdlsPeerStateParams.peerCap.prefOffChanNum =
						CFG_TDLS_PREFERRED_OFF_CHANNEL_NUM_DEFAULT;
				}

				QDF_TRACE(QDF_MODULE_ID_HDD,
					  QDF_TRACE_LEVEL_INFO,
					  "%s: Peer " MAC_ADDRESS_STR
					  "vdevId: %d, peerState: %d, isPeerResponder: %d, uapsdQueues: 0x%x, maxSp: 0x%x, peerBuffStaSupport: %d, peerOffChanSupport: %d, peerCurrOperClass: %d, selfCurrOperClass: %d, peerChanLen: %d, peerOperClassLen: %d, prefOffChanNum: %d, prefOffChanBandwidth: %d, op_class_for_pref_off_chan: %d",
					  __func__,
					  MAC_ADDR_ARRAY(peer),
					  smeTdlsPeerStateParams.vdevId,
					  smeTdlsPeerStateParams.
					  peerState,
					  smeTdlsPeerStateParams.
					  peerCap.isPeerResponder,
					  smeTdlsPeerStateParams.
					  peerCap.peerUapsdQueue,
					  smeTdlsPeerStateParams.
					  peerCap.peerMaxSp,
					  smeTdlsPeerStateParams.
					  peerCap.peerBuffStaSupport,
					  smeTdlsPeerStateParams.
					  peerCap.peerOffChanSupport,
					  smeTdlsPeerStateParams.
					  peerCap.peerCurrOperClass,
					  smeTdlsPeerStateParams.
					  peerCap.selfCurrOperClass,
					  smeTdlsPeerStateParams.
					  peerCap.peerChanLen,
					  smeTdlsPeerStateParams.
					  peerCap.peerOperClassLen,
					  smeTdlsPeerStateParams.
					  peerCap.prefOffChanNum,
					  smeTdlsPeerStateParams.
					  peerCap.prefOffChanBandwidth,
					  pTdlsPeer->
					  op_class_for_pref_off_chan);

				for (i = 0;
				     i <
				     pTdlsPeer->supported_channels_len;
				     i++) {
					smeTdlsPeerStateParams.peerCap.
					peerChan[i] =
						pTdlsPeer->
						supported_channels[i];
				}
				smeTdlsPeerStateParams.peerCap.
				peerOperClassLen =
					pTdlsPeer->
					supported_oper_classes_len;
				for (i = 0;
				     i <
				     pTdlsPeer->
				     supported_oper_classes_len; i++) {
					smeTdlsPeerStateParams.peerCap.
					peerOperClass[i] =
						pTdlsPeer->
						supported_oper_classes[i];
				}
				mutex_unlock(&pHddCtx->tdls_lock);

				qdf_ret_status =
					sme_update_tdls_peer_state(pHddCtx->
								   hHal,
								   &smeTdlsPeerStateParams);
				if (QDF_STATUS_SUCCESS !=
				    qdf_ret_status) {
					QDF_TRACE(QDF_MODULE_ID_HDD,
						  QDF_TRACE_LEVEL_ERROR,
						  "%s: sme_update_tdls_peer_state failed for "
						  MAC_ADDRESS_STR,
						  __func__,
						  MAC_ADDR_ARRAY(peer));
					return -EPERM;
				}
				wlan_hdd_tdls_increment_peer_count
					(pAdapter);
			} else
				mutex_unlock(&pHddCtx->tdls_lock);

			/* Update TL about the UAPSD masks,
			 * to route the packets to firmware
			 */
			if ((true ==
			     pHddCtx->config->fEnableTDLSBufferSta)
			    || pHddCtx->config->fTDLSUapsdMask) {
				int ac;
				uint8_t ucAc[4] = { SME_AC_VO,
						    SME_AC_VI,
						    SME_AC_BK,
						    SME_AC_BE};
				uint8_t tlTid[4] = { 7, 5, 2, 3 };

				hdd_info("Update TL about UAPSD masks");
				for (ac = 0; ac < 4; ac++) {
					status = sme_enable_uapsd_for_ac(
						 (WLAN_HDD_GET_CTX(pAdapter))->pcds_context,
						 peer_staid, ucAc[ac],
						 tlTid[ac], tlTid[ac], 0, 0,
						 SME_BI_DIR, 1,
						 pAdapter->sessionId,
						 pHddCtx->config->DelayedTriggerFrmInt);
				}
			}
		}
		hdd_wlan_tdls_enable_link_event(peer,
			peer_offchannelsupp,
			0, 0);
	}
	break;
	case NL80211_TDLS_DISABLE_LINK:
	{
		mutex_lock(&pHddCtx->tdls_lock);
		pTdlsPeer =
			wlan_hdd_tdls_find_peer(pAdapter, peer);

		if (NULL == pTdlsPeer) {
			mutex_unlock(&pHddCtx->tdls_lock);
			QDF_TRACE(QDF_MODULE_ID_HDD,
				  QDF_TRACE_LEVEL_ERROR,
				  "%s: peer matching " MAC_ADDRESS_STR
				  " not found, ignore NL80211_TDLS_DISABLE_LINK",
				  __func__, MAC_ADDR_ARRAY(peer));
			return -EINVAL;
		}

		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: NL80211_TDLS_DISABLE_LINK for peer "
			  MAC_ADDRESS_STR " link_status: %d",
			  __func__, MAC_ADDR_ARRAY(peer),
			  pTdlsPeer->link_status);
		peer_staid = pTdlsPeer->staId;
		mutex_unlock(&pHddCtx->tdls_lock);

		if (TDLS_STA_INDEX_VALID(peer_staid)) {
			unsigned long rc;

			INIT_COMPLETION(pAdapter->
					tdls_del_station_comp);

			sme_delete_tdls_peer_sta(WLAN_HDD_GET_HAL_CTX
							 (pAdapter),
						 pAdapter->sessionId,
						 peer);

			rc = wait_for_completion_timeout(&pAdapter->
							 tdls_del_station_comp,
							 msecs_to_jiffies
								 (WAIT_TIME_TDLS_DEL_STA));
			if (!rc) {
				QDF_TRACE(QDF_MODULE_ID_HDD,
					  QDF_TRACE_LEVEL_ERROR,
					  "%s: Del station timed out",
					  __func__);
				return -EPERM;
			}

			mutex_lock(&pHddCtx->tdls_lock);
			pTdlsPeer =
				wlan_hdd_tdls_find_peer(pAdapter, peer);
			if (NULL == pTdlsPeer) {
				mutex_unlock(&pHddCtx->tdls_lock);
				QDF_TRACE(QDF_MODULE_ID_HDD,
					  QDF_TRACE_LEVEL_ERROR,
					  "%s: peer matching " MAC_ADDRESS_STR
					  " (oper %d) peer got freed in other"
					  " context. ignored",
					  __func__, MAC_ADDR_ARRAY(peer),
					  (int)oper);
				return -EINVAL;
			}

			wlan_hdd_tdls_set_peer_link_status(pTdlsPeer,
						eTDLS_LINK_IDLE,
						(pTdlsPeer->link_status ==
							eTDLS_LINK_TEARING) ?
						eTDLS_LINK_UNSPECIFIED :
						eTDLS_LINK_DROPPED_BY_REMOTE);
			mutex_unlock(&pHddCtx->tdls_lock);
		} else {
			QDF_TRACE(QDF_MODULE_ID_HDD,
				  QDF_TRACE_LEVEL_ERROR,
				  "%s: TDLS Peer Station doesn't exist.",
				  __func__);
		}
	}
	break;
	case NL80211_TDLS_TEARDOWN:
	{
		status =
			wlan_hdd_tdls_extctrl_deconfig_peer(pAdapter, peer);

		if (0 != status) {
			QDF_TRACE(QDF_MODULE_ID_HDD,
				  QDF_TRACE_LEVEL_ERROR,
				  "%s: Error in TDLS Teardown",
				  __func__);
			return status;
		}
	}
	break;
	case NL80211_TDLS_SETUP:
	{
		status = wlan_hdd_tdls_extctrl_config_peer(pAdapter,
				peer, NULL,
				pHddCtx->config->fTDLSPrefOffChanNum, 0, 0, 0);
		if (0 != status) {
			QDF_TRACE(QDF_MODULE_ID_HDD,
				  QDF_TRACE_LEVEL_ERROR,
				  "%s: Error in TDLS Setup", __func__);
			return status;
		}
	}
	break;
	case NL80211_TDLS_DISCOVERY_REQ:
		/* We don't support in-driver setup/teardown/discovery */
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_WARN,
			  "%s: We don't support in-driver setup/teardown/discovery",
			  __func__);
		return -ENOTSUPP;
	default:
		QDF_TRACE(QDF_MODULE_ID_HDD, QDF_TRACE_LEVEL_ERROR,
			  "%s: unsupported event %d", __func__, oper);
		return -ENOTSUPP;
	}

	EXIT();
	return 0;
}
#endif

/**
 * wlan_hdd_cfg80211_tdls_oper() - handle cfg80211 operation on an TDLS peer
 * @wiphy: wiphy
 * @dev: net device
 * @peer: MAC address of the TDLS peer
 * @oper: cfg80211 TDLS operation
 *
 * Return: 0 on success; negative errno otherwise
 */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0))
int wlan_hdd_cfg80211_tdls_oper(struct wiphy *wiphy,
				struct net_device *dev,
				const uint8_t *peer,
				enum nl80211_tdls_operation oper)
#else
int wlan_hdd_cfg80211_tdls_oper(struct wiphy *wiphy,
				struct net_device *dev,
				uint8_t *peer,
				enum nl80211_tdls_operation oper)
#endif
{
	int ret;

	cds_ssr_protect(__func__);
	ret = __wlan_hdd_cfg80211_tdls_oper(wiphy, dev, peer, oper);
	cds_ssr_unprotect(__func__);

	return ret;
}

/**
 * wlan_hdd_cfg80211_send_tdls_discover_req() - send out TDLS discovery for
 *                                              a TDLS peer
 * @wiphy: wiphy
 * @dev: net device
 * @peer: MAC address of the peer
 *
 * Return: 0 if success; negative errno otherwise
 */
int wlan_hdd_cfg80211_send_tdls_discover_req(struct wiphy *wiphy,
					     struct net_device *dev, u8 *peer)
{
	hdd_notice("tdls send discover req: " MAC_ADDRESS_STR,
		   MAC_ADDR_ARRAY(peer));
#if TDLS_MGMT_VERSION2
	return wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer,
					   WLAN_TDLS_DISCOVERY_REQUEST, 1, 0, 0,
					   NULL, 0);
#else
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0))
	return wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer,
					   WLAN_TDLS_DISCOVERY_REQUEST, 1, 0,
					   0, 0, NULL, 0);
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0))
	return wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer,
					   WLAN_TDLS_DISCOVERY_REQUEST, 1, 0,
					   0, NULL, 0);
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0))
	return wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer,
					   WLAN_TDLS_DISCOVERY_REQUEST, 1, 0,
					   0, NULL, 0);
#else
	return wlan_hdd_cfg80211_tdls_mgmt(wiphy, dev, peer,
					   WLAN_TDLS_DISCOVERY_REQUEST, 1, 0,
					   NULL, 0);
#endif
#endif
}

#endif /* End of FEATURE_WLAN_TDLS */

/**
 * wlan_hdd_tdls_find_first_connected_peer() - find the 1st connected tdls peer
 * @adapter: Pointer to the HDD adapter
 *
 * This function searchs for the 1st connected TDLS peer
 *
 * Return: The first connected TDLS peer if found; NULL otherwise
 */
hddTdlsPeer_t *wlan_hdd_tdls_find_first_connected_peer(hdd_adapter_t *adapter)
{
	int i;
	struct list_head *head;
	struct list_head *pos;
	hddTdlsPeer_t *curr_peer = NULL;
	tdlsCtx_t *hdd_tdls_ctx;
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter);

	if (wlan_hdd_validate_context(hdd_ctx))
		return NULL;

	hdd_tdls_ctx = WLAN_HDD_GET_TDLS_CTX_PTR(adapter);
	if (NULL == hdd_tdls_ctx)
		return NULL;

	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &hdd_tdls_ctx->peer_list[i];
		list_for_each(pos, head) {
			curr_peer = list_entry(pos, hddTdlsPeer_t, node);
			if (curr_peer && (curr_peer->link_status ==
					eTDLS_LINK_CONNECTED)) {
				hdd_notice(MAC_ADDRESS_STR" eTDLS_LINK_CONNECTED",
					   MAC_ADDR_ARRAY(curr_peer->peerMac));
				return curr_peer;
			}
		}
	}
	return NULL;
}

/**
 * hdd_set_tdls_offchannel() - set tdls off-channel number
 * @adapter: Pointer to the HDD adapter
 * @offchanmode: tdls off-channel number
 *
 * This function sets tdls off-channel number
 *
 * Return: 0 on success; negative errno otherwise
 */
int hdd_set_tdls_offchannel(hdd_context_t *hdd_ctx, int offchannel)
{
	if ((true == hdd_ctx->config->fEnableTDLSOffChannel) &&
	    (eTDLS_SUPPORT_ENABLED == hdd_ctx->tdls_mode ||
	     eTDLS_SUPPORT_EXTERNAL_CONTROL == hdd_ctx->tdls_mode ||
	     eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY == hdd_ctx->tdls_mode)) {
		if (offchannel < CFG_TDLS_PREFERRED_OFF_CHANNEL_NUM_MIN ||
			offchannel > CFG_TDLS_PREFERRED_OFF_CHANNEL_NUM_MAX) {
			hdd_err("Invalid tdls off channel %u", offchannel);
			return -EINVAL;
		}
	} else {
		hdd_err("Either TDLS or TDLS Off-channel is not enabled");
		return  -ENOTSUPP;
	}
	hdd_notice("change tdls off channel from %d to %d",
		   hdd_ctx->tdls_off_channel, offchannel);
	hdd_ctx->tdls_off_channel = offchannel;
	return 0;
}

/**
 * hdd_set_tdls_secoffchanneloffset() - set secondary tdls off-channel offset
 * @adapter: Pointer to the HDD adapter
 * @offchanmode: tdls off-channel offset
 *
 * This function sets 2nd tdls off-channel offset
 *
 * Return: 0 on success; negative errno otherwise
 */
int hdd_set_tdls_secoffchanneloffset(hdd_context_t *hdd_ctx, int offchanoffset)
{
	if ((true == hdd_ctx->config->fEnableTDLSOffChannel) &&
	    (eTDLS_SUPPORT_ENABLED == hdd_ctx->tdls_mode ||
	     eTDLS_SUPPORT_EXTERNAL_CONTROL == hdd_ctx->tdls_mode ||
	     eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY == hdd_ctx->tdls_mode)) {
		hdd_ctx->tdls_channel_offset = 0;

		switch (offchanoffset) {
		case TDLS_SEC_OFFCHAN_OFFSET_0:
			hdd_ctx->tdls_channel_offset = (1 << BW_20_OFFSET_BIT);
			break;
		case TDLS_SEC_OFFCHAN_OFFSET_40PLUS:
		case TDLS_SEC_OFFCHAN_OFFSET_40MINUS:
			hdd_ctx->tdls_channel_offset = (1 << BW_40_OFFSET_BIT);
			break;
		case TDLS_SEC_OFFCHAN_OFFSET_80:
			hdd_ctx->tdls_channel_offset = (1 << BW_80_OFFSET_BIT);
			break;
		case TDLS_SEC_OFFCHAN_OFFSET_160:
			hdd_ctx->tdls_channel_offset = (1 << BW_160_OFFSET_BIT);
			break;
		default:
			hdd_err("Invalid tdls secondary off channel offset %d",
				offchanoffset);
			return -EINVAL;
		} /* end switch */
	} else {
		hdd_err("Either TDLS or TDLS Off-channel is not enabled");
		return  -ENOTSUPP;
	}
	hdd_notice("change tdls secondary off channel offset to 0x%x",
		   hdd_ctx->tdls_channel_offset);
	return 0;
}

/**
 * hdd_set_tdls_offchannelmode() - set tdls off-channel mode
 * @adapter: Pointer to the HDD adapter
 * @offchanmode: tdls off-channel mode
 *
 * This function sets tdls off-channel mode
 *
 * Return: 0 on success; negative errno otherwise
 */
int hdd_set_tdls_offchannelmode(hdd_adapter_t *adapter, int offchanmode)
{
	hddTdlsPeer_t *conn_peer = NULL;
	hdd_station_ctx_t *hdd_sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(adapter);
	hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter);
	sme_tdls_chan_switch_params chan_switch_params;
	QDF_STATUS status = QDF_STATUS_E_FAILURE;
	int ret_value = 0;

	if (offchanmode < ENABLE_CHANSWITCH ||
			offchanmode > DISABLE_CHANSWITCH) {
		hdd_err("Invalid tdls off channel mode %d", offchanmode);
		ret_value = -EINVAL;
		goto ret_status;
	}
	if (eConnectionState_Associated != hdd_sta_ctx->conn_info.connState) {
		hdd_err("tdls off channel mode req in not associated state %d",
			offchanmode);
		ret_value = -EPERM;
		goto ret_status;
	}
	if ((true == hdd_ctx->config->fEnableTDLSOffChannel) &&
		(eTDLS_SUPPORT_ENABLED == hdd_ctx->tdls_mode ||
		 eTDLS_SUPPORT_EXTERNAL_CONTROL == hdd_ctx->tdls_mode ||
		 eTDLS_SUPPORT_EXPLICIT_TRIGGER_ONLY == hdd_ctx->tdls_mode)) {
		mutex_lock(&hdd_ctx->tdls_lock);
		conn_peer = wlan_hdd_tdls_find_first_connected_peer(adapter);
		if (NULL == conn_peer) {
			hdd_err("No TDLS Connected Peer");
			ret_value = -EPERM;
			goto rel_lock;
		}
	} else {
		hdd_err("TDLS Connection not supported");
		ret_value = -ENOTSUPP;
		goto ret_status;
	}

	hdd_notice("TDLS Channel Switch in swmode=%d tdls_off_channel %d offchanoffset %d",
		   offchanmode, hdd_ctx->tdls_off_channel,
		   hdd_ctx->tdls_channel_offset);

	switch (offchanmode) {
	case ENABLE_CHANSWITCH:
		if (hdd_ctx->tdls_off_channel &&
			hdd_ctx->tdls_channel_offset) {
			chan_switch_params.tdls_off_channel =
				hdd_ctx->tdls_off_channel;
			chan_switch_params.tdls_off_ch_bw_offset =
				hdd_ctx->tdls_channel_offset;
			chan_switch_params.opclass =
			   wlan_hdd_find_opclass(WLAN_HDD_GET_HAL_CTX(adapter),
					chan_switch_params.tdls_off_channel,
					chan_switch_params.tdls_off_ch_bw_offset);
		} else {
			hdd_err("TDLS off-channel parameters are not set yet!!!");
			ret_value = -EINVAL;
			goto rel_lock;
		}
		break;
	case DISABLE_CHANSWITCH:
		chan_switch_params.tdls_off_channel = 0;
		chan_switch_params.tdls_off_ch_bw_offset = 0;
		chan_switch_params.opclass = 0;
		break;
	default:
		hdd_err("Incorrect Parameters mode: %d tdls_off_channel: %d offchanoffset: %d",
			offchanmode, hdd_ctx->tdls_off_channel,
			hdd_ctx->tdls_channel_offset);
		ret_value = -EINVAL;
		goto rel_lock;
	} /* end switch */

	chan_switch_params.vdev_id = adapter->sessionId;
	chan_switch_params.tdls_off_ch_mode = offchanmode;
	chan_switch_params.is_responder =
		conn_peer->is_responder;
	qdf_mem_copy(&chan_switch_params.peer_mac_addr,
		     &conn_peer->peerMac,
		     sizeof(tSirMacAddr));
	hdd_info("Peer " MAC_ADDRESS_STR
		 " vdevId: %d, off channel: %d, offset: %d, mode: %d, is_responder: %d",
		 MAC_ADDR_ARRAY(chan_switch_params.peer_mac_addr),
		 chan_switch_params.vdev_id,
		 chan_switch_params.tdls_off_channel,
		 chan_switch_params.tdls_off_ch_bw_offset,
		 chan_switch_params.tdls_off_ch_mode,
		 chan_switch_params.is_responder);
	mutex_unlock(&hdd_ctx->tdls_lock);

	status = sme_send_tdls_chan_switch_req(WLAN_HDD_GET_HAL_CTX(adapter),
			&chan_switch_params);

	if (status != QDF_STATUS_SUCCESS) {
		hdd_err("Failed to send channel switch request to sme");
		ret_value = -EINVAL;
		goto ret_status;
	}

	hdd_ctx->tdls_fw_off_chan_mode = offchanmode;

	if (ENABLE_CHANSWITCH == offchanmode) {
		mutex_lock(&hdd_ctx->tdls_lock);
		conn_peer = wlan_hdd_tdls_find_first_connected_peer(adapter);
		if (NULL == conn_peer) {
			hdd_err("No TDLS Connected Peer");
			ret_value = -EPERM;
			goto rel_lock;
		}
		conn_peer->pref_off_chan_num =
			chan_switch_params.tdls_off_channel;
		conn_peer->op_class_for_pref_off_chan =
			chan_switch_params.opclass;
		goto rel_lock;
	}
	goto ret_status;

rel_lock:
	mutex_unlock(&hdd_ctx->tdls_lock);
ret_status:
	return ret_value;
}

/**
 * wlan_hdd_tdls_ct_sampling_tx_rx() - collect tx/rx traffic sample
 * @adapter: pointer to hdd adapter
 * @hdd_ctx: hdd context
 *
 * Function to update data traffic information in tdls connection
 * tracker data structure for connection tracker operation
 *
 * Return: None
 */
static void wlan_hdd_tdls_ct_sampling_tx_rx(hdd_adapter_t *adapter,
				     hdd_context_t *hdd_ctx)
{
	hddTdlsPeer_t *curr_peer;
	uint8_t mac[QDF_MAC_ADDR_SIZE];
	uint8_t mac_cnt;
	uint8_t valid_mac_entries;
	struct tdls_ct_mac_table ct_peer_mac_table[TDLS_CT_MAC_MAX_TABLE_SIZE];

	qdf_spin_lock_bh(&hdd_ctx->tdls_ct_spinlock);

	if (0 == hdd_ctx->valid_mac_entries) {
		qdf_spin_unlock_bh(&hdd_ctx->tdls_ct_spinlock);
		return;
	}

	valid_mac_entries = hdd_ctx->valid_mac_entries;

	memcpy(ct_peer_mac_table, hdd_ctx->ct_peer_mac_table,
	       (sizeof(struct tdls_ct_mac_table)) * valid_mac_entries);

	memset(hdd_ctx->ct_peer_mac_table, 0,
	       (sizeof(struct tdls_ct_mac_table)) * valid_mac_entries);

	hdd_ctx->valid_mac_entries = 0;

	qdf_spin_unlock_bh(&hdd_ctx->tdls_ct_spinlock);
	mutex_lock(&hdd_ctx->tdls_lock);
	for (mac_cnt = 0; mac_cnt < valid_mac_entries; mac_cnt++) {
		memcpy(mac, ct_peer_mac_table[mac_cnt].mac_address.bytes,
		       QDF_MAC_ADDR_SIZE);
		curr_peer = wlan_hdd_tdls_get_peer(adapter, mac);
		if (NULL != curr_peer) {
			curr_peer->tx_pkt =
			ct_peer_mac_table[mac_cnt].tx_packet_cnt;
			curr_peer->rx_pkt =
			ct_peer_mac_table[mac_cnt].rx_packet_cnt;
		}
	}
	mutex_unlock(&hdd_ctx->tdls_lock);
}

/**
 * wlan_hdd_tdls_update_rx_pkt_cnt() - Update rx packet count
 * @adapter: pointer to hdd adapter
 * @skb: pointer to sk_buff
 *
 * Increase the rx packet count, if the sender is not bssid and the packet is
 * not broadcast and muticast packet
 *
 * This sampling information will be used in TDLS connection tracker
 *
 * This function expected to be called in an atomic context so blocking APIs
 * not allowed
 *
 * Return: None
 */
void wlan_hdd_tdls_update_rx_pkt_cnt(hdd_adapter_t *adapter,
				     struct sk_buff *skb)
{
	hdd_context_t *hdd_ctx;
	hdd_station_ctx_t *hdd_sta_ctx;
	uint8_t mac_cnt;
	uint8_t valid_mac_entries;
	struct qdf_mac_addr *mac_addr;

	hdd_ctx = WLAN_HDD_GET_CTX(adapter);

	if (hdd_ctx->tdls_umac_comp_active)
		return;

	if (!hdd_ctx->enable_tdls_connection_tracker)
		return;

	mac_addr = (struct qdf_mac_addr *)(skb->data+QDF_MAC_ADDR_SIZE);
	if (qdf_is_macaddr_group(mac_addr))
		return;

	hdd_sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(adapter);
	if (memcmp(hdd_sta_ctx->conn_info.bssId.bytes,
			mac_addr, QDF_MAC_ADDR_SIZE) == 0)
		return;


	qdf_spin_lock_bh(&hdd_ctx->tdls_ct_spinlock);
	valid_mac_entries = hdd_ctx->valid_mac_entries;

	for (mac_cnt = 0; mac_cnt < valid_mac_entries; mac_cnt++) {
		if (memcmp(hdd_ctx->ct_peer_mac_table[mac_cnt].mac_address.bytes,
		    mac_addr, QDF_MAC_ADDR_SIZE) == 0) {
			hdd_ctx->ct_peer_mac_table[mac_cnt].rx_packet_cnt++;
			goto rx_cnt_return;
		}
	}

	/* If we have more than 8 peers within 30 mins. we will
	 *  stop tracking till the old entries are removed
	 */
	if (mac_cnt < TDLS_CT_MAC_MAX_TABLE_SIZE) {
		memcpy(hdd_ctx->ct_peer_mac_table[mac_cnt].mac_address.bytes,
		       mac_addr, QDF_MAC_ADDR_SIZE);
		hdd_ctx->valid_mac_entries = mac_cnt+1;
		hdd_ctx->ct_peer_mac_table[mac_cnt].rx_packet_cnt = 1;
	}

rx_cnt_return:
	qdf_spin_unlock_bh(&hdd_ctx->tdls_ct_spinlock);
}

/**
 * wlan_hdd_tdls_update_tx_pkt_cnt() - update tx packet
 * @adapter: pointer to hdd adapter
 * @skb: pointer to sk_buff
 *
 * Increase the tx packet count, if the sender is not bssid and the packet is
 * not broadcast and muticast packet
 *
 * This sampling information will be used in TDLS connection tracker
 *
 * This function expected to be called in an atomic context so blocking APIs
 * not allowed
 *
 * Return: None
 */
void wlan_hdd_tdls_update_tx_pkt_cnt(hdd_adapter_t *adapter,
				     struct sk_buff *skb)
{
	hdd_context_t *hdd_ctx;
	hdd_station_ctx_t *hdd_sta_ctx;
	uint8_t mac_cnt;
	uint8_t valid_mac_entries;
	struct qdf_mac_addr *mac_addr;

	hdd_ctx = WLAN_HDD_GET_CTX(adapter);

	if (hdd_ctx->tdls_umac_comp_active)
		return;

	if (!hdd_ctx->enable_tdls_connection_tracker)
		return;

	mac_addr = (struct qdf_mac_addr *)skb->data;
	if (qdf_is_macaddr_group(mac_addr))
		return;

	hdd_sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(adapter);
	if (memcmp(hdd_sta_ctx->conn_info.bssId.bytes, mac_addr,
	    QDF_MAC_ADDR_SIZE) == 0)
		return;

	qdf_spin_lock_bh(&hdd_ctx->tdls_ct_spinlock);
	valid_mac_entries = hdd_ctx->valid_mac_entries;

	for (mac_cnt = 0; mac_cnt < valid_mac_entries; mac_cnt++) {
		if (memcmp(hdd_ctx->ct_peer_mac_table[mac_cnt].mac_address.bytes,
		    mac_addr, QDF_MAC_ADDR_SIZE) == 0) {
			hdd_ctx->ct_peer_mac_table[mac_cnt].tx_packet_cnt++;
			goto tx_cnt_return;
		}
	}

	/* If we have more than 8 peers within 30 mins. we will
	 *  stop tracking till the old entries are removed
	 */
	if (mac_cnt < TDLS_CT_MAC_MAX_TABLE_SIZE) {
		memcpy(hdd_ctx->ct_peer_mac_table[mac_cnt].mac_address.bytes,
			mac_addr, QDF_MAC_ADDR_SIZE);
		hdd_ctx->ct_peer_mac_table[mac_cnt].tx_packet_cnt = 1;
		hdd_ctx->valid_mac_entries++;
	}

tx_cnt_return:
	qdf_spin_unlock_bh(&hdd_ctx->tdls_ct_spinlock);
}

/**
 * wlan_hdd_tdls_implicit_send_discovery_request() - send discovery request
 * @hdd_tdls_ctx: tdls context
 *
 * Return: None
 */
void wlan_hdd_tdls_implicit_send_discovery_request(tdlsCtx_t *hdd_tdls_ctx)
{
	hdd_context_t *hdd_ctx;
	hddTdlsPeer_t *curr_peer, *temp_peer;

	ENTER();
	if (NULL == hdd_tdls_ctx) {
		hdd_info("hdd_tdls_ctx is NULL");
		return;
	}

	hdd_ctx = WLAN_HDD_GET_CTX(hdd_tdls_ctx->pAdapter);

	if (0 != (wlan_hdd_validate_context(hdd_ctx)))
		return;

	curr_peer = hdd_tdls_ctx->curr_candidate;
	if (NULL == curr_peer) {
		hdd_err("curr_peer is NULL");
		return;
	}

	/* This function is called in mutex_lock */
	temp_peer = wlan_hdd_tdls_is_progress(hdd_ctx, NULL, 0);
	if (NULL != temp_peer) {
		hdd_info(MAC_ADDRESS_STR " ongoing. pre_setup ignored",
			 MAC_ADDR_ARRAY(temp_peer->peerMac));
		goto done;
	}

	if (eTDLS_CAP_UNKNOWN != curr_peer->tdls_support)
		wlan_hdd_tdls_set_peer_link_status(curr_peer,
					   eTDLS_LINK_DISCOVERING,
					   eTDLS_LINK_SUCCESS);

	hdd_debug("Implicit TDLS, Send Discovery request event");
	cfg80211_tdls_oper_request(hdd_tdls_ctx->pAdapter->dev,
				   curr_peer->peerMac,
				   NL80211_TDLS_DISCOVERY_REQ,
				   false,
				   GFP_KERNEL);
	hdd_tdls_ctx->discovery_sent_cnt++;

	wlan_hdd_tdls_timer_restart(hdd_tdls_ctx->pAdapter,
				&hdd_tdls_ctx->peerDiscoveryTimeoutTimer,
				hdd_tdls_ctx->threshold_config.tx_period_t -
				TDLS_DISCOVERY_TIMEOUT_BEFORE_UPDATE);

	hdd_debug("discovery count %u timeout %u msec",
		 hdd_tdls_ctx->discovery_sent_cnt,
		 hdd_tdls_ctx->threshold_config.tx_period_t -
		 TDLS_DISCOVERY_TIMEOUT_BEFORE_UPDATE);
done:
	hdd_tdls_ctx->curr_candidate = NULL;
	hdd_tdls_ctx->magic = 0;
	EXIT();
}

/**
 * wlan_hdd_get_conn_info() - get the tdls connection information.
 * @hdd_ctx: hdd context
 * @hdd_ctx: sta id
 *
 * Function to check tdls sta index
 *
 * Return: None
 */
static tdlsConnInfo_t *wlan_hdd_get_conn_info(hdd_context_t *hdd_ctx,
				       uint8_t idx)
{
	uint8_t sta_idx;

	/* check if there is available index for this new TDLS STA */
	for (sta_idx = 0; sta_idx < HDD_MAX_NUM_TDLS_STA; sta_idx++) {
		if (idx == hdd_ctx->tdlsConnInfo[sta_idx].staId) {
			hdd_info("tdls peer with staIdx %u exists", idx);
			return &hdd_ctx->tdlsConnInfo[sta_idx];
		}
	}
	hdd_err("tdls peer with staIdx %u not exists", idx);
	return NULL;
}

/**
 * wlan_hdd_tdls_idle_handler() - Check tdls idle traffic
 * @user_data: data from tdls idle timer
 *
 * Function to check the tdls idle traffic and make a decision about
 * tdls teardown
 *
 * Return: None
 */
static void wlan_hdd_tdls_idle_handler(void *user_data)
{
	tdlsConnInfo_t *tdls_info = (tdlsConnInfo_t *) user_data;
	hddTdlsPeer_t *curr_peer;
	tdlsCtx_t *hdd_tdls_ctx;
	hdd_context_t *hdd_ctx;
	v_CONTEXT_t cds_context;
	hdd_adapter_t *adapter;

	if (!tdls_info->staId) {
		hdd_err("peer (staidx %u) doesn't exists", tdls_info->staId);
		return;
	}

	cds_context = cds_get_global_context();
	if (NULL == cds_context) {
		hdd_err("cds_context points to NULL");
		return;
	}

	hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
	if (0 != (wlan_hdd_validate_context(hdd_ctx)))
		return;

	adapter = hdd_get_adapter_by_sme_session_id(hdd_ctx,
						    tdls_info->sessionId);

	if (!adapter) {
		hdd_err("adapter is NULL");
		return;
	}

	mutex_lock(&hdd_ctx->tdls_lock);
	curr_peer = wlan_hdd_tdls_find_peer(adapter,
		(u8 *) &tdls_info->peerMac.bytes[0]);

	if (NULL == curr_peer) {
		hdd_err("Invalid tdls idle timer expired");
		goto error_idle_return;
	}

	hdd_tdls_ctx = curr_peer->pHddTdlsCtx;
	if (NULL == hdd_tdls_ctx) {
		hdd_err("Invalid hdd_tdls_ctx context");
		goto error_idle_return;
	}

	hdd_info(MAC_ADDRESS_STR " tx_pkt: %d, rx_pkt: %d, idle_packet_n: %d",
		 MAC_ADDR_ARRAY(curr_peer->peerMac),
		curr_peer->tx_pkt,
		curr_peer->rx_pkt,
		curr_peer->pHddTdlsCtx->threshold_config.idle_packet_n);

	/* Check tx/rx statistics on this tdls link for recent activities and
	 * then decide whether to tear down the link or keep it.
	 */
	if ((curr_peer->tx_pkt >=
	     curr_peer->pHddTdlsCtx->threshold_config.idle_packet_n) ||
	    (curr_peer->rx_pkt >=
	     curr_peer->pHddTdlsCtx->threshold_config.idle_packet_n)) {
		/* this tdls link got back to normal, so keep it */
		hdd_info("tdls link to " MAC_ADDRESS_STR
			 " back to normal, will stay",
			  MAC_ADDR_ARRAY(curr_peer->peerMac));
	} else {
		/* this tdls link needs to get torn down */
		hdd_info("trigger tdls link to "MAC_ADDRESS_STR
			 " down", MAC_ADDR_ARRAY(curr_peer->peerMac));
		wlan_hdd_tdls_indicate_teardown(curr_peer->pHddTdlsCtx->pAdapter,
					curr_peer,
					eSIR_MAC_TDLS_TEARDOWN_UNSPEC_REASON);
	}
error_idle_return:
	mutex_unlock(&hdd_ctx->tdls_lock);
}

/**
 * tdls_ct_process_idle_and_discovery() - process the traffic data
 * @curr_peer: tdls peer needs to be examined
 * @hdd_ctx: hdd context
 * @hdd_tdls_ctx: tdls context
 *
 * Function to check the peer traffic data in idle link and  tdls
 * discovering link
 *
 * Return: None
 */
static void tdls_ct_process_idle_and_discovery(hddTdlsPeer_t *curr_peer,
					       tdlsCtx_t *hdd_tdls_ctx)
{
	uint16_t valid_peers;

	valid_peers = wlan_hdd_tdls_connected_peers(hdd_tdls_ctx->pAdapter);

	if ((curr_peer->tx_pkt + curr_peer->rx_pkt) >=
	     hdd_tdls_ctx->threshold_config.tx_packet_n) {
		if (HDD_MAX_NUM_TDLS_STA > valid_peers) {
			hdd_info("Tput trigger TDLS pre-setup");
			hdd_tdls_ctx->curr_candidate = curr_peer;
			wlan_hdd_tdls_implicit_send_discovery_request(
								  hdd_tdls_ctx);
		} else {
			hdd_info("Maximum peers connected already! %d",
				 valid_peers);
		}
	}
}


/**
 * tdls_ct_process_connected_link() - process the traffic
 * @curr_peer: tdls peer needs to be examined
 * @hdd_ctx: hdd context
 * @hdd_tdls_ctx: tdls context
 *
 * Function to check the peer traffic data in active STA
 * session
 *
 * Return: None
 */
static void tdls_ct_process_connected_link(hddTdlsPeer_t *curr_peer,
					   hdd_context_t *hdd_ctx,
					   tdlsCtx_t *hdd_tdls_ctx)
{
	if ((int32_t)curr_peer->rssi <
	    (int32_t)hdd_tdls_ctx->threshold_config.rssi_teardown_threshold) {
		hdd_warn("Tear down - low RSSI: " MAC_ADDRESS_STR "!",
			 MAC_ADDR_ARRAY(curr_peer->peerMac));
		wlan_hdd_tdls_indicate_teardown(hdd_tdls_ctx->pAdapter,
					curr_peer,
					eSIR_MAC_TDLS_TEARDOWN_UNSPEC_REASON);
		return;
	}

	/* Only teardown based on non zero idle packet threshold, to address
	 * a use case where this threshold does not get consider for TEAR DOWN
	 */
	if ((0 != hdd_tdls_ctx->threshold_config.idle_packet_n) &&
	    ((curr_peer->tx_pkt <
	      hdd_tdls_ctx->threshold_config.idle_packet_n) &&
	     (curr_peer->rx_pkt <
	      hdd_tdls_ctx->threshold_config.idle_packet_n))) {
		if (!curr_peer->is_peer_idle_timer_initialised) {
			uint8_t staId = (uint8_t)curr_peer->staId;

			tdlsConnInfo_t *tdls_info;

			tdls_info = wlan_hdd_get_conn_info(hdd_ctx, staId);
			qdf_mc_timer_init(&curr_peer->peer_idle_timer,
					  QDF_TIMER_TYPE_SW,
					  wlan_hdd_tdls_idle_handler,
					  tdls_info);
			curr_peer->is_peer_idle_timer_initialised = true;
		}
		if (QDF_TIMER_STATE_RUNNING !=
		    curr_peer->peer_idle_timer.state) {
			hdd_warn("Tx/Rx Idle timer start: " MAC_ADDRESS_STR "!",
				MAC_ADDR_ARRAY(curr_peer->peerMac));
			wlan_hdd_tdls_timer_restart(hdd_tdls_ctx->pAdapter,
						    &curr_peer->peer_idle_timer,
						    hdd_tdls_ctx->threshold_config.idle_timeout_t);
		}
	} else if (QDF_TIMER_STATE_RUNNING ==
		   curr_peer->peer_idle_timer.state) {
		hdd_warn("Tx/Rx Idle timer stop: " MAC_ADDRESS_STR "!",
			 MAC_ADDR_ARRAY(curr_peer->peerMac));
		qdf_mc_timer_stop(&curr_peer->peer_idle_timer);
	}
}

/**
 * wlan_hdd_tdls_ct_process_cap_supported() - process TDLS supported peer.
 * @curr_peer: tdls peer needs to be examined
 * @hdd_ctx: hdd context
 * @hdd_tdls_ctx: tdls context
 *
 * Function to check the peer traffic data  for tdls supported peer
 *
 * Return: None
 */
static void wlan_hdd_tdls_ct_process_cap_supported(hddTdlsPeer_t *curr_peer,
						   hdd_context_t *hdd_ctx,
						   tdlsCtx_t *hdd_tdls_ctx)
{
	hdd_debug("tx %d, rx %d (thr.pkt %d/idle %d), rssi %d (thr.trig %d/tear %d)",
		 curr_peer->tx_pkt, curr_peer->rx_pkt,
		 hdd_tdls_ctx->threshold_config.tx_packet_n,
		 hdd_tdls_ctx->threshold_config.idle_packet_n,
		 curr_peer->rssi,
		 hdd_tdls_ctx->threshold_config.rssi_trigger_threshold,
		 hdd_tdls_ctx->threshold_config.rssi_teardown_threshold);

	switch (curr_peer->link_status) {
	case eTDLS_LINK_IDLE:
	case eTDLS_LINK_DISCOVERING:
		if (hdd_ctx->config->fTDLSExternalControl &&
		    (!curr_peer->isForcedPeer))
			break;
		tdls_ct_process_idle_and_discovery(curr_peer, hdd_tdls_ctx);
		break;
	case eTDLS_LINK_CONNECTED:
		tdls_ct_process_connected_link(curr_peer, hdd_ctx,
					       hdd_tdls_ctx);
		break;
	default:
		break;
	}
}

/**
 * wlan_hdd_tdls_ct_process_cap_unknown() - process unknown peer
 * @curr_peer: tdls peer needs to be examined
 * @hdd_ctx: hdd context
 * @hdd_tdls_ctx: tdls context
 *
 * Function check the peer traffic data , when tdls capability is unknown
 *
 * Return: None
 */
static void wlan_hdd_tdls_ct_process_cap_unknown(hddTdlsPeer_t *curr_peer,
						 hdd_context_t *hdd_ctx,
						 tdlsCtx_t *hdd_tdls_ctx)
{
	if (hdd_ctx->config->fTDLSExternalControl &&
	    (!curr_peer->isForcedPeer)) {
		return;
	}

	hdd_debug("threshold_config.tx_packet_n = %d curr_peer->tx_pkt = %d curr_peer->rx_pkt = %d ",
		hdd_tdls_ctx->threshold_config.tx_packet_n, curr_peer->tx_pkt,
		curr_peer->rx_pkt);

	if (!TDLS_IS_CONNECTED(curr_peer) &&
	    ((curr_peer->tx_pkt + curr_peer->rx_pkt) >=
	    hdd_tdls_ctx->threshold_config.tx_packet_n)) {
		/* Ignore discovery attempt if External Control is enabled, that
		 * is, peer is forced. In that case, continue discovery attempt
		 * regardless attempt count
		 */
		hdd_debug("TDLS UNKNOWN pre discover ");
		if (curr_peer->isForcedPeer || curr_peer->discovery_attempt++ <
		    hdd_tdls_ctx->threshold_config.discovery_tries_n) {
			hdd_debug("TDLS UNKNOWN discover ");
			hdd_tdls_ctx->curr_candidate = curr_peer;
			wlan_hdd_tdls_implicit_send_discovery_request(hdd_tdls_ctx);
		} else {
			curr_peer->tdls_support = eTDLS_CAP_NOT_SUPPORTED;
			wlan_hdd_tdls_set_peer_link_status(
				    curr_peer,
				    eTDLS_LINK_IDLE,
				    eTDLS_LINK_NOT_SUPPORTED);
		}
	}
}


/**
 * wlan_hdd_tdls_ct_process_peers() - process the peer
 * @curr_peer: tdls peer needs to be examined
 * @hdd_ctx: hdd context
 * @hdd_tdls_ctx: tdls context
 *
 * This function check the peer capability and process the metadata from
 * the peer
 *
 * Return: None
 */
static void wlan_hdd_tdls_ct_process_peers(hddTdlsPeer_t *curr_peer,
					   hdd_context_t *hdd_ctx,
					   tdlsCtx_t *hdd_tdls_ctx)
{
	hdd_debug(MAC_ADDRESS_STR " link_status %d tdls_support %d",
		 MAC_ADDR_ARRAY(curr_peer->peerMac),
		 curr_peer->link_status, curr_peer->tdls_support);

	switch (curr_peer->tdls_support) {
	case eTDLS_CAP_SUPPORTED:
		wlan_hdd_tdls_ct_process_cap_supported(curr_peer, hdd_ctx,
						       hdd_tdls_ctx);
		break;

	case eTDLS_CAP_UNKNOWN:
		wlan_hdd_tdls_ct_process_cap_unknown(curr_peer, hdd_ctx,
						     hdd_tdls_ctx);
		break;
	default:
		break;
	}

}

/**
 * wlan_hdd_tdls_ct_handler() - TDLS connection tracker handler
 * @user_data: user data from timer
 *
 * tdls connection tracker timer starts, when the STA connected to AP
 * and it's scan the traffic between two STA peers and make TDLS
 * connection and teardown, based on the traffic threshold
 *
 * Return: None
 */
static void wlan_hdd_tdls_ct_handler(void *user_data)
{
	int i;
	hdd_adapter_t *adapter;
	struct list_head *head;
	struct list_head *pos;
	hddTdlsPeer_t *curr_peer;
	tdlsCtx_t *hdd_tdls_ctx;
	hdd_context_t *hdd_ctx;

	adapter = (hdd_adapter_t *)user_data;

	if (NULL == adapter) {
		hdd_err("Invalid adapter context");
		return;
	}

	hdd_ctx = WLAN_HDD_GET_CTX(adapter);

	if (0 != (wlan_hdd_validate_context(hdd_ctx)))
		return;
	/* If any concurrency is detected */
	if (!hdd_ctx->enable_tdls_connection_tracker) {
		hdd_info("Connection tracker is disabled");
		return;
	}

	/* Update tx rx traffic sample in tdls data structures */
	wlan_hdd_tdls_ct_sampling_tx_rx(adapter, hdd_ctx);

	mutex_lock(&hdd_ctx->tdls_lock);
	hdd_tdls_ctx = WLAN_HDD_GET_TDLS_CTX_PTR(adapter);

	if (NULL == hdd_tdls_ctx) {
		mutex_unlock(&hdd_ctx->tdls_lock);
		hdd_err("Invalid hdd_tdls_ctx context");
		return;
	}

	for (i = 0; i < TDLS_PEER_LIST_SIZE; i++) {
		head = &hdd_tdls_ctx->peer_list[i];
		list_for_each(pos, head) {
			curr_peer = list_entry(pos, hddTdlsPeer_t, node);
			wlan_hdd_tdls_ct_process_peers(curr_peer, hdd_ctx,
						       hdd_tdls_ctx);
			curr_peer->tx_pkt = 0;
			curr_peer->rx_pkt = 0;
		}
	}

	wlan_hdd_tdls_timer_restart(hdd_tdls_ctx->pAdapter,
				    &hdd_tdls_ctx->peer_update_timer,
				    hdd_tdls_ctx->threshold_config.tx_period_t);
	mutex_unlock(&hdd_ctx->tdls_lock);
}

/**
 * hdd_set_tdls_scan_type - set scan during active tdls session
 * @hdd_ctx: ptr to hdd context.
 * @val: scan type value: 0 or 1.
 *
 * Set scan type during tdls session. If set to 1, that means driver
 * shall maintain tdls link and allow scan regardless if tdls peer is
 * buffer sta capable or not and/or if device is sleep sta capable or
 * not. If tdls peer is not buffer sta capable then during scan there
 * will be loss of Rx packets and Tx would stop when device moves away
 * from tdls channel. If set to 0, then driver shall teardown tdls link
 * before initiating scan if peer is not buffer sta capable and device
 * is not sleep sta capable. By default, scan type is set to 0.
 *
 * Return: success (0) or failure (errno value)
 */
int hdd_set_tdls_scan_type(hdd_context_t *hdd_ctx, int val)
{
	if ((val != 0) && (val != 1)) {
		hdd_err("Incorrect value of tdls scan type: %d", val);
		return -EINVAL;
	}

	hdd_ctx->config->enable_tdls_scan = val;
	return 0;
}

/**
 * wlan_hdd_tdls_teardown_links() - teardown tdls links
 * @hddCtx : pointer to hdd context
 *
 * Return: 0 if success else non zero
 */
static int wlan_hdd_tdls_teardown_links(hdd_context_t *hddctx,
					uint32_t mode)
{
	uint16_t connected_tdls_peers = 0;
	uint8_t staidx;
	hddTdlsPeer_t *curr_peer;
	hdd_adapter_t *adapter;
	int ret = 0;

	if (eTDLS_SUPPORT_NOT_ENABLED == hddctx->tdls_mode) {
		hdd_info("TDLS mode is disabled OR not enabled in FW");
		return 0;
	}

	adapter = hdd_get_adapter(hddctx, QDF_STA_MODE);

	if (adapter == NULL) {
		hdd_info("Station Adapter Not Found");
		return 0;
	}

	connected_tdls_peers = wlan_hdd_tdls_connected_peers(adapter);

	if (!connected_tdls_peers)
		return 0;

	for (staidx = 0; staidx < hddctx->max_num_tdls_sta;
							staidx++) {
		if (!hddctx->tdlsConnInfo[staidx].staId)
			continue;

		mutex_lock(&hddctx->tdls_lock);
		curr_peer = wlan_hdd_tdls_find_all_peer(hddctx,
			hddctx->tdlsConnInfo[staidx].peerMac.bytes);

		if (!curr_peer) {
			mutex_unlock(&hddctx->tdls_lock);
			continue;
		}

		/* Check if connected peer supports more than one stream */
		if (curr_peer->spatial_streams == TDLS_NSS_1x1_MODE) {
			mutex_unlock(&hddctx->tdls_lock);
			continue;
		}

		hdd_info("Indicate TDLS teardown (staId %d)",
			 curr_peer->staId);

		wlan_hdd_tdls_indicate_teardown(
					curr_peer->pHddTdlsCtx->pAdapter,
					curr_peer,
					eSIR_MAC_TDLS_TEARDOWN_UNSPEC_REASON);
		hddctx->tdls_teardown_peers_cnt++;
		mutex_unlock(&hddctx->tdls_lock);
	}
	mutex_lock(&hddctx->tdls_lock);
	if (hddctx->tdls_teardown_peers_cnt >= 1) {
		hddctx->tdls_nss_switch_in_progress = true;
		hdd_info("TDLS peers to be torn down = %d",
			 hddctx->tdls_teardown_peers_cnt);
		/*  Antenna switch 2x2 to 1x1 */
		if (mode == HDD_ANTENNA_MODE_1X1) {
			hddctx->tdls_nss_transition_mode =
				TDLS_NSS_TRANSITION_2x2_to_1x1;
			ret = -EAGAIN;
		} else {
		/*  Antenna switch 1x1 to 2x2 */
			hddctx->tdls_nss_transition_mode =
				TDLS_NSS_TRANSITION_1x1_to_2x2;
			ret = 0;
		}
		hdd_info("TDLS teardown for antenna switch operation starts");
	}
	mutex_unlock(&hddctx->tdls_lock);
	return ret;
}

/**
 * wlan_hdd_tdls_antenna_switch() - Dynamic TDLS antenna  switch 1x1 <-> 2x2
 * antenna mode in standalone station
 * @hdd_ctx: Pointer to hdd contex
 * @adapter: Pointer to hdd adapter
 *
 * Return: 0 if success else non zero
 */
int wlan_hdd_tdls_antenna_switch(hdd_context_t *hdd_ctx,
					hdd_adapter_t *adapter, uint32_t mode)
{
	uint8_t tdls_peer_cnt;
	uint32_t vdev_nss;
	hdd_station_ctx_t *sta_ctx;

	if (hdd_ctx->connected_peer_count == 0)
		return 0;

	sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(adapter);
	/* Check whether TDLS antenna switch is in progress */
	if (hdd_ctx->tdls_nss_switch_in_progress) {
		if (hdd_ctx->tdls_nss_teardown_complete == false) {
			hdd_err("TDLS antenna switch is in progress");
			return -EAGAIN;
		}
		goto tdls_ant_sw_done;
	}

	/* Check whether TDLS is connected or not */
	mutex_lock(&hdd_ctx->tdls_lock);
	tdls_peer_cnt = hdd_ctx->connected_peer_count;
	mutex_unlock(&hdd_ctx->tdls_lock);
	if (tdls_peer_cnt <= 0) {
		hdd_info("No TDLS connection established");
		goto tdls_ant_sw_done;
	}

	/* Check the supported nss for TDLS */
	if (IS_5G_CH(sta_ctx->conn_info.operationChannel))
		vdev_nss = CFG_TDLS_NSS(
			hdd_ctx->config->vdev_type_nss_5g);
	else
		vdev_nss = CFG_TDLS_NSS(
			hdd_ctx->config->vdev_type_nss_2g);

	if (vdev_nss == HDD_ANTENNA_MODE_1X1) {
		hdd_info("Supported NSS is 1X1, no need to teardown TDLS links");
		goto tdls_ant_sw_done;
	}

	/* teardown all the tdls connections */
	return wlan_hdd_tdls_teardown_links(hdd_ctx, mode);

tdls_ant_sw_done:
	return 0;
}

/**
 * wlan_hdd_change_tdls_mode - Change TDLS mode
 * @data: void pointer
 *
 * Return: None
 */
void wlan_hdd_change_tdls_mode(void *data)
{
	hdd_context_t *hdd_ctx = (hdd_context_t *)data;

	wlan_hdd_tdls_set_mode(hdd_ctx, hdd_ctx->tdls_mode_last, false,
			       HDD_SET_TDLS_MODE_SOURCE_P2P);
}

void hdd_tdls_notify_p2p_roc(hdd_context_t *hdd_ctx,
				enum tdls_concerned_external_events event)
{
	enum tdls_support_mode tdls_mode;

	qdf_mc_timer_stop(&hdd_ctx->tdls_source_timer);

	if (event == P2P_ROC_START) {
		tdls_mode = eTDLS_SUPPORT_DISABLED;
		wlan_hdd_tdls_set_mode(hdd_ctx, tdls_mode, false,
				HDD_SET_TDLS_MODE_SOURCE_P2P);
		wlan_hdd_tdls_disable_offchan_and_teardown_links(hdd_ctx);
	}

	qdf_mc_timer_start(&hdd_ctx->tdls_source_timer,
			   hdd_ctx->config->tdls_enable_defer_time);
}

bool cds_check_is_tdls_allowed(enum tQDF_ADAPTER_MODE device_mode)
{
	bool state = false;
	uint32_t count;
	hdd_context_t *hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);

	count = policy_mgr_get_connection_count(hdd_ctx->hdd_psoc);

	if (count > 1)
		state = false;
	else if (device_mode == QDF_STA_MODE ||
		device_mode == QDF_P2P_CLIENT_MODE)
		state = true;

	/* If any concurrency is detected */
	if (!state)
		hdd_ipa_set_tx_flow_info();

	return state;
}

void cds_set_tdls_ct_mode(hdd_context_t *hdd_ctx)
{
	bool state = false;

	/* If any concurrency is detected, skip tdls pkt tracker */
	if (policy_mgr_get_connection_count(hdd_ctx->hdd_psoc) > 1) {
		state = false;
		goto set_state;
	}

	if (eTDLS_SUPPORT_DISABLED == hdd_ctx->tdls_mode ||
		eTDLS_SUPPORT_NOT_ENABLED == hdd_ctx->tdls_mode ||
		(!hdd_ctx->config->fEnableTDLSImplicitTrigger)) {
		state = false;
		goto set_state;
	} else if (policy_mgr_mode_specific_connection_count(hdd_ctx->hdd_psoc,
		QDF_STA_MODE, NULL) == 1) {
		state = true;
	} else if (policy_mgr_mode_specific_connection_count(hdd_ctx->hdd_psoc,
		QDF_P2P_CLIENT_MODE, NULL) == 1){
		state = true;
	} else {
		state = false;
		goto set_state;
	}

	/* In case of TDLS external control, peer should be added
	 * by the user space to start connection tracker.
	 */
	if (hdd_ctx->config->fTDLSExternalControl) {
		if (hdd_ctx->tdls_external_peer_count)
			state = true;
		else
			state = false;
	}

set_state:
	mutex_lock(&hdd_ctx->tdls_lock);
	hdd_ctx->enable_tdls_connection_tracker = state;
	mutex_unlock(&hdd_ctx->tdls_lock);

	hdd_info("enable_tdls_connection_tracker %d",
		hdd_ctx->enable_tdls_connection_tracker);
}

QDF_STATUS hdd_tdls_register_tdls_peer(void *userdata, uint32_t vdev_id,
				       const uint8_t *mac, uint16_t sta_id,
				       uint8_t ucastsig, uint8_t qos)
{
	hdd_adapter_t *adapter;
	hdd_context_t *hddctx;

	hddctx = userdata;
	if (!hddctx) {
		hdd_err("Invalid hddctx");
		return QDF_STATUS_E_INVAL;
	}
	adapter = hdd_get_adapter_by_vdev(hddctx, vdev_id);
	if (!adapter) {
		hdd_err("Invalid adapter");
		return QDF_STATUS_E_FAILURE;
	}

	return hdd_roam_register_tdlssta(adapter, mac, sta_id, ucastsig, qos);
}

QDF_STATUS hdd_tdls_deregister_tdl_peer(void *userdata,
					uint32_t vdev_id, uint8_t sta_id)
{
	hdd_adapter_t *adapter;
	hdd_context_t *hddctx;

	hddctx = userdata;
	if (!hddctx) {
		hdd_err("Invalid hddctx");
		return QDF_STATUS_E_INVAL;
	}
	adapter = hdd_get_adapter_by_vdev(hddctx, vdev_id);
	if (!adapter) {
		hdd_err("Invalid adapter");
		return QDF_STATUS_E_FAILURE;
	}

	return hdd_roam_deregister_tdlssta(adapter, sta_id);
}
