blob: aacf866760d62a30edec5021a06c925a488959e2 [file] [log] [blame]
/*
* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/**
* DOC: contains nud event tracking main function definitions
*/
#include "osif_sync.h"
#include "wlan_hdd_main.h"
#include "wlan_blm_ucfg_api.h"
#include "hdd_dp_cfg.h"
void hdd_nud_set_gateway_addr(struct hdd_adapter *adapter,
struct qdf_mac_addr gw_mac_addr)
{
qdf_mem_copy(adapter->nud_tracking.gw_mac_addr.bytes,
gw_mac_addr.bytes,
sizeof(struct qdf_mac_addr));
adapter->nud_tracking.is_gw_updated = true;
}
void hdd_nud_incr_gw_rx_pkt_cnt(struct hdd_adapter *adapter,
struct qdf_mac_addr *mac_addr)
{
if (!adapter->nud_tracking.is_gw_rx_pkt_track_enabled)
return;
if (!adapter->nud_tracking.is_gw_updated)
return;
if (qdf_is_macaddr_equal(&adapter->nud_tracking.gw_mac_addr,
mac_addr))
qdf_atomic_inc(&adapter
->nud_tracking.tx_rx_stats.gw_rx_packets);
}
void hdd_nud_flush_work(struct hdd_adapter *adapter)
{
struct hdd_context *hdd_ctx;
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
if (adapter->device_mode == QDF_STA_MODE &&
hdd_ctx->config->enable_nud_tracking) {
hdd_debug("Flush the NUD work");
qdf_disable_work(&adapter->nud_tracking.nud_event_work);
}
}
void hdd_nud_deinit_tracking(struct hdd_adapter *adapter)
{
struct hdd_context *hdd_ctx;
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
if (adapter->device_mode == QDF_STA_MODE &&
hdd_ctx->config->enable_nud_tracking) {
hdd_debug("DeInitialize the NUD tracking");
qdf_destroy_work(NULL, &adapter->nud_tracking.nud_event_work);
}
}
void hdd_nud_ignore_tracking(struct hdd_adapter *adapter, bool ignoring)
{
struct hdd_context *hdd_ctx;
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
if (adapter->device_mode == QDF_STA_MODE &&
hdd_ctx->config->enable_nud_tracking)
adapter->nud_tracking.ignore_nud_tracking = ignoring;
}
void hdd_nud_reset_tracking(struct hdd_adapter *adapter)
{
struct hdd_context *hdd_ctx;
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
if (adapter->device_mode == QDF_STA_MODE &&
hdd_ctx->config->enable_nud_tracking) {
hdd_debug("Reset the NUD tracking");
qdf_zero_macaddr(&adapter->nud_tracking.gw_mac_addr);
adapter->nud_tracking.is_gw_updated = false;
qdf_mem_zero(&adapter->nud_tracking.tx_rx_stats,
sizeof(struct hdd_nud_tx_rx_stats));
adapter->nud_tracking.curr_state = NUD_NONE;
qdf_atomic_set(&adapter
->nud_tracking.tx_rx_stats.gw_rx_packets, 0);
}
}
/**
* hdd_nud_stats_info() - display wlan NUD stats info
* @hdd_adapter: Pointer to hdd adapter
*
* Return: None
*/
static void hdd_nud_stats_info(struct hdd_adapter *adapter)
{
struct netdev_queue *txq;
int i = 0;
hdd_debug("**** NUD STATS: ****");
hdd_debug("NUD Probe Tx : %d",
adapter->nud_tracking.tx_rx_stats.pre_tx_packets);
hdd_debug("NUD Probe Ack : %d",
adapter->nud_tracking.tx_rx_stats.pre_tx_acked);
hdd_debug("NUD Probe Rx : %d",
adapter->nud_tracking.tx_rx_stats.pre_rx_packets);
hdd_debug("NUD Failure Tx : %d",
adapter->nud_tracking.tx_rx_stats.post_tx_packets);
hdd_debug("NUD Failure Ack : %d",
adapter->nud_tracking.tx_rx_stats.post_tx_acked);
hdd_debug("NUD Failure Rx : %d",
adapter->nud_tracking.tx_rx_stats.post_rx_packets);
hdd_debug("NUD Gateway Rx : %d",
qdf_atomic_read(&adapter
->nud_tracking.tx_rx_stats.gw_rx_packets));
hdd_debug("carrier state: %d", netif_carrier_ok(adapter->dev));
for (i = 0; i < NUM_TX_QUEUES; i++) {
txq = netdev_get_tx_queue(adapter->dev, i);
hdd_debug("Queue: %d status: %d txq->trans_start: %lu",
i, netif_tx_queue_stopped(txq), txq->trans_start);
}
hdd_debug("Current pause_map value %x", adapter->pause_map);
}
/**
* hdd_nud_capture_stats() - capture wlan NUD stats
* @hdd_adapter: Pointer to hdd adapter
* @nud_state: NUD state for which stats to capture
*
* Return: None
*/
static void hdd_nud_capture_stats(struct hdd_adapter *adapter,
uint8_t nud_state)
{
switch (nud_state) {
case NUD_INCOMPLETE:
case NUD_PROBE:
adapter->nud_tracking.tx_rx_stats.pre_tx_packets =
adapter->stats.tx_packets;
adapter->nud_tracking.tx_rx_stats.pre_rx_packets =
adapter->stats.rx_packets;
adapter->nud_tracking.tx_rx_stats.pre_tx_acked =
hdd_txrx_get_tx_ack_count(adapter);
break;
case NUD_FAILED:
adapter->nud_tracking.tx_rx_stats.post_tx_packets =
adapter->stats.tx_packets;
adapter->nud_tracking.tx_rx_stats.post_rx_packets =
adapter->stats.rx_packets;
adapter->nud_tracking.tx_rx_stats.post_tx_acked =
hdd_txrx_get_tx_ack_count(adapter);
break;
default:
break;
}
}
/**
* hdd_nud_honour_failure() - check if nud failure to be honored
* @hdd_adapter: Pointer to hdd_adapter
*
* Return: true if nud failure to be honored, else false.
*/
static bool hdd_nud_honour_failure(struct hdd_adapter *adapter)
{
uint32_t tx_transmitted;
uint32_t tx_acked;
uint32_t gw_rx_pkt;
tx_transmitted = adapter->nud_tracking.tx_rx_stats.post_tx_packets -
adapter->nud_tracking.tx_rx_stats.pre_tx_packets;
tx_acked = adapter->nud_tracking.tx_rx_stats.post_tx_acked -
adapter->nud_tracking.tx_rx_stats.pre_tx_acked;
gw_rx_pkt = qdf_atomic_read(&adapter
->nud_tracking.tx_rx_stats.gw_rx_packets);
if (!tx_transmitted || !tx_acked || !gw_rx_pkt) {
hdd_debug("NUD_FAILURE_HONORED [mac:"QDF_MAC_ADDR_FMT"]",
QDF_MAC_ADDR_REF(adapter->nud_tracking.gw_mac_addr.bytes));
hdd_nud_stats_info(adapter);
return true;
}
hdd_debug("NUD_FAILURE_NOT_HONORED [mac:"QDF_MAC_ADDR_FMT"]",
QDF_MAC_ADDR_REF(adapter->nud_tracking.gw_mac_addr.bytes));
hdd_nud_stats_info(adapter);
return false;
}
/**
* hdd_nud_set_tracking() - set the NUD tracking info
* @hdd_adapter: Pointer to hdd_adapter
* @nud_state: Current NUD state to set
* @capture_enabled: GW Rx packet to be capture or not
*
* Return: None
*/
static void hdd_nud_set_tracking(struct hdd_adapter *adapter,
uint8_t nud_state,
bool capture_enabled)
{
adapter->nud_tracking.curr_state = nud_state;
qdf_atomic_set(&adapter->nud_tracking.tx_rx_stats.gw_rx_packets, 0);
adapter->nud_tracking.is_gw_rx_pkt_track_enabled = capture_enabled;
}
static void
hdd_handle_nud_fail_sta(struct hdd_context *hdd_ctx,
struct hdd_adapter *adapter)
{
struct reject_ap_info ap_info;
struct hdd_station_ctx *sta_ctx = WLAN_HDD_GET_STATION_CTX_PTR(adapter);
qdf_mutex_acquire(&adapter->disconnection_status_lock);
if (adapter->disconnection_in_progress) {
qdf_mutex_release(&adapter->disconnection_status_lock);
hdd_debug("Disconnect is in progress");
return;
}
qdf_mutex_release(&adapter->disconnection_status_lock);
if (hdd_is_roaming_in_progress(hdd_ctx)) {
hdd_debug("Roaming already in progress, cannot trigger roam.");
return;
}
hdd_debug("nud fail detected, try roaming to better BSSID, vdev id: %d",
adapter->vdev_id);
ap_info.bssid = sta_ctx->conn_info.bssid;
ap_info.reject_ap_type = DRIVER_AVOID_TYPE;
ap_info.reject_reason = REASON_NUD_FAILURE;
ap_info.source = ADDED_BY_DRIVER;
ucfg_blm_add_bssid_to_reject_list(hdd_ctx->pdev, &ap_info);
if (roaming_offload_enabled(hdd_ctx))
sme_roam_invoke_nud_fail(hdd_ctx->mac_handle,
adapter->vdev_id);
}
static void
hdd_handle_nud_fail_non_sta(struct hdd_adapter *adapter)
{
int status;
qdf_mutex_acquire(&adapter->disconnection_status_lock);
if (adapter->disconnection_in_progress) {
qdf_mutex_release(&adapter->disconnection_status_lock);
hdd_debug("Disconnect is in progress");
return;
}
adapter->disconnection_in_progress = true;
qdf_mutex_release(&adapter->disconnection_status_lock);
hdd_debug("Disconnecting vdev with vdev id: %d",
adapter->vdev_id);
/* Issue Disconnect */
status = wlan_hdd_disconnect(adapter, eCSR_DISCONNECT_REASON_DEAUTH,
eSIR_MAC_GATEWAY_REACHABILITY_FAILURE);
if (0 != status) {
hdd_err("wlan_hdd_disconnect failed, status: %d",
status);
hdd_set_disconnect_status(adapter, false);
}
}
#ifdef WLAN_NUD_TRACKING
static bool
hdd_is_roam_after_nud_enabled(struct hdd_config *config)
{
if (config->enable_nud_tracking == ROAM_AFTER_NUD_FAIL ||
config->enable_nud_tracking == DISCONNECT_AFTER_ROAM_FAIL)
return true;
return false;
}
#else
static bool
hdd_is_roam_after_nud_enabled(struct hdd_config *config)
{
return false;
}
#endif
/**
* __hdd_nud_failure_work() - work for nud event
* @adapter: Pointer to hdd_adapter
*
* Return: None
*/
static void __hdd_nud_failure_work(struct hdd_adapter *adapter)
{
struct hdd_context *hdd_ctx;
eConnectionState conn_state;
int status;
hdd_enter();
status = hdd_validate_adapter(adapter);
if (status)
return;
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
status = wlan_hdd_validate_context(hdd_ctx);
if (0 != status)
return;
conn_state = (WLAN_HDD_GET_STATION_CTX_PTR(adapter))
->conn_info.conn_state;
if (eConnectionState_Associated != conn_state) {
hdd_debug("Not in Connected State");
return;
}
if (adapter->nud_tracking.curr_state != NUD_FAILED) {
hdd_debug("Not in NUD_FAILED state");
return;
}
if (adapter->device_mode == QDF_STA_MODE &&
hdd_is_roam_after_nud_enabled(hdd_ctx->config)) {
hdd_handle_nud_fail_sta(hdd_ctx, adapter);
return;
}
hdd_handle_nud_fail_non_sta(adapter);
hdd_exit();
}
/**
* hdd_nud_failure_work() - work for nud event
* @data: Pointer to hdd_adapter
*
* Return: None
*/
static void hdd_nud_failure_work(void *data)
{
struct hdd_adapter *adapter = data;
struct osif_vdev_sync *vdev_sync;
if (osif_vdev_sync_op_start(adapter->dev, &vdev_sync))
return;
__hdd_nud_failure_work(adapter);
osif_vdev_sync_op_stop(vdev_sync);
}
void hdd_nud_init_tracking(struct hdd_adapter *adapter)
{
struct hdd_context *hdd_ctx;
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
if (adapter->device_mode == QDF_STA_MODE &&
hdd_ctx->config->enable_nud_tracking) {
hdd_debug("Initialize the NUD tracking");
qdf_zero_macaddr(&adapter->nud_tracking.gw_mac_addr);
qdf_mem_zero(&adapter->nud_tracking.tx_rx_stats,
sizeof(struct hdd_nud_tx_rx_stats));
adapter->nud_tracking.curr_state = NUD_NONE;
adapter->nud_tracking.ignore_nud_tracking = false;
adapter->nud_tracking.is_gw_updated = false;
qdf_atomic_init(&adapter
->nud_tracking.tx_rx_stats.gw_rx_packets);
qdf_create_work(0, &adapter->nud_tracking.nud_event_work,
hdd_nud_failure_work, adapter);
}
}
/**
* hdd_nud_process_failure_event() - processing NUD_FAILED event
* @hdd_adapter: Pointer to hdd_adapter
*
* Return: None
*/
static void hdd_nud_process_failure_event(struct hdd_adapter *adapter)
{
uint8_t curr_state;
curr_state = adapter->nud_tracking.curr_state;
if (curr_state == NUD_PROBE || curr_state == NUD_INCOMPLETE) {
hdd_nud_capture_stats(adapter, NUD_FAILED);
if (hdd_nud_honour_failure(adapter)) {
adapter->nud_tracking.curr_state = NUD_FAILED;
qdf_sched_work(0, &adapter
->nud_tracking.nud_event_work);
} else {
hdd_debug("NUD_START [0x%x]", NUD_INCOMPLETE);
hdd_nud_capture_stats(adapter, NUD_INCOMPLETE);
hdd_nud_set_tracking(adapter, NUD_INCOMPLETE, true);
}
} else {
hdd_debug("NUD FAILED -> Current State [0x%x]", curr_state);
}
}
/**
* hdd_nud_filter_netevent() - filter netevents for STA interface
* @neighbour: Pointer to neighbour
*
* Return: None
*/
static void hdd_nud_filter_netevent(struct neighbour *neigh)
{
int status;
struct hdd_adapter *adapter;
struct hdd_context *hdd_ctx;
eConnectionState conn_state;
const struct net_device *netdev = neigh->dev;
hdd_enter();
hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
status = wlan_hdd_validate_context(hdd_ctx);
if (0 != status)
return;
adapter = hdd_get_adapter_by_macaddr(hdd_ctx, netdev->dev_addr);
if (!adapter)
return;
status = hdd_validate_adapter(adapter);
if (status)
return;
if (adapter->nud_tracking.ignore_nud_tracking) {
hdd_debug("NUD Tracking is Disabled");
return;
}
if (!adapter->nud_tracking.is_gw_updated)
return;
if (adapter->device_mode != QDF_STA_MODE)
return;
conn_state = (WLAN_HDD_GET_STATION_CTX_PTR(adapter))
->conn_info.conn_state;
if (eConnectionState_Associated != conn_state) {
hdd_debug("Not in Connected State");
return;
}
if (!qdf_is_macaddr_equal(&adapter->nud_tracking.gw_mac_addr,
(struct qdf_mac_addr *)&neigh->ha[0]))
return;
switch (neigh->nud_state) {
case NUD_PROBE:
case NUD_INCOMPLETE:
hdd_debug("NUD_START [0x%x]", neigh->nud_state);
hdd_nud_capture_stats(adapter, neigh->nud_state);
hdd_nud_set_tracking(adapter,
neigh->nud_state,
true);
break;
case NUD_REACHABLE:
hdd_debug("NUD_REACHABLE [0x%x]", neigh->nud_state);
hdd_nud_set_tracking(adapter, NUD_NONE, false);
break;
case NUD_FAILED:
hdd_debug("NUD_FAILED [0x%x]", neigh->nud_state);
hdd_nud_process_failure_event(adapter);
break;
default:
hdd_debug("NUD Event For Other State [0x%x]",
neigh->nud_state);
break;
}
hdd_exit();
}
/**
* __hdd_nud_netevent_cb() - netevent callback
* @neighbor: neighbor used in the nud event
*
* Return: None
*/
static void __hdd_nud_netevent_cb(struct neighbour *neighbor)
{
hdd_enter();
hdd_nud_filter_netevent(neighbor);
hdd_exit();
}
/**
* hdd_nud_netevent_cb() - netevent callback
* @nb: Pointer to notifier block
* @event: Net Event triggered
* @data: Pointer to neighbour struct
*
* Callback for netevent
*
* Return: 0 on success
*/
static int hdd_nud_netevent_cb(struct notifier_block *nb, unsigned long event,
void *data)
{
struct neighbour *neighbor = data;
struct osif_vdev_sync *vdev_sync;
int errno;
errno = osif_vdev_sync_op_start(neighbor->dev, &vdev_sync);
if (errno)
return errno;
switch (event) {
case NETEVENT_NEIGH_UPDATE:
__hdd_nud_netevent_cb(neighbor);
break;
default:
break;
}
osif_vdev_sync_op_stop(vdev_sync);
return 0;
}
static struct notifier_block wlan_netevent_nb = {
.notifier_call = hdd_nud_netevent_cb
};
int hdd_nud_register_netevent_notifier(struct hdd_context *hdd_ctx)
{
int ret = 0;
if (hdd_ctx->config->enable_nud_tracking) {
ret = register_netevent_notifier(&wlan_netevent_nb);
if (!ret)
hdd_debug("Registered netevent notifier");
}
return ret;
}
void hdd_nud_unregister_netevent_notifier(struct hdd_context *hdd_ctx)
{
int ret;
if (hdd_ctx->config->enable_nud_tracking) {
ret = unregister_netevent_notifier(&wlan_netevent_nb);
if (!ret)
hdd_debug("Unregistered netevent notifier");
}
}