blob: e4283f0d7acf2a8aa28245a7e400f350b918bdbc [file] [log] [blame]
/*
* Copyright (c) 2017-2019 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/**
* DOC: wlan_tdls_ct.c
*
* TDLS connection tracker function definitions
*/
#include "wlan_tdls_main.h"
#include "wlan_tdls_peer.h"
#include "wlan_tdls_ct.h"
#include "wlan_tdls_cmds_process.h"
bool tdls_is_vdev_authenticated(struct wlan_objmgr_vdev *vdev)
{
struct wlan_objmgr_peer *peer;
bool is_authenticated = false;
peer = wlan_objmgr_vdev_try_get_bsspeer(vdev, WLAN_TDLS_NB_ID);
if (!peer) {
tdls_err("peer is null");
return false;
}
is_authenticated = wlan_peer_mlme_get_auth_state(peer);
wlan_objmgr_peer_release_ref(peer, WLAN_TDLS_NB_ID);
return is_authenticated;
}
/**
* tdls_peer_reset_discovery_processed() - reset discovery status
* @tdls_vdev: TDLS vdev object
*
* 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 tdls_peer_reset_discovery_processed(
struct tdls_vdev_priv_obj *tdls_vdev)
{
int i;
qdf_list_t *head;
qdf_list_node_t *p_node;
struct tdls_peer *peer;
QDF_STATUS status;
tdls_vdev->discovery_peer_cnt = 0;
for (i = 0; i < WLAN_TDLS_PEER_LIST_SIZE; i++) {
head = &tdls_vdev->peer_list[i];
status = qdf_list_peek_front(head, &p_node);
while (QDF_IS_STATUS_SUCCESS(status)) {
peer = qdf_container_of(p_node, struct tdls_peer, node);
peer->discovery_processed = 0;
status = qdf_list_peek_next(head, p_node, &p_node);
}
}
return 0;
}
void tdls_discovery_timeout_peer_cb(void *user_data)
{
int i;
qdf_list_t *head;
qdf_list_node_t *p_node;
struct tdls_peer *peer;
QDF_STATUS status;
struct tdls_vdev_priv_obj *tdls_vdev;
if (!user_data) {
tdls_err("discovery time out data is null");
return;
}
tdls_vdev = (struct tdls_vdev_priv_obj *) user_data;
for (i = 0; i < WLAN_TDLS_PEER_LIST_SIZE; i++) {
head = &tdls_vdev->peer_list[i];
status = qdf_list_peek_front(head, &p_node);
while (QDF_IS_STATUS_SUCCESS(status)) {
peer = qdf_container_of(p_node, struct tdls_peer,
node);
if (TDLS_LINK_DISCOVERING != peer->link_status) {
status = qdf_list_peek_next(head, p_node,
&p_node);
continue;
}
tdls_debug(QDF_MAC_ADDR_STR " to idle state",
QDF_MAC_ADDR_ARRAY(peer->peer_mac.bytes));
tdls_set_peer_link_status(peer,
TDLS_LINK_IDLE,
TDLS_LINK_NOT_SUPPORTED);
}
}
tdls_vdev->discovery_sent_cnt = 0;
/* add tdls power save prohibited */
return;
}
/**
* tdls_reset_tx_rx() - reset tx/rx counters for all tdls peers
* @tdls_vdev: TDLS vdev object
*
* Caller has to take the TDLS lock before calling this function
*
* Return: Void
*/
static void tdls_reset_tx_rx(struct tdls_vdev_priv_obj *tdls_vdev)
{
int i;
qdf_list_t *head;
qdf_list_node_t *p_node;
struct tdls_peer *peer;
QDF_STATUS status;
for (i = 0; i < WLAN_TDLS_PEER_LIST_SIZE; i++) {
head = &tdls_vdev->peer_list[i];
status = qdf_list_peek_front(head, &p_node);
while (QDF_IS_STATUS_SUCCESS(status)) {
peer = qdf_container_of(p_node, struct tdls_peer, node);
peer->tx_pkt = 0;
peer->rx_pkt = 0;
status = qdf_list_peek_next(head, p_node, &p_node);
}
}
return;
}
void tdls_implicit_disable(struct tdls_vdev_priv_obj *tdls_vdev)
{
tdls_debug("Disable Implicit TDLS");
tdls_timers_stop(tdls_vdev);
}
/**
* tdls_implicit_enable() - enable implicit tdls triggering
* @tdls_vdev: TDLS vdev
*
* Return: Void
*/
void tdls_implicit_enable(struct tdls_vdev_priv_obj *tdls_vdev)
{
tdls_debug("Enable Implicit TDLS");
if (!tdls_vdev)
return;
tdls_peer_reset_discovery_processed(tdls_vdev);
tdls_reset_tx_rx(tdls_vdev);
/* TODO check whether tdls power save prohibited */
/* Restart the connection tracker timer */
tdls_timer_restart(tdls_vdev->vdev, &tdls_vdev->peer_update_timer,
tdls_vdev->threshold_config.tx_period_t);
}
/**
* tdls_ct_sampling_tx_rx() - collect tx/rx traffic sample
* @tdls_vdev_obj: tdls vdev object
* @tdls_soc_obj: tdls soc object
*
* Function to update data traffic information in tdls connection
* tracker data structure for connection tracker operation
*
* Return: None
*/
static void tdls_ct_sampling_tx_rx(struct tdls_vdev_priv_obj *tdls_vdev,
struct tdls_soc_priv_obj *tdls_soc)
{
struct tdls_peer *curr_peer;
uint8_t mac[QDF_MAC_ADDR_SIZE];
uint8_t mac_cnt;
uint8_t mac_entries;
struct tdls_conn_tracker_mac_table mac_table[WLAN_TDLS_CT_TABLE_SIZE];
qdf_spin_lock_bh(&tdls_soc->tdls_ct_spinlock);
if (0 == tdls_vdev->valid_mac_entries) {
qdf_spin_unlock_bh(&tdls_soc->tdls_ct_spinlock);
return;
}
mac_entries = QDF_MIN(tdls_vdev->valid_mac_entries,
WLAN_TDLS_CT_TABLE_SIZE);
qdf_mem_copy(mac_table, tdls_vdev->ct_peer_table,
(sizeof(struct tdls_conn_tracker_mac_table)) * mac_entries);
qdf_mem_zero(tdls_vdev->ct_peer_table,
(sizeof(struct tdls_conn_tracker_mac_table)) * mac_entries);
tdls_vdev->valid_mac_entries = 0;
qdf_spin_unlock_bh(&tdls_soc->tdls_ct_spinlock);
for (mac_cnt = 0; mac_cnt < mac_entries; mac_cnt++) {
qdf_mem_copy(mac, mac_table[mac_cnt].mac_address.bytes,
QDF_MAC_ADDR_SIZE);
curr_peer = tdls_get_peer(tdls_vdev, mac);
if (curr_peer) {
curr_peer->tx_pkt =
mac_table[mac_cnt].tx_packet_cnt;
curr_peer->rx_pkt =
mac_table[mac_cnt].rx_packet_cnt;
}
}
}
void tdls_update_rx_pkt_cnt(struct wlan_objmgr_vdev *vdev,
struct qdf_mac_addr *mac_addr,
struct qdf_mac_addr *dest_mac_addr)
{
struct tdls_vdev_priv_obj *tdls_vdev_obj;
struct tdls_soc_priv_obj *tdls_soc_obj;
uint8_t mac_cnt;
uint8_t valid_mac_entries;
struct tdls_conn_tracker_mac_table *mac_table;
struct wlan_objmgr_peer *bss_peer;
if (QDF_STATUS_SUCCESS != tdls_get_vdev_objects(vdev, &tdls_vdev_obj,
&tdls_soc_obj))
return;
if (!tdls_soc_obj->enable_tdls_connection_tracker)
return;
if (qdf_is_macaddr_group(mac_addr))
return;
if (qdf_is_macaddr_group(dest_mac_addr))
return;
if (!qdf_mem_cmp(vdev->vdev_mlme.macaddr, mac_addr,
QDF_MAC_ADDR_SIZE))
return;
bss_peer = wlan_objmgr_vdev_try_get_bsspeer(vdev, WLAN_TDLS_NB_ID);
if (bss_peer) {
if (!qdf_mem_cmp(bss_peer->macaddr, mac_addr,
QDF_MAC_ADDR_SIZE)) {
wlan_objmgr_peer_release_ref(bss_peer, WLAN_TDLS_NB_ID);
return;
}
wlan_objmgr_peer_release_ref(bss_peer, WLAN_TDLS_NB_ID);
}
qdf_spin_lock_bh(&tdls_soc_obj->tdls_ct_spinlock);
valid_mac_entries = tdls_vdev_obj->valid_mac_entries;
mac_table = tdls_vdev_obj->ct_peer_table;
for (mac_cnt = 0; mac_cnt < valid_mac_entries; mac_cnt++) {
if (qdf_mem_cmp(mac_table[mac_cnt].mac_address.bytes,
mac_addr, QDF_MAC_ADDR_SIZE) == 0) {
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 < WLAN_TDLS_CT_TABLE_SIZE) {
qdf_mem_copy(mac_table[mac_cnt].mac_address.bytes,
mac_addr, QDF_MAC_ADDR_SIZE);
tdls_vdev_obj->valid_mac_entries = mac_cnt+1;
mac_table[mac_cnt].rx_packet_cnt = 1;
}
rx_cnt_return:
qdf_spin_unlock_bh(&tdls_soc_obj->tdls_ct_spinlock);
return;
}
void tdls_update_tx_pkt_cnt(struct wlan_objmgr_vdev *vdev,
struct qdf_mac_addr *mac_addr)
{
struct tdls_vdev_priv_obj *tdls_vdev_obj;
struct tdls_soc_priv_obj *tdls_soc_obj;
uint8_t mac_cnt;
uint8_t valid_mac_entries;
struct tdls_conn_tracker_mac_table *mac_table;
struct wlan_objmgr_peer *bss_peer;
if (QDF_STATUS_SUCCESS != tdls_get_vdev_objects(vdev, &tdls_vdev_obj,
&tdls_soc_obj))
return;
if (!tdls_soc_obj->enable_tdls_connection_tracker)
return;
if (qdf_is_macaddr_group(mac_addr))
return;
if (!qdf_mem_cmp(vdev->vdev_mlme.macaddr, mac_addr,
QDF_MAC_ADDR_SIZE))
return;
bss_peer = wlan_objmgr_vdev_try_get_bsspeer(vdev, WLAN_TDLS_NB_ID);
if (bss_peer) {
if (!qdf_mem_cmp(bss_peer->macaddr, mac_addr,
QDF_MAC_ADDR_SIZE)) {
wlan_objmgr_peer_release_ref(bss_peer, WLAN_TDLS_NB_ID);
return;
}
wlan_objmgr_peer_release_ref(bss_peer, WLAN_TDLS_NB_ID);
}
qdf_spin_lock_bh(&tdls_soc_obj->tdls_ct_spinlock);
mac_table = tdls_vdev_obj->ct_peer_table;
valid_mac_entries = tdls_vdev_obj->valid_mac_entries;
for (mac_cnt = 0; mac_cnt < valid_mac_entries; mac_cnt++) {
if (qdf_mem_cmp(mac_table[mac_cnt].mac_address.bytes,
mac_addr, QDF_MAC_ADDR_SIZE) == 0) {
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 < WLAN_TDLS_CT_TABLE_SIZE) {
qdf_mem_copy(mac_table[mac_cnt].mac_address.bytes,
mac_addr, QDF_MAC_ADDR_SIZE);
mac_table[mac_cnt].tx_packet_cnt = 1;
tdls_vdev_obj->valid_mac_entries++;
}
tx_cnt_return:
qdf_spin_unlock_bh(&tdls_soc_obj->tdls_ct_spinlock);
return;
}
void tdls_implicit_send_discovery_request(
struct tdls_vdev_priv_obj *tdls_vdev_obj)
{
struct tdls_peer *curr_peer;
struct tdls_peer *temp_peer;
struct tdls_soc_priv_obj *tdls_psoc;
struct tdls_osif_indication tdls_ind;
if (!tdls_vdev_obj) {
tdls_notice("tdls_vdev_obj is NULL");
return;
}
tdls_psoc = wlan_vdev_get_tdls_soc_obj(tdls_vdev_obj->vdev);
if (!tdls_psoc) {
tdls_notice("tdls_psoc_obj is NULL");
return;
}
curr_peer = tdls_vdev_obj->curr_candidate;
if (!curr_peer) {
tdls_err("curr_peer is NULL");
return;
}
/* This function is called in mutex_lock */
temp_peer = tdls_is_progress(tdls_vdev_obj, NULL, 0);
if (temp_peer) {
tdls_notice(QDF_MAC_ADDR_STR " ongoing. pre_setup ignored",
QDF_MAC_ADDR_ARRAY(temp_peer->peer_mac.bytes));
goto done;
}
if (TDLS_CAP_UNKNOWN != curr_peer->tdls_support)
tdls_set_peer_link_status(curr_peer,
TDLS_LINK_DISCOVERING,
TDLS_LINK_SUCCESS);
qdf_mem_copy(tdls_ind.peer_mac, curr_peer->peer_mac.bytes,
QDF_MAC_ADDR_SIZE);
tdls_ind.vdev = tdls_vdev_obj->vdev;
tdls_debug("Implicit TDLS, Send Discovery request event");
tdls_psoc->tdls_event_cb(tdls_psoc->tdls_evt_cb_data,
TDLS_EVENT_DISCOVERY_REQ, &tdls_ind);
tdls_vdev_obj->discovery_sent_cnt++;
tdls_timer_restart(tdls_vdev_obj->vdev,
&tdls_vdev_obj->peer_discovery_timer,
tdls_vdev_obj->threshold_config.tx_period_t -
TDLS_DISCOVERY_TIMEOUT_ERE_UPDATE);
tdls_debug("discovery count %u timeout %u msec",
tdls_vdev_obj->discovery_sent_cnt,
tdls_vdev_obj->threshold_config.tx_period_t -
TDLS_DISCOVERY_TIMEOUT_ERE_UPDATE);
done:
tdls_vdev_obj->curr_candidate = NULL;
tdls_vdev_obj->magic = 0;
return;
}
int tdls_recv_discovery_resp(struct tdls_vdev_priv_obj *tdls_vdev,
const uint8_t *mac)
{
struct tdls_peer *curr_peer;
struct tdls_soc_priv_obj *tdls_soc;
struct tdls_osif_indication indication;
struct tdls_config_params *tdls_cfg;
int status = 0;
if (!tdls_vdev)
return -EINVAL;
tdls_soc = wlan_vdev_get_tdls_soc_obj(tdls_vdev->vdev);
if (!tdls_soc) {
tdls_err("tdls soc is NULL");
return -EINVAL;
}
curr_peer = tdls_get_peer(tdls_vdev, mac);
if (!curr_peer) {
tdls_err("curr_peer is NULL");
return -EINVAL;
}
if (tdls_vdev->discovery_sent_cnt)
tdls_vdev->discovery_sent_cnt--;
if (0 == tdls_vdev->discovery_sent_cnt)
qdf_mc_timer_stop(&tdls_vdev->peer_discovery_timer);
tdls_debug("Discovery(%u) Response from " QDF_MAC_ADDR_STR
" link_status %d", tdls_vdev->discovery_sent_cnt,
QDF_MAC_ADDR_ARRAY(curr_peer->peer_mac.bytes),
curr_peer->link_status);
tdls_cfg = &tdls_vdev->threshold_config;
if (TDLS_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) tdls_cfg->rssi_trigger_threshold) {
tdls_set_peer_link_status(curr_peer,
TDLS_LINK_DISCOVERED,
TDLS_LINK_SUCCESS);
tdls_debug("Rssi Threshold met: " QDF_MAC_ADDR_STR
" rssi = %d threshold= %d",
QDF_MAC_ADDR_ARRAY(curr_peer->peer_mac.bytes),
curr_peer->rssi,
tdls_cfg->rssi_trigger_threshold);
qdf_mem_copy(indication.peer_mac, mac,
QDF_MAC_ADDR_SIZE);
indication.vdev = tdls_vdev->vdev;
tdls_soc->tdls_event_cb(tdls_soc->tdls_evt_cb_data,
TDLS_EVENT_SETUP_REQ,
&indication);
} else {
tdls_debug("Rssi Threshold not met: " QDF_MAC_ADDR_STR
" rssi = %d threshold = %d ",
QDF_MAC_ADDR_ARRAY(curr_peer->peer_mac.bytes),
curr_peer->rssi,
tdls_cfg->rssi_trigger_threshold);
tdls_set_peer_link_status(curr_peer,
TDLS_LINK_IDLE,
TDLS_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 = TDLS_CAP_SUPPORTED;
return status;
}
void tdls_indicate_teardown(struct tdls_vdev_priv_obj *tdls_vdev,
struct tdls_peer *curr_peer,
uint16_t reason)
{
struct tdls_soc_priv_obj *tdls_soc;
struct tdls_osif_indication indication;
if (!tdls_vdev || !curr_peer) {
tdls_err("tdls_vdev: %pK, curr_peer: %pK",
tdls_vdev, curr_peer);
return;
}
tdls_soc = wlan_vdev_get_tdls_soc_obj(tdls_vdev->vdev);
if (!tdls_soc) {
tdls_err("tdls_soc: %pK", tdls_soc);
return;
}
if (TDLS_LINK_CONNECTED != curr_peer->link_status)
return;
tdls_set_peer_link_status(curr_peer,
TDLS_LINK_TEARING,
TDLS_LINK_UNSPECIFIED);
tdls_notice("Teardown reason %d", reason);
if (tdls_soc->tdls_dp_vdev_update)
tdls_soc->tdls_dp_vdev_update(&tdls_soc->soc,
&curr_peer->peer_mac,
tdls_soc->tdls_update_dp_vdev_flags,
false);
indication.reason = reason;
indication.vdev = tdls_vdev->vdev;
qdf_mem_copy(indication.peer_mac, curr_peer->peer_mac.bytes,
QDF_MAC_ADDR_SIZE);
if (tdls_soc->tdls_event_cb)
tdls_soc->tdls_event_cb(tdls_soc->tdls_evt_cb_data,
TDLS_EVENT_TEARDOWN_REQ, &indication);
}
/**
* tdls_get_conn_info() - get the tdls connection information.
* @tdls_soc: tdls soc object
* @peer_mac: peer MAC address
*
* Function to check tdls sta index
*
* Return: tdls connection information
*/
static struct tdls_conn_info *
tdls_get_conn_info(struct tdls_soc_priv_obj *tdls_soc,
struct qdf_mac_addr *peer_mac)
{
uint8_t sta_idx;
/* check if there is available index for this new TDLS STA */
for (sta_idx = 0; sta_idx < WLAN_TDLS_STA_MAX_NUM; sta_idx++) {
if (!qdf_mem_cmp(
tdls_soc->tdls_conn_info[sta_idx].peer_mac.bytes,
peer_mac->bytes, QDF_MAC_ADDR_SIZE)) {
tdls_debug("tdls peer exists %pM", peer_mac->bytes);
tdls_soc->tdls_conn_info[sta_idx].index = sta_idx;
return &tdls_soc->tdls_conn_info[sta_idx];
}
}
tdls_err("tdls peer does not exists");
return NULL;
}
static void
tdls_ct_process_idle_handler(struct wlan_objmgr_vdev *vdev,
struct tdls_conn_info *tdls_info)
{
struct tdls_peer *curr_peer;
struct tdls_vdev_priv_obj *tdls_vdev_obj;
struct tdls_soc_priv_obj *tdls_soc_obj;
if (QDF_STATUS_SUCCESS != tdls_get_vdev_objects(vdev, &tdls_vdev_obj,
&tdls_soc_obj))
return;
if (!tdls_info->valid_entry) {
tdls_err("peer doesn't exists");
return;
}
curr_peer = tdls_find_peer(tdls_vdev_obj,
(u8 *) &tdls_info->peer_mac.bytes[0]);
if (!curr_peer) {
tdls_err("Invalid tdls idle timer expired");
return;
}
tdls_debug(QDF_MAC_ADDR_STR
" tx_pkt: %d, rx_pkt: %d, idle_packet_n: %d",
QDF_MAC_ADDR_ARRAY(curr_peer->peer_mac.bytes),
curr_peer->tx_pkt,
curr_peer->rx_pkt,
tdls_vdev_obj->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 >=
tdls_vdev_obj->threshold_config.idle_packet_n) ||
(curr_peer->rx_pkt >=
tdls_vdev_obj->threshold_config.idle_packet_n)) {
/* this tdls link got back to normal, so keep it */
tdls_debug("tdls link to " QDF_MAC_ADDR_STR
" back to normal, will stay",
QDF_MAC_ADDR_ARRAY(curr_peer->peer_mac.bytes));
} else {
/* this tdls link needs to get torn down */
tdls_notice("trigger tdls link to "QDF_MAC_ADDR_STR" down",
QDF_MAC_ADDR_ARRAY(curr_peer->peer_mac.bytes));
tdls_indicate_teardown(tdls_vdev_obj,
curr_peer,
TDLS_TEARDOWN_PEER_UNSPEC_REASON);
}
return;
}
void tdls_ct_idle_handler(void *user_data)
{
struct wlan_objmgr_vdev *vdev;
struct tdls_conn_info *tdls_info;
struct tdls_soc_priv_obj *tdls_soc_obj;
uint32_t idx;
tdls_info = (struct tdls_conn_info *)user_data;
if (!tdls_info)
return;
idx = tdls_info->index;
if (idx == INVALID_TDLS_PEER_INDEX || idx >= WLAN_TDLS_STA_MAX_NUM)
return;
tdls_soc_obj = qdf_container_of(tdls_info, struct tdls_soc_priv_obj,
tdls_conn_info[idx]);
vdev = tdls_get_vdev(tdls_soc_obj->soc, WLAN_TDLS_NB_ID);
if (!vdev) {
tdls_err("Unable to fetch the vdev");
return;
}
tdls_ct_process_idle_handler(vdev, tdls_info);
wlan_objmgr_vdev_release_ref(vdev,
WLAN_TDLS_NB_ID);
}
/**
* tdls_ct_process_idle_and_discovery() - process the traffic data
* @curr_peer: tdls peer needs to be examined
* @tdls_vdev_obj: tdls vdev object
* @tdls_soc_obj: tdls soc object
*
* Function to check the peer traffic data in idle link and tdls
* discovering link
*
* Return: None
*/
static void
tdls_ct_process_idle_and_discovery(struct tdls_peer *curr_peer,
struct tdls_vdev_priv_obj *tdls_vdev_obj,
struct tdls_soc_priv_obj *tdls_soc_obj)
{
uint16_t valid_peers;
valid_peers = tdls_soc_obj->connected_peer_count;
if ((curr_peer->tx_pkt + curr_peer->rx_pkt) >=
tdls_vdev_obj->threshold_config.tx_packet_n) {
if (WLAN_TDLS_STA_MAX_NUM > valid_peers) {
tdls_notice("Tput trigger TDLS pre-setup");
tdls_vdev_obj->curr_candidate = curr_peer;
tdls_implicit_send_discovery_request(tdls_vdev_obj);
} else {
tdls_notice("Maximum peers connected already! %d",
valid_peers);
}
}
}
/**
* tdls_ct_process_connected_link() - process the traffic
* @curr_peer: tdls peer needs to be examined
* @tdls_vdev_obj: tdls vdev
* @tdls_soc_obj: tdls soc context
*
* Function to check the peer traffic data in active STA
* session
*
* Return: None
*/
static void tdls_ct_process_connected_link(
struct tdls_peer *curr_peer,
struct tdls_vdev_priv_obj *tdls_vdev,
struct tdls_soc_priv_obj *tdls_soc)
{
/* Don't trigger low rssi tear down here since FW will do it */
/* 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 != tdls_vdev->threshold_config.idle_packet_n) &&
((curr_peer->tx_pkt <
tdls_vdev->threshold_config.idle_packet_n) &&
(curr_peer->rx_pkt <
tdls_vdev->threshold_config.idle_packet_n))) {
if (!curr_peer->is_peer_idle_timer_initialised) {
struct tdls_conn_info *tdls_info;
tdls_info = tdls_get_conn_info(tdls_soc,
&curr_peer->peer_mac);
qdf_mc_timer_init(&curr_peer->peer_idle_timer,
QDF_TIMER_TYPE_SW,
tdls_ct_idle_handler,
(void *)tdls_info);
curr_peer->is_peer_idle_timer_initialised = true;
}
if (QDF_TIMER_STATE_RUNNING !=
curr_peer->peer_idle_timer.state) {
tdls_warn("Tx/Rx Idle timer start: "
QDF_MAC_ADDR_STR "!",
QDF_MAC_ADDR_ARRAY(curr_peer->peer_mac.bytes));
tdls_timer_restart(tdls_vdev->vdev,
&curr_peer->peer_idle_timer,
tdls_vdev->threshold_config.idle_timeout_t);
}
} else if (QDF_TIMER_STATE_RUNNING ==
curr_peer->peer_idle_timer.state) {
tdls_warn("Tx/Rx Idle timer stop: " QDF_MAC_ADDR_STR "!",
QDF_MAC_ADDR_ARRAY(curr_peer->peer_mac.bytes));
qdf_mc_timer_stop(&curr_peer->peer_idle_timer);
}
}
/**
* tdls_ct_process_cap_supported() - process TDLS supported peer.
* @curr_peer: tdls peer needs to be examined
* @tdls_vdev_obj: tdls vdev context
* @tdls_soc_obj: tdls soc context
*
* Function to check the peer traffic data for tdls supported peer
*
* Return: None
*/
static void tdls_ct_process_cap_supported(struct tdls_peer *curr_peer,
struct tdls_vdev_priv_obj *tdls_vdev,
struct tdls_soc_priv_obj *tdls_soc_obj)
{
tdls_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,
tdls_vdev->threshold_config.tx_packet_n,
tdls_vdev->threshold_config.idle_packet_n,
curr_peer->rssi,
tdls_vdev->threshold_config.rssi_trigger_threshold,
tdls_vdev->threshold_config.rssi_teardown_threshold);
switch (curr_peer->link_status) {
case TDLS_LINK_IDLE:
case TDLS_LINK_DISCOVERING:
if (TDLS_IS_EXTERNAL_CONTROL_ENABLED(
tdls_soc_obj->tdls_configs.tdls_feature_flags) &&
(!curr_peer->is_forced_peer))
break;
tdls_ct_process_idle_and_discovery(curr_peer, tdls_vdev,
tdls_soc_obj);
break;
case TDLS_LINK_CONNECTED:
tdls_ct_process_connected_link(curr_peer, tdls_vdev,
tdls_soc_obj);
break;
default:
break;
}
}
/**
* tdls_ct_process_cap_unknown() - process unknown peer
* @curr_peer: tdls peer needs to be examined
* @tdls_vdev_obj: tdls vdev object
* @tdls_soc_obj: tdls soc object
*
* Function check the peer traffic data , when tdls capability is unknown
*
* Return: None
*/
static void tdls_ct_process_cap_unknown(struct tdls_peer *curr_peer,
struct tdls_vdev_priv_obj *tdls_vdev,
struct tdls_soc_priv_obj *tdlsa_soc)
{
if (TDLS_IS_EXTERNAL_CONTROL_ENABLED(
tdlsa_soc->tdls_configs.tdls_feature_flags) &&
(!curr_peer->is_forced_peer))
return;
tdls_debug("threshold tx pkt = %d peer tx_pkt = %d & rx_pkt = %d ",
tdls_vdev->threshold_config.tx_packet_n, curr_peer->tx_pkt,
curr_peer->rx_pkt);
if (!TDLS_IS_LINK_CONNECTED(curr_peer) &&
((curr_peer->tx_pkt + curr_peer->rx_pkt) >=
tdls_vdev->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
*/
tdls_debug("TDLS UNKNOWN pre discover ");
if (curr_peer->is_forced_peer ||
curr_peer->discovery_attempt++ <
tdls_vdev->threshold_config.discovery_tries_n) {
tdls_debug("TDLS UNKNOWN discover ");
tdls_vdev->curr_candidate = curr_peer;
tdls_implicit_send_discovery_request(tdls_vdev);
} else {
curr_peer->tdls_support = TDLS_CAP_NOT_SUPPORTED;
tdls_set_peer_link_status(
curr_peer,
TDLS_LINK_IDLE,
TDLS_LINK_NOT_SUPPORTED);
}
}
}
/**
* tdls_ct_process_peers() - process the peer
* @curr_peer: tdls peer needs to be examined
* @tdls_vdev_obj: tdls vdev object
* @tdls_soc_obj: tdls soc object
*
* This function check the peer capability and process the metadata from
* the peer
*
* Return: None
*/
static void tdls_ct_process_peers(struct tdls_peer *curr_peer,
struct tdls_vdev_priv_obj *tdls_vdev_obj,
struct tdls_soc_priv_obj *tdls_soc_obj)
{
tdls_debug(QDF_MAC_ADDR_STR " link_status %d tdls_support %d",
QDF_MAC_ADDR_ARRAY(curr_peer->peer_mac.bytes),
curr_peer->link_status, curr_peer->tdls_support);
switch (curr_peer->tdls_support) {
case TDLS_CAP_SUPPORTED:
tdls_ct_process_cap_supported(curr_peer, tdls_vdev_obj,
tdls_soc_obj);
break;
case TDLS_CAP_UNKNOWN:
tdls_ct_process_cap_unknown(curr_peer, tdls_vdev_obj,
tdls_soc_obj);
break;
default:
break;
}
}
static void tdls_ct_process_handler(struct wlan_objmgr_vdev *vdev)
{
int i;
qdf_list_t *head;
qdf_list_node_t *list_node;
struct tdls_peer *curr_peer;
QDF_STATUS status;
struct tdls_vdev_priv_obj *tdls_vdev_obj;
struct tdls_soc_priv_obj *tdls_soc_obj;
if (QDF_STATUS_SUCCESS != tdls_get_vdev_objects(vdev, &tdls_vdev_obj,
&tdls_soc_obj))
return;
/* If any concurrency is detected */
if (!tdls_soc_obj->enable_tdls_connection_tracker) {
tdls_notice("Connection tracker is disabled");
return;
}
/* Update tx rx traffic sample in tdls data structures */
tdls_ct_sampling_tx_rx(tdls_vdev_obj, tdls_soc_obj);
for (i = 0; i < WLAN_TDLS_PEER_LIST_SIZE; i++) {
head = &tdls_vdev_obj->peer_list[i];
status = qdf_list_peek_front(head, &list_node);
while (QDF_IS_STATUS_SUCCESS(status)) {
curr_peer = qdf_container_of(list_node,
struct tdls_peer, node);
tdls_ct_process_peers(curr_peer, tdls_vdev_obj,
tdls_soc_obj);
curr_peer->tx_pkt = 0;
curr_peer->rx_pkt = 0;
status = qdf_list_peek_next(head,
list_node, &list_node);
}
}
tdls_timer_restart(tdls_vdev_obj->vdev,
&tdls_vdev_obj->peer_update_timer,
tdls_vdev_obj->threshold_config.tx_period_t);
}
void tdls_ct_handler(void *user_data)
{
struct wlan_objmgr_vdev *vdev;
if (!user_data)
return;
vdev = (struct wlan_objmgr_vdev *)user_data;
if (QDF_STATUS_SUCCESS != wlan_objmgr_vdev_try_get_ref(vdev,
WLAN_TDLS_NB_ID))
return;
tdls_ct_process_handler(vdev);
wlan_objmgr_vdev_release_ref(vdev,
WLAN_TDLS_NB_ID);
}
int tdls_set_tdls_offchannel(struct tdls_soc_priv_obj *tdls_soc,
int offchannel)
{
uint32_t tdls_feature_flags;
tdls_feature_flags = tdls_soc->tdls_configs.tdls_feature_flags;
if (TDLS_IS_OFF_CHANNEL_ENABLED(tdls_feature_flags) &&
(TDLS_SUPPORT_EXP_TRIG_ONLY == tdls_soc->tdls_current_mode ||
TDLS_SUPPORT_IMP_MODE == tdls_soc->tdls_current_mode ||
TDLS_SUPPORT_EXT_CONTROL == tdls_soc->tdls_current_mode)) {
if (offchannel < TDLS_PREFERRED_OFF_CHANNEL_NUM_MIN ||
offchannel > TDLS_PREFERRED_OFF_CHANNEL_NUM_MAX) {
tdls_err("Invalid tdls off channel %u", offchannel);
return -EINVAL;
}
} else {
tdls_err("Either TDLS or TDLS Off-channel is not enabled");
return -ENOTSUPP;
}
tdls_notice("change tdls off channel from %d to %d",
tdls_soc->tdls_off_channel, offchannel);
tdls_soc->tdls_off_channel = offchannel;
return 0;
}
int tdls_set_tdls_secoffchanneloffset(struct tdls_soc_priv_obj *tdls_soc,
int offchanoffset)
{
uint32_t tdls_feature_flags;
tdls_feature_flags = tdls_soc->tdls_configs.tdls_feature_flags;
if (!TDLS_IS_OFF_CHANNEL_ENABLED(tdls_feature_flags) ||
TDLS_SUPPORT_SUSPENDED >= tdls_soc->tdls_current_mode) {
tdls_err("Either TDLS or TDLS Off-channel is not enabled");
return -ENOTSUPP;
}
tdls_soc->tdls_channel_offset = BW_INVALID;
switch (offchanoffset) {
case TDLS_SEC_OFFCHAN_OFFSET_0:
tdls_soc->tdls_channel_offset = BW20;
break;
case TDLS_SEC_OFFCHAN_OFFSET_40PLUS:
tdls_soc->tdls_channel_offset = BW40_HIGH_PRIMARY;
break;
case TDLS_SEC_OFFCHAN_OFFSET_40MINUS:
tdls_soc->tdls_channel_offset = BW40_LOW_PRIMARY;
break;
case TDLS_SEC_OFFCHAN_OFFSET_80:
tdls_soc->tdls_channel_offset = BW80;
break;
case TDLS_SEC_OFFCHAN_OFFSET_160:
tdls_soc->tdls_channel_offset = BWALL;
break;
default:
tdls_err("Invalid tdls secondary off channel offset %d",
offchanoffset);
return -EINVAL;
} /* end switch */
tdls_notice("change tdls secondary off channel offset to 0x%x",
tdls_soc->tdls_channel_offset);
return 0;
}
int tdls_set_tdls_offchannelmode(struct wlan_objmgr_vdev *vdev,
int offchanmode)
{
struct tdls_peer *conn_peer = NULL;
struct tdls_channel_switch_params chan_switch_params;
QDF_STATUS status = QDF_STATUS_E_FAILURE;
int ret_value = 0;
struct tdls_vdev_priv_obj *tdls_vdev;
struct tdls_soc_priv_obj *tdls_soc;
uint32_t tdls_feature_flags;
status = tdls_get_vdev_objects(vdev, &tdls_vdev, &tdls_soc);
if (status != QDF_STATUS_SUCCESS)
return -EINVAL;
if (offchanmode < ENABLE_CHANSWITCH ||
offchanmode > DISABLE_CHANSWITCH) {
tdls_err("Invalid tdls off channel mode %d", offchanmode);
return -EINVAL;
}
if (wlan_vdev_is_up(vdev) != QDF_STATUS_SUCCESS) {
tdls_err("tdls off channel req in not associated state %d",
offchanmode);
return -EPERM;
}
tdls_feature_flags = tdls_soc->tdls_configs.tdls_feature_flags;
if (!TDLS_IS_OFF_CHANNEL_ENABLED(tdls_feature_flags) ||
TDLS_SUPPORT_SUSPENDED >= tdls_soc->tdls_current_mode) {
tdls_err("Either TDLS or TDLS Off-channel is not enabled");
return -ENOTSUPP;
}
conn_peer = tdls_find_first_connected_peer(tdls_vdev);
if (!conn_peer) {
tdls_err("No TDLS Connected Peer");
return -EPERM;
}
tdls_notice("TDLS Channel Switch in swmode=%d tdls_off_channel %d offchanoffset %d",
offchanmode, tdls_soc->tdls_off_channel,
tdls_soc->tdls_channel_offset);
switch (offchanmode) {
case ENABLE_CHANSWITCH:
if (tdls_soc->tdls_off_channel &&
tdls_soc->tdls_channel_offset != BW_INVALID) {
chan_switch_params.tdls_off_ch =
tdls_soc->tdls_off_channel;
chan_switch_params.tdls_off_ch_bw_offset =
tdls_soc->tdls_channel_offset;
chan_switch_params.oper_class =
tdls_find_opclass(tdls_soc->soc,
chan_switch_params.tdls_off_ch,
chan_switch_params.tdls_off_ch_bw_offset);
if (!chan_switch_params.oper_class) {
if (chan_switch_params.tdls_off_ch_bw_offset ==
BW40_HIGH_PRIMARY)
chan_switch_params.oper_class =
tdls_find_opclass(tdls_soc->soc,
chan_switch_params.tdls_off_ch,
BW40_LOW_PRIMARY);
else if (chan_switch_params.
tdls_off_ch_bw_offset ==
BW40_LOW_PRIMARY)
chan_switch_params.oper_class =
tdls_find_opclass(tdls_soc->soc,
chan_switch_params.tdls_off_ch,
BW40_HIGH_PRIMARY);
tdls_debug("oper_class:%d",
chan_switch_params.oper_class);
}
} else {
tdls_err("TDLS off-channel parameters are not set yet!!!");
return -EINVAL;
}
break;
case DISABLE_CHANSWITCH:
chan_switch_params.tdls_off_ch = 0;
chan_switch_params.tdls_off_ch_bw_offset = 0;
chan_switch_params.oper_class = 0;
break;
default:
tdls_err("Incorrect Parameters mode: %d tdls_off_channel: %d offchanoffset: %d",
offchanmode, tdls_soc->tdls_off_channel,
tdls_soc->tdls_channel_offset);
return -EINVAL;
} /* end switch */
chan_switch_params.vdev_id = tdls_vdev->session_id;
chan_switch_params.tdls_sw_mode = offchanmode;
chan_switch_params.is_responder =
conn_peer->is_responder;
qdf_mem_copy(&chan_switch_params.peer_mac_addr,
&conn_peer->peer_mac.bytes,
QDF_MAC_ADDR_SIZE);
tdls_notice("Peer " QDF_MAC_ADDR_STR " vdevId: %d, off channel: %d, offset: %d, mode: %d, is_responder: %d",
QDF_MAC_ADDR_ARRAY(chan_switch_params.peer_mac_addr),
chan_switch_params.vdev_id,
chan_switch_params.tdls_off_ch,
chan_switch_params.tdls_off_ch_bw_offset,
chan_switch_params.tdls_sw_mode,
chan_switch_params.is_responder);
status = tdls_set_offchan_mode(tdls_soc->soc,
&chan_switch_params);
if (status != QDF_STATUS_SUCCESS) {
tdls_err("Failed to send channel switch request to wmi");
return -EINVAL;
}
tdls_soc->tdls_fw_off_chan_mode = offchanmode;
if (ENABLE_CHANSWITCH == offchanmode) {
conn_peer = tdls_find_first_connected_peer(tdls_vdev);
if (!conn_peer) {
tdls_err("No TDLS Connected Peer");
return -EPERM;
}
conn_peer->pref_off_chan_num =
chan_switch_params.tdls_off_ch;
conn_peer->op_class_for_pref_off_chan =
chan_switch_params.oper_class;
}
return ret_value;
}
static QDF_STATUS tdls_delete_all_tdls_peers_flush_cb(struct scheduler_msg *msg)
{
if (msg && msg->bodyptr) {
qdf_mem_free(msg->bodyptr);
msg->bodyptr = NULL;
}
return QDF_STATUS_SUCCESS;
}
/**
* tdls_delete_all_tdls_peers(): send request to delete tdls peers
* @vdev: vdev object
* @tdls_soc: tdls soc object
*
* This function sends request to lim to delete tdls peers
*
* Return: QDF_STATUS
*/
QDF_STATUS tdls_delete_all_tdls_peers(struct wlan_objmgr_vdev *vdev,
struct tdls_soc_priv_obj *tdls_soc)
{
struct wlan_objmgr_peer *peer;
struct tdls_del_all_tdls_peers *del_msg;
struct scheduler_msg msg = {0};
QDF_STATUS status;
peer = wlan_objmgr_vdev_try_get_bsspeer(vdev, WLAN_TDLS_SB_ID);
if (!peer) {
tdls_err("bss peer is null");
return QDF_STATUS_E_FAILURE;
}
del_msg = qdf_mem_malloc(sizeof(*del_msg));
if (!del_msg) {
tdls_err("memory alloc failed");
wlan_objmgr_peer_release_ref(peer, WLAN_TDLS_SB_ID);
return QDF_STATUS_E_FAILURE;
}
qdf_mem_copy(del_msg->bssid.bytes,
wlan_peer_get_macaddr(peer), QDF_MAC_ADDR_SIZE);
wlan_objmgr_peer_release_ref(peer, WLAN_TDLS_SB_ID);
del_msg->msg_type = tdls_soc->tdls_del_all_peers;
del_msg->msg_len = (uint16_t) sizeof(*del_msg);
/* Send the request to PE. */
qdf_mem_zero(&msg, sizeof(msg));
tdls_debug("sending delete all peers req to PE ");
msg.type = del_msg->msg_type;
msg.bodyptr = del_msg;
msg.flush_callback = tdls_delete_all_tdls_peers_flush_cb;
status = scheduler_post_message(QDF_MODULE_ID_TDLS,
QDF_MODULE_ID_PE,
QDF_MODULE_ID_PE, &msg);
if (QDF_IS_STATUS_ERROR(status)) {
tdls_err("post delete all peer req failed, status %d", status);
qdf_mem_free(del_msg);
}
return status;
}
void tdls_disable_offchan_and_teardown_links(
struct wlan_objmgr_vdev *vdev)
{
uint16_t connected_tdls_peers = 0;
uint8_t staidx;
struct tdls_peer *curr_peer = NULL;
struct tdls_vdev_priv_obj *tdls_vdev;
struct tdls_soc_priv_obj *tdls_soc;
QDF_STATUS status;
uint8_t vdev_id;
bool tdls_in_progress = false;
status = tdls_get_vdev_objects(vdev, &tdls_vdev, &tdls_soc);
if (QDF_STATUS_SUCCESS != status) {
tdls_err("tdls objects are NULL ");
return;
}
if (TDLS_SUPPORT_SUSPENDED >= tdls_soc->tdls_current_mode) {
tdls_notice("TDLS mode %d is disabled OR not suspended now",
tdls_soc->tdls_current_mode);
return;
}
connected_tdls_peers = tdls_soc->connected_peer_count;
if (tdls_is_progress(tdls_vdev, NULL, 0))
tdls_in_progress = true;
if (!(connected_tdls_peers || tdls_in_progress)) {
tdls_notice("No TDLS connected/progress peers to delete");
vdev_id = vdev->vdev_objmgr.vdev_id;
if (tdls_soc->set_state_info.set_state_cnt > 0) {
tdls_debug("Disable the tdls in FW as second interface is coming up");
tdls_send_update_to_fw(tdls_vdev, tdls_soc, true,
true, false, vdev_id);
}
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
*/
tdls_set_tdls_offchannel(tdls_soc,
tdls_soc->tdls_configs.tdls_pre_off_chan_num);
tdls_set_tdls_secoffchanneloffset(tdls_soc,
TDLS_SEC_OFFCHAN_OFFSET_40PLUS);
tdls_set_tdls_offchannelmode(vdev, DISABLE_CHANSWITCH);
/* Send Msg to PE for deleting all the TDLS peers */
tdls_delete_all_tdls_peers(vdev, tdls_soc);
for (staidx = 0; staidx < tdls_soc->max_num_tdls_sta;
staidx++) {
if (!tdls_soc->tdls_conn_info[staidx].valid_entry)
continue;
curr_peer = tdls_find_all_peer(tdls_soc,
tdls_soc->tdls_conn_info[staidx].peer_mac.bytes);
if (!curr_peer)
continue;
tdls_notice("indicate TDLS teardown %pM",
curr_peer->peer_mac.bytes);
/* Indicate teardown to supplicant */
tdls_indicate_teardown(tdls_vdev,
curr_peer,
TDLS_TEARDOWN_PEER_UNSPEC_REASON);
/*
* Del Sta happened already as part of tdls_delete_all_tdls_peers
* Hence clear tdls vdev data structure.
*/
tdls_reset_peer(tdls_vdev, curr_peer->peer_mac.bytes);
if (tdls_soc->tdls_dereg_peer)
tdls_soc->tdls_dereg_peer(
tdls_soc->tdls_peer_context,
wlan_vdev_get_id(vdev),
&curr_peer->peer_mac);
tdls_decrement_peer_count(tdls_soc);
tdls_soc->tdls_conn_info[staidx].valid_entry = false;
tdls_soc->tdls_conn_info[staidx].session_id = 255;
tdls_soc->tdls_conn_info[staidx].index =
INVALID_TDLS_PEER_INDEX;
qdf_mem_zero(&tdls_soc->tdls_conn_info[staidx].peer_mac,
sizeof(struct qdf_mac_addr));
}
}
void tdls_teardown_connections(struct wlan_objmgr_psoc *psoc)
{
struct tdls_osif_indication indication;
struct tdls_soc_priv_obj *tdls_soc;
struct wlan_objmgr_vdev *tdls_vdev;
tdls_soc = wlan_psoc_get_tdls_soc_obj(psoc);
if (!tdls_soc)
return;
/* Get the tdls specific vdev and clear the links */
tdls_vdev = tdls_get_vdev(psoc, WLAN_TDLS_SB_ID);
if (!tdls_vdev) {
tdls_err("Unable get the vdev");
return;
}
tdls_disable_offchan_and_teardown_links(tdls_vdev);
indication.vdev = tdls_vdev;
if (tdls_soc->tdls_event_cb)
tdls_soc->tdls_event_cb(tdls_soc->tdls_evt_cb_data,
TDLS_EVENT_TEARDOWN_LINKS_DONE,
&indication);
wlan_objmgr_vdev_release_ref(tdls_vdev, WLAN_TDLS_SB_ID);
}