blob: 758149ff39d9a5ace31cb81584866363ca7421c7 [file] [log] [blame]
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
* Copyright (c) 2020 The Linux Foundation. All rights reserved.
*/
#ifdef CONFIG_ATH11K_NSS_SUPPORT
#include "mac.h"
#include "nss.h"
#include "debug_nss.h"
#include "core.h"
#include "peer.h"
#include "dp_tx.h"
#include "dp_rx.h"
#include "dp_tx.h"
#include "hif.h"
#include "wmi.h"
#include "../../../../../net/mac80211/sta_info.h"
enum nss_wifi_mesh_mpp_learning_mode mpp_mode = NSS_WIFI_MESH_MPP_LEARNING_MODE_INDEPENDENT_NSS;
LIST_HEAD(mesh_vaps);
/*-----------------------------ATH11K-NSS Helpers--------------------------*/
static enum ath11k_nss_opmode
ath11k_nss_get_vdev_opmode(struct ath11k_vif *arvif)
{
switch (arvif->vdev_type) {
case WMI_VDEV_TYPE_AP:
return ATH11K_NSS_OPMODE_AP;
case WMI_VDEV_TYPE_STA:
return ATH11K_NSS_OPMODE_STA;
default:
ath11k_warn(arvif->ar->ab, "unsupported nss vdev type %d\n",
arvif->vdev_type);
}
return ATH11K_NSS_OPMODE_UNKNOWN;
}
static struct ath11k_vif *ath11k_nss_get_arvif_from_dev(struct net_device *dev)
{
struct wireless_dev *wdev;
struct ieee80211_vif *vif;
struct ath11k_vif *arvif;
if (!dev)
return NULL;
wdev = dev->ieee80211_ptr;
if (!wdev)
return NULL;
vif = wdev_to_ieee80211_vif(wdev);
if (!vif)
return NULL;
arvif = (struct ath11k_vif *)vif->drv_priv;
if (!arvif)
return NULL;
return arvif;
}
static void ath11k_nss_wifili_stats_sync(struct ath11k_base *ab,
struct nss_wifili_stats_sync_msg *wlsoc_stats)
{
struct nss_wifili_device_stats *devstats = &wlsoc_stats->stats;
struct ath11k_soc_dp_stats *soc_stats = &ab->soc_stats;
int i;
spin_lock_bh(&ab->base_lock);
soc_stats->err_ring_pkts += devstats->rxwbm_stats.err_src_rxdma;
soc_stats->invalid_rbm += devstats->rxwbm_stats.invalid_buf_mgr;
for (i = 0; i < HAL_REO_ENTR_RING_RXDMA_ECODE_MAX; i++)
soc_stats->rxdma_error[i] += devstats->rxwbm_stats.err_dma_codes[i];
for (i = 0; i < HAL_REO_DEST_RING_ERROR_CODE_MAX; i++)
soc_stats->reo_error[i] += devstats->rxwbm_stats.err_reo_codes[i];
for (i = 0; i < DP_REO_DST_RING_MAX; i++)
soc_stats->hal_reo_error[i] += devstats->rxreo_stats[i].ring_error;
for (i = 0; i < DP_TCL_NUM_RING_MAX; i++)
soc_stats->tx_err.desc_na[i] += devstats->tcl_stats[i].tcl_ring_full;
for (i = 0; i < NSS_WIFILI_MAX_TCL_DATA_RINGS_MSG; i++)
atomic_add(devstats->txcomp_stats[i].invalid_bufsrc
+ devstats->txcomp_stats[i].invalid_cookie
+ devstats->tx_sw_pool_stats[i].desc_alloc_fail
+ devstats->tx_ext_sw_pool_stats[i].desc_alloc_fail,
&soc_stats->tx_err.misc_fail);
for (i = 0; i < DP_TCL_NUM_RING_MAX; i++)
atomic_add(devstats->tx_data_stats[i].tx_send_fail_cnt,
&soc_stats->tx_err.misc_fail);
spin_unlock_bh(&ab->base_lock);
}
static void ath11k_nss_get_peer_tx_tid_stats(struct ath11k_base *ab, int npeers, struct nss_wifili_sojourn_peer_stats *stats)
{
struct ath11k_peer *peer;
int peer_idx, tid_idx;
for (peer_idx = 0; peer_idx < npeers; peer_idx++) {
rcu_read_lock();
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find_by_id(ab, (&stats[peer_idx])->peer_id);
if (!peer || !peer->sta) {
ath11k_dbg(ab, ATH11K_DBG_NSS, "unable to find peer %d\n", (&stats[peer_idx])->peer_id);
spin_unlock_bh(&ab->base_lock);
rcu_read_unlock();
continue;
}
if (!peer->nss.nss_stats) {
spin_unlock_bh(&ab->base_lock);
rcu_read_unlock();
return;
}
for (tid_idx = 0; tid_idx < NSS_TX_TID_MAX; tid_idx++)
peer->nss.nss_stats->tx_tid_msdu[tid_idx] += (&stats[peer_idx])->stats[tid_idx].num_msdus;
spin_unlock_bh(&ab->base_lock);
rcu_read_unlock();
}
}
static void ath11k_nss_get_peer_stats(struct ath11k_base *ab, struct nss_wifili_peer_stats *stats)
{
struct ath11k_peer *peer;
struct nss_wifili_peer_ctrl_stats *pstats = NULL;
int i, j;
for (i = 0; i < stats->npeers; i++) {
pstats = &stats->wpcs[i];
rcu_read_lock();
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find_by_id(ab, pstats->peer_id);
if (!peer || !peer->sta) {
ath11k_dbg(ab, ATH11K_DBG_NSS, "unable to find peer %d\n", pstats->peer_id);
spin_unlock_bh(&ab->base_lock);
rcu_read_unlock();
continue;
}
if (!peer->nss.nss_stats) {
spin_unlock_bh(&ab->base_lock);
rcu_read_unlock();
return;
}
if (pstats->tx.tx_success_cnt)
peer->nss.nss_stats->last_ack = jiffies;
if (pstats->rx.rx_recvd) {
peer->nss.nss_stats->last_rx = jiffies;
}
peer->nss.nss_stats->tx_packets += pstats->tx.tx_mcast_cnt +
pstats->tx.tx_ucast_cnt +
pstats->tx.tx_bcast_cnt;
peer->nss.nss_stats->tx_bytes += pstats->tx.tx_mcast_bytes +
pstats->tx.tx_ucast_bytes +
pstats->tx.tx_bcast_bytes;
peer->nss.nss_stats->tx_retries += pstats->tx.retries;
for (j = 0; j < NSS_WIFILI_TQM_RR_MAX; j++)
peer->nss.nss_stats->tx_failed += pstats->tx.dropped.drop_stats[j];
peer->nss.nss_stats->rx_packets += pstats->rx.rx_recvd;
peer->nss.nss_stats->rx_bytes += pstats->rx.rx_recvd_bytes;
peer->nss.nss_stats->rx_dropped += pstats->rx.err.mic_err +
pstats->rx.err.decrypt_err;
spin_unlock_bh(&ab->base_lock);
rcu_read_unlock();
}
}
void ath11k_nss_ext_rx_stats(struct ath11k_base *ab, struct htt_rx_ring_tlv_filter *tlv_filter)
{
if (ab->nss.enabled)
tlv_filter->rx_filter |= HTT_RX_FILTER_TLV_FLAGS_PPDU_END_USER_STATS;
}
static u32 ath11k_nss_cipher_type(struct ath11k_base *ab, u32 cipher)
{
switch (cipher) {
case WLAN_CIPHER_SUITE_CCMP:
return PEER_SEC_TYPE_AES_CCMP;
case WLAN_CIPHER_SUITE_TKIP:
return PEER_SEC_TYPE_TKIP;
case WLAN_CIPHER_SUITE_CCMP_256:
return PEER_SEC_TYPE_AES_CCMP_256;
case WLAN_CIPHER_SUITE_GCMP:
return PEER_SEC_TYPE_AES_GCMP;
case WLAN_CIPHER_SUITE_GCMP_256:
return PEER_SEC_TYPE_AES_GCMP_256;
default:
ath11k_warn(ab, "unknown cipher type %d\n", cipher);
return PEER_SEC_TYPE_NONE;
}
}
static void ath11k_nss_tx_encap_nwifi(struct sk_buff *skb)
{
struct ieee80211_hdr *hdr = (void *)skb->data;
u8 *qos_ctl;
if (!ieee80211_is_data_qos(hdr->frame_control))
return;
qos_ctl = ieee80211_get_qos_ctl(hdr);
memmove(skb->data + IEEE80211_QOS_CTL_LEN,
skb->data, (void *)qos_ctl - (void *)skb->data);
skb_pull(skb, IEEE80211_QOS_CTL_LEN);
hdr = (void *)skb->data;
hdr->frame_control &= ~__cpu_to_le16(IEEE80211_STYPE_QOS_DATA);
}
static void ath11k_nss_tx_encap_raw(struct sk_buff *skb)
{
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
struct ieee80211_hdr *hdr = (void *)skb->data;
u32 cipher;
if (!ieee80211_has_protected(hdr->frame_control))
return;
/* Include length for MIC */
skb_put(skb, IEEE80211_CCMP_MIC_LEN);
/* Include length for ICV if TKIP is used */
cipher = info->control.hw_key->cipher;
if (cipher == WLAN_CIPHER_SUITE_TKIP)
skb_put(skb, IEEE80211_TKIP_ICV_LEN);
}
static void ath11k_nss_peer_mem_free(struct ath11k_base *ab, u32 peer_id)
{
struct ath11k_peer *peer;
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find_by_id(ab, peer_id);
if (!peer) {
spin_unlock_bh(&ab->base_lock);
ath11k_warn(ab, "ath11k_nss: unable to free peer mem%d\n",
peer_id);
return;
}
dma_unmap_single(ab->dev, peer->nss.paddr,
WIFILI_NSS_PEER_BYTE_SIZE, DMA_TO_DEVICE);
kfree(peer->nss.vaddr);
if (peer->nss.nss_stats) {
kfree(peer->nss.nss_stats);
peer->nss.nss_stats = NULL;
}
complete(&peer->nss.complete);
spin_unlock_bh(&ab->base_lock);
ath11k_dbg(ab, ATH11K_DBG_NSS, "nss peer %d mem freed\n", peer_id);
}
/*-----------------------------Events/Callbacks------------------------------*/
void ath11k_nss_wifili_event_receive(struct ath11k_base *ab, struct nss_wifili_msg *msg)
{
u32 msg_type = msg->cm.type;
enum nss_cmn_response response = msg->cm.response;
u32 error = msg->cm.error;
u32 peer_id;
struct nss_wifili_peer_stats *peer_stats;
struct nss_wifili_sojourn_stats_msg *stats_msg;
if (!ab)
return;
ath11k_dbg(ab, ATH11K_DBG_NSS, "nss wifili event received %d response %d error %d\n",
msg_type, response, error);
switch (msg_type) {
case NSS_WIFILI_INIT_MSG:
ab->nss.response = response;
complete(&ab->nss.complete);
break;
case NSS_WIFILI_PDEV_INIT_MSG:
case NSS_WIFILI_START_MSG:
case NSS_WIFILI_SOC_RESET_MSG:
case NSS_WIFILI_STOP_MSG:
case NSS_WIFILI_PDEV_DEINIT_MSG:
ab->nss.response = response;
complete(&ab->nss.complete);
break;
case NSS_WIFILI_PEER_CREATE_MSG:
if (response != NSS_CMN_RESPONSE_EMSG)
break;
peer_id = (&msg->msg.peermsg)->peer_id;
/* free peer memory allocated during peer create due to failure */
ath11k_nss_peer_mem_free(ab, peer_id);
break;
case NSS_WIFILI_PEER_DELETE_MSG:
peer_id = (&msg->msg.peermsg)->peer_id;
if (response == NSS_CMN_RESPONSE_EMSG)
ath11k_warn(ab, "ath11k_nss: peer delete failed%d\n",
peer_id);
/* free peer memory allocated during peer create irrespective of
* delete status
*/
ath11k_nss_peer_mem_free(ab, peer_id);
break;
case NSS_WIFILI_PEER_SECURITY_TYPE_MSG:
if (response == NSS_CMN_RESPONSE_EMSG)
ath11k_warn(ab, "peer securty config failed\n");
break;
case NSS_WIFILI_STATS_MSG:
if (response == NSS_CMN_RESPONSE_EMSG) {
ath11k_warn(ab, "soc_dp_stats failed to get updated\n");
break;
}
ath11k_nss_wifili_stats_sync(ab, &msg->msg.wlsoc_stats);
break;
case NSS_WIFILI_PEER_STATS_MSG:
peer_stats = &msg->msg.peer_stats.stats;
if (response == NSS_CMN_RESPONSE_EMSG) {
ath11k_warn(ab, "peer stats msg failed with error = %u\n", error);
break;
}
ath11k_nss_get_peer_stats(ab, peer_stats);
break;
case NSS_WIFILI_SOJOURN_STATS_MSG:
stats_msg = &msg->msg.sj_stats_msg;
if (response == NSS_CMN_RESPONSE_EMSG) {
ath11k_warn(ab, "tid peer stats msg failed with error = %u\n", error);
break;
}
ath11k_nss_get_peer_tx_tid_stats(ab, stats_msg->npeers, &stats_msg->sj_peer_stats[0]);
break;
case NSS_WIFILI_TID_REOQ_SETUP_MSG:
/* TODO setup tidq */
break;
case NSS_WIFILI_WDS_PEER_ADD_MSG:
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS, "nss wifili wds peer add event received %d response %d error %d\n",
msg_type, response, error);
break;
case NSS_WIFILI_WDS_PEER_UPDATE_MSG:
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS, "nss wifili wds peer update event received %d response %d error %d\n",
msg_type, response, error);
break;
case NSS_WIFILI_WDS_PEER_MAP_MSG:
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS, "nss wifili wds peer map event received %d response %d error %d\n",
msg_type, response, error);
break;
case NSS_WIFILI_WDS_PEER_DEL_MSG:
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS, "nss wifili wds peer del event received %d response %d error %d\n",
msg_type, response, error);
break;
case NSS_WIFILI_PEER_4ADDR_EVENT_MSG:
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS, "nss wifili peer 4addr event received %d response %d error %d\n",
msg_type, response, error);
break;
case NSS_WIFILI_SEND_MESH_CAPABILITY_INFO:
complete(&ab->nss.complete);
if (response != NSS_CMN_RESPONSE_EMSG)
ab->nss.mesh_nss_offload_enabled = true;
ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, "nss wifili mesh capability response %d\n",
ab->nss.mesh_nss_offload_enabled);
break;
default:
ath11k_dbg(ab, ATH11K_DBG_NSS, "unhandled event %d\n", msg_type);
break;
}
}
void ath11k_nss_process_mic_error(struct ath11k_base *ab, struct sk_buff *skb)
{
struct ath11k_vif *arvif;
struct ath11k_peer *peer = NULL;
struct hal_rx_desc *desc = (struct hal_rx_desc *)skb->data;
struct wireless_dev *wdev;
u16 peer_id;
u8 peer_addr[ETH_ALEN];
u8 ucast_keyidx, mcast_keyidx;
bool is_mcbc;
if (!ath11k_dp_rx_h_msdu_end_first_msdu(ab, desc))
goto fail;
is_mcbc = ath11k_dp_rx_h_attn_is_mcbc(ab, desc);
peer_id = ath11k_dp_rx_h_mpdu_start_peer_id(ab, desc);
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find_by_id(ab, peer_id);
if (!peer) {
ath11k_info(ab, "ath11k_nss:peer not found");
spin_unlock_bh(&ab->base_lock);
goto fail;
}
if (!peer->vif) {
ath11k_warn(ab, "ath11k_nss:vif not found");
spin_unlock_bh(&ab->base_lock);
goto fail;
}
ether_addr_copy(peer_addr, peer->addr);
mcast_keyidx = peer->mcast_keyidx;
ucast_keyidx = peer->ucast_keyidx;
arvif = ath11k_vif_to_arvif(peer->vif);
spin_unlock_bh(&ab->base_lock);
if (!arvif->is_started) {
ath11k_warn(ab, "ath11k_nss:arvif not started");
goto fail;
}
wdev = ieee80211_vif_to_wdev_relaxed(arvif->vif);
if (!wdev) {
ath11k_warn(ab, "ath11k_nss: wdev is null\n");
goto fail;
}
if (!wdev->netdev) {
ath11k_warn(ab, "ath11k_nss: netdev is null\n");
goto fail;
}
cfg80211_michael_mic_failure(wdev->netdev, peer_addr,
is_mcbc ? NL80211_KEYTYPE_GROUP :
NL80211_KEYTYPE_PAIRWISE,
is_mcbc ? mcast_keyidx : ucast_keyidx,
NULL, GFP_ATOMIC);
kfree(skb);
return;
fail:
kfree(skb);
ath11k_warn(ab, "ath11k_nss: Failed to handle mic error\n");
return;
}
static void
ath11k_nss_wifili_ext_callback_fn(struct ath11k_base *ab, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct nss_wifili_soc_per_packet_metadata *wepm;
wepm = (struct nss_wifili_soc_per_packet_metadata *)(skb->head +
NSS_WIFILI_SOC_PER_PACKET_METADATA_OFFSET);
switch (wepm->pkt_type) {
case NSS_WIFILI_SOC_EXT_DATA_PKT_MIC_ERROR:
ath11k_nss_process_mic_error(ab, skb);
break;
default:
ath11k_dbg(ab, ATH11K_DBG_NSS, "unknown packet type received in wifili ext cb %d",
wepm->pkt_type);
dev_kfree_skb_any(skb);
break;
}
}
void ath11k_nss_vdev_cfg_cb(void *app_data, struct nss_cmn_msg *msg)
{
struct ath11k_vif *arvif = (struct ath11k_vif *)app_data;
if (!arvif)
return;
ath11k_dbg(arvif->ar->ab, ATH11K_DBG_NSS, "vdev cfg msg callback received msg:%d rsp:%d\n",
msg->type, msg->response);
complete(&arvif->nss.complete);
}
static void ath11k_nss_vdev_event_receive(void *dev, struct nss_cmn_msg *vdev_msg)
{
/*TODO*/
}
/* TODO: move to mac80211 after cleanups/refactoring required after feature completion */
static int ath11k_nss_deliver_rx(struct ieee80211_vif *vif, struct sk_buff *skb,
bool eth, int data_offs, struct napi_struct *napi)
{
struct sk_buff_head subframe_list;
struct ieee80211_hdr *hdr;
struct sk_buff *subframe;
struct net_device *dev;
int hdr_len;
u8 *qc;
dev = skb->dev;
if (eth)
goto deliver_msdu;
hdr = (struct ieee80211_hdr *)skb->data;
hdr_len = ieee80211_hdrlen(hdr->frame_control);
if (ieee80211_is_data_qos(hdr->frame_control)) {
qc = ieee80211_get_qos_ctl(hdr);
if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT)
goto deliver_amsdu;
}
if (ieee80211_data_to_8023_exthdr(skb, NULL, vif->addr, vif->type,
data_offs - hdr_len)) {
dev_kfree_skb_any(skb);
return -EINVAL;
}
deliver_msdu:
skb->protocol = eth_type_trans(skb, dev);
napi_gro_receive(napi, skb);
return 0;
deliver_amsdu:
/* Move to the start of the first subframe */
skb_pull(skb, data_offs);
__skb_queue_head_init(&subframe_list);
/* create list containing all the subframes */
ieee80211_amsdu_to_8023s(skb, &subframe_list, NULL,
vif->type, 0, NULL, NULL);
/* This shouldn't happen, indicating error during defragmentation */
if (skb_queue_empty(&subframe_list))
return -EINVAL;
while (!skb_queue_empty(&subframe_list)) {
subframe = __skb_dequeue(&subframe_list);
subframe->protocol = eth_type_trans(subframe, dev);
napi_gro_receive(napi, subframe);
}
return 0;
}
static int ath11k_nss_undecap_raw(struct ath11k_vif *arvif, struct sk_buff *skb,
int *data_offset)
{
struct ath11k_base *ab = arvif->ar->ab;
struct ath11k *ar = arvif->ar;
enum hal_encrypt_type enctype;
struct ath11k_peer *peer = NULL;
struct ieee80211_hdr *hdr;
int hdr_len;
hdr = (struct ieee80211_hdr *)skb->data;
hdr_len = ieee80211_hdrlen(hdr->frame_control);
*data_offset = hdr_len;
/* FCS is included in the raw mode skb, we can trim it, fcs error
* packets are not expected to be received in this path
*/
skb_trim(skb, skb->len - FCS_LEN);
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find_by_addr(ab, hdr->addr2);
if (!peer) {
ath11k_warn(ab, "peer not found for raw/nwifi undecap, drop this packet\n");
spin_unlock_bh(&ab->base_lock);
return -EINVAL;
}
enctype = peer->sec_type;
spin_unlock_bh(&ab->base_lock);
*data_offset += ath11k_dp_rx_crypto_param_len(ar, enctype);
/* Strip ICV, MIC, MMIC */
skb_trim(skb, skb->len -
ath11k_dp_rx_crypto_mic_len(ar, enctype));
skb_trim(skb, skb->len -
ath11k_dp_rx_crypto_icv_len(ar, enctype));
if (enctype == HAL_ENCRYPT_TYPE_TKIP_MIC)
skb_trim(skb, skb->len - IEEE80211_CCMP_MIC_LEN);
return 0;
}
static int ath11k_nss_undecap_nwifi(struct ath11k_vif *arvif, struct sk_buff *skb,
int *data_offset)
{
struct ieee80211_hdr *hdr;
int hdr_len;
hdr = (struct ieee80211_hdr *)skb->data;
hdr_len = ieee80211_hdrlen(hdr->frame_control);
*data_offset = hdr_len;
/* We dont receive the IV from nss host on slow path
* hence we can return only the header length as offset.
**/
return 0;
}
static void ath11k_nss_wds_type_rx(struct ath11k *ar, struct net_device *dev,
u8* src_mac, u8 is_sa_valid, u8 addr4_valid,
u16 peer_id, bool *drop)
{
struct ath11k_base *ab = ar->ab;
struct ath11k_ast_entry *ast_entry = NULL;
struct ath11k_peer *ta_peer = NULL;
spin_lock_bh(&ab->base_lock);
ta_peer = ath11k_peer_find_by_id(ab, peer_id);
if (!ta_peer) {
spin_unlock_bh(&ab->base_lock);
return;
}
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,"ath11k_nss_wds_type_rx ta_peer %pM\n",
ta_peer->addr);
if (addr4_valid) {
ast_entry = ath11k_peer_ast_find_by_addr(ab, src_mac);
if (!is_sa_valid) {
ath11k_peer_add_ast(ar, ta_peer, src_mac,
ATH11K_AST_TYPE_WDS);
if (!ta_peer->nss.ext_vdev_up)
ieee80211_rx_nss_notify_4addr(dev, ta_peer->addr);
} else {
if (!ast_entry) {
ath11k_peer_add_ast(ar, ta_peer, src_mac,
ATH11K_AST_TYPE_WDS);
if (!ta_peer->nss.ext_vdev_up)
ieee80211_rx_nss_notify_4addr(dev, ta_peer->addr);
} else if (ast_entry->type == ATH11K_AST_TYPE_WDS) {
ath11k_peer_update_ast(ar, ta_peer, ast_entry);
ath11k_nss_update_wds_peer(ar, ta_peer, src_mac);
}
}
if (!ta_peer->nss.ext_vdev_up)
drop = true;
}
spin_unlock_bh(&ab->base_lock);
}
static void ath11k_nss_mec_handler(struct ath11k_vif *arvif, u8* mec_mac_addr)
{
struct ath11k *ar = arvif->ar;
struct ath11k_base *ab = ar->ab;
struct ath11k_peer *peer = ar->bss_peer;
u8 mac_addr[ETH_ALEN];
u32 *mac_addr_l32;
u16 *mac_addr_h16;
if (!peer)
return;
/* mec_mac_addr has the swapped mac_addr after 4 bytes (sizeof(u32))
* mec_mac_addr[0]
* |
* 03:0a:00:00:2d:15:22:f0:fd:8c
* ^
* Swapped MAC address present after 4 bytes
* MAC address after swapping is 8c:fd:f0:22:15:2d */
mac_addr_l32 = (u32 *) (mec_mac_addr + sizeof(u32));
mac_addr_h16 = (u16 *) (mec_mac_addr + sizeof(u32) + sizeof(u32));
*mac_addr_l32 = swab32(*mac_addr_l32);
*mac_addr_h16 = swab16(*mac_addr_h16);
memcpy(mac_addr, mac_addr_h16, ETH_ALEN - 4);
memcpy(mac_addr + 2, mac_addr_l32, 4);
if (!ether_addr_equal(arvif->vif->addr, mac_addr)) {
spin_lock_bh(&ab->base_lock);
ath11k_peer_add_ast(ar, peer, mac_addr,
ATH11K_AST_TYPE_MEC);
spin_unlock_bh(&ab->base_lock);
}
}
static void ath11k_nss_vdev_spl_receive_ext_wdsdata(struct ath11k_vif *arvif,
struct sk_buff *skb,
struct nss_wifi_vdev_wds_per_packet_metadata *wds_metadata,
bool *drop)
{
struct ath11k *ar = arvif->ar;
struct ath11k_base *ab = ar->ab;
enum wifi_vdev_ext_wds_info_type wds_type;
u8 is_sa_valid = 0, addr4_valid = 0;
u16 peer_id;
u8 src_mac[ETH_ALEN];
is_sa_valid = wds_metadata->is_sa_valid;
addr4_valid = wds_metadata->addr4_valid;
wds_type = wds_metadata->wds_type;
peer_id = wds_metadata->peer_id;
memcpy(src_mac, ((struct ethhdr *)skb->data)->h_source, ETH_ALEN);
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,"receive_ext_wdsdata wds_type %d peer id %u sa_valid %d addr4_valid %d src_mac %pM\n",
wds_type, peer_id, is_sa_valid, addr4_valid, src_mac);
switch (wds_type) {
case NSS_WIFI_VDEV_WDS_TYPE_RX:
ath11k_nss_wds_type_rx(ar, skb->dev, src_mac, is_sa_valid,
addr4_valid, peer_id, &drop);
break;
case NSS_WIFI_VDEV_WDS_TYPE_MEC:
ath11k_nss_mec_handler(arvif, (u8 *)(skb->data));
break;
default:
ath11k_warn(ab, "unsupported wds_type %d\n", wds_type);
break;
}
}
static bool ath11k_nss_vdev_data_receive_mec_check(struct ath11k *ar,
struct sk_buff *skb)
{
struct ath11k_base *ab = ar->ab;
struct ath11k_ast_entry *ast_entry = NULL;
u8 src_mac[ETH_ALEN];
memcpy(src_mac, ((struct ethhdr *)skb->data)->h_source, ETH_ALEN);
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"ath11k_nss_vdev_data_receive_mec_check src mac %pM\n",
src_mac);
spin_lock_bh(&ab->base_lock);
ast_entry = ath11k_peer_ast_find_by_addr(ab, src_mac);
if (ast_entry && ast_entry->type == ATH11K_AST_TYPE_MEC) {
spin_unlock_bh(&ab->base_lock);
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"dropping mec traffic from %pM\n", ast_entry->addr);
return true;
}
spin_unlock_bh(&ab->base_lock);
return false;
}
static int ath11k_nss_undecap(struct ath11k_vif *arvif, struct sk_buff *skb,
int *data_offs, bool *eth_decap)
{
enum ath11k_hw_txrx_mode decap_type;
decap_type = arvif->nss.decap;
switch (decap_type) {
case ATH11K_HW_TXRX_RAW:
return ath11k_nss_undecap_raw(arvif, skb, data_offs);
case ATH11K_HW_TXRX_NATIVE_WIFI:
return ath11k_nss_undecap_nwifi(arvif, skb, data_offs);
case ATH11K_HW_TXRX_ETHERNET:
*eth_decap = true;
return 0;
default:
return -EINVAL;
}
}
static void
ath11k_nss_vdev_special_data_receive(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct nss_wifi_vdev_per_packet_metadata *wifi_metadata = NULL;
struct nss_wifi_vdev_wds_per_packet_metadata *wds_metadata = NULL;
struct ath11k_vif *arvif;
struct ath11k_base *ab;
struct ath11k_skb_rxcb *rxcb;
bool drop = false;
bool eth_decap = false;
int data_offs = 0;
int ret = 0;
arvif = ath11k_nss_get_arvif_from_dev(dev);
if (!arvif) {
dev_kfree_skb_any(skb);
return;
}
ab = arvif->ar->ab;
skb->dev = dev;
wifi_metadata = (struct nss_wifi_vdev_per_packet_metadata *)(skb->head +
NSS_WIFI_VDEV_PER_PACKET_METADATA_OFFSET);
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"dp special data from nss: wifi_metadata->pkt_type %d",
wifi_metadata->pkt_type);
ret = ath11k_nss_undecap(arvif, skb, &data_offs, &eth_decap);
if (ret) {
ath11k_warn(ab, "error in nss rx undecap, type %d err %d\n",
arvif->nss.decap, ret);
dev_kfree_skb_any(skb);
return;
}
switch(wifi_metadata->pkt_type) {
case NSS_WIFI_VDEV_EXT_DATA_PKT_TYPE_WDS_LEARN:
if (eth_decap) {
wds_metadata = &wifi_metadata->metadata.wds_metadata;
ath11k_nss_vdev_spl_receive_ext_wdsdata(arvif, skb,
wds_metadata, &drop);
}
if (!drop)
ath11k_nss_deliver_rx(arvif->vif, skb, eth_decap, data_offs, napi);
break;
case NSS_WIFI_VDEV_EXT_DATA_PKT_TYPE_MCBC_RX:
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "",
"mcbc packet exception from nss: ",
skb->data, skb->len);
rxcb = ATH11K_SKB_RXCB(skb);
rxcb->rx_desc = (struct hal_rx_desc *)skb->head;
rxcb->is_first_msdu = rxcb->is_last_msdu = true;
rxcb->is_continuation = false;
ath11k_dp_rx_from_nss(arvif->ar, skb, napi);
break;
default:
ath11k_warn(ab, "unsupported pkt_type %d from nss\n", wifi_metadata->pkt_type);
dev_kfree_skb_any(skb);
}
}
static void
ath11k_nss_vdev_data_receive(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct wireless_dev *wdev;
struct ieee80211_vif *vif;
struct ath11k_vif *arvif;
struct ath11k_base *ab;
bool eth_decap = false;
int data_offs = 0;
int ret;
if (!dev) {
dev_kfree_skb_any(skb);
return;
}
wdev = dev->ieee80211_ptr;
if (!wdev) {
dev_kfree_skb_any(skb);
return;
}
vif = wdev_to_ieee80211_vif(wdev);
if (!vif) {
dev_kfree_skb_any(skb);
return;
}
arvif = (struct ath11k_vif *)vif->drv_priv;
if (!arvif) {
dev_kfree_skb_any(skb);
return;
}
ab = arvif->ar->ab;
skb->dev = dev;
/* log the original skb received from nss */
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", "dp rx msdu from nss: ",
skb->data, skb->len);
if ((vif->type == NL80211_IFTYPE_STATION && wdev->use_4addr) &&
ath11k_nss_vdev_data_receive_mec_check(arvif->ar, skb)) {
dev_kfree_skb_any(skb);
return;
}
ret = ath11k_nss_undecap(arvif, skb, &data_offs, &eth_decap);
if (ret) {
ath11k_warn(ab, "error in nss rx undecap, type %d err %d\n",
arvif->nss.decap, ret);
dev_kfree_skb_any(skb);
return;
}
ath11k_nss_deliver_rx(arvif->vif, skb, eth_decap, data_offs, napi);
}
static void
ath11k_nss_ext_vdev_special_data_receive(struct net_device *dev,
struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
dev_kfree_skb_any(skb);
}
static void
ath11k_nss_ext_vdev_data_receive(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct wireless_dev *wdev;
struct ieee80211_vif *vif;
struct ath11k_vif *arvif;
struct ath11k_base *ab;
bool eth_decap = false;
int data_offs = 0;
int ret;
if (!dev) {
dev_kfree_skb_any(skb);
return;
}
wdev = dev->ieee80211_ptr;
if (!wdev) {
dev_kfree_skb_any(skb);
return;
}
vif = wdev_to_ieee80211_vif(wdev);
if (!vif) {
dev_kfree_skb_any(skb);
return;
}
arvif = (struct ath11k_vif *)vif->drv_priv;
if (!arvif) {
dev_kfree_skb_any(skb);
return;
}
ab = arvif->ar->ab;
skb->dev = dev;
/* log the original skb received from nss */
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", "dp rx msdu from nss ext : ",
skb->data, skb->len);
ret = ath11k_nss_undecap(arvif, skb, &data_offs, &eth_decap);
if (ret) {
ath11k_warn(ab, "error in nss ext rx undecap, type %d err %d\n",
arvif->nss.decap, ret);
dev_kfree_skb_any(skb);
return;
}
ath11k_nss_deliver_rx(arvif->vif, skb, eth_decap, data_offs, napi);
}
/*------Mesh offload------*/
void ath11k_nss_mesh_wifili_event_receive(void *app_data,
struct nss_cmn_msg *cmn_msg)
{
struct nss_wifi_mesh_msg *msg = (struct nss_wifi_mesh_msg *)cmn_msg;
struct ath11k_base *ab = app_data;
u32 msg_type = msg->cm.type;
enum nss_cmn_response response = msg->cm.response;
u32 error = msg->cm.error;
if (!ab)
return;
ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, "nss mesh event received %d response %d error %d\n",
msg_type, response, error);
switch (msg_type) {
case NSS_WIFI_MESH_MSG_MPATH_ADD:
if (response == NSS_CMN_RESPONSE_EMSG)
ath11k_warn(ab,"failed to add an entry to mpath table mesh_da %pM vdev_id %d\n",
(&msg->msg.mpath_add)->dest_mac_addr,
(&msg->msg.mpath_add)->link_vap_id);
break;
case NSS_WIFI_MESH_MSG_MPATH_UPDATE:
if (response == NSS_CMN_RESPONSE_EMSG)
ath11k_warn(ab, "failed to update mpath entry mesh_da %pM vdev_id %d"
"next_hop %pM metric %d flags 0x%u hop_count %d"
"exp_time %u mesh_gate %u\n",
(&msg->msg.mpath_update)->dest_mac_addr,
(&msg->msg.mpath_update)->link_vap_id,
(&msg->msg.mpath_update)->next_hop_mac_addr,
(&msg->msg.mpath_update)->metric,
(&msg->msg.mpath_update)->path_flags,
(&msg->msg.mpath_update)->hop_count,
(&msg->msg.mpath_update)->expiry_time,
(&msg->msg.mpath_update)->is_mesh_gate);
break;
case NSS_WIFI_MESH_MSG_MPATH_DELETE:
if (response == NSS_CMN_RESPONSE_EMSG)
ath11k_warn(ab,"failed to remove mpath entry mesh_da %pM"
"vdev_id %d\n",
(&msg->msg.mpath_del)->mesh_dest_mac_addr,
(&msg->msg.mpath_del)->link_vap_id);
break;
case NSS_WIFI_MESH_MSG_PROXY_PATH_ADD:
if (response == NSS_CMN_RESPONSE_EMSG)
ath11k_warn(ab,"failed to add proxy entry da %pM mesh_da %pM \n",
(&msg->msg.proxy_add_msg)->dest_mac_addr,
(&msg->msg.proxy_add_msg)->mesh_dest_mac);
break;
case NSS_WIFI_MESH_MSG_PROXY_PATH_UPDATE:
if (response == NSS_CMN_RESPONSE_EMSG)
ath11k_warn(ab,"failed to update proxy path da %pM mesh_da %pM\n",
(&msg->msg.proxy_update_msg)->dest_mac_addr,
(&msg->msg.proxy_update_msg)->mesh_dest_mac);
break;
case NSS_WIFI_MESH_MSG_PROXY_PATH_DELETE:
if (response == NSS_CMN_RESPONSE_EMSG)
ath11k_warn(ab,"failed to remove proxy path entry da %pM mesh_da %pM\n",
(&msg->msg.proxy_del_msg)->dest_mac_addr,
(&msg->msg.proxy_del_msg)->mesh_dest_mac_addr);
break;
case NSS_WIFI_MESH_MSG_EXCEPTION_FLAG:
if (response == NSS_CMN_RESPONSE_EMSG)
ath11k_warn(ab,"failed to add the exception da %pM\n",
(&msg->msg.exception_msg)->dest_mac_addr);
break;
default:
ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, "unhandled event %d\n", msg_type);
break;
}
}
static void nss_mesh_convert_path_flags(u16 *dest, u16 *src, bool to_nss)
{
if (to_nss) {
if (*src & IEEE80211_MESH_PATH_ACTIVE)
*dest |= NSS_WIFI_MESH_PATH_FLAG_ACTIVE;
if (*src & IEEE80211_MESH_PATH_RESOLVING)
*dest |= NSS_WIFI_MESH_PATH_FLAG_RESOLVING;
if (*src & IEEE80211_MESH_PATH_RESOLVED)
*dest |= NSS_WIFI_MESH_PATH_FLAG_RESOLVED;
if (*src & IEEE80211_MESH_PATH_FIXED)
*dest |= NSS_WIFI_MESH_PATH_FLAG_FIXED;
} else {
if (*src & NSS_WIFI_MESH_PATH_FLAG_ACTIVE)
*dest |= IEEE80211_MESH_PATH_ACTIVE;
if (*src & NSS_WIFI_MESH_PATH_FLAG_RESOLVING)
*dest |= IEEE80211_MESH_PATH_RESOLVING;
if (*src & NSS_WIFI_MESH_PATH_FLAG_RESOLVED)
*dest |= IEEE80211_MESH_PATH_RESOLVED;
if (*src & NSS_WIFI_MESH_PATH_FLAG_FIXED)
*dest |= IEEE80211_MESH_PATH_FIXED;
}
}
static void ath11k_nss_mesh_mpath_refresh(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct ath11k_base *ab = arvif->ar->ab;
struct nss_wifi_mesh_path_refresh_msg *refresh_msg;
struct ieee80211_mesh_path_offld path = {0};
int ret;
refresh_msg = &msg->msg.path_refresh_msg;
ether_addr_copy(path.mesh_da, refresh_msg->dest_mac_addr);
ether_addr_copy(path.next_hop, refresh_msg->next_hop_mac_addr);
ath11k_dbg(ab, ATH11K_DBG_NSS,
"Mesh path refresh event from nss, mDA %pM next_hop %pM link_vdev %d\n",
refresh_msg->dest_mac_addr, refresh_msg->next_hop_mac_addr,
refresh_msg->link_vap_id);
if (ab->nss.debug_mode)
return;
ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path,
IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_REFRESH);
if (ret)
ath11k_warn(ab, "failed to notify mpath refresh nss event %d\n", ret);
}
static void ath11k_nss_mesh_path_not_found(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct ath11k_base *ab = arvif->ar->ab;
struct nss_wifi_mesh_mpath_not_found_msg *err_msg;
struct ieee80211_mesh_path_offld path = {0};
int ret;
err_msg = &msg->msg.mpath_not_found_msg;
ether_addr_copy(path.da, err_msg->dest_mac_addr);
if (err_msg->is_mesh_forward_path)
ether_addr_copy(path.ta, err_msg->transmitter_mac_addr);
ath11k_dbg(ab, ATH11K_DBG_NSS,
"Mesh path not found event from nss, (m)DA %pM ta %pM link vap %d\n",
err_msg->dest_mac_addr, err_msg->transmitter_mac_addr, err_msg->link_vap_id);
if (ab->nss.debug_mode)
return;
ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path,
IEEE80211_MESH_PATH_OFFLD_ACTION_PATH_NOT_FOUND);
if (ret)
ath11k_warn(ab, "failed to notify mpath not found nss event %d\n", ret);
}
static void ath11k_nss_mesh_path_delete(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct ath11k_base *ab = arvif->ar->ab;
struct nss_wifi_mesh_mpath_del_msg *del_msg = &msg->msg.mpath_del;
struct ieee80211_mesh_path_offld path = {0};
int ret;
ether_addr_copy(path.mesh_da, del_msg->mesh_dest_mac_addr);
ath11k_dbg(ab, ATH11K_DBG_NSS,
"Mesh path delete event from nss, mDA %pM vap_id %d\n",
del_msg->mesh_dest_mac_addr, del_msg->link_vap_id);
ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path,
IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_DEL);
if (ret)
ath11k_warn(ab, "failed to notify mpath delete nss event %d\n", ret);
}
static void ath11k_nss_mesh_path_expiry(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct ath11k_base *ab = arvif->ar->ab;
struct nss_wifi_mesh_path_expiry_msg *exp_msg = &msg->msg.path_expiry_msg;
struct ieee80211_mesh_path_offld path = {0};
int ret;
ether_addr_copy(path.mesh_da, exp_msg->mesh_dest_mac_addr);
ether_addr_copy(path.next_hop, exp_msg->next_hop_mac_addr);
ath11k_dbg(ab, ATH11K_DBG_NSS,
"Mesh path delete event from nss, mDA %pM next_hop %pM if_num %d\n",
exp_msg->mesh_dest_mac_addr, exp_msg->next_hop_mac_addr,
arvif->nss.if_num);
ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path,
IEEE80211_MESH_PATH_OFFLD_ACTION_MPATH_EXP);
if (ret)
ath11k_warn(ab, "failed to notify mpath expiry nss event %d\n", ret);
}
static void ath11k_nss_mesh_mpp_learn(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct ath11k_base *ab = arvif->ar->ab;
struct nss_wifi_mesh_proxy_path_learn_msg *learn_msg;
struct ieee80211_mesh_path_offld path = {0};
int ret;
learn_msg = &msg->msg.proxy_learn_msg;
ether_addr_copy(path.mesh_da, learn_msg->mesh_dest_mac);
ether_addr_copy(path.da, learn_msg->dest_mac_addr);
nss_mesh_convert_path_flags(&path.flags, &learn_msg->path_flags, false);
ath11k_dbg(ab, ATH11K_DBG_NSS,
"Mesh proxy learn event from nss, mDA %pM da %pM flags 0x%x if_num %d\n",
learn_msg->mesh_dest_mac, learn_msg->dest_mac_addr,
learn_msg->path_flags, arvif->nss.if_num);
ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path,
IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_LEARN);
if (ret)
ath11k_warn(ab, "failed to notify proxy learn event %d\n", ret);
}
static void ath11k_nss_mesh_mpp_add(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct ath11k_base *ab = arvif->ar->ab;
struct nss_wifi_mesh_proxy_path_add_msg *add_msg = &msg->msg.proxy_add_msg;
struct ieee80211_mesh_path_offld path = {0};
int ret;
ether_addr_copy(path.mesh_da, add_msg->mesh_dest_mac);
ether_addr_copy(path.da, add_msg->dest_mac_addr);
nss_mesh_convert_path_flags(&path.flags, &add_msg->path_flags, false);
ath11k_dbg(ab, ATH11K_DBG_NSS,
"Mesh proxy add event from nss, mDA %pM da %pM flags 0x%x if_num %d\n",
add_msg->mesh_dest_mac, add_msg->dest_mac_addr, add_msg->path_flags,
arvif->nss.if_num);
ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path,
IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_ADD);
if (ret)
ath11k_warn(ab, "failed to notify proxy add event %d\n", ret);
}
static void ath11k_nss_mesh_mpp_update(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct ath11k_base *ab = arvif->ar->ab;
struct nss_wifi_mesh_proxy_path_update_msg *umsg;
struct ieee80211_mesh_path_offld path = {0};
int ret;
umsg = &msg->msg.proxy_update_msg;
ether_addr_copy(path.mesh_da, umsg->mesh_dest_mac);
ether_addr_copy(path.da, umsg->dest_mac_addr);
nss_mesh_convert_path_flags(&path.flags, &umsg->path_flags, false);
ath11k_dbg(ab, ATH11K_DBG_NSS,
"Mesh proxy update event from nss, mDA %pM da %pM flags 0x%x if_num %d\n",
umsg->mesh_dest_mac, umsg->dest_mac_addr, umsg->path_flags, arvif->nss.if_num);
ret = ieee80211_mesh_path_offld_change_notify(arvif->vif, &path,
IEEE80211_MESH_PATH_OFFLD_ACTION_MPP_UPDATE);
if (ret)
ath11k_warn(ab, "failed to notify proxy update event %d\n", ret);
}
static void ath11k_nss_mesh_stats_sync(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct nss_wifi_mesh_stats_sync_msg *stats_msg, *stats;
struct ath11k *ar = arvif->ar;
spin_lock_bh(&ar->data_lock);
stats = &arvif->nss.mesh_stats;
stats_msg = &msg->msg.stats_sync_msg;
memcpy(stats, stats_msg, sizeof(struct nss_wifi_mesh_stats_sync_msg));
spin_unlock_bh(&ar->data_lock);
}
static int
ath11k_nss_mesh_process_path_table_dump_msg(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct nss_wifi_mesh_path_table_dump *mpath_dump = &msg->msg.mpath_table_dump;
struct ath11k_nss_mpath_entry *entry;
struct ath11k *ar = arvif->ar;
ssize_t len;
len = sizeof(struct nss_wifi_mesh_path_dump_entry) * mpath_dump->num_entries;
entry = kzalloc(sizeof(*entry) + len, GFP_ATOMIC);
if (!entry)
return -ENOMEM;
memcpy(entry->mpath, mpath_dump->path_entry, len);
entry->num_entries = mpath_dump->num_entries;
spin_lock_bh(&ar->nss.dump_lock);
list_add_tail(&entry->list, &arvif->nss.mpath_dump);
arvif->nss.mpath_dump_num_entries += mpath_dump->num_entries;
spin_unlock_bh(&ar->nss.dump_lock);
if (!mpath_dump->more_events)
complete(&arvif->nss.dump_mpath_complete);
return 0;
}
static int
ath11k_nss_mesh_process_mpp_table_dump_msg(struct ath11k_vif *arvif,
struct nss_wifi_mesh_msg *msg)
{
struct nss_wifi_mesh_proxy_path_table_dump *mpp_dump;
struct ath11k_nss_mpp_entry *entry;
struct ath11k *ar = arvif->ar;
ssize_t len;
mpp_dump = &msg->msg.proxy_path_table_dump;
len = sizeof(struct nss_wifi_mesh_proxy_path_dump_entry) * mpp_dump->num_entries;
entry = kzalloc(sizeof(*entry) + len, GFP_ATOMIC);
if (!entry)
return -ENOMEM;
memcpy(entry->mpp, mpp_dump->path_entry, len);
entry->num_entries = mpp_dump->num_entries;
spin_lock_bh(&ar->nss.dump_lock);
list_add_tail(&entry->list, &arvif->nss.mpp_dump);
arvif->nss.mpp_dump_num_entries += mpp_dump->num_entries;
spin_unlock_bh(&ar->nss.dump_lock);
if (!mpp_dump->more_events)
complete(&arvif->nss.dump_mpp_complete);
return 0;
}
int ath11k_nss_mesh_exception_flags(struct ath11k_vif *arvif,
struct nss_wifi_mesh_exception_flag_msg *nss_msg)
{
nss_wifi_mesh_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret = 0;
msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive;
status = nss_wifi_meshmgr_mesh_path_exception(arvif->nss.mesh_handle, nss_msg,
msg_cb, arvif->ar->ab);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(arvif->ar->ab, "failed to set the exception flags\n");
ret = -EINVAL;
}
return ret;
}
int ath11k_nss_exc_rate_config(struct ath11k_vif *arvif,
struct nss_wifi_mesh_rate_limit_config *nss_exc_cfg)
{
nss_tx_status_t status;
int ret = 0;
status = nss_wifi_meshmgr_config_mesh_exception_sync(arvif->nss.mesh_handle, nss_exc_cfg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(arvif->ar->ab, "failed to set the exception rate ctrl\n");
ret = -EINVAL;
}
return ret;
}
static void ath11k_nss_mesh_obj_vdev_event_receive(struct net_device *dev,
struct nss_wifi_mesh_msg *msg)
{
struct ath11k_base *ab;
struct ath11k_vif *arvif;
int ret;
arvif = ath11k_nss_get_arvif_from_dev(dev);
if (!arvif)
return;
ab = arvif->ar->ab;
switch (msg->cm.type) {
case NSS_WIFI_MESH_MSG_PATH_REFRESH:
ath11k_nss_mesh_mpath_refresh(arvif, msg);
break;
case NSS_WIFI_MESH_MSG_PATH_NOT_FOUND:
ath11k_nss_mesh_path_not_found(arvif, msg);
break;
case NSS_WIFI_MESH_MSG_MPATH_DELETE:
ath11k_nss_mesh_path_delete(arvif, msg);
break;
case NSS_WIFI_MESH_MSG_PATH_EXPIRY:
ath11k_nss_mesh_path_expiry(arvif, msg);
break;
case NSS_WIFI_MESH_MSG_PROXY_PATH_LEARN:
ath11k_nss_mesh_mpp_learn(arvif, msg);
break;
case NSS_WIFI_MESH_MSG_PROXY_PATH_ADD:
ath11k_nss_mesh_mpp_add(arvif, msg);
break;
case NSS_WIFI_MESH_MSG_PROXY_PATH_UPDATE:
ath11k_nss_mesh_mpp_update(arvif, msg);
break;
case NSS_WIFI_MESH_MSG_STATS_SYNC:
ath11k_nss_mesh_stats_sync(arvif, msg);
break;
case NSS_WIFI_MESH_MSG_PATH_TABLE_DUMP:
ret = ath11k_nss_mesh_process_path_table_dump_msg(arvif, msg);
if (ret)
ath11k_warn(arvif->ar->ab, "failed mpath table dump message %d\n",
ret);
break;
case NSS_WIFI_MESH_MSG_PROXY_PATH_TABLE_DUMP:
ret = ath11k_nss_mesh_process_mpp_table_dump_msg(arvif, msg);
if (ret)
ath11k_warn(arvif->ar->ab, "failed mpp table dump message %d\n",
ret);
break;
default:
ath11k_dbg(ab, ATH11K_DBG_NSS, "unknown message type on mesh obj vap %d\n",
msg->cm.type);
break;
}
}
static int ath11k_nss_mesh_mpath_add(struct ath11k_vif *arvif,
struct ieee80211_mesh_path_offld *path)
{
nss_wifi_mesh_msg_callback_t msg_cb;
struct nss_wifi_mesh_mpath_add_msg *msg;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "add mpath for mesh_da %pM on radio %d\n",
path->mesh_da, ar->pdev->pdev_id);
msg = kzalloc(sizeof(struct nss_wifi_mesh_mpath_add_msg), GFP_ATOMIC);
if (!msg)
return -ENOMEM;
msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive;
ether_addr_copy(msg->dest_mac_addr, path->mesh_da);
ether_addr_copy(msg->next_hop_mac_addr, path->next_hop);
msg->hop_count = path->hop_count;
msg->metric = path->metric;
nss_mesh_convert_path_flags(&msg->path_flags, &path->flags, true);
msg->link_vap_id = arvif->nss.if_num;
msg->block_mesh_fwd = path->block_mesh_fwd;
msg->metadata_type = path->metadata_type ? NSS_WIFI_MESH_PRE_HEADER_80211: NSS_WIFI_MESH_PRE_HEADER_NONE;
status = nss_wifi_meshmgr_mesh_path_add(arvif->nss.mesh_handle, msg,
msg_cb, ar->ab);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab,
"failed to add mpath entry mesh_da %pM radio_id %d status %d\n",
path->mesh_da, arvif->nss.if_num, status);
ret = -EINVAL;
}
kfree(msg);
return ret;
}
static int ath11k_nss_mesh_mpath_update(struct ath11k_vif *arvif,
struct ieee80211_mesh_path_offld *path)
{
nss_wifi_mesh_msg_callback_t msg_cb;
struct nss_wifi_mesh_mpath_update_msg *msg;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH,
"update mpath mesh_da %pM radio %d next_hop %pM metric %d flags 0x%x hop_count %d"
"exp_time %lu mesh_gate %d\n",
path->mesh_da, ar->pdev->pdev_id, path->next_hop, path->metric,
path->flags, path->hop_count, path->exp_time, path->mesh_gate);
msg = kzalloc(sizeof(struct nss_wifi_mesh_mpath_update_msg), GFP_ATOMIC);
if (!msg)
return -ENOMEM;
msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive;
ether_addr_copy(msg->dest_mac_addr, path->mesh_da);
ether_addr_copy(msg->next_hop_mac_addr, path->next_hop);
msg->hop_count = path->hop_count;
msg->metric = path->metric;
nss_mesh_convert_path_flags(&msg->path_flags, &path->flags, true);
msg->link_vap_id = arvif->nss.if_num;
msg->is_mesh_gate = path->mesh_gate;
msg->expiry_time = path->exp_time;
msg->block_mesh_fwd = path->block_mesh_fwd;
msg->metadata_type = path->metadata_type ? NSS_WIFI_MESH_PRE_HEADER_80211: NSS_WIFI_MESH_PRE_HEADER_NONE;
msg->update_flags = NSS_WIFI_MESH_PATH_UPDATE_FLAG_NEXTHOP |
NSS_WIFI_MESH_PATH_UPDATE_FLAG_HOPCOUNT |
NSS_WIFI_MESH_PATH_UPDATE_FLAG_METRIC |
NSS_WIFI_MESH_PATH_UPDATE_FLAG_MESH_FLAGS |
NSS_WIFI_MESH_PATH_UPDATE_FLAG_BLOCK_MESH_FWD |
NSS_WIFI_MESH_PATH_UPDATE_FLAG_METADATA_ENABLE_VALID;
status = nss_wifi_meshmgr_mesh_path_update(arvif->nss.mesh_handle, msg,
msg_cb, ar->ab);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab,
"failed to update mpath entry mesh_da %pM radio_id %d status %d\n",
path->mesh_da, arvif->nss.if_num, status);
ret = -EINVAL;
}
kfree(msg);
return ret;
}
static int ath11k_nss_mesh_mpath_del(struct ath11k_vif *arvif,
struct ieee80211_mesh_path_offld *path)
{
nss_wifi_mesh_msg_callback_t msg_cb;
struct nss_wifi_mesh_mpath_del_msg *msg;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "del mpath for mesh_da %pM on radio %d\n",
path->mesh_da, ar->pdev->pdev_id);
msg = kzalloc(sizeof(struct nss_wifi_mesh_mpath_del_msg), GFP_ATOMIC);
if (!msg)
return -ENOMEM;
msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive;
ether_addr_copy(msg->mesh_dest_mac_addr, path->mesh_da);
msg->link_vap_id = arvif->nss.if_num;
status = nss_wifi_meshmgr_mesh_path_delete(arvif->nss.mesh_handle,
msg, msg_cb, ar->ab);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab,
"failed to del mpath entry mesh_da %pM radio_id %d status %d\n",
path->mesh_da, arvif->nss.if_num, status);
ret = -EINVAL;
}
kfree(msg);
return ret;
}
static int ath11k_nss_mesh_mpp_add_cmd(struct ath11k_vif *arvif,
struct ieee80211_mesh_path_offld *path)
{
nss_wifi_mesh_msg_callback_t msg_cb;
struct nss_wifi_mesh_proxy_path_add_msg *msg;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "add mpp mesh_da %pM da %pM\n",
path->mesh_da, path->da);
msg = kzalloc(sizeof(struct nss_wifi_mesh_proxy_path_add_msg), GFP_ATOMIC);
if (!msg)
return -ENOMEM;
msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive;
ether_addr_copy(msg->dest_mac_addr, path->da);
ether_addr_copy(msg->mesh_dest_mac, path->mesh_da);
nss_mesh_convert_path_flags(&msg->path_flags, &path->flags, true);
status = nss_wifi_meshmgr_mesh_proxy_path_add(arvif->nss.mesh_handle,
msg, msg_cb, ar->ab);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab,
"failed to add mpp entry da %pM mesh_da %pM status %d\n",
path->da, path->mesh_da, status);
ret = -EINVAL;
}
kfree(msg);
return ret;
}
static int ath11k_nss_mesh_mpp_update_cmd(struct ath11k_vif *arvif,
struct ieee80211_mesh_path_offld *path)
{
nss_wifi_mesh_msg_callback_t msg_cb;
struct nss_wifi_mesh_proxy_path_update_msg *msg;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "update mpp da %pM mesh_da %pM on vap_id %d\n",
path->da, path->mesh_da, arvif->nss.if_num);
msg = kzalloc(sizeof(struct nss_wifi_mesh_proxy_path_update_msg), GFP_ATOMIC);
if (!msg)
return -ENOMEM;
msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive;
ether_addr_copy(msg->dest_mac_addr, path->da);
ether_addr_copy(msg->mesh_dest_mac, path->mesh_da);
nss_mesh_convert_path_flags(&msg->path_flags, &path->flags, true);
msg->bitmap = NSS_WIFI_MESH_PATH_UPDATE_FLAG_NEXTHOP |
NSS_WIFI_MESH_PATH_UPDATE_FLAG_HOPCOUNT;
status = nss_wifi_meshmgr_mesh_proxy_path_update(arvif->nss.mesh_handle,
msg, msg_cb, ar->ab);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab,
"failed to update mpp da %pM mesh_da %pM status %d\n",
path->da, path->mesh_da, status);
ret = -EINVAL;
}
kfree(msg);
return ret;
}
static int ath11k_nss_mesh_mpp_del_cmd(struct ath11k_vif *arvif,
struct ieee80211_mesh_path_offld *path)
{
nss_wifi_mesh_msg_callback_t msg_cb;
struct nss_wifi_mesh_proxy_path_del_msg *msg;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_MESH, "del mpath for mesh_da %pM\n",
path->mesh_da);
msg = kzalloc(sizeof(struct nss_wifi_mesh_proxy_path_del_msg), GFP_ATOMIC);
if (!msg)
return -ENOMEM;
msg_cb = (nss_wifi_mesh_msg_callback_t)ath11k_nss_mesh_wifili_event_receive;
ether_addr_copy(msg->dest_mac_addr, path->da);
ether_addr_copy(msg->mesh_dest_mac_addr, path->mesh_da);
status = nss_wifi_meshmgr_mesh_proxy_path_delete(arvif->nss.mesh_handle, msg,
msg_cb, ar->ab);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab,
"failed to add mpath entry mesh_da %pM status %d\n",
path->mesh_da, status);
ret = -EINVAL;
}
kfree(msg);
return ret;
}
int ath11k_nss_mesh_config_path(struct ath11k *ar, struct ath11k_vif *arvif,
enum ieee80211_mesh_path_offld_cmd cmd,
struct ieee80211_mesh_path_offld *path)
{
int ret;
if (!ar->ab->nss.enabled)
return 0;
switch (cmd) {
case IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPATH:
ret = ath11k_nss_mesh_mpath_add(arvif, path);
break;
case IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPATH:
ret = ath11k_nss_mesh_mpath_update(arvif, path);
break;
case IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPATH:
ret = ath11k_nss_mesh_mpath_del(arvif, path);
break;
case IEEE80211_MESH_PATH_OFFLD_CMD_ADD_MPP:
ret = ath11k_nss_mesh_mpp_add_cmd(arvif, path);
break;
case IEEE80211_MESH_PATH_OFFLD_CMD_UPDATE_MPP:
ret = ath11k_nss_mesh_mpp_update_cmd(arvif, path);
break;
case IEEE80211_MESH_PATH_OFFLD_CMD_DELETE_MPP:
ret = ath11k_nss_mesh_mpp_del_cmd(arvif, path);
break;
default:
ath11k_warn(ar->ab, "unknown mesh path table command type %d\n", cmd);
return -EINVAL;
}
return ret;
}
int ath11k_nss_mesh_config_update(struct ieee80211_vif *vif, int changed)
{
struct ath11k_vif *arvif = ath11k_vif_to_arvif(vif);
struct ath11k_base *ab = arvif->ar->ab;
struct nss_wifi_mesh_config_msg *nss_msg;
struct arvif_nss *nss = &arvif->nss;
nss_tx_status_t status;
int ret = 0;
if (!ab->nss.enabled)
return 0;
if (!ab->nss.mesh_nss_offload_enabled)
return -ENOTSUPP;
if (!changed)
return 0;
nss_msg = kzalloc(sizeof(*nss_msg), GFP_KERNEL);
if (!nss_msg)
return -ENOMEM;
if (changed & BSS_CHANGED_NSS_MESH_TTL) {
nss_msg->ttl = vif->bss_conf.nss_offld_ttl;
nss->mesh_ttl = vif->bss_conf.nss_offld_ttl;
nss_msg->config_flags |= NSS_WIFI_MESH_CONFIG_FLAG_TTL_VALID;
}
if (changed & BSS_CHANGED_NSS_MESH_REFRESH_TIME) {
nss_msg->mesh_path_refresh_time =
vif->bss_conf.nss_offld_mpath_refresh_time;
nss->mpath_refresh_time =
vif->bss_conf.nss_offld_mpath_refresh_time;
nss_msg->config_flags |= NSS_WIFI_MESH_CONFIG_FLAG_MPATH_REFRESH_VALID;
}
if (changed & BSS_CHANGED_NSS_MESH_FWD_ENABLED) {
nss_msg->block_mesh_forwarding =
vif->bss_conf.nss_offld_mesh_forward_enabled;
nss->mesh_forward_enabled =
vif->bss_conf.nss_offld_mesh_forward_enabled;
nss_msg->config_flags |= NSS_WIFI_MESH_CONFIG_FLAG_BLOCK_MESH_FWD_VALID;
nss_msg->metadata_type = arvif->nss.metadata_type;
nss_msg->config_flags |= NSS_WIFI_MESH_CONFIG_FLAG_METADATA_ENABLE_VALID;
}
status = nss_wifi_meshmgr_mesh_config_update_sync(arvif->nss.mesh_handle,
nss_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "failed to configure nss mesh obj vdev nss_err:%d\n",
status);
ret = -EINVAL;
}
kfree(nss_msg);
return ret;
}
int ath11k_nss_dump_mpath_request(struct ath11k_vif *arvif)
{
struct ath11k_base *ab = arvif->ar->ab;
struct ath11k *ar = arvif->ar;
struct arvif_nss *nss = &arvif->nss;
struct ath11k_nss_mpath_entry *entry, *tmp;
LIST_HEAD(local_entry);
nss_tx_status_t status;
/* Clean up any stale entries from old events */
spin_lock_bh(&ar->nss.dump_lock);
list_splice_tail(&nss->mpath_dump, &local_entry);
arvif->nss.mpath_dump_num_entries = 0;
spin_unlock_bh(&ar->nss.dump_lock);
list_for_each_entry_safe(entry, tmp, &local_entry, list)
kfree(entry);
status = nss_wifi_meshmgr_dump_mesh_path_sync(arvif->nss.mesh_handle);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "failed to send mpath dump command on mesh obj vdev nss_err:%d\n",
status);
return -EINVAL;
}
return 0;
}
int ath11k_nss_dump_mpp_request(struct ath11k_vif *arvif)
{
struct ath11k_base *ab = arvif->ar->ab;
struct ath11k *ar = arvif->ar;
struct arvif_nss *nss = &arvif->nss;
struct ath11k_nss_mpp_entry *entry, *tmp;
LIST_HEAD(local_entry);
nss_tx_status_t status;
/* Clean up any stale entries from old events */
spin_lock_bh(&ar->nss.dump_lock);
list_for_each_entry_safe(entry, tmp, &nss->mpp_dump, list)
list_move_tail(&entry->list, &local_entry);
arvif->nss.mpath_dump_num_entries = 0;
spin_unlock_bh(&ar->nss.dump_lock);
list_for_each_entry_safe(entry, tmp, &local_entry, list)
kfree(entry);
status = nss_wifi_meshmgr_dump_mesh_proxy_path_sync(arvif->nss.mesh_handle);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "failed to send mpp dump command on mesh obj vdev nss_err:%d\n",
status);
return -EINVAL;
}
return 0;
}
static void
ath11k_nss_mesh_obj_vdev_data_receive(struct net_device *dev, struct sk_buff *skb,
struct napi_struct *napi)
{
struct ath11k_vif *arvif;
struct ath11k_base *ab;
char dump_msg[100] = {0};
struct nss_wifi_mesh_per_packet_metadata *wifi_metadata = NULL;
arvif = ath11k_nss_get_arvif_from_dev(dev);
if (!arvif) {
dev_kfree_skb_any(skb);
return;
}
ab = arvif->ar->ab;
skb->dev = dev;
snprintf(dump_msg, sizeof(dump_msg), "nss mesh obj vdev: link id %d ",
arvif->nss.if_num);
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "dp rx msdu from nss", dump_msg,
skb->data, skb->len);
if (arvif->nss.metadata_type == NSS_WIFI_MESH_PRE_HEADER_80211) {
wifi_metadata = (struct nss_wifi_mesh_per_packet_metadata *)(skb->data -
(sizeof(struct nss_wifi_mesh_per_packet_metadata)));
ath11k_dbg(ab, ATH11K_DBG_NSS_MESH,
"exception from nss on mesh obj vap: pkt_type %d\n",
wifi_metadata->pkt_type);
switch (wifi_metadata->pkt_type) {
case NSS_WIFI_MESH_PRE_HEADER_80211:
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "",
"wifi header from nss on mesh obj vdev: ",
skb->data - sizeof(*wifi_metadata), sizeof(*wifi_metadata) + skb->len);
dev_kfree_skb_any(skb);
break;
default:
dev_kfree_skb_any(skb);
}
return;
}
ath11k_nss_deliver_rx(arvif->vif, skb, true, 0, napi);
}
static void
ath11k_nss_mesh_obj_ext_data_callback(struct net_device *dev, struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct ath11k_vif *arvif;
struct ath11k_base *ab;
arvif = ath11k_nss_get_arvif_from_dev(dev);
if (!arvif) {
dev_kfree_skb_any(skb);
return;
}
ab = arvif->ar->ab;
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", "msdu from nss ext_data _cb on mesh obj vdev: ",
skb->data, skb->len);
dev_kfree_skb_any(skb);
}
static void
ath11k_nss_mesh_link_vdev_data_receive(void *dev,
struct sk_buff *skb,
struct napi_struct *napi)
{
struct ieee80211_vif *vif;
struct ath11k_vif *arvif;
struct ath11k_base *ab;
struct wireless_dev *wdev = (struct wireless_dev *)dev;
vif = wdev_to_ieee80211_vif(wdev);
if (!vif) {
dev_kfree_skb_any(skb);
return;
}
arvif = (struct ath11k_vif *)vif->drv_priv;
if (!arvif) {
dev_kfree_skb_any(skb);
return;
}
ab = arvif->ar->ab;
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "", "msdu from nss data_receive_cb on mesh link vdev: ",
skb->data, skb->len);
/* data callback for mesh link vap is not expected */
dev_kfree_skb_any(skb);
}
static void
ath11k_nss_mesh_link_vdev_special_data_receive(void *dev,
struct sk_buff *skb,
__attribute__((unused)) struct napi_struct *napi)
{
struct ieee80211_vif *vif;
struct ath11k_base *ab;
struct nss_wifi_vdev_per_packet_metadata *wifi_metadata = NULL;
struct ath11k_skb_rxcb *rxcb;
struct ath11k_vif *arvif;
struct wireless_dev *wdev = (struct wireless_dev *)dev;
vif = wdev_to_ieee80211_vif(wdev);
if (!vif) {
dev_kfree_skb_any(skb);
return;
}
arvif = (struct ath11k_vif *)vif->drv_priv;
if (!arvif) {
dev_kfree_skb_any(skb);
return;
}
ab = arvif->ar->ab;
wifi_metadata = (struct nss_wifi_vdev_per_packet_metadata *)(skb->head +
NSS_WIFI_VDEV_PER_PACKET_METADATA_OFFSET);
ath11k_dbg(ab, ATH11K_DBG_NSS_MESH,
"dp special data from nss on mesh link vap: pkt_type %d\n",
wifi_metadata->pkt_type);
switch (wifi_metadata->pkt_type) {
case NSS_WIFI_VDEV_MESH_EXT_DATA_PKT_TYPE_RX_SPL_PACKET:
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "",
"special packet meta data from nss on mesh link vdev: ",
wifi_metadata,
sizeof(struct nss_wifi_vdev_per_packet_metadata));
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "",
"special packet payload from nss on mesh link vdev: ",
skb->data, skb->len);
dev_kfree_skb_any(skb);
break;
case NSS_WIFI_VDEV_EXT_DATA_PKT_TYPE_MCBC_RX:
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "",
"mcast packet exception from nss on mesh link vdev: ",
skb->data, skb->len);
rxcb = ATH11K_SKB_RXCB(skb);
rxcb->rx_desc = (struct hal_rx_desc *)skb->head;
rxcb->is_first_msdu = rxcb->is_last_msdu = true;
rxcb->is_continuation = false;
ath11k_dp_rx_from_nss(arvif->ar, skb, napi);
break;
case NSS_WIFI_VDEV_EXT_DATA_PKT_TYPE_MESH:
ath11k_dbg_dump(ab, ATH11K_DBG_DP_RX, "",
"static exception path from nss on mesh link vdev: ",
skb->data, skb->len);
dev_kfree_skb_any(skb);
break;
default:
ath11k_warn(ab, "unknown packet type received in mesh link vdev %d",
wifi_metadata->pkt_type);
dev_kfree_skb_any(skb);
break;
}
}
bool ath11k_nss_mesh_offload_enabled(struct ath11k_base *ab)
{
ab->nss.mesh_nss_offload_enabled;
}
bool ath11k_nss_offload_enabled(struct ath11k_base *ab)
{
return ab->nss.enabled;
}
void ath11k_nss_set_enabled(struct ath11k_base *ab, bool enable)
{
ab->nss.enabled = enable;
}
bool ath11k_nss_debug_mode(struct ath11k_vif *arvif)
{
return arvif->ar->ab->nss.debug_mode;
}
int ath11k_nss_tx(struct ath11k_vif *arvif, struct sk_buff *skb)
{
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int encap_type = ath11k_dp_tx_get_encap_type(arvif, skb);
struct ath11k_soc_dp_stats *soc_stats = &ar->ab->soc_stats;
char dump_msg[100] = {0};
if (!arvif->ar->ab->nss.debug_mode && encap_type != arvif->nss.encap) {
ath11k_warn(ar->ab, "encap mismatch in nss tx skb encap type %d" \
"vif encap type %d\n", encap_type, arvif->nss.encap);
goto drop;
}
if (encap_type == HAL_TCL_ENCAP_TYPE_ETHERNET)
goto send;
if (encap_type == HAL_TCL_ENCAP_TYPE_RAW)
ath11k_nss_tx_encap_raw(skb);
else
ath11k_nss_tx_encap_nwifi(skb);
send:
if (arvif->vif->type == NL80211_IFTYPE_AP_VLAN) {
ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, "ext vdev",
"nss tx msdu: ", skb->data, skb->len);
status = nss_wifi_ext_vdev_tx_buf(arvif->nss.ctx, skb,
arvif->nss.if_num);
} else {
if (arvif->ar->ab->nss.debug_mode) {
if (encap_type == HAL_TCL_ENCAP_TYPE_ETHERNET &&
!is_multicast_ether_addr(skb->data)) {
snprintf(dump_msg, sizeof(dump_msg),
"nss tx ucast msdu: %d ",
arvif->nss.mesh_handle);
ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, "mesh",
dump_msg, skb->data, skb->len);
status = nss_wifi_meshmgr_tx_buf(arvif->nss.mesh_handle,
skb);
} else {
snprintf(dump_msg, sizeof(dump_msg),
"nss tx mcast msdu: %d ",
arvif->nss.if_num);
ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, "mesh",
dump_msg, skb->data, skb->len);
status = nss_wifi_vdev_tx_buf(arvif->ar->nss.ctx, skb,
arvif->nss.if_num);
}
} else {
snprintf(dump_msg, sizeof(dump_msg),
"nss tx msdu: %d ",
arvif->nss.if_num);
ath11k_dbg_dump(ar->ab, ATH11K_DBG_DP_TX, "",
dump_msg, skb->data, skb->len);
status = nss_wifi_vdev_tx_buf(arvif->ar->nss.ctx, skb,
arvif->nss.if_num);
}
}
if (status != NSS_TX_SUCCESS)
ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss tx failure\n");
return status;
drop:
atomic_inc(&soc_stats->tx_err.misc_fail);
WARN_ON_ONCE(1);
return -EINVAL;
}
int ath11k_nss_vdev_set_cmd(struct ath11k_vif *arvif, int cmd, int val)
{
struct nss_wifi_vdev_msg *vdev_msg = NULL;
struct nss_wifi_vdev_cmd_msg *vdev_cmd;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
if (!ar->ab->nss.enabled)
return 0;
/* Monitor interface is not offloaded to nss */
if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR)
return 0;
vdev_msg = kzalloc(sizeof(*vdev_msg), GFP_ATOMIC);
if (!vdev_msg)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ar->ab, malloc_size, sizeof(*vdev_msg));
/* TODO: Convert to function for conversion in case of many
* such commands
*/
if (cmd == NSS_WIFI_VDEV_SECURITY_TYPE_CMD)
val = ath11k_nss_cipher_type(ar->ab, val);
if (cmd == NSS_WIFI_VDEV_ENCAP_TYPE_CMD)
arvif->nss.encap = val;
else if (cmd == NSS_WIFI_VDEV_DECAP_TYPE_CMD)
arvif->nss.decap = val;
vdev_cmd = &vdev_msg->msg.vdev_cmd;
vdev_cmd->cmd = cmd;
vdev_cmd->value = val;
nss_wifi_vdev_msg_init(vdev_msg, arvif->nss.if_num,
NSS_WIFI_VDEV_INTERFACE_CMD_MSG,
sizeof(struct nss_wifi_vdev_cmd_msg),
NULL, NULL);
status = nss_wifi_vdev_tx_msg(ar->nss.ctx, vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss vdev set cmd failure cmd:%d val:%d",
cmd, val);
goto free;
}
ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss vdev set cmd success cmd:%d val:%d\n",
cmd, val);
free:
ATH11K_MEMORY_STATS_DEC(ar->ab, malloc_size, sizeof(*vdev_msg));
kfree(vdev_msg);
return status;
}
static int ath11k_nss_vdev_configure(struct ath11k_vif *arvif)
{
struct ath11k *ar = arvif->ar;
struct nss_wifi_vdev_msg *vdev_msg;
struct nss_wifi_vdev_config_msg *vdev_cfg;
nss_tx_status_t status;
int ret;
vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC);
if (!vdev_msg)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ar->ab, malloc_size,
sizeof(struct nss_wifi_vdev_msg));
vdev_cfg = &vdev_msg->msg.vdev_config;
if (arvif->vif->type == NL80211_IFTYPE_MESH_POINT)
vdev_cfg->vap_ext_mode = WIFI_VDEV_EXT_MODE_MESH_LINK;
vdev_cfg->radio_ifnum = ar->nss.if_num;
vdev_cfg->vdev_id = arvif->vdev_id;
vdev_cfg->opmode = ath11k_nss_get_vdev_opmode(arvif);
ether_addr_copy(vdev_cfg->mac_addr, arvif->vif->addr);
reinit_completion(&arvif->nss.complete);
nss_wifi_vdev_msg_init(vdev_msg, arvif->nss.if_num,
NSS_WIFI_VDEV_INTERFACE_CONFIGURE_MSG,
sizeof(struct nss_wifi_vdev_config_msg),
(nss_wifi_vdev_msg_callback_t *)ath11k_nss_vdev_cfg_cb,
arvif);
status = nss_wifi_vdev_tx_msg(ar->nss.ctx, vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "failed to configure nss vdev nss_err:%d\n",
status);
ret = -EINVAL;
goto free;
}
ret = wait_for_completion_timeout(&arvif->nss.complete,
msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS));
if (!ret) {
ath11k_warn(ar->ab, "timeout in receiving nss vdev cfg response\n");
ret = -ETIMEDOUT;
goto free;
}
ret = 0;
free:
ATH11K_MEMORY_STATS_DEC(ar->ab, malloc_size,
sizeof(struct nss_wifi_vdev_msg));
kfree(vdev_msg);
return ret;
}
static int ath11k_nss_mesh_obj_assoc_link_vap(struct ath11k_vif *arvif)
{
struct nss_wifi_mesh_assoc_link_vap *msg;
struct ath11k_base *ab = arvif->ar->ab;
nss_tx_status_t status;
int ret;
msg = kzalloc(sizeof(struct nss_wifi_mesh_assoc_link_vap), GFP_ATOMIC);
if (!msg)
return -ENOMEM;
msg->link_vap_id = arvif->nss.if_num;
ath11k_dbg(ab, ATH11K_DBG_NSS_MESH, "nss mesh assoc link vap %d, mesh handle %d\n",
arvif->nss.if_num, arvif->nss.mesh_handle);
status = nss_wifi_meshmgr_assoc_link_vap_sync(arvif->nss.mesh_handle, msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "failed mesh obj vdev tx msg for assoc link vap nss_err:%d\n",
status);
ret = -EINVAL;
goto free;
}
ret = 0;
free:
kfree(msg);
return ret;
}
static void ath11k_nss_vdev_unregister(struct ath11k_vif *arvif)
{
struct ath11k_base *ab = arvif->ar->ab;
switch (arvif->vif->type) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_STATION:
nss_unregister_wifi_vdev_if(arvif->nss.if_num);
ath11k_dbg(ab, ATH11K_DBG_NSS, "unregistered nss vdev %d \n",
arvif->nss.if_num);
break;
case NL80211_IFTYPE_MESH_POINT:
nss_unregister_wifi_vdev_if(arvif->nss.if_num);
ath11k_dbg(ab, ATH11K_DBG_NSS,
"unregistered nss mesh vdevs mesh link %d\n",
arvif->nss.if_num);
break;
default:
ath11k_warn(ab, "unsupported interface type %d for nss vdev unregister\n",
arvif->vif->type);
return;
}
}
static int ath11k_nss_mesh_alloc_register(struct ath11k_vif *arvif,
struct net_device *netdev)
{
struct ath11k_base *ab = arvif->ar->ab;
struct nss_wifi_mesh_config_msg *nss_msg;
struct arvif_nss *nss = &arvif->nss;
int ret = 0;
nss->mesh_ttl = ATH11K_MESH_DEFAULT_ELEMENT_TTL;
nss->mpath_refresh_time = 1000; /* msecs */
nss->mesh_forward_enabled = true;
nss_msg = kzalloc(sizeof(*nss_msg), GFP_KERNEL);
if (!nss_msg)
return -ENOMEM;
nss_msg->ttl = nss->mesh_ttl;
nss_msg->mesh_path_refresh_time = nss->mpath_refresh_time;
nss_msg->mpp_learning_mode = mpp_mode;
nss_msg->block_mesh_forwarding = 0;
ether_addr_copy(nss_msg->local_mac_addr, arvif->vif->addr);
nss_msg->config_flags =
NSS_WIFI_MESH_CONFIG_FLAG_TTL_VALID |
NSS_WIFI_MESH_CONFIG_FLAG_MPATH_REFRESH_VALID |
NSS_WIFI_MESH_CONFIG_FLAG_MPP_LEARNING_MODE_VALID |
NSS_WIFI_MESH_CONFIG_FLAG_BLOCK_MESH_FWD_VALID |
NSS_WIFI_MESH_CONFIG_FLAG_LOCAL_MAC_VALID;
arvif->nss.mesh_handle = nss_wifi_meshmgr_if_create_sync(netdev, nss_msg,
ath11k_nss_mesh_obj_vdev_data_receive,
ath11k_nss_mesh_obj_ext_data_callback,
ath11k_nss_mesh_obj_vdev_event_receive);
if (arvif->nss.mesh_handle == NSS_WIFI_MESH_HANDLE_INVALID) {
ath11k_warn(ab, "failed to create meshmgr\n");
ret = -EINVAL;
}
kfree(nss_msg);
return ret;
}
static int ath11k_nss_mesh_vdev_register(struct ath11k_vif *arvif,
struct net_device *netdev)
{
struct ath11k *ar = arvif->ar;
struct ath11k_base *ab = ar->ab;
nss_tx_status_t status;
u32 features = 0;
status = nss_register_wifi_vdev_if(ar->nss.ctx,
arvif->nss.if_num,
ath11k_nss_mesh_link_vdev_data_receive,
ath11k_nss_mesh_link_vdev_special_data_receive,
ath11k_nss_vdev_event_receive,
(struct net_device *)netdev->ieee80211_ptr,
features);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "failed to register nss mesh link vdev if_num %d nss_err:%d\n",
arvif->nss.if_num, status);
nss_unregister_wifi_vdev_if(arvif->nss.if_num);
return -EINVAL;
}
ath11k_dbg(ab, ATH11K_DBG_NSS, "registered nss mesh link vdev if_num %d\n",
arvif->nss.if_num);
return 0;
}
static int ath11k_nss_vdev_register(struct ath11k_vif *arvif,
struct net_device *netdev)
{
struct ath11k *ar = arvif->ar;
struct ath11k_base *ab = ar->ab;
nss_tx_status_t status;
u32 features = 0;
switch (arvif->vif->type) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_STATION:
status = nss_register_wifi_vdev_if(ar->nss.ctx,
arvif->nss.if_num,
ath11k_nss_vdev_data_receive,
ath11k_nss_vdev_special_data_receive,
ath11k_nss_vdev_event_receive,
netdev, features);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "failed to register nss vdev if_num %d nss_err:%d\n",
arvif->nss.if_num, status);
return -EINVAL;
}
ath11k_dbg(ab, ATH11K_DBG_NSS, "registered nss vdev if_num %d\n",
arvif->nss.if_num);
break;
case NL80211_IFTYPE_MESH_POINT:
if (!ab->nss.mesh_nss_offload_enabled)
return -ENOTSUPP;
if (ath11k_nss_mesh_vdev_register(arvif, netdev))
return -EINVAL;
break;
default:
ath11k_warn(ab, "unsupported interface type %d for nss vdev register\n",
arvif->vif->type);
return -ENOTSUPP;
}
return 0;
}
static void ath11k_nss_mesh_vdev_free(struct ath11k_vif *arvif)
{
struct ath11k_base *ab = arvif->ar->ab;
struct ath11k *ar = arvif->ar;
struct ath11k_nss_mpath_entry *mpath_entry, *mpath_tmp;
struct ath11k_nss_mpp_entry *mpp_entry, *mpp_tmp;
struct arvif_nss *nss = &arvif->nss, *nss_entry, *nss_tmp;
LIST_HEAD(mpath_local_entry);
LIST_HEAD(mpp_local_entry);
nss_tx_status_t status;
spin_lock_bh(&ar->nss.dump_lock);
list_for_each_entry_safe(mpath_entry, mpath_tmp, &nss->mpath_dump, list)
list_move_tail(&mpath_entry->list, &mpath_local_entry);
spin_unlock_bh(&ar->nss.dump_lock);
list_for_each_entry_safe(mpath_entry, mpath_tmp, &mpath_local_entry, list)
kfree(mpath_entry);
spin_lock_bh(&ar->nss.dump_lock);
list_for_each_entry_safe(mpp_entry, mpp_tmp, &nss->mpp_dump, list)
list_move_tail(&mpp_entry->list, &mpp_local_entry);
spin_unlock_bh(&ar->nss.dump_lock);
list_for_each_entry_safe(mpp_entry, mpp_tmp, &mpp_local_entry, list)
kfree(mpp_entry);
list_for_each_entry_safe(nss_entry, nss_tmp, &mesh_vaps, list)
list_del(&nss_entry->list);
status = nss_dynamic_interface_dealloc_node(
arvif->nss.if_num,
NSS_DYNAMIC_INTERFACE_TYPE_VAP);
if (status != NSS_TX_SUCCESS)
ath11k_warn(ab, "failed to free nss mesh link vdev nss_err:%d\n",
status);
else
ath11k_dbg(ab, ATH11K_DBG_NSS,
"nss mesh link vdev interface deallocated\n");
status = nss_wifi_meshmgr_if_destroy_sync(arvif->nss.mesh_handle);
if (status != NSS_TX_SUCCESS)
ath11k_warn(ab, "failed to free nss mesh object vdev nss_err:%d\n",
status);
else
ath11k_dbg(ab, ATH11K_DBG_NSS,
"nss mesh object vdev interface deallocated\n");
}
void ath11k_nss_vdev_free(struct ath11k_vif *arvif)
{
struct ath11k_base *ab = arvif->ar->ab;
nss_tx_status_t status;
switch (arvif->vif->type) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_STATION:
status = nss_dynamic_interface_dealloc_node(
arvif->nss.if_num,
NSS_DYNAMIC_INTERFACE_TYPE_VAP);
if (status != NSS_TX_SUCCESS)
ath11k_warn(ab, "failed to free nss vdev nss_err:%d\n",
status);
else
ath11k_dbg(ab, ATH11K_DBG_NSS,
"nss vdev interface deallocated\n");
return;
case NL80211_IFTYPE_MESH_POINT:
ath11k_nss_mesh_vdev_free(arvif);
return;
default:
ath11k_warn(ab, "unsupported interface type %d for nss vdev dealloc\n",
arvif->vif->type);
return;
}
}
struct arvif_nss *ath11k_nss_find_arvif_by_if_num(int if_num)
{
struct arvif_nss *nss;
list_for_each_entry(nss, &mesh_vaps, list) {
if (if_num == nss->if_num)
return nss;
}
return NULL;
}
int ath11k_nss_assoc_link_arvif_to_ifnum(struct ath11k_vif *arvif, int if_num)
{
struct ath11k_base *ab = arvif->ar->ab;
struct ath11k_vif *arvif_link;
struct wireless_dev *wdev;
struct arvif_nss *nss;
int ret;
wdev = ieee80211_vif_to_wdev_relaxed(arvif->vif);
if (!wdev) {
ath11k_warn(ab, "ath11k_nss: wdev is null\n");
return -EINVAL;
}
if (!wdev->netdev) {
ath11k_warn(ab, "ath11k_nss: netdev is null\n");
return -EINVAL;
}
nss = ath11k_nss_find_arvif_by_if_num(if_num);
if (!nss) {
ath11k_warn(ab, "ath11k_nss: unable to find if_num %d\n",if_num);
return -EINVAL;
}
arvif_link = container_of(nss, struct ath11k_vif, nss);
ath11k_dbg(ab, ATH11K_DBG_NSS_MESH,
"assoc link vap ifnum %d to mesh handle of link id %d\n",
arvif_link->nss.if_num, arvif->nss.if_num);
arvif_link->nss.mesh_handle = arvif->nss.mesh_handle;
ret = ath11k_nss_mesh_obj_assoc_link_vap(arvif_link);
if (ret)
ath11k_warn(ab, "failed to associate link vap to mesh vap %d\n", ret);
return 0;
}
static int ath11k_nss_mesh_vdev_alloc(struct ath11k_vif *arvif,
struct net_device *netdev)
{
struct ath11k_base *ab = arvif->ar->ab;
int if_num;
if (!ab->nss.mesh_nss_offload_enabled)
return -ENOTSUPP;
if_num = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_VAP);
if (if_num < 0) {
ath11k_warn(ab, "failed to allocate nss mesh link vdev\n");
return -EINVAL;
}
arvif->nss.if_num = if_num;
INIT_LIST_HEAD(&arvif->nss.list);
list_add_tail(&arvif->nss.list, &mesh_vaps);
INIT_LIST_HEAD(&arvif->nss.mpath_dump);
init_completion(&arvif->nss.dump_mpath_complete);
INIT_LIST_HEAD(&arvif->nss.mpp_dump);
init_completion(&arvif->nss.dump_mpp_complete);
return 0;
}
static int ath11k_nss_vdev_alloc(struct ath11k_vif *arvif,
struct net_device *netdev)
{
struct ath11k_base *ab = arvif->ar->ab;
enum nss_dynamic_interface_type if_type;
int if_num;
int ret;
/* Initialize completion for verifying NSS message response */
init_completion(&arvif->nss.complete);
switch (arvif->vif->type) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_STATION:
if_type = NSS_DYNAMIC_INTERFACE_TYPE_VAP;
/* allocate interface context with NSS driver for the new vdev */
if_num = nss_dynamic_interface_alloc_node(if_type);
if (if_num < 0) {
ath11k_warn(ab, "failed to allocate nss vdev\n");
return -EINVAL;
}
arvif->nss.if_num = if_num;
ath11k_dbg(ab, ATH11K_DBG_NSS, "allocated nss vdev if_num %d\n",
arvif->nss.if_num);
break;
case NL80211_IFTYPE_MESH_POINT:
ret = ath11k_nss_mesh_vdev_alloc(arvif, netdev);
if (ret) {
ath11k_warn(ab, "failed to allocate nss vdev of mesh type %d\n",
ret);
return ret;
}
break;
default:
ath11k_warn(ab, "unsupported interface type %d for nss vdev alloc\n",
arvif->vif->type);
return -ENOTSUPP;
}
return 0;
}
int ath11k_nss_vdev_create(struct ath11k_vif *arvif)
{
struct ath11k *ar = arvif->ar;
struct ath11k_base *ab = ar->ab;
struct wireless_dev *wdev;
int ret;
if (!ab->nss.enabled)
return 0;
/* Monitor interface is not offloaded to nss */
if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR)
return 0;
if (arvif->nss.created)
return 0;
wdev = ieee80211_vif_to_wdev_relaxed(arvif->vif);
if (!wdev) {
ath11k_warn(ab, "ath11k_nss: wdev is null\n");
return -EINVAL;
}
if (!wdev->netdev) {
ath11k_warn(ab, "ath11k_nss: netdev is null\n");
return -EINVAL;
}
ret = ath11k_nss_vdev_alloc(arvif, wdev->netdev);
if (ret)
return ret;
ret = ath11k_nss_vdev_register(arvif, wdev->netdev);
if (ret)
goto free_vdev;
switch (arvif->vif->type) {
case NL80211_IFTYPE_STATION:
ret = ath11k_nss_vdev_set_cmd(arvif,
NSS_WIFI_VDEV_CFG_MCBC_EXC_TO_HOST_CMD,
ATH11K_NSS_ENABLE_MCBC_EXC);
if (ret) {
ath11k_err(ab, "failed to set MCBC in nss %d\n", ret);
goto unregister_vdev;
}
case NL80211_IFTYPE_AP:
ret = ath11k_nss_vdev_configure(arvif);
if (ret)
goto unregister_vdev;
break;
case NL80211_IFTYPE_MESH_POINT:
ret = ath11k_nss_mesh_alloc_register(arvif, wdev->netdev);
if (ret) {
ath11k_warn(ab, "failed to alloc and register mesh vap %d\n", ret);
goto unregister_vdev;
}
ret = ath11k_nss_vdev_configure(arvif);
if (ret) {
ath11k_warn(ab, "failed to configure nss mesh link vdev\n");
goto unregister_vdev;
}
ret = ath11k_nss_mesh_obj_assoc_link_vap(arvif);
if (ret) {
ath11k_warn(ab, "failed to associate link vap to mesh vap %d\n", ret);
goto unregister_vdev;
}
ret = ath11k_nss_vdev_set_cmd(arvif,
NSS_WIFI_VDEV_CFG_MCBC_EXC_TO_HOST_CMD, 1);
if (ret) {
ath11k_warn(ab, "failed to enable mcast/bcast exception %d\n", ret);
goto unregister_vdev;
}
ath11k_debugfs_nss_mesh_vap_create(arvif);
break;
default:
ret = -ENOTSUPP;
goto unregister_vdev;
}
arvif->nss.created = true;
ath11k_dbg(ab, ATH11K_DBG_NSS,
"nss vdev interface created ctx %pK, ifnum %d\n",
ar->nss.ctx, arvif->nss.if_num);
return ret;
unregister_vdev:
ath11k_nss_vdev_unregister(arvif);
free_vdev:
ath11k_nss_vdev_free(arvif);
return ret;
}
void ath11k_nss_vdev_delete(struct ath11k_vif *arvif)
{
if (!arvif->ar->ab->nss.enabled)
return;
/* Monitor interface is not offloaded to nss */
if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR)
return;
if (!arvif->nss.created)
return;
ath11k_nss_vdev_unregister(arvif);
ath11k_nss_vdev_free(arvif);
arvif->nss.created = false;
}
int ath11k_nss_vdev_up(struct ath11k_vif *arvif)
{
struct nss_wifi_vdev_msg *vdev_msg = NULL;
struct nss_wifi_vdev_enable_msg *vdev_en;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
if (!ar->ab->nss.enabled)
return 0;
/* Monitor interface is not offloaded to nss */
if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR)
return 0;
if (arvif->vif->type == NL80211_IFTYPE_MESH_POINT) {
status = nss_wifi_meshmgr_if_up(arvif->nss.mesh_handle);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss mesh vdev up error %d\n", status);
return -EINVAL;
}
}
vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC);
if (!vdev_msg)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ar->ab, malloc_size,
sizeof(struct nss_wifi_vdev_msg));
vdev_en = &vdev_msg->msg.vdev_enable;
ether_addr_copy(vdev_en->mac_addr, arvif->vif->addr);
nss_wifi_vdev_msg_init(vdev_msg, arvif->nss.if_num,
NSS_WIFI_VDEV_INTERFACE_UP_MSG,
sizeof(struct nss_wifi_vdev_enable_msg),
NULL, NULL);
status = nss_wifi_vdev_tx_msg(ar->nss.ctx, vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss vdev up tx msg error %d\n", status);
ret = -EINVAL;
goto free;
}
ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss vdev up tx msg success\n");
free:
ATH11K_MEMORY_STATS_DEC(ar->ab, malloc_size,
sizeof(struct nss_wifi_vdev_msg));
kfree(vdev_msg);
return ret;
}
int ath11k_nss_vdev_down(struct ath11k_vif *arvif)
{
struct nss_wifi_vdev_msg *vdev_msg = NULL;
struct ath11k *ar = arvif->ar;
struct ath11k_vif *ap_vlan_arvif, *tmp;
nss_tx_status_t status;
int ret = 0;
if (!ar->ab->nss.enabled)
return 0;
/* Monitor interface is not offloaded to nss */
if (arvif->vdev_type == WMI_VDEV_TYPE_MONITOR)
return 0;
if (arvif->vif->type == NL80211_IFTYPE_MESH_POINT) {
status = nss_wifi_meshmgr_if_down(arvif->nss.mesh_handle);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss mesh vdev up error %d\n", status);
return -EINVAL;
}
}
vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC);
if (!vdev_msg)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ar->ab, malloc_size,
sizeof(struct nss_wifi_vdev_msg));
nss_wifi_vdev_msg_init(vdev_msg, arvif->nss.if_num,
NSS_WIFI_VDEV_INTERFACE_DOWN_MSG,
sizeof(struct nss_wifi_vdev_disable_msg),
NULL, NULL);
status = nss_wifi_vdev_tx_msg(ar->nss.ctx, vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss vdev down tx msg error %d\n", status);
ret = -EINVAL;
goto free;
}
ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss vdev down tx msg success\n");
if (arvif->vif->type == NL80211_IFTYPE_AP)
list_for_each_entry_safe(ap_vlan_arvif, tmp, &arvif->ap_vlan_arvifs,
list)
ath11k_nss_ext_vdev_down(ap_vlan_arvif);
free:
ATH11K_MEMORY_STATS_DEC(ar->ab, malloc_size,
sizeof(struct nss_wifi_vdev_msg));
kfree(vdev_msg);
return ret;
}
int ath11k_nss_ext_vdev_cfg_wds_peer(struct ath11k_vif *arvif,
u8 *wds_addr, u32 wds_peer_id)
{
struct ath11k *ar = arvif->ar;
struct nss_wifi_ext_vdev_msg *ext_vdev_msg = NULL;
struct nss_wifi_ext_vdev_wds_msg *cfg_wds_msg = NULL;
nss_tx_status_t status;
int ret;
if (arvif->vif->type != NL80211_IFTYPE_AP_VLAN)
return -EINVAL;
ext_vdev_msg = kzalloc(sizeof(struct nss_wifi_ext_vdev_msg), GFP_ATOMIC);
if (!ext_vdev_msg)
return -ENOMEM;
cfg_wds_msg = &ext_vdev_msg->msg.wmsg;
cfg_wds_msg->wds_peer_id = wds_peer_id;
ether_addr_copy(cfg_wds_msg->mac_addr, wds_addr);
nss_wifi_ext_vdev_msg_init(ext_vdev_msg, arvif->nss.if_num,
NSS_WIFI_EXT_VDEV_MSG_CONFIGURE_WDS,
sizeof(struct nss_wifi_ext_vdev_wds_msg),
NULL, arvif);
status = nss_wifi_ext_vdev_tx_msg_sync(arvif->nss.ctx, ext_vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "failed to configure wds peer nss_err:%d\n",
status);
ret = -EINVAL;
goto free;
}
ret = 0;
free:
kfree(ext_vdev_msg);
return ret;
}
int ath11k_nss_ext_vdev_wds_4addr_allow(struct ath11k_vif *arvif,
u32 wds_peer_id)
{
struct ath11k *ar = arvif->ar;
struct nss_wifili_peer_wds_4addr_allow_msg *cfg_4addr_msg = NULL;
nss_wifili_msg_callback_t msg_cb;
struct nss_wifili_msg *wlmsg;
nss_tx_status_t status;
if (arvif->vif->type != NL80211_IFTYPE_AP_VLAN)
return -EINVAL;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
cfg_4addr_msg = &wlmsg->msg.wpswm;
cfg_4addr_msg->peer_id = wds_peer_id;
cfg_4addr_msg->if_num = arvif->nss.if_num;
cfg_4addr_msg->enable = true;
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ar->ab->nss.if_num,
NSS_WIFILI_PEER_4ADDR_EVENT_MSG,
sizeof(struct nss_wifili_peer_wds_4addr_allow_msg),
msg_cb, NULL);
status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss wds 4addr allow if_num %d, peer_id %d cfg fail: %d\n",
arvif->nss.if_num, wds_peer_id, status);
goto free;
}
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_WDS, "nss wds 4addr allow if_num %d, peer_id %d cfg complete\n",
arvif->nss.if_num, wds_peer_id);
free:
kfree(wlmsg);
return status;
}
int ath11k_nss_ext_vdev_configure(struct ath11k_vif *arvif)
{
struct ath11k *ar = arvif->ar;
struct ath11k_vif *ap_vif = arvif->nss.ap_vif;
struct nss_wifi_ext_vdev_msg *ext_vdev_msg = NULL;
struct nss_wifi_ext_vdev_configure_if_msg *ext_vdev_cfg = NULL;
nss_tx_status_t status;
int ret;
if (arvif->vif->type != NL80211_IFTYPE_AP_VLAN)
return -EINVAL;
ext_vdev_msg = kzalloc(sizeof(struct nss_wifi_ext_vdev_msg), GFP_ATOMIC);
if (!ext_vdev_msg)
return -ENOMEM;
ext_vdev_cfg = &ext_vdev_msg->msg.cmsg;
ext_vdev_cfg->radio_ifnum = ar->nss.if_num;
ext_vdev_cfg->pvap_ifnum = ap_vif->nss.if_num;
ether_addr_copy(ext_vdev_cfg->mac_addr, arvif->vif->addr);
nss_wifi_ext_vdev_msg_init(ext_vdev_msg, arvif->nss.if_num,
NSS_WIFI_EXT_VDEV_MSG_CONFIGURE_IF,
sizeof(struct nss_wifi_ext_vdev_configure_if_msg),
NULL, arvif);
status = nss_wifi_ext_vdev_tx_msg_sync(arvif->ar->nss.ctx, ext_vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "failed to configure nss ext vdev nss_err:%d\n",
status);
ret = -EINVAL;
goto free;
}
ret = 0;
free:
kfree(ext_vdev_msg);
return ret;
}
void ath11k_nss_ext_vdev_unregister(struct ath11k_vif *arvif)
{
struct ath11k_base *ab = arvif->ar->ab;
if (arvif->vif->type != NL80211_IFTYPE_AP_VLAN)
return;
nss_wifi_ext_vdev_unregister_if(arvif->nss.if_num);
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS, "unregistered nss vdev %d \n",
arvif->nss.if_num);
}
static int ath11k_nss_ext_vdev_register(struct ath11k_vif *arvif,
struct net_device *netdev)
{
struct ath11k *ar = arvif->ar;
struct ath11k_base *ab = ar->ab;
nss_tx_status_t status;
u32 features = 0;
if (arvif->vif->type != NL80211_IFTYPE_AP_VLAN || arvif->nss.ctx)
return -EINVAL;
arvif->nss.ctx = nss_wifi_ext_vdev_register_if(arvif->nss.if_num,
ath11k_nss_ext_vdev_data_receive,
ath11k_nss_ext_vdev_special_data_receive,
NULL, netdev, features,
arvif);
if (!arvif->nss.ctx) {
ath11k_warn(ab, "failed to register nss vdev if_num %d nss_err:%d\n",
arvif->nss.if_num, status);
return -EINVAL;
}
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS, "registered nss ext vdev if_num %d\n",
arvif->nss.if_num);
return 0;
}
static void ath11k_nss_ext_vdev_free(struct ath11k_vif *arvif)
{
struct ath11k_base *ab = arvif->ar->ab;
nss_tx_status_t status;
if (arvif->vif->type != NL80211_IFTYPE_AP_VLAN)
return;
status = nss_dynamic_interface_dealloc_node(
arvif->nss.if_num,
arvif->nss.di_type);
if (status != NSS_TX_SUCCESS)
ath11k_warn(ab, "failed to free nss ext vdev err:%d\n",
status);
else
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"nss ext vdev interface deallocated\n");
}
static int ath11k_nss_ext_vdev_alloc(struct ath11k_vif *arvif,
struct wireless_dev *wdev)
{
struct ath11k_base *ab = arvif->ar->ab;
enum nss_dynamic_interface_type di_type;
int if_num;
if (wdev->use_4addr)
di_type = NSS_DYNAMIC_INTERFACE_TYPE_WIFI_EXT_VDEV_WDS;
else
di_type = NSS_DYNAMIC_INTERFACE_TYPE_WIFI_EXT_VDEV_VLAN;
arvif->nss.di_type = di_type;
if_num = nss_dynamic_interface_alloc_node(di_type);
if (if_num < 0) {
ath11k_warn(ab, "failed to allocate nss ext vdev\n");
return -EINVAL;
}
arvif->nss.if_num = if_num;
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"nss ext vdev interface %pM di_type %d allocated if_num %d\n",
arvif->vif->addr, di_type, if_num);
return 0;
}
int ath11k_nss_ext_vdev_create(struct ath11k_vif *arvif)
{
struct ath11k *ar = arvif->ar;
struct ath11k_base *ab = ar->ab;
struct wireless_dev *wdev;
int ret;
if (!ab->nss.enabled)
return 0;
if (arvif->nss.created)
return 0;
wdev = ieee80211_vif_to_wdev_relaxed(arvif->vif);
if (!wdev) {
ath11k_warn(ab, "ath11k_nss: ext wdev is null\n");
return -EINVAL;
}
if (!wdev->netdev) {
ath11k_warn(ab, "ath11k_nss: ext netdev is null\n");
return -EINVAL;
}
ret = ath11k_nss_ext_vdev_alloc(arvif, wdev);
if (ret)
return ret;
ret = ath11k_nss_ext_vdev_register(arvif, wdev->netdev);
if (ret)
goto free_ext_vdev;
arvif->nss.created = true;
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"nss ext vdev interface created ctx %pK, ifnum %d\n",
arvif->nss.ctx, arvif->nss.if_num);
return ret;
free_ext_vdev:
ath11k_nss_ext_vdev_free(arvif);
return ret;
}
void ath11k_nss_ext_vdev_delete(struct ath11k_vif *arvif)
{
if (!arvif->ar->ab->nss.enabled)
return;
if (!arvif->nss.created)
return;
ath11k_dbg(arvif->ar->ab, ATH11K_DBG_NSS_WDS,
"nss ext vdev interface delete ctx %pK, ifnum %d\n",
arvif->nss.ctx, arvif->nss.if_num);
ath11k_nss_ext_vdev_unregister(arvif);
ath11k_nss_ext_vdev_free(arvif);
arvif->nss.created = false;
}
int ath11k_nss_ext_vdev_up(struct ath11k_vif *arvif)
{
struct nss_wifi_ext_vdev_msg *ext_vdev_msg = NULL;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
if (!ar->ab->nss.enabled)
return 0;
if (arvif->vif->type != NL80211_IFTYPE_AP_VLAN)
return -EINVAL;
ext_vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC);
if (!ext_vdev_msg)
return -ENOMEM;
nss_wifi_ext_vdev_msg_init(ext_vdev_msg, arvif->nss.if_num, NSS_IF_OPEN,
sizeof(struct nss_if_open), NULL, arvif);
status = nss_wifi_ext_vdev_tx_msg_sync(arvif->nss.ctx, ext_vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss ext vdev up tx msg error %d\n", status);
ret = -EINVAL;
goto free;
}
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_WDS, "nss ext vdev up tx msg success\n");
free:
kfree(ext_vdev_msg);
return ret;
}
int ath11k_nss_ext_vdev_down(struct ath11k_vif *arvif)
{
struct nss_wifi_ext_vdev_msg *ext_vdev_msg = NULL;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
if (!ar->ab->nss.enabled)
return 0;
if (arvif->vif->type != NL80211_IFTYPE_AP_VLAN)
return -EINVAL;
ext_vdev_msg = kzalloc(sizeof(struct nss_wifi_ext_vdev_msg), GFP_ATOMIC);
if (!ext_vdev_msg)
return -ENOMEM;
nss_wifi_ext_vdev_msg_init(ext_vdev_msg, arvif->nss.if_num, NSS_IF_CLOSE,
sizeof(struct nss_if_close), NULL, arvif);
status = nss_wifi_ext_vdev_tx_msg_sync(arvif->nss.ctx, ext_vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss ext vdev down tx msg error %d\n", status);
ret = -EINVAL;
goto free;
}
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_WDS, "nss ext vdev down tx msg success\n");
free:
kfree(ext_vdev_msg);
return ret;
}
int ath11k_nss_ext_vdev_cfg_dyn_vlan(struct ath11k_vif *arvif, u16 vlan_id)
{
struct ath11k *ar = arvif->ar;
struct nss_wifi_ext_vdev_msg *ext_vdev_msg = NULL;
struct nss_wifi_ext_vdev_vlan_msg *cfg_dyn_vlan_msg = NULL;
nss_tx_status_t status;
int ret;
if (!ar->ab->nss.enabled)
return 0;
if (arvif->vif->type != NL80211_IFTYPE_AP_VLAN)
return -EINVAL;
ext_vdev_msg = kzalloc(sizeof(struct nss_wifi_ext_vdev_msg), GFP_ATOMIC);
if (!ext_vdev_msg)
return -ENOMEM;
cfg_dyn_vlan_msg = &ext_vdev_msg->msg.vmsg;
cfg_dyn_vlan_msg->vlan_id = vlan_id;
nss_wifi_ext_vdev_msg_init(ext_vdev_msg, arvif->nss.if_num,
NSS_WIFI_EXT_VDEV_MSG_CONFIGURE_VLAN,
sizeof(struct nss_wifi_ext_vdev_vlan_msg),
NULL, arvif);
status = nss_wifi_ext_vdev_tx_msg_sync(arvif->nss.ctx, ext_vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "failed to configure dyn vlan nss_err:%d\n",
status);
ret = -EINVAL;
goto free;
}
ret = 0;
free:
kfree(ext_vdev_msg);
return ret;
}
int ath11k_nss_dyn_vlan_set_group_key(struct ath11k_vif *arvif, u16 vlan_id,
u16 group_key)
{
struct nss_wifi_vdev_msg *vdev_msg = NULL;
struct nss_wifi_vdev_set_vlan_group_key *vlan_group_key;
struct ath11k *ar = arvif->ar;
nss_tx_status_t status;
int ret = 0;
if (!ar->ab->nss.enabled)
return 0;
vdev_msg = kzalloc(sizeof(struct nss_wifi_vdev_msg), GFP_ATOMIC);
if (!vdev_msg)
return -ENOMEM;
vlan_group_key = &vdev_msg->msg.vlan_group_key;
vlan_group_key->vlan_id = vlan_id;
vlan_group_key->group_key = group_key;
nss_wifi_vdev_msg_init(vdev_msg, arvif->nss.if_num,
NSS_WIFI_VDEV_SET_GROUP_KEY,
sizeof(struct nss_wifi_vdev_set_vlan_group_key),
NULL, NULL);
status = nss_wifi_vdev_tx_msg(ar->nss.ctx, vdev_msg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss vdev set vlan group key error %d\n", status);
ret = -EINVAL;
goto free;
}
ath11k_dbg(ar->ab, ATH11K_DBG_NSS_WDS, "nss vdev set vlan group key success\n");
free:
kfree(vdev_msg);
return ret;
}
/*----------------------------Peer Setup/Config -----------------------------*/
int ath11k_nss_set_peer_sec_type(struct ath11k *ar,
struct ath11k_peer *peer,
struct ieee80211_key_conf *key_conf)
{
struct nss_wifili_peer_security_type_msg *sec_msg;
nss_wifili_msg_callback_t msg_cb;
struct nss_wifili_msg *wlmsg;
nss_tx_status_t status;
u8 *mic_key;
if (!ar->ab->nss.enabled)
return 0;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ar->ab, malloc_size,
sizeof(struct nss_wifili_msg));
sec_msg = &wlmsg->msg.securitymsg;
sec_msg->peer_id = peer->peer_id;
/* 0 -unicast , 1 - mcast/unicast */
sec_msg->pkt_type = !(key_conf->flags & IEEE80211_KEY_FLAG_PAIRWISE);
sec_msg->security_type = ath11k_nss_cipher_type(ar->ab,
key_conf->cipher);
if (sec_msg->security_type == PEER_SEC_TYPE_TKIP) {
mic_key = &key_conf->key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY];
memcpy(&sec_msg->mic_key[0], mic_key, NSS_WIFILI_MIC_KEY_LEN);
}
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ar->ab->nss.if_num,
NSS_WIFILI_PEER_SECURITY_TYPE_MSG,
sizeof(struct nss_wifili_peer_security_type_msg),
msg_cb, NULL);
status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss peer %d security cfg fail %d\n",
peer->peer_id, status);
goto free;
}
ath11k_dbg(ar->ab, ATH11K_DBG_NSS, "nss peer id %d security cfg complete\n",
peer->peer_id);
free:
ATH11K_MEMORY_STATS_DEC(ar->ab, malloc_size,
sizeof(struct nss_wifili_msg));
kfree(wlmsg);
return status;
}
void ath11k_nss_update_sta_stats(struct ath11k_vif *arvif,
struct station_info *sinfo,
struct ieee80211_sta *sta)
{
struct sta_info *stainfo;
struct ath11k_peer *peer;
int tid_idx;
struct ath11k *ar = arvif->ar;
struct ath11k_base *ab = ar->ab;
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find_by_addr(ab, sta->addr);
if (!peer) {
ath11k_dbg(ab, ATH11K_DBG_NSS, "unable to find peer %pM\n",
sta->addr);
goto exit;
}
if (!peer->nss.nss_stats)
goto exit;
stainfo = container_of(sta, struct sta_info, sta);
if (peer->nss.nss_stats->last_rx &&
time_after((unsigned long)peer->nss.nss_stats->last_rx, stainfo->rx_stats.last_rx))
stainfo->rx_stats.last_rx = peer->nss.nss_stats->last_rx;
if (peer->nss.nss_stats->last_ack &&
time_after((unsigned long)peer->nss.nss_stats->last_ack, stainfo->status_stats.last_ack))
stainfo->status_stats.last_ack = peer->nss.nss_stats->last_ack;
stainfo->rx_stats.dropped += peer->nss.nss_stats->rx_dropped -
peer->nss.nss_stats->last_rxdrop;
peer->nss.nss_stats->last_rxdrop = peer->nss.nss_stats->rx_dropped;
sinfo->tx_packets = 0;
/* Add only ac-0 count as mgmt packets uses WME_AC_BE */
sinfo->tx_packets += stainfo->tx_stats.packets[WME_AC_BE];
sinfo->tx_packets += peer->nss.nss_stats->tx_packets;
sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_PACKETS);
sinfo->tx_bytes = 0;
/* Add only ac-0 count as mgmt packets uses WME_AC_BE */
sinfo->tx_bytes += stainfo->tx_stats.bytes[WME_AC_BE];
sinfo->tx_bytes += peer->nss.nss_stats->tx_bytes;
sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES);
sinfo->tx_failed = stainfo->status_stats.retry_failed +
peer->nss.nss_stats->tx_failed;
sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_FAILED);
sinfo->tx_retries = stainfo->status_stats.retry_count +
peer->nss.nss_stats->tx_retries;
sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_RETRIES);
sinfo->rx_packets = stainfo->rx_stats.packets +
peer->nss.nss_stats->rx_packets;
sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_PACKETS);
sinfo->rx_bytes = stainfo->rx_stats.bytes +
peer->nss.nss_stats->rx_bytes;
sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES);
if (peer->nss.nss_stats->rxrate.legacy || peer->nss.nss_stats->rxrate.nss) {
if (peer->nss.nss_stats->rxrate.legacy) {
sinfo->rxrate.legacy = peer->nss.nss_stats->rxrate.legacy;
} else {
sinfo->rxrate.mcs = peer->nss.nss_stats->rxrate.mcs;
sinfo->rxrate.nss = peer->nss.nss_stats->rxrate.nss;
sinfo->rxrate.bw = peer->nss.nss_stats->rxrate.bw;
sinfo->rxrate.he_gi = peer->nss.nss_stats->rxrate.he_gi;
sinfo->rxrate.he_dcm = peer->nss.nss_stats->rxrate.he_dcm;
sinfo->rxrate.he_ru_alloc = peer->nss.nss_stats->rxrate.he_ru_alloc;
}
sinfo->rxrate.flags = peer->nss.nss_stats->rxrate.flags;
sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BITRATE);
}
if (!sinfo->pertid && cfg80211_sinfo_alloc_tid_stats(sinfo, GFP_KERNEL))
goto exit;
for (tid_idx = 0; tid_idx < NSS_TX_TID_MAX; tid_idx++) {
sinfo->pertid[tid_idx].tx_msdu = peer->nss.nss_stats->tx_tid_msdu[tid_idx];
sinfo->pertid[tid_idx].filled |= BIT_ULL(NL80211_TID_STATS_TX_MSDU);
}
for (tid_idx = 0; tid_idx < IEEE80211_NUM_TIDS; tid_idx++) {
sinfo->pertid[tid_idx].rx_msdu = peer->nss.nss_stats->rx_tid_msdu[tid_idx];
sinfo->pertid[tid_idx].filled |= BIT_ULL(NL80211_TID_STATS_RX_MSDU);
}
exit:
spin_unlock_bh(&ab->base_lock);
}
void ath11k_nss_update_sta_rxrate(struct hal_rx_mon_ppdu_info *ppdu_info,
struct ath11k_peer *peer,
struct hal_rx_user_status *user_stats)
{
u16 ath11k_hal_rx_legacy_rates[] =
{ 10, 20, 55, 60, 90, 110, 120, 180, 240, 360, 480, 540 };
u16 rate = 0;
u32 preamble_type;
u8 mcs, nss;
struct ath11k_vif *arvif = ath11k_vif_to_arvif(peer->vif);
struct ath11k *ar = arvif->ar;
struct ath11k_base *ab = ar->ab;
if (!ab->nss.enabled)
return;
if (!peer->nss.nss_stats)
return;
if (user_stats) {
mcs = user_stats->mcs;
nss = user_stats->nss;
preamble_type = user_stats->preamble_type;
} else {
mcs = ppdu_info->mcs;
nss = ppdu_info->nss;
preamble_type = ppdu_info->preamble_type;
}
if ((preamble_type == WMI_RATE_PREAMBLE_CCK ||
preamble_type == WMI_RATE_PREAMBLE_OFDM) &&
(ppdu_info->rate < ATH11K_LEGACY_NUM)) {
rate = ath11k_hal_rx_legacy_rates[ppdu_info->rate];
}
memset(&peer->nss.nss_stats->rxrate, 0, sizeof(peer->nss.nss_stats->rxrate));
switch (preamble_type) {
case WMI_RATE_PREAMBLE_OFDM:
peer->nss.nss_stats->rxrate.legacy = rate;
break;
case WMI_RATE_PREAMBLE_CCK:
peer->nss.nss_stats->rxrate.legacy = rate;
break;
case WMI_RATE_PREAMBLE_HT:
if (mcs >= ATH11K_HT_MCS_NUM) {
ath11k_dbg(ar->ab, ATH11K_DBG_NSS,
"Received invalid mcs in HT mode %d\n", mcs);
return;
}
peer->nss.nss_stats->rxrate.mcs = mcs;
peer->nss.nss_stats->rxrate.flags = RATE_INFO_FLAGS_MCS;
if (ppdu_info->gi)
peer->nss.nss_stats->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
break;
case WMI_RATE_PREAMBLE_VHT:
if (mcs > ATH11K_VHT_MCS_MAX) {
ath11k_dbg(ar->ab, ATH11K_DBG_NSS,
"Received invalid mcs in VHT mode %d\n", mcs);
return;
}
peer->nss.nss_stats->rxrate.mcs = mcs;
peer->nss.nss_stats->rxrate.flags = RATE_INFO_FLAGS_VHT_MCS;
if (ppdu_info->gi)
peer->nss.nss_stats->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI;
break;
case WMI_RATE_PREAMBLE_HE:
if (mcs > ATH11K_HE_MCS_MAX) {
ath11k_dbg(ar->ab, ATH11K_DBG_NSS,
"Received invalid mcs in HE mode %d\n", mcs);
return;
}
peer->nss.nss_stats->rxrate.mcs = mcs;
peer->nss.nss_stats->rxrate.flags = RATE_INFO_FLAGS_HE_MCS;
peer->nss.nss_stats->rxrate.he_dcm = ppdu_info->dcm;
peer->nss.nss_stats->rxrate.he_gi = ath11k_he_gi_to_nl80211_he_gi(ppdu_info->gi);
peer->nss.nss_stats->rxrate.he_ru_alloc = ppdu_info->ru_alloc;
break;
}
peer->nss.nss_stats->rxrate.nss = nss;
peer->nss.nss_stats->rxrate.bw = ath11k_mac_bw_to_mac80211_bw(ppdu_info->bw);
if (ppdu_info->tid <= IEEE80211_NUM_TIDS)
peer->nss.nss_stats->rx_tid_msdu[ppdu_info->tid] += num_msdu;
}
int ath11k_nss_peer_delete(struct ath11k_base *ab, u8 *addr)
{
struct nss_wifili_peer_msg *peer_msg;
struct nss_wifili_msg *wlmsg = NULL;
struct ath11k_peer *peer;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret;
if (!ab->nss.enabled)
return 0;
spin_lock_bh(&ab->base_lock);
peer = ath11k_peer_find_by_addr(ab, addr);
if (!peer) {
ath11k_warn(ab, "peer not found for nss peer delete\n");
spin_unlock_bh(&ab->base_lock);
return -EINVAL;
}
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg) {
ath11k_warn(ab, "nss send peer delete msg alloc failure\n");
ret = -ENOMEM;
goto free_peer;
}
peer_msg = &wlmsg->msg.peermsg;
peer_msg->vdev_id = peer->vdev_id;
peer_msg->peer_id = peer->peer_id;
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_PEER_DELETE_MSG,
sizeof(struct nss_wifili_peer_msg),
msg_cb, NULL);
reinit_completion(&peer->nss.complete);
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "nss send peer delete msg tx error %d\n", status);
ret = -EINVAL;
kfree(wlmsg);
goto free_peer;
} else {
ath11k_dbg(ab, ATH11K_DBG_NSS, "nss peer delete message success : peer_id %d\n",
peer->peer_id);
ret = 0;
}
spin_unlock_bh(&ab->base_lock);
kfree(wlmsg);
/* No need to return failure or free up here, since the msg was tx succesfully
* the peer delete response would be received from NSS which will free up
* the allocated memory
*/
ret = wait_for_completion_timeout(&peer->nss.complete,
msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS));
if (!ret)
ath11k_warn(ab, "timeout while waiting for nss peer delete msg response\n");
return 0;
free_peer:
dma_unmap_single(ab->dev, peer->nss.paddr,
WIFILI_NSS_PEER_BYTE_SIZE, DMA_TO_DEVICE);
kfree(peer->nss.vaddr);
if (peer->nss.nss_stats) {
kfree(peer->nss.nss_stats);
peer->nss.nss_stats = NULL;
}
spin_unlock_bh(&ab->base_lock);
return ret;
}
int ath11k_nss_peer_create(struct ath11k_base *ab, struct ath11k_peer *peer)
{
struct nss_wifili_peer_msg *peer_msg;
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret;
if (!ab->nss.enabled)
return 0;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
peer_msg = &wlmsg->msg.peermsg;
peer_msg->vdev_id = peer->vdev_id;
peer_msg->peer_id = peer->peer_id;
peer_msg->hw_ast_idx = peer->hw_peer_id;
peer_msg->tx_ast_hash = peer->ast_hash;
ether_addr_copy(peer_msg->peer_mac_addr, peer->addr);
peer->nss.vaddr = kzalloc(WIFILI_NSS_PEER_BYTE_SIZE, GFP_ATOMIC);
/* Initialize completion for verifying Peer NSS message response */
init_completion(&peer->nss.complete);
if (!peer->nss.vaddr) {
ath11k_warn(ab, "failed to allocate memory for nss peer info\n");
kfree(wlmsg);
return -ENOMEM;
}
peer->nss.paddr = dma_map_single(ab->dev, peer->nss.vaddr,
WIFILI_NSS_PEER_BYTE_SIZE, DMA_TO_DEVICE);
ret = dma_mapping_error(ab->dev, peer->nss.paddr);
if (ret) {
ath11k_warn(ab, "error during nss peer info memalloc\n");
kfree(peer->nss.vaddr);
ret = -ENOMEM;
goto msg_free;
}
peer_msg->nss_peer_mem = peer->nss.paddr;
peer_msg->psta_vdev_id = peer->vdev_id;
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_PEER_CREATE_MSG,
sizeof(struct nss_wifili_peer_msg),
msg_cb, NULL);
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ret = -EINVAL;
ath11k_warn(ab, "nss send peer create msg tx error\n");
goto peer_mem_free;
}
ret = 0;
ath11k_dbg(ab, ATH11K_DBG_NSS,
"nss peer_create msg success mac:%pM vdev:%d peer_id:%d hw_ast_idx:%d ast_hash:%d\n",
peer_msg->peer_mac_addr, peer_msg->vdev_id, peer_msg->peer_id,
peer_msg->hw_ast_idx, peer_msg->tx_ast_hash);
peer->nss.nss_stats = kzalloc(sizeof(*peer->nss.nss_stats), GFP_ATOMIC);
if (!peer->nss.nss_stats)
ath11k_warn(ab, "Unable to create nss stats memory\n");
goto msg_free;
peer_mem_free:
dma_unmap_single(ab->dev, peer->nss.paddr,
WIFILI_NSS_PEER_BYTE_SIZE, DMA_TO_DEVICE);
kfree(peer->nss.vaddr);
msg_free:
kfree(wlmsg);
return ret;
}
int ath11k_nss_add_wds_peer(struct ath11k *ar, struct ath11k_peer *peer,
u8 *dest_mac, enum ath11k_ast_entry_type type)
{
struct ath11k_base *ab = ar->ab;
struct nss_wifili_wds_peer_msg *wds_peer_msg;
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret = 0;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (wlmsg == NULL)
return -ENOMEM;
wds_peer_msg = &wlmsg->msg.wdspeermsg;
wds_peer_msg->pdev_id = ar->pdev->pdev_id;
wds_peer_msg->ast_type = type;
wds_peer_msg->peer_id = peer->peer_id;
ether_addr_copy(wds_peer_msg->peer_mac, peer->addr);
ether_addr_copy(wds_peer_msg->dest_mac, dest_mac);
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_WDS_PEER_ADD_MSG,
sizeof(struct nss_wifili_wds_peer_msg),
msg_cb, NULL);
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ret = -EINVAL;
ath11k_warn(ab, "nss send wds add peer msg tx error\n");
goto msg_free;
}
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"nss add wds peer success pdev:%d peer mac:%pM dest mac:%pM peer_id:%d\n",
wds_peer_msg->pdev_id, wds_peer_msg->peer_mac, wds_peer_msg->dest_mac, wds_peer_msg->peer_id);
msg_free:
kfree(wlmsg);
return ret;
}
int ath11k_nss_update_wds_peer(struct ath11k *ar, struct ath11k_peer *peer,
u8 *dest_mac)
{
struct ath11k_base *ab = ar->ab;
struct nss_wifili_wds_peer_msg *wds_peer_msg;
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret = 0;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (wlmsg == NULL)
return -ENOMEM;
wds_peer_msg = &wlmsg->msg.wdspeermsg;
wds_peer_msg->pdev_id = ar->pdev->pdev_id;
wds_peer_msg->ast_type = ATH11K_AST_TYPE_WDS;
wds_peer_msg->peer_id = peer->peer_id;
ether_addr_copy(wds_peer_msg->peer_mac, peer->addr);
ether_addr_copy(wds_peer_msg->dest_mac, dest_mac);
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_WDS_PEER_UPDATE_MSG,
sizeof(struct nss_wifili_wds_peer_msg),
msg_cb, NULL);
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ret = -EINVAL;
ath11k_warn(ab, "nss send wds update peer msg tx error\n");
goto msg_free;
}
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"nss update wds peer success pdev:%d peer mac:%pM dest mac:%pM peer_id:%d\n",
wds_peer_msg->pdev_id, wds_peer_msg->peer_mac, wds_peer_msg->dest_mac, wds_peer_msg->peer_id);
msg_free:
kfree(wlmsg);
return ret;
}
int ath11k_nss_map_wds_peer(struct ath11k *ar, struct ath11k_peer *peer,
u8 *dest_mac, enum ath11k_ast_entry_type type)
{
struct ath11k_base *ab = ar->ab;
struct nss_wifili_wds_peer_map_msg *wds_peer_map_msg;
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret = 0;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (wlmsg == NULL)
return -ENOMEM;
wds_peer_map_msg = &wlmsg->msg.wdspeermapmsg;
wds_peer_map_msg->vdev_id = peer->vdev_id;
wds_peer_map_msg->ast_idx = peer->hw_peer_id;
if (type == ATH11K_AST_TYPE_MEC)
wds_peer_map_msg->peer_id = NSS_WIFILI_MEC_PEER_ID;
else
wds_peer_map_msg->peer_id = peer->peer_id;
ether_addr_copy(wds_peer_map_msg->dest_mac, dest_mac);
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_WDS_PEER_MAP_MSG,
sizeof(struct nss_wifili_wds_peer_map_msg),
msg_cb, NULL);
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ret = -EINVAL;
ath11k_warn(ab, "nss send wds peer map msg tx error\n");
goto msg_free;
}
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"nss wds peer map success mac:%pM hw_ast_idx:%d peer_id:%d\n",
wds_peer_map_msg->dest_mac, wds_peer_map_msg->ast_idx, wds_peer_map_msg->peer_id);
msg_free:
kfree(wlmsg);
return ret;
}
int ath11k_nss_del_wds_peer(struct ath11k *ar, u8 *peer_addr, int peer_id,
u8 *dest_mac)
{
struct ath11k_base *ab = ar->ab;
struct nss_wifili_wds_peer_msg *wds_peer_msg;
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret = 0;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (wlmsg == NULL)
return -ENOMEM;
wds_peer_msg = &wlmsg->msg.wdspeermsg;
wds_peer_msg->pdev_id = ar->pdev->pdev_id;
wds_peer_msg->ast_type = ATH11K_AST_TYPE_NONE;
wds_peer_msg->peer_id = peer_id;
ether_addr_copy(wds_peer_msg->peer_mac, peer_addr);
ether_addr_copy(wds_peer_msg->dest_mac, dest_mac);
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_WDS_PEER_DEL_MSG,
sizeof(struct nss_wifili_wds_peer_msg),
msg_cb, NULL);
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ret = -EINVAL;
ath11k_warn(ab, "nss send wds del peer msg tx error\n");
goto msg_free;
}
ath11k_dbg(ab, ATH11K_DBG_NSS_WDS,
"nss del wds peer success peer mac:%pM dest mac:%pM peer_id:%d\n",
wds_peer_msg->peer_mac, wds_peer_msg->dest_mac, wds_peer_msg->peer_id);
msg_free:
kfree(wlmsg);
return ret;
}
/*-------------------------------INIT/DEINIT---------------------------------*/
static int ath11k_nss_radio_buf_cfg(struct ath11k *ar, int range, int buf_sz)
{
struct nss_wifili_radio_buf_cfg_msg *buf_cfg;
struct nss_wifili_radio_cfg_msg *radio_buf_cfg_msg;
struct nss_wifili_msg *wlmsg = NULL;
nss_tx_status_t status;
int ret = 0;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
radio_buf_cfg_msg = &wlmsg->msg.radiocfgmsg;
radio_buf_cfg_msg->radio_if_num = ar->nss.if_num;
buf_cfg = &wlmsg->msg.radiocfgmsg.radiomsg.radiobufcfgmsg;
buf_cfg->range = range;
buf_cfg->buf_cnt = buf_sz;
nss_cmn_msg_init(&wlmsg->cm, ar->ab->nss.if_num,
NSS_WIFILI_RADIO_BUF_CFG,
sizeof(struct nss_wifili_radio_buf_cfg_msg),
NULL, NULL);
status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ar->ab, "nss radio buf cfg send failed %d\n", status);
ret = -EINVAL;
} else {
ath11k_dbg(ar->ab, ATH11K_DBG_NSS,
"nss radio cfg message range:%d buf_sz:%d if_num:%d ctx:%p\n",
range, buf_sz, ar->nss.if_num, ar->nss.ctx);
}
kfree(wlmsg);
return ret;
}
static void ath11k_nss_fill_srng_info(struct ath11k_base *ab, int ring_id,
struct nss_wifili_hal_srng_info *hsi)
{
struct ath11k_hal *hal = &ab->hal;
struct hal_srng *srng;
u32 offset;
int i;
if (ring_id < 0) {
ath11k_warn(ab, "Invalid ring id used for nss init\n");
WARN_ON(1);
return;
}
srng = &hal->srng_list[ring_id];
hsi->ring_id = srng->ring_id;
hsi->ring_dir = srng->ring_dir;
hsi->ring_base_paddr = srng->ring_base_paddr;
hsi->entry_size = srng->entry_size;
hsi->num_entries = srng->num_entries;
hsi->flags = srng->flags;
ath11k_dbg(ab, ATH11K_DBG_NSS,
"Ring info to send to nss - ring_id:%d ring_dir:%d ring_paddr:%d entry_size:%d num_entries:%d flags:%d\n",
hsi->ring_id, hsi->ring_dir, hsi->ring_base_paddr,
hsi->entry_size, hsi->num_entries, hsi->flags);
for (i = 0; i < HAL_SRNG_NUM_REG_GRP; i++) {
offset = srng->hwreg_base[i];
/* For PCI based devices, get the umac ring base address offset
* based on window register configuration.
*/
if (!(srng->flags & HAL_SRNG_FLAGS_LMAC_RING))
offset = ath11k_hif_get_window_offset(ab, srng->hwreg_base[i]);
hsi->hwreg_base[i] = (u32)ab->mem_pa + offset;
ath11k_dbg(ab, ATH11K_DBG_NSS, "SRNG Register %d address %d\n",
i, hsi->hwreg_base[i]);
}
}
static void ath11k_nss_tx_desc_mem_free(struct ath11k_base *ab)
{
int i;
for (i = 0; i < ATH11K_NSS_MAX_NUMBER_OF_PAGE; i++) {
if (!ab->nss.tx_desc_paddr[i])
continue;
dma_free_coherent(ab->dev,
ab->nss.tx_desc_size[i],
ab->nss.tx_desc_vaddr[i],
ab->nss.tx_desc_paddr[i]);
ab->nss.tx_desc_vaddr[i] = NULL;
ATH11K_MEMORY_STATS_DEC(ab, dma_alloc, ab->nss.tx_desc_size[i]);
}
ath11k_dbg(ab, ATH11K_DBG_NSS, "allocated tx desc mem freed\n");
}
static int ath11k_nss_tx_desc_mem_alloc(struct ath11k_base *ab, u32 required_size, u32 *page_idx)
{
int i, alloc_size;
int curr_page_idx;
ath11k_dbg(ab, ATH11K_DBG_NSS, "tx desc mem alloc size: %d\n", required_size);
curr_page_idx = *page_idx;
for (i = 0, alloc_size = 0; i < required_size; i += alloc_size) {
alloc_size = required_size - i;
if (alloc_size > WIFILI_NSS_MAX_MEM_PAGE_SIZE)
alloc_size = WIFILI_NSS_MAX_MEM_PAGE_SIZE;
ab->nss.tx_desc_vaddr[curr_page_idx] =
dma_alloc_coherent(ab->dev, alloc_size,
&ab->nss.tx_desc_paddr[curr_page_idx],
GFP_KERNEL);
if (!ab->nss.tx_desc_vaddr[curr_page_idx])
return -ENOMEM;
ab->nss.tx_desc_size[curr_page_idx] = alloc_size;
curr_page_idx++;
ATH11K_MEMORY_STATS_INC(ab, dma_alloc, alloc_size);
ath11k_dbg(ab, ATH11K_DBG_NSS,
"curr page %d, allocated %d, total allocated %d\n",
curr_page_idx, alloc_size, i + alloc_size);
if (curr_page_idx == ATH11K_NSS_MAX_NUMBER_OF_PAGE) {
ath11k_warn(ab, "max page number reached while tx desc mem allocation\n");
return -EINVAL;
}
}
*page_idx = curr_page_idx;
return 0;
}
static int ath11k_nss_fill_tx_desc_info(struct ath11k_base *ab,
struct nss_wifili_init_msg *wim)
{
struct nss_wifili_tx_desc_addtnl_mem_msg *dam;
u32 required_size, required_size_ext;
struct nss_wifili_tx_desc_init_msg *dim;
u32 tx_desc_limit_0 = 0;
u32 tx_desc_limit_1 = 0;
u32 tx_desc_limit_2 = 0;
u32 dam_page_idx = 0;
int page_idx = 0;
int i;
wim->tx_sw_internode_queue_size = ATH11K_WIFIILI_MAX_TX_PROCESSQ;
dim = &wim->wtdim;
dam = &wim->wtdam;
dim->num_pool = ab->num_radios;
dim->num_tx_device_limit = ATH11K_WIFILI_MAX_TX_DESC;
//TODO Revisit below calc based on platform/mem cfg
switch (dim->num_pool) {
case 1:
tx_desc_limit_0 = ATH11K_WIFILI_DBDC_NUM_TX_DESC;
break;
case 2:
tx_desc_limit_0 = ATH11K_WIFILI_DBDC_NUM_TX_DESC;
tx_desc_limit_1 = ATH11K_WIFILI_DBDC_NUM_TX_DESC;
break;
case 3:
tx_desc_limit_0 = ATH11K_WIFILI_DBTC_NUM_TX_DESC;
tx_desc_limit_1 = ATH11K_WIFILI_DBTC_NUM_TX_DESC;
tx_desc_limit_2 = ATH11K_WIFILI_DBTC_NUM_TX_DESC;
break;
default:
ath11k_warn(ab, "unexpected num radios during tx desc alloc\n");
return -EINVAL;
}
dim->num_tx_desc = tx_desc_limit_0;
dim->num_tx_desc_ext = tx_desc_limit_0;
dim->num_tx_desc_2 = tx_desc_limit_1;
dim->num_tx_desc_ext_2 = tx_desc_limit_1;
dim->num_tx_desc_3 = tx_desc_limit_2;
dim->num_tx_desc_ext_3 = tx_desc_limit_2;
required_size = (dim->num_tx_desc + dim->num_tx_desc_2 +
dim->num_tx_desc_3 +
dim->num_pool) * WIFILI_NSS_TX_DESC_SIZE;
required_size_ext = (dim->num_tx_desc_ext + dim->num_tx_desc_ext_2 +
dim->num_tx_desc_ext_3 +
dim->num_pool) * WIFILI_NSS_TX_EXT_DESC_SIZE;
if (ath11k_nss_tx_desc_mem_alloc(ab, required_size, &page_idx)) {
ath11k_warn(ab, "memory allocation for tx desc of size %d failed\n",
required_size);
return -ENOMEM;
}
/* Fill the page number from where extension tx descriptor is available */
dim->ext_desc_page_num = page_idx;
if (ath11k_nss_tx_desc_mem_alloc(ab, required_size_ext, &page_idx)) {
ath11k_warn(ab, "memory allocation for extension tx desc of size %d failed\n",
required_size_ext);
return -ENOMEM;
}
for (i = 0; i < page_idx; i++) {
if (i < NSS_WIFILI_MAX_NUMBER_OF_PAGE_MSG) {
dim->memory_addr[i] = (u32)ab->nss.tx_desc_paddr[i];
dim->memory_size[i] = (u32)ab->nss.tx_desc_size[i];
dim->num_memaddr++;
} else {
dam_page_idx = i - NSS_WIFILI_MAX_NUMBER_OF_PAGE_MSG;
dam->addtnl_memory_addr[dam_page_idx] = (u32)ab->nss.tx_desc_paddr[i];
dam->addtnl_memory_size[dam_page_idx] = (u32)ab->nss.tx_desc_size[i];
dam->num_addtnl_addr++;
}
}
if (i > NSS_WIFILI_MAX_NUMBER_OF_PAGE_MSG)
wim->flags |= WIFILI_ADDTL_MEM_SEG_SET;
return 0;
}
static int ath11k_nss_get_target_type(struct ath11k_base *ab)
{
switch (ab->hw_rev) {
case ATH11K_HW_IPQ8074:
return ATH11K_WIFILI_TARGET_TYPE_QCA8074V2;
case ATH11K_HW_IPQ6018_HW10:
return ATH11K_WIFILI_TARGET_TYPE_QCA6018;
case ATH11K_HW_QCN9074_HW10:
return ATH11K_WIFILI_TARGET_TYPE_QCN9074;
case ATH11K_HW_IPQ5018:
return ATH11K_WIFILI_TARGET_TYPE_QCA5018;
case ATH11K_HW_QCN6122:
return ATH11K_WIFILI_TARGET_TYPE_QCN6122;
default:
ath11k_warn(ab, "NSS Offload not supported for this HW\n");
return ATH11K_WIFILI_TARGET_TYPE_UNKNOWN;
}
}
static int ath11k_nss_get_interface_type(struct ath11k_base *ab)
{
switch (ab->hw_rev) {
case ATH11K_HW_IPQ8074:
case ATH11K_HW_IPQ6018_HW10:
case ATH11K_HW_IPQ5018:
return NSS_WIFILI_INTERNAL_INTERFACE;
case ATH11K_HW_QCN9074_HW10:
case ATH11K_HW_QCN6122:
return nss_get_available_wifili_external_if();
default:
/* This can't happen since we validated target type earlier */
WARN_ON(1);
return NSS_MAX_NET_INTERFACES;
}
}
static int ath11k_nss_get_dynamic_interface_type(struct ath11k_base *ab)
{
switch (ab->nss.if_num) {
case NSS_WIFILI_INTERNAL_INTERFACE:
return NSS_DYNAMIC_INTERFACE_TYPE_WIFILI_INTERNAL;
case NSS_WIFILI_EXTERNAL_INTERFACE0:
return NSS_DYNAMIC_INTERFACE_TYPE_WIFILI_EXTERNAL0;
case NSS_WIFILI_EXTERNAL_INTERFACE1:
return NSS_DYNAMIC_INTERFACE_TYPE_WIFILI_EXTERNAL1;
default:
ath11k_warn(ab, "NSS Offload invalid interface\n");
return NSS_DYNAMIC_INTERFACE_TYPE_NONE;
}
}
static int ath11k_nss_mesh_capability(struct ath11k_base *ab)
{
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret = 0;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
reinit_completion(&ab->nss.complete);
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_SEND_MESH_CAPABILITY_INFO,
sizeof(struct nss_wifili_mesh_capability_info),
msg_cb, NULL);
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "nss failed to get mesh capability msg %d\n", status);
ret = -EINVAL;
goto free;
}
ret = wait_for_completion_timeout(&ab->nss.complete,
msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS));
if (!ret) {
ath11k_warn(ab, "timeout while waiting for mesh capability check\n");
ret = -ETIMEDOUT;
goto free;
}
kfree(wlmsg);
return 0;
free:
kfree(wlmsg);
return ret;
}
static int ath11k_nss_init(struct ath11k_base *ab)
{
struct nss_wifili_init_msg *wim = NULL;
struct nss_wifili_msg *wlmsg = NULL;
struct nss_ctx_instance *nss_contex;
nss_wifili_msg_callback_t msg_cb;
u32 target_type;
u32 features = 0;
nss_tx_status_t status;
struct ath11k_dp *dp;
int i, ret;
dp = &ab->dp;
target_type = ath11k_nss_get_target_type(ab);
if (target_type == ATH11K_WIFILI_TARGET_TYPE_UNKNOWN)
return -ENOTSUPP;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ab, malloc_size, sizeof(struct nss_wifili_msg));
wim = &wlmsg->msg.init;
wim->target_type = target_type;
/* fill rx parameters to initialize rx context */
wim->wrip.tlv_size = sizeof(struct hal_rx_desc);
wim->wrip.rx_buf_len = DP_RXDMA_NSS_REFILL_RING_SIZE;
/* fill hal srng message */
wim->hssm.dev_base_addr = (u32)ab->mem_pa;
wim->hssm.shadow_rdptr_mem_addr = (u32)ab->hal.rdp.paddr;
wim->hssm.shadow_wrptr_mem_addr = (u32)ab->hal.wrp.paddr;
/* fill TCL data/completion ring info */
wim->num_tcl_data_rings = DP_TCL_NUM_RING_MAX;
for (i = 0; i < DP_TCL_NUM_RING_MAX; i++) {
ath11k_nss_fill_srng_info(ab, dp->tx_ring[i].tcl_data_ring.ring_id,
&wim->tcl_ring_info[i]);
ath11k_nss_fill_srng_info(ab, dp->tx_ring[i].tcl_comp_ring.ring_id,
&wim->tx_comp_ring[i]);
}
/* allocate tx desc memory for NSS and fill corresponding info */
ret = ath11k_nss_fill_tx_desc_info(ab, wim);
if (ret)
goto free;
/* fill reo dest ring info */
wim->num_reo_dest_rings = DP_REO_DST_RING_MAX;
for (i = 0; i < DP_REO_DST_RING_MAX; i++) {
ath11k_nss_fill_srng_info(ab, dp->reo_dst_ring[i].ring_id,
&wim->reo_dest_ring[i]);
}
/* fill reo reinject ring info */
ath11k_nss_fill_srng_info(ab, dp->reo_reinject_ring.ring_id,
&wim->reo_reinject_ring);
/* fill reo release ring info */
ath11k_nss_fill_srng_info(ab, dp->rx_rel_ring.ring_id,
&wim->rx_rel_ring);
/* fill reo exception ring info */
ath11k_nss_fill_srng_info(ab, dp->reo_except_ring.ring_id,
&wim->reo_exception_ring);
wim->flags |= WIFILI_NSS_CCE_DISABLED;
ab->nss.if_num = ath11k_nss_get_interface_type(ab);
ath11k_info(ab, "nss init soc nss if_num %d userpd_id %d\n", ab->nss.if_num, ab->userpd_id);
if (ab->nss.if_num >= NSS_MAX_NET_INTERFACES) {
ath11k_warn(ab, "NSS invalid interface\n");
goto free;
}
/* register callbacks for events and exceptions with nss */
nss_contex = nss_register_wifili_if(ab->nss.if_num, NULL,
(nss_wifili_callback_t)ath11k_nss_wifili_ext_callback_fn,
(nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive,
(struct net_device *)ab, features);
if (!nss_contex) {
ath11k_warn(ab, "nss wifili register failure\n");
goto free;
}
if (nss_cmn_get_state(nss_contex) != NSS_STATE_INITIALIZED) {
ath11k_warn(ab, "nss state in default init state\n");
goto free;
}
/* The registered soc context is stored in ab, and will be used for
* all soc related messages with nss
*/
ab->nss.ctx = nss_contex;
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
/* Initialize the common part of the wlmsg */
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_INIT_MSG,
sizeof(struct nss_wifili_init_msg),
msg_cb, NULL);
reinit_completion(&ab->nss.complete);
/* Note: response is contention free during init sequence */
ab->nss.response = ATH11K_NSS_MSG_ACK;
status = nss_wifili_tx_msg(nss_contex, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "failure to send nss init msg\n");
goto unregister;
}
ret = wait_for_completion_timeout(&ab->nss.complete,
msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS));
if (!ret) {
ath11k_warn(ab, "timeout while waiting for nss init msg response\n");
goto unregister;
}
/* Check if the response is success from the callback */
if (ab->nss.response != ATH11K_NSS_MSG_ACK)
goto unregister;
kfree(wlmsg);
/* Create a mesh links read debugfs entry */
ath11k_debugfs_nss_soc_create(ab);
/* Check for mesh capability */
ret = ath11k_nss_mesh_capability(ab);
if (ret)
ath11k_err(ab, "Mesh offload is not enabled %d\n", ret);
ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS Init Message TX Success %p %d\n",
ab->nss.ctx, ab->nss.if_num);
return 0;
unregister:
nss_unregister_wifili_if(ab->nss.if_num);
free:
ath11k_nss_tx_desc_mem_free(ab);
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, sizeof(struct nss_wifili_msg));
kfree(wlmsg);
return -EINVAL;
}
static int ath11k_nss_stats_cfg(struct ath11k *ar, int nss_msg, int enable)
{
struct nss_wifili_msg *wlmsg = NULL;
struct nss_wifili_stats_cfg_msg *stats_cfg;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
struct ath11k_base *ab = ar->ab;
int ret = 0;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
stats_cfg = &wlmsg->msg.scm;
stats_cfg->cfg = enable;
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
nss_msg,
sizeof(struct nss_wifili_stats_cfg_msg),
msg_cb, NULL);
status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "nss stats cfg %d msg tx failure\n", nss_msg);
ret = -EINVAL;
goto free;
}
ath11k_dbg(ab, ATH11K_DBG_NSS, "nss stats %d enable %d\n", nss_msg, enable);
free:
kfree(wlmsg);
return ret;
}
static void ath11k_nss_stats_disable(struct ath11k *ar)
{
/*TODO Revisit if this required */
ath11k_nss_stats_cfg(ar, NSS_WIFILI_STATS_CFG_MSG,
ATH11K_NSS_STATS_DISABLE);
ath11k_nss_stats_cfg(ar, NSS_WIFILI_STATS_V2_CFG_MSG,
ATH11K_NSS_STATS_DISABLE);
}
static void ath11k_nss_stats_enable(struct ath11k *ar)
{
/*TODO Revisit if we need to enable both these stats */
ath11k_nss_stats_cfg(ar, NSS_WIFILI_STATS_CFG_MSG,
ATH11K_NSS_STATS_ENABLE);
ath11k_nss_stats_cfg(ar, NSS_WIFILI_STATS_V2_CFG_MSG,
ATH11K_NSS_STATS_ENABLE);
}
int ath11k_nss_pdev_init(struct ath11k_base *ab, int radio_id)
{
struct ath11k *ar = ab->pdevs[radio_id].ar;
struct nss_wifili_pdev_init_msg *pdevmsg;
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int radio_if_num = -1;
int refill_ring_id;
int features = 0;
int dyn_if_type;
int ret, i;
dyn_if_type = ath11k_nss_get_dynamic_interface_type(ab);
/* Allocate a node for dynamic interface */
radio_if_num = nss_dynamic_interface_alloc_node(dyn_if_type);
if (radio_if_num < 0)
return -EINVAL;
/* The ifnum and registered radio context is stored in ar and used
* for messages related to vdev/radio
*/
ar->nss.if_num = radio_if_num;
/* No callbacks are registered for radio specific events/data */
ar->nss.ctx = nss_register_wifili_radio_if((u32)radio_if_num, NULL,
NULL, NULL, (struct net_device *)ar,
features);
if (!ar->nss.ctx) {
ath11k_warn(ab, "failure during nss pdev register\n");
ret = -EINVAL;
goto dealloc;
}
ath11k_dbg(ab, ATH11K_DBG_NSS, "nss pdev init - id:%d init ctxt:%p ifnum:%d\n",
ar->pdev->pdev_id, ar->nss.ctx, ar->nss.if_num);
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg) {
ret = -ENOMEM;
goto unregister;
}
ATH11K_MEMORY_STATS_INC(ab, malloc_size, sizeof(struct nss_wifili_msg));
pdevmsg = &wlmsg->msg.pdevmsg;
pdevmsg->radio_id = radio_id;
pdevmsg->lmac_id = ar->lmac_id;
pdevmsg->target_pdev_id = ar->pdev->pdev_id;
pdevmsg->num_rx_swdesc = WIFILI_RX_DESC_POOL_WEIGHT * DP_RXDMA_BUF_RING_SIZE;
/* Store rxdma ring info to the message */
refill_ring_id = ar->dp.rx_refill_buf_ring.refill_buf_ring.ring_id;
ath11k_nss_fill_srng_info(ab, refill_ring_id, &pdevmsg->rxdma_ring);
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_PDEV_INIT_MSG,
sizeof(struct nss_wifili_pdev_init_msg),
msg_cb, NULL);
reinit_completion(&ab->nss.complete);
/* Note: response is contention free during init sequence */
ab->nss.response = ATH11K_NSS_MSG_ACK;
status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "nss send pdev msg tx error : %d\n", status);
ret = -EINVAL;
goto free;
}
ret = wait_for_completion_timeout(&ab->nss.complete,
msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS));
if (!ret) {
ath11k_warn(ab, "timeout while waiting for pdev init msg response\n");
ret = -ETIMEDOUT;
goto free;
}
/* Check if the response is success from the callback */
if (ab->nss.response != ATH11K_NSS_MSG_ACK) {
ret = -EINVAL;
goto free;
}
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, sizeof(struct nss_wifili_msg));
kfree(wlmsg);
/* Enable nss stats */
ath11k_nss_stats_enable(ar);
/*TODO CFG Tx buffer limit as per no clients range per radio
* this needs to be based on target/mem cfg
* similar to tx desc cfg at soc init per radio
*/
for (i = 0; i < ATH11K_NSS_RADIO_TX_LIMIT_RANGE; i++)
ath11k_nss_radio_buf_cfg(ar, i, ATH11K_NSS_RADIO_TX_LIMIT_RANGE3);
return 0;
free:
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, sizeof(struct nss_wifili_msg));
kfree(wlmsg);
unregister:
nss_unregister_wifili_radio_if(ar->nss.if_num);
dealloc:
nss_dynamic_interface_dealloc_node(ar->nss.if_num, dyn_if_type);
return ret;
}
/* TODO : Check if start, reset and stop messages can be done using single function as
* body is similar, having it now for clarity */
int ath11k_nss_start(struct ath11k_base *ab)
{
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret;
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ab, malloc_size, sizeof(struct nss_wifili_msg));
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
/* Empty message for NSS Start message */
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_START_MSG,
0,
msg_cb, NULL);
reinit_completion(&ab->nss.complete);
/* Note: response is contention free during init sequence */
ab->nss.response = ATH11K_NSS_MSG_ACK;
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "nss send start msg tx error %d\n", status);
ret = -EINVAL;
goto free;
}
ret = wait_for_completion_timeout(&ab->nss.complete,
msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS));
if (!ret) {
ath11k_warn(ab, "timeout while waiting for response for nss start msg\n");
ret = -ETIMEDOUT;
goto free;
}
/* Check if the response is success from the callback */
if (ab->nss.response != ATH11K_NSS_MSG_ACK) {
ret = -EINVAL;
goto free;
}
/* NSS Start success */
ret = 0;
ath11k_dbg(ab, ATH11K_DBG_NSS, "nss start success\n");
free:
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, sizeof(struct nss_wifili_msg));
kfree(wlmsg);
return ret;
}
static void ath11k_nss_reset(struct ath11k_base *ab)
{
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret;
ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS reset\n");
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg) {
ath11k_warn(ab, "mem allocation failure during nss reset\n");
return;
}
ATH11K_MEMORY_STATS_INC(ab, malloc_size, sizeof(struct nss_wifili_msg));
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
/* Empty message for NSS Reset message */
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_SOC_RESET_MSG,
0,
msg_cb, NULL);
reinit_completion(&ab->nss.complete);
/* Note: response is contention free during deinit sequence */
ab->nss.response = ATH11K_NSS_MSG_ACK;
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
/* Add a retry mechanism to reset nss until success */
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "nss send reset msg tx error %d\n", status);
goto free;
}
ret = wait_for_completion_timeout(&ab->nss.complete,
msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS));
if (!ret) {
ath11k_warn(ab, "timeout while waiting for response for nss reset msg\n");
goto free;
}
/* Check if the response is success from the callback */
if (ab->nss.response != ATH11K_NSS_MSG_ACK) {
ath11k_warn(ab, "failure response during nss reset %d\n", ab->nss.response);
goto free;
}
/* Unregister wifili interface */
nss_unregister_wifili_if(ab->nss.if_num);
free:
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, sizeof(struct nss_wifili_msg));
kfree(wlmsg);
}
static int ath11k_nss_stop(struct ath11k_base *ab)
{
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int ret;
ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS stop\n");
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ab, malloc_size, sizeof(struct nss_wifili_msg));
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
/* Empty message for Stop command */
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_STOP_MSG,
0,
msg_cb, NULL);
reinit_completion(&ab->nss.complete);
/* Note: response is contention free during deinit sequence */
ab->nss.response = ATH11K_NSS_MSG_ACK;
status = nss_wifili_tx_msg(ab->nss.ctx, wlmsg);
/* Add a retry mechanism to stop nss until success */
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "nss send stop msg tx error %d\n", status);
ret = -EINVAL;
goto free;
}
ret = wait_for_completion_timeout(&ab->nss.complete,
msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS));
if (!ret) {
ath11k_warn(ab, "timeout while waiting for response for nss stop msg\n");
ret = -ETIMEDOUT;
goto free;
}
/* Check if the response is success from the callback */
if (ab->nss.response != ATH11K_NSS_MSG_ACK) {
ret = -EINVAL;
goto free;
}
/* NSS Stop success */
ret = 0;
free:
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, sizeof(struct nss_wifili_msg));
kfree(wlmsg);
return ret;
}
int ath11k_nss_pdev_deinit(struct ath11k_base *ab, int radio_id)
{
struct ath11k *ar = ab->pdevs[radio_id].ar;
struct nss_wifili_pdev_deinit_msg *deinit;
struct nss_wifili_msg *wlmsg = NULL;
nss_wifili_msg_callback_t msg_cb;
nss_tx_status_t status;
int dyn_if_type;
int ret;
ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS pdev %d deinit\n", radio_id);
dyn_if_type = ath11k_nss_get_dynamic_interface_type(ab);
/* Disable stats before teardown */
ath11k_nss_stats_disable(ar);
wlmsg = kzalloc(sizeof(struct nss_wifili_msg), GFP_ATOMIC);
if (!wlmsg)
return -ENOMEM;
ATH11K_MEMORY_STATS_INC(ab, malloc_size, sizeof(struct nss_wifili_msg));
deinit = &wlmsg->msg.pdevdeinit;
deinit->ifnum = radio_id;
msg_cb = (nss_wifili_msg_callback_t)ath11k_nss_wifili_event_receive;
nss_cmn_msg_init(&wlmsg->cm, ab->nss.if_num,
NSS_WIFILI_PDEV_DEINIT_MSG,
sizeof(struct nss_wifili_pdev_deinit_msg),
msg_cb, NULL);
reinit_completion(&ab->nss.complete);
/* Note: response is contention free during deinit sequence */
ab->nss.response = ATH11K_NSS_MSG_ACK;
status = nss_wifili_tx_msg(ar->nss.ctx, wlmsg);
if (status != NSS_TX_SUCCESS) {
ath11k_warn(ab, "nss send pdev deinit msg tx error %d\n", status);
ret = -EINVAL;
goto free;
}
ret = wait_for_completion_timeout(&ab->nss.complete,
msecs_to_jiffies(ATH11K_NSS_MSG_TIMEOUT_MS));
if (!ret) {
ath11k_warn(ab, "timeout while waiting for pdev deinit msg response\n");
ret = -ETIMEDOUT;
goto free;
}
/* Check if the response is success from the callback */
if (ab->nss.response != ATH11K_NSS_MSG_ACK) {
ret = -EINVAL;
goto free;
}
/* pdev deinit msg success, dealloc, deregister and return */
ret = 0;
nss_dynamic_interface_dealloc_node(ar->nss.if_num, dyn_if_type);
nss_unregister_wifili_radio_if(ar->nss.if_num);
free:
ATH11K_MEMORY_STATS_DEC(ab, malloc_size, sizeof(struct nss_wifili_msg));
kfree(wlmsg);
return ret;
}
int ath11k_nss_teardown(struct ath11k_base *ab)
{
int i, ret;
if (!ab->nss.enabled)
return 0;
ath11k_nss_stop(ab);
for (i = 0; i < ab->num_radios ; i++) {
ret = ath11k_nss_pdev_deinit(ab, i);
if (ret)
ath11k_warn(ab, "failure during pdev%d deinit\n", i);
}
ath11k_nss_reset(ab);
ath11k_nss_tx_desc_mem_free(ab);
ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS Teardown Complete\n");
return 0;
}
int ath11k_nss_setup(struct ath11k_base *ab)
{
int i;
int ret = 0;
u32 target_type;
if (!ab->nss.enabled)
return 0;
target_type = ath11k_nss_get_target_type(ab);
if (target_type == ATH11K_WIFILI_TARGET_TYPE_UNKNOWN)
return -ENOTSUPP;
/* Verify NSS support is enabled */
if (nss_cmn_get_nss_enabled() == false) {
ath11k_warn(ab, "NSS offload support disabled, falling back to default mode\n");
return -ENOTSUPP;
}
/* Initialize completion for verifying NSS message response */
init_completion(&ab->nss.complete);
/* Setup common resources for NSS */
ret = ath11k_nss_init(ab);
if (ret) {
ath11k_warn(ab, "NSS SOC Initialization Failed :%d\n", ret);
goto fail;
}
/* Setup pdev related resources for NSS */
for (i = 0; i < ab->num_radios; i++) {
ret = ath11k_nss_pdev_init(ab, i);
if (ret) {
ath11k_warn(ab, "NSS PDEV %d Initialization Failed :%d\n", i, ret);
goto pdev_deinit;
}
}
/* Set the NSS statemachine to start */
ret = ath11k_nss_start(ab);
if (ret) {
ath11k_warn(ab, "NSS Start Failed : %d\n", ret);
goto pdev_deinit;
}
/* Default nexthop interface is set to ETH RX */
ret = nss_wifi_vdev_base_set_next_hop(ab->nss.ctx, NSS_ETH_RX_INTERFACE);
if (ret != NSS_TX_SUCCESS) {
ath11k_warn(ab, "Failure to set default next hop : %d\n", ret);
goto stop;
}
ath11k_dbg(ab, ATH11K_DBG_NSS, "NSS Setup Complete\n");
return ret;
stop:
ath11k_nss_stop(ab);
pdev_deinit:
for (i -= 1; i >= 0; i--)
ath11k_nss_pdev_deinit(ab, i);
ath11k_nss_reset(ab);
ath11k_nss_tx_desc_mem_free(ab);
fail:
return ret;
}
#endif